mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 11:52:19 +00:00
Merge branch 'assoc-list-dict' of https://github.com/rtfeldman/roc into assoc-list-dict
This commit is contained in:
commit
2a963ca3ba
42 changed files with 961 additions and 143 deletions
|
@ -121,6 +121,7 @@ comptime {
|
||||||
exportStrFn(str.countSegments, "count_segments");
|
exportStrFn(str.countSegments, "count_segments");
|
||||||
exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters");
|
exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters");
|
||||||
exportStrFn(str.countUtf8Bytes, "count_utf8_bytes");
|
exportStrFn(str.countUtf8Bytes, "count_utf8_bytes");
|
||||||
|
exportStrFn(str.getCapacity, "capacity");
|
||||||
exportStrFn(str.startsWith, "starts_with");
|
exportStrFn(str.startsWith, "starts_with");
|
||||||
exportStrFn(str.startsWithScalar, "starts_with_scalar");
|
exportStrFn(str.startsWithScalar, "starts_with_scalar");
|
||||||
exportStrFn(str.endsWith, "ends_with");
|
exportStrFn(str.endsWith, "ends_with");
|
||||||
|
|
|
@ -1210,6 +1210,10 @@ pub fn countUtf8Bytes(string: RocStr) callconv(.C) usize {
|
||||||
return string.len();
|
return string.len();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getCapacity(string: RocStr) callconv(.C) usize {
|
||||||
|
return string.getCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn substringUnsafe(string: RocStr, start: usize, length: usize) callconv(.C) RocStr {
|
pub fn substringUnsafe(string: RocStr, start: usize, length: usize) callconv(.C) RocStr {
|
||||||
const slice = string.asSlice()[start .. start + length];
|
const slice = string.asSlice()[start .. start + length];
|
||||||
return RocStr.fromSlice(slice);
|
return RocStr.fromSlice(slice);
|
||||||
|
|
|
@ -314,6 +314,7 @@ pub const STR_STR_SPLIT: &str = "roc_builtins.str.str_split";
|
||||||
pub const STR_TO_SCALARS: &str = "roc_builtins.str.to_scalars";
|
pub const STR_TO_SCALARS: &str = "roc_builtins.str.to_scalars";
|
||||||
pub const STR_COUNT_GRAPEHEME_CLUSTERS: &str = "roc_builtins.str.count_grapheme_clusters";
|
pub const STR_COUNT_GRAPEHEME_CLUSTERS: &str = "roc_builtins.str.count_grapheme_clusters";
|
||||||
pub const STR_COUNT_UTF8_BYTES: &str = "roc_builtins.str.count_utf8_bytes";
|
pub const STR_COUNT_UTF8_BYTES: &str = "roc_builtins.str.count_utf8_bytes";
|
||||||
|
pub const STR_CAPACITY: &str = "roc_builtins.str.capacity";
|
||||||
pub const STR_STARTS_WITH: &str = "roc_builtins.str.starts_with";
|
pub const STR_STARTS_WITH: &str = "roc_builtins.str.starts_with";
|
||||||
pub const STR_STARTS_WITH_SCALAR: &str = "roc_builtins.str.starts_with_scalar";
|
pub const STR_STARTS_WITH_SCALAR: &str = "roc_builtins.str.starts_with_scalar";
|
||||||
pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with";
|
pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with";
|
||||||
|
|
|
@ -124,6 +124,7 @@ map_symbol_to_lowlevel_and_arity! {
|
||||||
StrAppendScalar; STR_APPEND_SCALAR_UNSAFE; 2,
|
StrAppendScalar; STR_APPEND_SCALAR_UNSAFE; 2,
|
||||||
StrGetScalarUnsafe; STR_GET_SCALAR_UNSAFE; 2,
|
StrGetScalarUnsafe; STR_GET_SCALAR_UNSAFE; 2,
|
||||||
StrToNum; STR_TO_NUM; 1,
|
StrToNum; STR_TO_NUM; 1,
|
||||||
|
StrGetCapacity; STR_CAPACITY; 1,
|
||||||
|
|
||||||
ListLen; LIST_LEN; 1,
|
ListLen; LIST_LEN; 1,
|
||||||
ListWithCapacity; LIST_WITH_CAPACITY; 1,
|
ListWithCapacity; LIST_WITH_CAPACITY; 1,
|
||||||
|
@ -142,6 +143,7 @@ map_symbol_to_lowlevel_and_arity! {
|
||||||
ListSublist; LIST_SUBLIST_LOWLEVEL; 3,
|
ListSublist; LIST_SUBLIST_LOWLEVEL; 3,
|
||||||
ListDropAt; LIST_DROP_AT; 2,
|
ListDropAt; LIST_DROP_AT; 2,
|
||||||
ListSwap; LIST_SWAP; 3,
|
ListSwap; LIST_SWAP; 3,
|
||||||
|
ListGetCapacity; LIST_CAPACITY; 1,
|
||||||
|
|
||||||
NumAdd; NUM_ADD; 2,
|
NumAdd; NUM_ADD; 2,
|
||||||
NumAddWrap; NUM_ADD_WRAP; 2,
|
NumAddWrap; NUM_ADD_WRAP; 2,
|
||||||
|
|
|
@ -220,6 +220,9 @@ pub enum Expr {
|
||||||
lambda_set_variables: Vec<LambdaSet>,
|
lambda_set_variables: Vec<LambdaSet>,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Opaque as a function, e.g. @Id as a shorthand for \x -> @Id x
|
||||||
|
OpaqueWrapFunction(OpaqueWrapFunctionData),
|
||||||
|
|
||||||
// Test
|
// Test
|
||||||
Expect {
|
Expect {
|
||||||
loc_condition: Box<Loc<Expr>>,
|
loc_condition: Box<Loc<Expr>>,
|
||||||
|
@ -269,6 +272,9 @@ impl Expr {
|
||||||
args_count: 0,
|
args_count: 0,
|
||||||
},
|
},
|
||||||
&Self::OpaqueRef { name, .. } => Category::OpaqueWrap(name),
|
&Self::OpaqueRef { name, .. } => Category::OpaqueWrap(name),
|
||||||
|
&Self::OpaqueWrapFunction(OpaqueWrapFunctionData { opaque_name, .. }) => {
|
||||||
|
Category::OpaqueWrap(opaque_name)
|
||||||
|
}
|
||||||
Self::Expect { .. } => Category::Expect,
|
Self::Expect { .. } => Category::Expect,
|
||||||
|
|
||||||
// these nodes place no constraints on the expression's type
|
// these nodes place no constraints on the expression's type
|
||||||
|
@ -380,6 +386,76 @@ impl AccessorData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An opaque wrapper like `@Foo`, which is equivalent to `\p -> @Foo p`
|
||||||
|
/// These are desugared to closures, but we distinguish them so we can have
|
||||||
|
/// better error messages during constraint generation.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct OpaqueWrapFunctionData {
|
||||||
|
pub opaque_name: Symbol,
|
||||||
|
pub opaque_var: Variable,
|
||||||
|
// The following fields help link the concrete opaque type; see
|
||||||
|
// `Expr::OpaqueRef` for more info on how they're used.
|
||||||
|
pub specialized_def_type: Type,
|
||||||
|
pub type_arguments: Vec<OptAbleVar>,
|
||||||
|
pub lambda_set_variables: Vec<LambdaSet>,
|
||||||
|
|
||||||
|
pub function_name: Symbol,
|
||||||
|
pub function_var: Variable,
|
||||||
|
pub argument_var: Variable,
|
||||||
|
pub closure_var: Variable,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpaqueWrapFunctionData {
|
||||||
|
pub fn to_closure_data(self, argument_symbol: Symbol) -> ClosureData {
|
||||||
|
let OpaqueWrapFunctionData {
|
||||||
|
opaque_name,
|
||||||
|
opaque_var,
|
||||||
|
specialized_def_type,
|
||||||
|
type_arguments,
|
||||||
|
lambda_set_variables,
|
||||||
|
function_name,
|
||||||
|
function_var,
|
||||||
|
argument_var,
|
||||||
|
closure_var,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
// IDEA: convert
|
||||||
|
//
|
||||||
|
// @Foo
|
||||||
|
//
|
||||||
|
// into
|
||||||
|
//
|
||||||
|
// (\p -> @Foo p)
|
||||||
|
let body = Expr::OpaqueRef {
|
||||||
|
opaque_var,
|
||||||
|
name: opaque_name,
|
||||||
|
argument: Box::new((argument_var, Loc::at_zero(Expr::Var(argument_symbol)))),
|
||||||
|
specialized_def_type: Box::new(specialized_def_type),
|
||||||
|
type_arguments,
|
||||||
|
lambda_set_variables,
|
||||||
|
};
|
||||||
|
|
||||||
|
let loc_body = Loc::at_zero(body);
|
||||||
|
|
||||||
|
let arguments = vec![(
|
||||||
|
argument_var,
|
||||||
|
AnnotatedMark::known_exhaustive(),
|
||||||
|
Loc::at_zero(Pattern::Identifier(argument_symbol)),
|
||||||
|
)];
|
||||||
|
|
||||||
|
ClosureData {
|
||||||
|
function_type: function_var,
|
||||||
|
closure_type: closure_var,
|
||||||
|
return_type: opaque_var,
|
||||||
|
name: function_name,
|
||||||
|
captured_symbols: vec![],
|
||||||
|
recursive: Recursive::NotRecursive,
|
||||||
|
arguments,
|
||||||
|
loc_body: Box::new(loc_body),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Field {
|
pub struct Field {
|
||||||
pub var: Variable,
|
pub var: Variable,
|
||||||
|
@ -835,15 +911,41 @@ pub fn canonicalize_expr<'a>(
|
||||||
Output::default(),
|
Output::default(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ast::Expr::OpaqueRef(opaque_ref) => {
|
ast::Expr::OpaqueRef(name) => {
|
||||||
// If we're here, the opaque reference is definitely not wrapping an argument - wrapped
|
// If we're here, the opaque reference is definitely not wrapping an argument - wrapped
|
||||||
// arguments are handled in the Apply branch.
|
// arguments are handled in the Apply branch.
|
||||||
let problem = roc_problem::can::RuntimeError::OpaqueNotApplied(Loc::at(
|
// Treat this as a function \payload -> @Opaque payload
|
||||||
region,
|
match scope.lookup_opaque_ref(name, region) {
|
||||||
(*opaque_ref).into(),
|
Err(runtime_error) => {
|
||||||
));
|
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
||||||
env.problem(Problem::RuntimeError(problem.clone()));
|
(RuntimeError(runtime_error), Output::default())
|
||||||
(RuntimeError(problem), Output::default())
|
}
|
||||||
|
Ok((name, opaque_def)) => {
|
||||||
|
let mut output = Output::default();
|
||||||
|
output.references.insert_type_lookup(name);
|
||||||
|
|
||||||
|
let (type_arguments, lambda_set_variables, specialized_def_type) =
|
||||||
|
freshen_opaque_def(var_store, opaque_def);
|
||||||
|
|
||||||
|
let fn_symbol = scope.gen_unique_symbol();
|
||||||
|
|
||||||
|
(
|
||||||
|
OpaqueWrapFunction(OpaqueWrapFunctionData {
|
||||||
|
opaque_name: name,
|
||||||
|
opaque_var: var_store.fresh(),
|
||||||
|
specialized_def_type,
|
||||||
|
type_arguments,
|
||||||
|
lambda_set_variables,
|
||||||
|
|
||||||
|
function_name: fn_symbol,
|
||||||
|
function_var: var_store.fresh(),
|
||||||
|
argument_var: var_store.fresh(),
|
||||||
|
closure_var: var_store.fresh(),
|
||||||
|
}),
|
||||||
|
output,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ast::Expr::Expect(condition, continuation) => {
|
ast::Expr::Expect(condition, continuation) => {
|
||||||
let mut output = Output::default();
|
let mut output = Output::default();
|
||||||
|
@ -1420,7 +1522,8 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
||||||
| other @ AbilityMember(..)
|
| other @ AbilityMember(..)
|
||||||
| other @ RunLowLevel { .. }
|
| other @ RunLowLevel { .. }
|
||||||
| other @ TypedHole { .. }
|
| other @ TypedHole { .. }
|
||||||
| other @ ForeignCall { .. } => other,
|
| other @ ForeignCall { .. }
|
||||||
|
| other @ OpaqueWrapFunction(_) => other,
|
||||||
|
|
||||||
List {
|
List {
|
||||||
elem_var,
|
elem_var,
|
||||||
|
@ -2387,7 +2490,8 @@ fn get_lookup_symbols(expr: &Expr, var_store: &mut VarStore) -> Vec<(Symbol, Var
|
||||||
| Expr::SingleQuote(_)
|
| Expr::SingleQuote(_)
|
||||||
| Expr::EmptyRecord
|
| Expr::EmptyRecord
|
||||||
| Expr::TypedHole(_)
|
| Expr::TypedHole(_)
|
||||||
| Expr::RuntimeError(_) => {}
|
| Expr::RuntimeError(_)
|
||||||
|
| Expr::OpaqueWrapFunction(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -962,5 +962,6 @@ fn fix_values_captured_in_closure_expr(
|
||||||
let (_, loc_arg) = &mut **argument;
|
let (_, loc_arg) = &mut **argument;
|
||||||
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
|
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
|
||||||
}
|
}
|
||||||
|
OpaqueWrapFunction(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,10 @@ use roc_types::subs::Variable;
|
||||||
use crate::{
|
use crate::{
|
||||||
abilities::AbilitiesStore,
|
abilities::AbilitiesStore,
|
||||||
def::{Annotation, Declaration, Def},
|
def::{Annotation, Declaration, Def},
|
||||||
expr::{self, AccessorData, AnnotatedMark, ClosureData, Declarations, Expr, Field},
|
expr::{
|
||||||
|
self, AccessorData, AnnotatedMark, ClosureData, Declarations, Expr, Field,
|
||||||
|
OpaqueWrapFunctionData,
|
||||||
|
},
|
||||||
pattern::{DestructType, Pattern, RecordDestruct},
|
pattern::{DestructType, Pattern, RecordDestruct},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -220,6 +223,7 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
|
||||||
ext_var: _,
|
ext_var: _,
|
||||||
} => visitor.visit_expr(&loc_expr.value, loc_expr.region, *field_var),
|
} => visitor.visit_expr(&loc_expr.value, loc_expr.region, *field_var),
|
||||||
Expr::Accessor(AccessorData { .. }) => { /* terminal */ }
|
Expr::Accessor(AccessorData { .. }) => { /* terminal */ }
|
||||||
|
Expr::OpaqueWrapFunction(OpaqueWrapFunctionData { .. }) => { /* terminal */ }
|
||||||
Expr::Update {
|
Expr::Update {
|
||||||
record_var: _,
|
record_var: _,
|
||||||
ext_var: _,
|
ext_var: _,
|
||||||
|
|
|
@ -13,7 +13,7 @@ use roc_can::expected::PExpected;
|
||||||
use roc_can::expr::Expr::{self, *};
|
use roc_can::expr::Expr::{self, *};
|
||||||
use roc_can::expr::{
|
use roc_can::expr::{
|
||||||
AccessorData, AnnotatedMark, ClosureData, DeclarationTag, Declarations, DestructureDef, Field,
|
AccessorData, AnnotatedMark, ClosureData, DeclarationTag, Declarations, DestructureDef, Field,
|
||||||
FunctionDef, WhenBranch,
|
FunctionDef, OpaqueWrapFunctionData, WhenBranch,
|
||||||
};
|
};
|
||||||
use roc_can::pattern::Pattern;
|
use roc_can::pattern::Pattern;
|
||||||
use roc_can::traverse::symbols_introduced_from_pattern;
|
use roc_can::traverse::symbols_introduced_from_pattern;
|
||||||
|
@ -1114,6 +1114,100 @@ pub fn constrain_expr(
|
||||||
|
|
||||||
constraints.exists_many(vars, [arg_con, opaque_con, link_type_variables_con])
|
constraints.exists_many(vars, [arg_con, opaque_con, link_type_variables_con])
|
||||||
}
|
}
|
||||||
|
OpaqueWrapFunction(OpaqueWrapFunctionData {
|
||||||
|
opaque_name,
|
||||||
|
opaque_var,
|
||||||
|
specialized_def_type,
|
||||||
|
type_arguments,
|
||||||
|
lambda_set_variables,
|
||||||
|
function_name,
|
||||||
|
function_var,
|
||||||
|
argument_var,
|
||||||
|
closure_var,
|
||||||
|
}) => {
|
||||||
|
let argument_type = Type::Variable(*argument_var);
|
||||||
|
|
||||||
|
let opaque_type = Type::Alias {
|
||||||
|
symbol: *opaque_name,
|
||||||
|
type_arguments: type_arguments
|
||||||
|
.iter()
|
||||||
|
.map(|v| OptAbleType {
|
||||||
|
typ: Type::Variable(v.var),
|
||||||
|
opt_ability: v.opt_ability,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
lambda_set_variables: lambda_set_variables.clone(),
|
||||||
|
actual: Box::new(argument_type.clone()),
|
||||||
|
kind: AliasKind::Opaque,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tie the opaque type to the opaque_var
|
||||||
|
let opaque_con = constraints.equal_types_var(
|
||||||
|
*opaque_var,
|
||||||
|
Expected::NoExpectation(opaque_type),
|
||||||
|
Category::OpaqueWrap(*opaque_name),
|
||||||
|
region,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Tie the type of the value wrapped by the opaque to the opaque's type variables.
|
||||||
|
let link_type_variables_con = constraints.equal_types(
|
||||||
|
argument_type.clone(),
|
||||||
|
Expected::NoExpectation((*specialized_def_type).clone()),
|
||||||
|
Category::OpaqueArg,
|
||||||
|
region,
|
||||||
|
);
|
||||||
|
|
||||||
|
let lambda_set = Type::ClosureTag {
|
||||||
|
name: *function_name,
|
||||||
|
captures: vec![],
|
||||||
|
ambient_function: *function_var,
|
||||||
|
};
|
||||||
|
|
||||||
|
let closure_type = Type::Variable(*closure_var);
|
||||||
|
|
||||||
|
let opaque_type = Type::Variable(*opaque_var);
|
||||||
|
|
||||||
|
let function_type = Type::Function(
|
||||||
|
vec![argument_type],
|
||||||
|
Box::new(closure_type),
|
||||||
|
Box::new(opaque_type),
|
||||||
|
);
|
||||||
|
|
||||||
|
let cons = [
|
||||||
|
opaque_con,
|
||||||
|
link_type_variables_con,
|
||||||
|
constraints.equal_types_var(
|
||||||
|
*closure_var,
|
||||||
|
NoExpectation(lambda_set),
|
||||||
|
Category::OpaqueWrap(*opaque_name),
|
||||||
|
region,
|
||||||
|
),
|
||||||
|
constraints.equal_types_var(
|
||||||
|
*function_var,
|
||||||
|
Expected::NoExpectation(function_type),
|
||||||
|
Category::OpaqueWrap(*opaque_name),
|
||||||
|
region,
|
||||||
|
),
|
||||||
|
constraints.equal_types_var(
|
||||||
|
*function_var,
|
||||||
|
expected,
|
||||||
|
Category::OpaqueWrap(*opaque_name),
|
||||||
|
region,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut vars = vec![*argument_var, *opaque_var];
|
||||||
|
|
||||||
|
// Also add the fresh variables we created for the type argument and lambda sets
|
||||||
|
vars.extend(type_arguments.iter().map(|v| v.var));
|
||||||
|
vars.extend(lambda_set_variables.iter().map(|v| {
|
||||||
|
v.0.expect_variable("all lambda sets should be fresh variables here")
|
||||||
|
}));
|
||||||
|
|
||||||
|
vars.extend([*function_var, *closure_var]);
|
||||||
|
|
||||||
|
constraints.exists_many(vars, cons)
|
||||||
|
}
|
||||||
|
|
||||||
RunLowLevel { args, ret_var, op } => {
|
RunLowLevel { args, ret_var, op } => {
|
||||||
// This is a modified version of what we do for function calls.
|
// This is a modified version of what we do for function calls.
|
||||||
|
|
|
@ -3,9 +3,9 @@ use crate::llvm::bitcode::{
|
||||||
call_str_bitcode_fn, call_void_bitcode_fn,
|
call_str_bitcode_fn, call_void_bitcode_fn,
|
||||||
};
|
};
|
||||||
use crate::llvm::build_list::{
|
use crate::llvm::build_list::{
|
||||||
self, allocate_list, empty_polymorphic_list, list_append_unsafe, list_concat, list_drop_at,
|
self, allocate_list, empty_polymorphic_list, list_append_unsafe, list_capacity, list_concat,
|
||||||
list_get_unsafe, list_len, list_map, list_map2, list_map3, list_map4, list_prepend,
|
list_drop_at, list_get_unsafe, list_len, list_map, list_map2, list_map3, list_map4,
|
||||||
list_replace_unsafe, list_reserve, list_sort_with, list_sublist, list_swap,
|
list_prepend, list_replace_unsafe, list_reserve, list_sort_with, list_sublist, list_swap,
|
||||||
list_symbol_to_c_abi, list_to_c_abi, list_with_capacity, pass_update_mode,
|
list_symbol_to_c_abi, list_to_c_abi, list_with_capacity, pass_update_mode,
|
||||||
};
|
};
|
||||||
use crate::llvm::build_str::{str_from_float, str_from_int};
|
use crate::llvm::build_str::{str_from_float, str_from_int};
|
||||||
|
@ -5524,12 +5524,19 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||||
call_bitcode_fn(env, &[string, index], bitcode::STR_GET_SCALAR_UNSAFE)
|
call_bitcode_fn(env, &[string, index], bitcode::STR_GET_SCALAR_UNSAFE)
|
||||||
}
|
}
|
||||||
StrCountUtf8Bytes => {
|
StrCountUtf8Bytes => {
|
||||||
// Str.countGraphemes : Str -> Nat
|
// Str.countUtf8Bytes : Str -> Nat
|
||||||
debug_assert_eq!(args.len(), 1);
|
debug_assert_eq!(args.len(), 1);
|
||||||
|
|
||||||
let string = load_symbol(scope, &args[0]);
|
let string = load_symbol(scope, &args[0]);
|
||||||
call_bitcode_fn(env, &[string], bitcode::STR_COUNT_UTF8_BYTES)
|
call_bitcode_fn(env, &[string], bitcode::STR_COUNT_UTF8_BYTES)
|
||||||
}
|
}
|
||||||
|
StrGetCapacity => {
|
||||||
|
// Str.capacity : Str -> Nat
|
||||||
|
debug_assert_eq!(args.len(), 1);
|
||||||
|
|
||||||
|
let string = load_symbol(scope, &args[0]);
|
||||||
|
call_bitcode_fn(env, &[string], bitcode::STR_CAPACITY)
|
||||||
|
}
|
||||||
StrSubstringUnsafe => {
|
StrSubstringUnsafe => {
|
||||||
// Str.substringUnsafe : Str, Nat, Nat -> Str
|
// Str.substringUnsafe : Str, Nat, Nat -> Str
|
||||||
debug_assert_eq!(args.len(), 3);
|
debug_assert_eq!(args.len(), 3);
|
||||||
|
@ -5577,13 +5584,21 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||||
call_str_bitcode_fn(env, &[string], bitcode::STR_TRIM_RIGHT)
|
call_str_bitcode_fn(env, &[string], bitcode::STR_TRIM_RIGHT)
|
||||||
}
|
}
|
||||||
ListLen => {
|
ListLen => {
|
||||||
// List.len : List * -> Int
|
// List.len : List * -> Nat
|
||||||
debug_assert_eq!(args.len(), 1);
|
debug_assert_eq!(args.len(), 1);
|
||||||
|
|
||||||
let arg = load_symbol(scope, &args[0]);
|
let arg = load_symbol(scope, &args[0]);
|
||||||
|
|
||||||
list_len(env.builder, arg.into_struct_value()).into()
|
list_len(env.builder, arg.into_struct_value()).into()
|
||||||
}
|
}
|
||||||
|
ListGetCapacity => {
|
||||||
|
// List.capacity : List * -> Nat
|
||||||
|
debug_assert_eq!(args.len(), 1);
|
||||||
|
|
||||||
|
let arg = load_symbol(scope, &args[0]);
|
||||||
|
|
||||||
|
list_capacity(env.builder, arg.into_struct_value()).into()
|
||||||
|
}
|
||||||
ListWithCapacity => {
|
ListWithCapacity => {
|
||||||
// List.withCapacity : Nat -> List a
|
// List.withCapacity : Nat -> List a
|
||||||
debug_assert_eq!(args.len(), 1);
|
debug_assert_eq!(args.len(), 1);
|
||||||
|
|
|
@ -10,6 +10,7 @@ use crate::llvm::build_list::{layout_width, pass_as_opaque};
|
||||||
use crate::llvm::convert::{basic_type_from_layout, zig_dict_type};
|
use crate::llvm::convert::{basic_type_from_layout, zig_dict_type};
|
||||||
use crate::llvm::refcounting::Mode;
|
use crate::llvm::refcounting::Mode;
|
||||||
use inkwell::attributes::{Attribute, AttributeLoc};
|
use inkwell::attributes::{Attribute, AttributeLoc};
|
||||||
|
use inkwell::builder::Builder;
|
||||||
use inkwell::context::Context;
|
use inkwell::context::Context;
|
||||||
use inkwell::types::BasicType;
|
use inkwell::types::BasicType;
|
||||||
use inkwell::values::{BasicValue, BasicValueEnum, FunctionValue, IntValue, StructValue};
|
use inkwell::values::{BasicValue, BasicValueEnum, FunctionValue, IntValue, StructValue};
|
||||||
|
@ -679,6 +680,28 @@ pub fn dict_values<'a, 'ctx, 'env>(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Dict.capacity : Dict * * -> Nat
|
||||||
|
pub fn dict_capacity<'ctx>(
|
||||||
|
builder: &Builder<'ctx>,
|
||||||
|
wrapper_struct: StructValue<'ctx>,
|
||||||
|
) -> IntValue<'ctx> {
|
||||||
|
builder
|
||||||
|
.build_extract_value(wrapper_struct, Builtin::WRAPPER_CAPACITY, "dict_capacity")
|
||||||
|
.unwrap()
|
||||||
|
.into_int_value()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set.capacity : Set * -> Nat
|
||||||
|
pub fn set_capacity<'ctx>(
|
||||||
|
builder: &Builder<'ctx>,
|
||||||
|
wrapper_struct: StructValue<'ctx>,
|
||||||
|
) -> IntValue<'ctx> {
|
||||||
|
builder
|
||||||
|
.build_extract_value(wrapper_struct, Builtin::WRAPPER_CAPACITY, "set_capacity")
|
||||||
|
.unwrap()
|
||||||
|
.into_int_value()
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn set_from_list<'a, 'ctx, 'env>(
|
pub fn set_from_list<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
|
|
@ -351,7 +351,7 @@ fn bounds_check_comparison<'ctx>(
|
||||||
builder.build_int_compare(IntPredicate::ULT, elem_index, len, "bounds_check")
|
builder.build_int_compare(IntPredicate::ULT, elem_index, len, "bounds_check")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List.len : List elem -> Int
|
/// List.len : List * -> Nat
|
||||||
pub fn list_len<'ctx>(
|
pub fn list_len<'ctx>(
|
||||||
builder: &Builder<'ctx>,
|
builder: &Builder<'ctx>,
|
||||||
wrapper_struct: StructValue<'ctx>,
|
wrapper_struct: StructValue<'ctx>,
|
||||||
|
@ -362,6 +362,17 @@ pub fn list_len<'ctx>(
|
||||||
.into_int_value()
|
.into_int_value()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// List.capacity : List * -> Nat
|
||||||
|
pub fn list_capacity<'ctx>(
|
||||||
|
builder: &Builder<'ctx>,
|
||||||
|
wrapper_struct: StructValue<'ctx>,
|
||||||
|
) -> IntValue<'ctx> {
|
||||||
|
builder
|
||||||
|
.build_extract_value(wrapper_struct, Builtin::WRAPPER_CAPACITY, "list_capacity")
|
||||||
|
.unwrap()
|
||||||
|
.into_int_value()
|
||||||
|
}
|
||||||
|
|
||||||
/// List.sortWith : List a, (a, a -> Ordering) -> List a
|
/// List.sortWith : List a, (a, a -> Ordering) -> List a
|
||||||
pub fn list_sort_with<'a, 'ctx, 'env>(
|
pub fn list_sort_with<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
|
|
@ -243,6 +243,7 @@ impl<'a> LowLevelCall<'a> {
|
||||||
StrCountUtf8Bytes => {
|
StrCountUtf8Bytes => {
|
||||||
self.load_args_and_call_zig(backend, bitcode::STR_COUNT_UTF8_BYTES)
|
self.load_args_and_call_zig(backend, bitcode::STR_COUNT_UTF8_BYTES)
|
||||||
}
|
}
|
||||||
|
StrGetCapacity => self.load_args_and_call_zig(backend, bitcode::STR_CAPACITY),
|
||||||
StrToNum => {
|
StrToNum => {
|
||||||
let number_layout = match self.ret_layout {
|
let number_layout = match self.ret_layout {
|
||||||
Layout::Struct { field_layouts, .. } => field_layouts[0],
|
Layout::Struct { field_layouts, .. } => field_layouts[0],
|
||||||
|
@ -308,7 +309,37 @@ impl<'a> LowLevelCall<'a> {
|
||||||
let (local_id, offset) =
|
let (local_id, offset) =
|
||||||
location.local_and_offset(backend.storage.stack_frame_pointer);
|
location.local_and_offset(backend.storage.stack_frame_pointer);
|
||||||
backend.code_builder.get_local(local_id);
|
backend.code_builder.get_local(local_id);
|
||||||
backend.code_builder.i32_load(Align::Bytes4, offset + 4);
|
// List is stored as (pointer, length, capacity),
|
||||||
|
// with each of those fields being 4 bytes on wasm.
|
||||||
|
// So the length is 4 bytes after the start of the struct.
|
||||||
|
//
|
||||||
|
// WRAPPER_LEN represents the index of the length field
|
||||||
|
// (which is 1 as of the writing of this comment). If the field order
|
||||||
|
// ever changes, WRAPPER_LEN should be updated and this logic should
|
||||||
|
// continue to work even though this comment may become inaccurate.
|
||||||
|
backend
|
||||||
|
.code_builder
|
||||||
|
.i32_load(Align::Bytes4, offset + (4 * Builtin::WRAPPER_LEN));
|
||||||
|
}
|
||||||
|
_ => internal_error!("invalid storage for List"),
|
||||||
|
},
|
||||||
|
|
||||||
|
ListGetCapacity => match backend.storage.get(&self.arguments[0]) {
|
||||||
|
StoredValue::StackMemory { location, .. } => {
|
||||||
|
let (local_id, offset) =
|
||||||
|
location.local_and_offset(backend.storage.stack_frame_pointer);
|
||||||
|
backend.code_builder.get_local(local_id);
|
||||||
|
// List is stored as (pointer, length, capacity),
|
||||||
|
// with each of those fields being 4 bytes on wasm.
|
||||||
|
// So the capacity is 8 bytes after the start of the struct.
|
||||||
|
//
|
||||||
|
// WRAPPER_CAPACITY represents the index of the capacity field
|
||||||
|
// (which is 2 as of the writing of this comment). If the field order
|
||||||
|
// ever changes, WRAPPER_CAPACITY should be updated and this logic should
|
||||||
|
// continue to work even though this comment may become inaccurate.
|
||||||
|
backend
|
||||||
|
.code_builder
|
||||||
|
.i32_load(Align::Bytes4, offset + (4 * Builtin::WRAPPER_CAPACITY));
|
||||||
}
|
}
|
||||||
_ => internal_error!("invalid storage for List"),
|
_ => internal_error!("invalid storage for List"),
|
||||||
},
|
},
|
||||||
|
|
|
@ -29,6 +29,7 @@ pub enum LowLevel {
|
||||||
StrReserve,
|
StrReserve,
|
||||||
StrAppendScalar,
|
StrAppendScalar,
|
||||||
StrGetScalarUnsafe,
|
StrGetScalarUnsafe,
|
||||||
|
StrGetCapacity,
|
||||||
ListLen,
|
ListLen,
|
||||||
ListWithCapacity,
|
ListWithCapacity,
|
||||||
ListReserve,
|
ListReserve,
|
||||||
|
@ -46,6 +47,7 @@ pub enum LowLevel {
|
||||||
ListDropAt,
|
ListDropAt,
|
||||||
ListSwap,
|
ListSwap,
|
||||||
ListIsUnique,
|
ListIsUnique,
|
||||||
|
ListGetCapacity,
|
||||||
NumAdd,
|
NumAdd,
|
||||||
NumAddWrap,
|
NumAddWrap,
|
||||||
NumAddChecked,
|
NumAddChecked,
|
||||||
|
@ -246,7 +248,9 @@ map_symbol_to_lowlevel! {
|
||||||
StrAppendScalar <= STR_APPEND_SCALAR_UNSAFE,
|
StrAppendScalar <= STR_APPEND_SCALAR_UNSAFE,
|
||||||
StrGetScalarUnsafe <= STR_GET_SCALAR_UNSAFE,
|
StrGetScalarUnsafe <= STR_GET_SCALAR_UNSAFE,
|
||||||
StrToNum <= STR_TO_NUM,
|
StrToNum <= STR_TO_NUM,
|
||||||
|
StrGetCapacity <= STR_CAPACITY,
|
||||||
ListLen <= LIST_LEN,
|
ListLen <= LIST_LEN,
|
||||||
|
ListGetCapacity <= LIST_CAPACITY,
|
||||||
ListWithCapacity <= LIST_WITH_CAPACITY,
|
ListWithCapacity <= LIST_WITH_CAPACITY,
|
||||||
ListReserve <= LIST_RESERVE,
|
ListReserve <= LIST_RESERVE,
|
||||||
ListIsUnique <= LIST_IS_UNIQUE,
|
ListIsUnique <= LIST_IS_UNIQUE,
|
||||||
|
|
|
@ -1215,6 +1215,7 @@ define_builtins! {
|
||||||
46 STR_WALK_SCALARS_UNTIL: "walkScalarsUntil"
|
46 STR_WALK_SCALARS_UNTIL: "walkScalarsUntil"
|
||||||
47 STR_TO_NUM: "strToNum"
|
47 STR_TO_NUM: "strToNum"
|
||||||
48 STR_FROM_UTF8_RANGE_LOWLEVEL: "fromUtf8RangeLowlevel"
|
48 STR_FROM_UTF8_RANGE_LOWLEVEL: "fromUtf8RangeLowlevel"
|
||||||
|
49 STR_CAPACITY: "capacity"
|
||||||
}
|
}
|
||||||
5 LIST: "List" => {
|
5 LIST: "List" => {
|
||||||
0 LIST_LIST: "List" imported // the List.List type alias
|
0 LIST_LIST: "List" imported // the List.List type alias
|
||||||
|
@ -1285,6 +1286,7 @@ define_builtins! {
|
||||||
65 LIST_RESERVE: "reserve"
|
65 LIST_RESERVE: "reserve"
|
||||||
66 LIST_APPEND_UNSAFE: "appendUnsafe"
|
66 LIST_APPEND_UNSAFE: "appendUnsafe"
|
||||||
67 LIST_SUBLIST_LOWLEVEL: "sublistLowlevel"
|
67 LIST_SUBLIST_LOWLEVEL: "sublistLowlevel"
|
||||||
|
68 LIST_CAPACITY: "capacity"
|
||||||
}
|
}
|
||||||
6 RESULT: "Result" => {
|
6 RESULT: "Result" => {
|
||||||
0 RESULT_RESULT: "Result" // the Result.Result type alias
|
0 RESULT_RESULT: "Result" // the Result.Result type alias
|
||||||
|
@ -1320,6 +1322,7 @@ define_builtins! {
|
||||||
14 DICT_REMOVE_ALL: "removeAll" // difference
|
14 DICT_REMOVE_ALL: "removeAll" // difference
|
||||||
|
|
||||||
15 DICT_WITH_CAPACITY: "withCapacity"
|
15 DICT_WITH_CAPACITY: "withCapacity"
|
||||||
|
16 DICT_CAPACITY: "capacity"
|
||||||
}
|
}
|
||||||
8 SET: "Set" => {
|
8 SET: "Set" => {
|
||||||
0 SET_SET: "Set" // the Set.Set type alias
|
0 SET_SET: "Set" // the Set.Set type alias
|
||||||
|
@ -1337,6 +1340,7 @@ define_builtins! {
|
||||||
12 SET_WALK_USER_FUNCTION: "#walk_user_function"
|
12 SET_WALK_USER_FUNCTION: "#walk_user_function"
|
||||||
13 SET_CONTAINS: "contains"
|
13 SET_CONTAINS: "contains"
|
||||||
14 SET_TO_DICT: "toDict"
|
14 SET_TO_DICT: "toDict"
|
||||||
|
15 SET_CAPACITY: "capacity"
|
||||||
}
|
}
|
||||||
9 BOX: "Box" => {
|
9 BOX: "Box" => {
|
||||||
0 BOX_BOX_TYPE: "Box" imported // the Box.Box opaque type
|
0 BOX_BOX_TYPE: "Box" imported // the Box.Box opaque type
|
||||||
|
|
|
@ -874,9 +874,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
||||||
// - other refcounted arguments are Borrowed
|
// - other refcounted arguments are Borrowed
|
||||||
match op {
|
match op {
|
||||||
Unreachable => arena.alloc_slice_copy(&[irrelevant]),
|
Unreachable => arena.alloc_slice_copy(&[irrelevant]),
|
||||||
ListLen | StrIsEmpty | StrToScalars | StrCountGraphemes | StrCountUtf8Bytes => {
|
ListLen | StrIsEmpty | StrToScalars | StrCountGraphemes | StrCountUtf8Bytes
|
||||||
arena.alloc_slice_copy(&[borrowed])
|
| StrGetCapacity | ListGetCapacity => arena.alloc_slice_copy(&[borrowed]),
|
||||||
}
|
|
||||||
ListWithCapacity => arena.alloc_slice_copy(&[irrelevant]),
|
ListWithCapacity => arena.alloc_slice_copy(&[irrelevant]),
|
||||||
ListReplaceUnsafe => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
|
ListReplaceUnsafe => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
|
||||||
StrGetUnsafe | ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
StrGetUnsafe | ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use roc_can::{
|
use roc_can::{
|
||||||
def::Def,
|
def::Def,
|
||||||
expr::{AccessorData, ClosureData, Expr, Field, WhenBranch},
|
expr::{AccessorData, ClosureData, Expr, Field, OpaqueWrapFunctionData, WhenBranch},
|
||||||
};
|
};
|
||||||
use roc_module::ident::{Lowercase, TagName};
|
use roc_module::ident::{Lowercase, TagName};
|
||||||
use roc_types::{
|
use roc_types::{
|
||||||
|
@ -543,6 +543,29 @@ fn deep_copy_type_vars_into_expr_help<C: CopyEnv>(
|
||||||
lambda_set_variables: lambda_set_variables.clone(),
|
lambda_set_variables: lambda_set_variables.clone(),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
OpaqueWrapFunction(OpaqueWrapFunctionData {
|
||||||
|
opaque_name,
|
||||||
|
opaque_var,
|
||||||
|
specialized_def_type,
|
||||||
|
type_arguments,
|
||||||
|
lambda_set_variables,
|
||||||
|
function_name,
|
||||||
|
function_var,
|
||||||
|
argument_var,
|
||||||
|
closure_var,
|
||||||
|
}) => OpaqueWrapFunction(OpaqueWrapFunctionData {
|
||||||
|
opaque_name: *opaque_name,
|
||||||
|
opaque_var: sub!(*opaque_var),
|
||||||
|
function_name: *function_name,
|
||||||
|
function_var: sub!(*function_var),
|
||||||
|
argument_var: sub!(*argument_var),
|
||||||
|
closure_var: sub!(*closure_var),
|
||||||
|
// The following three are only used for constraining
|
||||||
|
specialized_def_type: specialized_def_type.clone(),
|
||||||
|
type_arguments: type_arguments.clone(),
|
||||||
|
lambda_set_variables: lambda_set_variables.clone(),
|
||||||
|
}),
|
||||||
|
|
||||||
Expect {
|
Expect {
|
||||||
loc_condition,
|
loc_condition,
|
||||||
loc_continuation,
|
loc_continuation,
|
||||||
|
|
|
@ -4387,6 +4387,60 @@ pub fn with_hole<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OpaqueWrapFunction(wrap_fn_data) => {
|
||||||
|
let opaque_var = wrap_fn_data.opaque_var;
|
||||||
|
let arg_symbol = env.unique_symbol();
|
||||||
|
|
||||||
|
let ClosureData {
|
||||||
|
name,
|
||||||
|
function_type,
|
||||||
|
arguments,
|
||||||
|
loc_body,
|
||||||
|
..
|
||||||
|
} = wrap_fn_data.to_closure_data(arg_symbol);
|
||||||
|
|
||||||
|
match procs.insert_anonymous(
|
||||||
|
env,
|
||||||
|
LambdaName::no_niche(name),
|
||||||
|
function_type,
|
||||||
|
arguments,
|
||||||
|
*loc_body,
|
||||||
|
CapturedSymbols::None,
|
||||||
|
opaque_var,
|
||||||
|
layout_cache,
|
||||||
|
) {
|
||||||
|
Ok(_) => {
|
||||||
|
let raw_layout = return_on_layout_error!(
|
||||||
|
env,
|
||||||
|
layout_cache.raw_from_var(env.arena, function_type, env.subs),
|
||||||
|
"Expr::OpaqueWrapFunction"
|
||||||
|
);
|
||||||
|
|
||||||
|
match raw_layout {
|
||||||
|
RawFunctionLayout::Function(_, lambda_set, _) => {
|
||||||
|
let lambda_name =
|
||||||
|
find_lambda_name(env, layout_cache, lambda_set, name, &[]);
|
||||||
|
construct_closure_data(
|
||||||
|
env,
|
||||||
|
lambda_set,
|
||||||
|
lambda_name,
|
||||||
|
&[],
|
||||||
|
assigned,
|
||||||
|
hole,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
RawFunctionLayout::ZeroArgumentThunk(_) => {
|
||||||
|
internal_error!("should not be a thunk!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(_error) => Stmt::RuntimeError(
|
||||||
|
"TODO convert anonymous function error to a RuntimeError string",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Update {
|
Update {
|
||||||
record_var,
|
record_var,
|
||||||
symbol: structure,
|
symbol: structure,
|
||||||
|
|
|
@ -1809,7 +1809,7 @@ impl<'a> Builtin<'a> {
|
||||||
pub const STR_WORDS: u32 = 3;
|
pub const STR_WORDS: u32 = 3;
|
||||||
pub const LIST_WORDS: u32 = 3;
|
pub const LIST_WORDS: u32 = 3;
|
||||||
|
|
||||||
/// Layout of collection wrapper for List and Str - a struct of (pointer, length, capacity).
|
/// Layout of collection wrapper for List, Str, Dict, and Set - a struct of (pointer, length, capacity).
|
||||||
pub const WRAPPER_PTR: u32 = 0;
|
pub const WRAPPER_PTR: u32 = 0;
|
||||||
pub const WRAPPER_LEN: u32 = 1;
|
pub const WRAPPER_LEN: u32 = 1;
|
||||||
pub const WRAPPER_CAPACITY: u32 = 2;
|
pub const WRAPPER_CAPACITY: u32 = 2;
|
||||||
|
|
|
@ -7341,4 +7341,30 @@ mod solve_expr {
|
||||||
"Rose I64",
|
"Rose I64",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn opaque_wrap_function() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
A := U8
|
||||||
|
List.map [1, 2, 3] @A
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"List A",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn opaque_wrap_function_with_inferred_arg() {
|
||||||
|
infer_eq_without_problem(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
A a := a
|
||||||
|
List.map [1u8, 2u8, 3u8] @A
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"List (A U8)",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use roc_can::def::Def;
|
use roc_can::def::Def;
|
||||||
use roc_can::expr::Expr::{self, *};
|
use roc_can::expr::Expr::{self, *};
|
||||||
use roc_can::expr::{ClosureData, WhenBranch};
|
use roc_can::expr::{ClosureData, OpaqueWrapFunctionData, WhenBranch};
|
||||||
use roc_can::pattern::{Pattern, RecordDestruct};
|
use roc_can::pattern::{Pattern, RecordDestruct};
|
||||||
|
|
||||||
use roc_module::symbol::Interns;
|
use roc_module::symbol::Interns;
|
||||||
|
@ -191,6 +191,9 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
|
||||||
} => expr(c, CallArg, f, &loc_expr.value)
|
} => expr(c, CallArg, f, &loc_expr.value)
|
||||||
.append(f.text(format!(".{}", field.as_str())))
|
.append(f.text(format!(".{}", field.as_str())))
|
||||||
.group(),
|
.group(),
|
||||||
|
OpaqueWrapFunction(OpaqueWrapFunctionData { opaque_name, .. }) => {
|
||||||
|
f.text(format!("@{}", opaque_name.as_str(c.interns)))
|
||||||
|
}
|
||||||
Accessor(_) => todo!(),
|
Accessor(_) => todo!(),
|
||||||
Update { .. } => todo!(),
|
Update { .. } => todo!(),
|
||||||
Tag { .. } => todo!(),
|
Tag { .. } => todo!(),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#![cfg(feature = "gen-llvm")]
|
#![cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
|
|
||||||
#[cfg(feature = "gen-llvm")]
|
#[cfg(feature = "gen-llvm")]
|
||||||
use crate::helpers::llvm::assert_evals_to;
|
use crate::helpers::llvm::assert_evals_to;
|
||||||
|
@ -6,8 +6,8 @@ use crate::helpers::llvm::assert_evals_to;
|
||||||
// #[cfg(feature = "gen-dev")]
|
// #[cfg(feature = "gen-dev")]
|
||||||
// use crate::helpers::dev::assert_evals_to;
|
// use crate::helpers::dev::assert_evals_to;
|
||||||
|
|
||||||
// #[cfg(feature = "gen-wasm")]
|
#[cfg(feature = "gen-wasm")]
|
||||||
// use crate::helpers::wasm::assert_evals_to;
|
use crate::helpers::wasm::assert_evals_to;
|
||||||
|
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use roc_std::{RocList, RocStr};
|
use roc_std::{RocList, RocStr};
|
||||||
|
@ -158,7 +158,7 @@ fn dict_nonempty_get() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn keys() {
|
fn keys() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -180,7 +180,7 @@ fn keys() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn values() {
|
fn values() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -202,7 +202,7 @@ fn values() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn from_list_with_fold_simple() {
|
fn from_list_with_fold_simple() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -221,7 +221,7 @@ fn from_list_with_fold_simple() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn from_list_with_fold_reallocates() {
|
fn from_list_with_fold_reallocates() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -251,7 +251,7 @@ fn from_list_with_fold_reallocates() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn small_str_keys() {
|
fn small_str_keys() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -273,7 +273,7 @@ fn small_str_keys() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn big_str_keys() {
|
fn big_str_keys() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -298,7 +298,7 @@ fn big_str_keys() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn big_str_values() {
|
fn big_str_values() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -381,7 +381,7 @@ fn insert_all() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn insert_all_prefer_first() {
|
fn insert_all_prefer_first() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -431,7 +431,7 @@ fn keep_shared() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn keep_shared_prefer_first() {
|
fn keep_shared_prefer_first() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
procedure Dict.1 ():
|
procedure Dict.1 ():
|
||||||
let Dict.101 : List {[], []} = Array [];
|
let Dict.102 : List {[], []} = Array [];
|
||||||
ret Dict.101;
|
ret Dict.102;
|
||||||
|
|
||||||
procedure Dict.7 (Dict.95):
|
procedure Dict.7 (Dict.96):
|
||||||
let Dict.100 : U64 = CallByName List.6 Dict.95;
|
let Dict.101 : U64 = CallByName List.6 Dict.96;
|
||||||
ret Dict.100;
|
ret Dict.101;
|
||||||
|
|
||||||
procedure List.6 (#Attr.2):
|
procedure List.6 (#Attr.2):
|
||||||
let List.295 : U64 = lowlevel ListLen #Attr.2;
|
let List.295 : U64 = lowlevel ListLen #Attr.2;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
procedure List.2 (List.78, List.79):
|
procedure List.2 (List.79, List.80):
|
||||||
let List.300 : U64 = CallByName List.6 List.78;
|
let List.300 : U64 = CallByName List.6 List.79;
|
||||||
let List.297 : Int1 = CallByName Num.22 List.79 List.300;
|
let List.297 : Int1 = CallByName Num.22 List.80 List.300;
|
||||||
if List.297 then
|
if List.297 then
|
||||||
let List.299 : {} = CallByName List.60 List.78 List.79;
|
let List.299 : {} = CallByName List.60 List.79 List.80;
|
||||||
let List.298 : [C {}, C {}] = TagId(1) List.299;
|
let List.298 : [C {}, C {}] = TagId(1) List.299;
|
||||||
ret List.298;
|
ret List.298;
|
||||||
else
|
else
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
procedure List.4 (List.89, List.90):
|
procedure List.4 (List.90, List.91):
|
||||||
let List.297 : U64 = 1i64;
|
let List.297 : U64 = 1i64;
|
||||||
let List.296 : List U8 = CallByName List.65 List.89 List.297;
|
let List.296 : List U8 = CallByName List.65 List.90 List.297;
|
||||||
let List.295 : List U8 = CallByName List.66 List.296 List.90;
|
let List.295 : List U8 = CallByName List.66 List.296 List.91;
|
||||||
ret List.295;
|
ret List.295;
|
||||||
|
|
||||||
procedure List.65 (#Attr.2, #Attr.3):
|
procedure List.65 (#Attr.2, #Attr.3):
|
||||||
|
|
|
@ -2,11 +2,11 @@ procedure Bool.7 (#Attr.2, #Attr.3):
|
||||||
let Bool.9 : Int1 = lowlevel Eq #Attr.2 #Attr.3;
|
let Bool.9 : Int1 = lowlevel Eq #Attr.2 #Attr.3;
|
||||||
ret Bool.9;
|
ret Bool.9;
|
||||||
|
|
||||||
procedure List.2 (List.78, List.79):
|
procedure List.2 (List.79, List.80):
|
||||||
let List.309 : U64 = CallByName List.6 List.78;
|
let List.309 : U64 = CallByName List.6 List.79;
|
||||||
let List.305 : Int1 = CallByName Num.22 List.79 List.309;
|
let List.305 : Int1 = CallByName Num.22 List.80 List.309;
|
||||||
if List.305 then
|
if List.305 then
|
||||||
let List.307 : I64 = CallByName List.60 List.78 List.79;
|
let List.307 : I64 = CallByName List.60 List.79 List.80;
|
||||||
let List.306 : [C {}, C I64] = TagId(1) List.307;
|
let List.306 : [C {}, C I64] = TagId(1) List.307;
|
||||||
ret List.306;
|
ret List.306;
|
||||||
else
|
else
|
||||||
|
@ -22,15 +22,15 @@ procedure List.60 (#Attr.2, #Attr.3):
|
||||||
let List.308 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
|
let List.308 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
|
||||||
ret List.308;
|
ret List.308;
|
||||||
|
|
||||||
procedure List.9 (List.206):
|
procedure List.9 (List.207):
|
||||||
let List.302 : U64 = 0i64;
|
let List.302 : U64 = 0i64;
|
||||||
let List.295 : [C {}, C I64] = CallByName List.2 List.206 List.302;
|
let List.295 : [C {}, C I64] = CallByName List.2 List.207 List.302;
|
||||||
let List.299 : U8 = 1i64;
|
let List.299 : U8 = 1i64;
|
||||||
let List.300 : U8 = GetTagId List.295;
|
let List.300 : U8 = GetTagId List.295;
|
||||||
let List.301 : Int1 = lowlevel Eq List.299 List.300;
|
let List.301 : Int1 = lowlevel Eq List.299 List.300;
|
||||||
if List.301 then
|
if List.301 then
|
||||||
let List.207 : I64 = UnionAtIndex (Id 1) (Index 0) List.295;
|
let List.208 : I64 = UnionAtIndex (Id 1) (Index 0) List.295;
|
||||||
let List.296 : [C Int1, C I64] = TagId(1) List.207;
|
let List.296 : [C Int1, C I64] = TagId(1) List.208;
|
||||||
ret List.296;
|
ret List.296;
|
||||||
else
|
else
|
||||||
let List.298 : Int1 = true;
|
let List.298 : Int1 = true;
|
||||||
|
@ -41,27 +41,27 @@ procedure Num.22 (#Attr.2, #Attr.3):
|
||||||
let Num.257 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
|
let Num.257 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
|
||||||
ret Num.257;
|
ret Num.257;
|
||||||
|
|
||||||
procedure Str.27 (Str.88):
|
procedure Str.27 (Str.89):
|
||||||
let Str.194 : [C Int1, C I64] = CallByName Str.67 Str.88;
|
let Str.195 : [C Int1, C I64] = CallByName Str.68 Str.89;
|
||||||
ret Str.194;
|
ret Str.195;
|
||||||
|
|
||||||
procedure Str.47 (#Attr.2):
|
procedure Str.47 (#Attr.2):
|
||||||
let Str.202 : {I64, U8} = lowlevel StrToNum #Attr.2;
|
let Str.203 : {I64, U8} = lowlevel StrToNum #Attr.2;
|
||||||
ret Str.202;
|
ret Str.203;
|
||||||
|
|
||||||
procedure Str.67 (Str.189):
|
procedure Str.68 (Str.190):
|
||||||
let Str.190 : {I64, U8} = CallByName Str.47 Str.189;
|
let Str.191 : {I64, U8} = CallByName Str.47 Str.190;
|
||||||
let Str.200 : U8 = StructAtIndex 1 Str.190;
|
let Str.201 : U8 = StructAtIndex 1 Str.191;
|
||||||
let Str.201 : U8 = 0i64;
|
let Str.202 : U8 = 0i64;
|
||||||
let Str.197 : Int1 = CallByName Bool.7 Str.200 Str.201;
|
let Str.198 : Int1 = CallByName Bool.7 Str.201 Str.202;
|
||||||
if Str.197 then
|
if Str.198 then
|
||||||
let Str.199 : I64 = StructAtIndex 0 Str.190;
|
let Str.200 : I64 = StructAtIndex 0 Str.191;
|
||||||
let Str.198 : [C Int1, C I64] = TagId(1) Str.199;
|
let Str.199 : [C Int1, C I64] = TagId(1) Str.200;
|
||||||
ret Str.198;
|
ret Str.199;
|
||||||
else
|
else
|
||||||
let Str.196 : Int1 = false;
|
let Str.197 : Int1 = false;
|
||||||
let Str.195 : [C Int1, C I64] = TagId(0) Str.196;
|
let Str.196 : [C Int1, C I64] = TagId(0) Str.197;
|
||||||
ret Str.195;
|
ret Str.196;
|
||||||
|
|
||||||
procedure Test.0 ():
|
procedure Test.0 ():
|
||||||
let Test.4 : Int1 = true;
|
let Test.4 : Int1 = true;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
procedure List.4 (List.89, List.90):
|
procedure List.4 (List.90, List.91):
|
||||||
let List.297 : U64 = 1i64;
|
let List.297 : U64 = 1i64;
|
||||||
let List.296 : List I64 = CallByName List.65 List.89 List.297;
|
let List.296 : List I64 = CallByName List.65 List.90 List.297;
|
||||||
let List.295 : List I64 = CallByName List.66 List.296 List.90;
|
let List.295 : List I64 = CallByName List.66 List.296 List.91;
|
||||||
ret List.295;
|
ret List.295;
|
||||||
|
|
||||||
procedure List.65 (#Attr.2, #Attr.3):
|
procedure List.65 (#Attr.2, #Attr.3):
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
procedure List.4 (List.89, List.90):
|
procedure List.4 (List.90, List.91):
|
||||||
let List.297 : U64 = 1i64;
|
let List.297 : U64 = 1i64;
|
||||||
let List.296 : List I64 = CallByName List.65 List.89 List.297;
|
let List.296 : List I64 = CallByName List.65 List.90 List.297;
|
||||||
let List.295 : List I64 = CallByName List.66 List.296 List.90;
|
let List.295 : List I64 = CallByName List.66 List.296 List.91;
|
||||||
ret List.295;
|
ret List.295;
|
||||||
|
|
||||||
procedure List.65 (#Attr.2, #Attr.3):
|
procedure List.65 (#Attr.2, #Attr.3):
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
procedure List.3 (List.86, List.87, List.88):
|
procedure List.3 (List.87, List.88, List.89):
|
||||||
let List.298 : {List I64, I64} = CallByName List.57 List.86 List.87 List.88;
|
let List.298 : {List I64, I64} = CallByName List.57 List.87 List.88 List.89;
|
||||||
let List.297 : List I64 = StructAtIndex 0 List.298;
|
let List.297 : List I64 = StructAtIndex 0 List.298;
|
||||||
inc List.297;
|
inc List.297;
|
||||||
dec List.298;
|
dec List.298;
|
||||||
ret List.297;
|
ret List.297;
|
||||||
|
|
||||||
procedure List.57 (List.83, List.84, List.85):
|
procedure List.57 (List.84, List.85, List.86):
|
||||||
let List.303 : U64 = CallByName List.6 List.83;
|
let List.303 : U64 = CallByName List.6 List.84;
|
||||||
let List.300 : Int1 = CallByName Num.22 List.84 List.303;
|
let List.300 : Int1 = CallByName Num.22 List.85 List.303;
|
||||||
if List.300 then
|
if List.300 then
|
||||||
let List.301 : {List I64, I64} = CallByName List.61 List.83 List.84 List.85;
|
let List.301 : {List I64, I64} = CallByName List.61 List.84 List.85 List.86;
|
||||||
ret List.301;
|
ret List.301;
|
||||||
else
|
else
|
||||||
let List.299 : {List I64, I64} = Struct {List.83, List.85};
|
let List.299 : {List I64, I64} = Struct {List.84, List.86};
|
||||||
ret List.299;
|
ret List.299;
|
||||||
|
|
||||||
procedure List.6 (#Attr.2):
|
procedure List.6 (#Attr.2):
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
procedure List.2 (List.78, List.79):
|
procedure List.2 (List.79, List.80):
|
||||||
let List.300 : U64 = CallByName List.6 List.78;
|
let List.300 : U64 = CallByName List.6 List.79;
|
||||||
let List.297 : Int1 = CallByName Num.22 List.79 List.300;
|
let List.297 : Int1 = CallByName Num.22 List.80 List.300;
|
||||||
if List.297 then
|
if List.297 then
|
||||||
let List.299 : I64 = CallByName List.60 List.78 List.79;
|
let List.299 : I64 = CallByName List.60 List.79 List.80;
|
||||||
let List.298 : [C {}, C I64] = TagId(1) List.299;
|
let List.298 : [C {}, C I64] = TagId(1) List.299;
|
||||||
ret List.298;
|
ret List.298;
|
||||||
else
|
else
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
procedure List.2 (List.78, List.79):
|
procedure List.2 (List.79, List.80):
|
||||||
let List.300 : U64 = CallByName List.6 List.78;
|
let List.300 : U64 = CallByName List.6 List.79;
|
||||||
let List.297 : Int1 = CallByName Num.22 List.79 List.300;
|
let List.297 : Int1 = CallByName Num.22 List.80 List.300;
|
||||||
if List.297 then
|
if List.297 then
|
||||||
let List.299 : Str = CallByName List.60 List.78 List.79;
|
let List.299 : Str = CallByName List.60 List.79 List.80;
|
||||||
let List.298 : [C {}, C Str] = TagId(1) List.299;
|
let List.298 : [C {}, C Str] = TagId(1) List.299;
|
||||||
ret List.298;
|
ret List.298;
|
||||||
else
|
else
|
||||||
|
@ -27,12 +27,12 @@ procedure Num.22 (#Attr.2, #Attr.3):
|
||||||
ret Num.257;
|
ret Num.257;
|
||||||
|
|
||||||
procedure Str.16 (#Attr.2, #Attr.3):
|
procedure Str.16 (#Attr.2, #Attr.3):
|
||||||
let Str.194 : Str = lowlevel StrRepeat #Attr.2 #Attr.3;
|
let Str.195 : Str = lowlevel StrRepeat #Attr.2 #Attr.3;
|
||||||
ret Str.194;
|
ret Str.195;
|
||||||
|
|
||||||
procedure Str.3 (#Attr.2, #Attr.3):
|
procedure Str.3 (#Attr.2, #Attr.3):
|
||||||
let Str.195 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
|
let Str.196 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
|
||||||
ret Str.195;
|
ret Str.196;
|
||||||
|
|
||||||
procedure Test.1 ():
|
procedure Test.1 ():
|
||||||
let Test.21 : Str = "lllllllllllllllllllllooooooooooong";
|
let Test.21 : Str = "lllllllllllllllllllllooooooooooong";
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
procedure List.2 (List.78, List.79):
|
procedure List.2 (List.79, List.80):
|
||||||
let List.300 : U64 = CallByName List.6 List.78;
|
let List.300 : U64 = CallByName List.6 List.79;
|
||||||
let List.297 : Int1 = CallByName Num.22 List.79 List.300;
|
let List.297 : Int1 = CallByName Num.22 List.80 List.300;
|
||||||
if List.297 then
|
if List.297 then
|
||||||
let List.299 : Str = CallByName List.60 List.78 List.79;
|
let List.299 : Str = CallByName List.60 List.79 List.80;
|
||||||
let List.298 : [C {}, C Str] = TagId(1) List.299;
|
let List.298 : [C {}, C Str] = TagId(1) List.299;
|
||||||
ret List.298;
|
ret List.298;
|
||||||
else
|
else
|
||||||
|
@ -29,8 +29,8 @@ procedure Num.22 (#Attr.2, #Attr.3):
|
||||||
ret Num.257;
|
ret Num.257;
|
||||||
|
|
||||||
procedure Str.3 (#Attr.2, #Attr.3):
|
procedure Str.3 (#Attr.2, #Attr.3):
|
||||||
let Str.195 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
|
let Str.196 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
|
||||||
ret Str.195;
|
ret Str.196;
|
||||||
|
|
||||||
procedure Test.1 ():
|
procedure Test.1 ():
|
||||||
let Test.21 : Str = "lllllllllllllllllllllooooooooooong";
|
let Test.21 : Str = "lllllllllllllllllllllooooooooooong";
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
procedure List.3 (List.86, List.87, List.88):
|
procedure List.3 (List.87, List.88, List.89):
|
||||||
let List.296 : {List I64, I64} = CallByName List.57 List.86 List.87 List.88;
|
let List.296 : {List I64, I64} = CallByName List.57 List.87 List.88 List.89;
|
||||||
let List.295 : List I64 = StructAtIndex 0 List.296;
|
let List.295 : List I64 = StructAtIndex 0 List.296;
|
||||||
inc List.295;
|
inc List.295;
|
||||||
dec List.296;
|
dec List.296;
|
||||||
ret List.295;
|
ret List.295;
|
||||||
|
|
||||||
procedure List.57 (List.83, List.84, List.85):
|
procedure List.57 (List.84, List.85, List.86):
|
||||||
let List.301 : U64 = CallByName List.6 List.83;
|
let List.301 : U64 = CallByName List.6 List.84;
|
||||||
let List.298 : Int1 = CallByName Num.22 List.84 List.301;
|
let List.298 : Int1 = CallByName Num.22 List.85 List.301;
|
||||||
if List.298 then
|
if List.298 then
|
||||||
let List.299 : {List I64, I64} = CallByName List.61 List.83 List.84 List.85;
|
let List.299 : {List I64, I64} = CallByName List.61 List.84 List.85 List.86;
|
||||||
ret List.299;
|
ret List.299;
|
||||||
else
|
else
|
||||||
let List.297 : {List I64, I64} = Struct {List.83, List.85};
|
let List.297 : {List I64, I64} = Struct {List.84, List.86};
|
||||||
ret List.297;
|
ret List.297;
|
||||||
|
|
||||||
procedure List.6 (#Attr.2):
|
procedure List.6 (#Attr.2):
|
||||||
|
|
|
@ -7,9 +7,9 @@ procedure List.28 (#Attr.2, #Attr.3):
|
||||||
decref #Attr.2;
|
decref #Attr.2;
|
||||||
ret List.297;
|
ret List.297;
|
||||||
|
|
||||||
procedure List.54 (List.201):
|
procedure List.54 (List.202):
|
||||||
let List.296 : {} = Struct {};
|
let List.296 : {} = Struct {};
|
||||||
let List.295 : List I64 = CallByName List.28 List.201 List.296;
|
let List.295 : List I64 = CallByName List.28 List.202 List.296;
|
||||||
ret List.295;
|
ret List.295;
|
||||||
|
|
||||||
procedure Num.46 (#Attr.2, #Attr.3):
|
procedure Num.46 (#Attr.2, #Attr.3):
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
procedure List.2 (List.78, List.79):
|
procedure List.2 (List.79, List.80):
|
||||||
let List.310 : U64 = CallByName List.6 List.78;
|
let List.310 : U64 = CallByName List.6 List.79;
|
||||||
let List.307 : Int1 = CallByName Num.22 List.79 List.310;
|
let List.307 : Int1 = CallByName Num.22 List.80 List.310;
|
||||||
if List.307 then
|
if List.307 then
|
||||||
let List.309 : I64 = CallByName List.60 List.78 List.79;
|
let List.309 : I64 = CallByName List.60 List.79 List.80;
|
||||||
let List.308 : [C {}, C I64] = TagId(1) List.309;
|
let List.308 : [C {}, C I64] = TagId(1) List.309;
|
||||||
ret List.308;
|
ret List.308;
|
||||||
else
|
else
|
||||||
|
@ -10,21 +10,21 @@ procedure List.2 (List.78, List.79):
|
||||||
let List.305 : [C {}, C I64] = TagId(0) List.306;
|
let List.305 : [C {}, C I64] = TagId(0) List.306;
|
||||||
ret List.305;
|
ret List.305;
|
||||||
|
|
||||||
procedure List.3 (List.86, List.87, List.88):
|
procedure List.3 (List.87, List.88, List.89):
|
||||||
let List.298 : {List I64, I64} = CallByName List.57 List.86 List.87 List.88;
|
let List.298 : {List I64, I64} = CallByName List.57 List.87 List.88 List.89;
|
||||||
let List.297 : List I64 = StructAtIndex 0 List.298;
|
let List.297 : List I64 = StructAtIndex 0 List.298;
|
||||||
inc List.297;
|
inc List.297;
|
||||||
dec List.298;
|
dec List.298;
|
||||||
ret List.297;
|
ret List.297;
|
||||||
|
|
||||||
procedure List.57 (List.83, List.84, List.85):
|
procedure List.57 (List.84, List.85, List.86):
|
||||||
let List.315 : U64 = CallByName List.6 List.83;
|
let List.315 : U64 = CallByName List.6 List.84;
|
||||||
let List.312 : Int1 = CallByName Num.22 List.84 List.315;
|
let List.312 : Int1 = CallByName Num.22 List.85 List.315;
|
||||||
if List.312 then
|
if List.312 then
|
||||||
let List.313 : {List I64, I64} = CallByName List.61 List.83 List.84 List.85;
|
let List.313 : {List I64, I64} = CallByName List.61 List.84 List.85 List.86;
|
||||||
ret List.313;
|
ret List.313;
|
||||||
else
|
else
|
||||||
let List.311 : {List I64, I64} = Struct {List.83, List.85};
|
let List.311 : {List I64, I64} = Struct {List.84, List.86};
|
||||||
ret List.311;
|
ret List.311;
|
||||||
|
|
||||||
procedure List.6 (#Attr.2):
|
procedure List.6 (#Attr.2):
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
procedure List.2 (List.78, List.79):
|
procedure List.2 (List.79, List.80):
|
||||||
let List.310 : U64 = CallByName List.6 List.78;
|
let List.310 : U64 = CallByName List.6 List.79;
|
||||||
let List.307 : Int1 = CallByName Num.22 List.79 List.310;
|
let List.307 : Int1 = CallByName Num.22 List.80 List.310;
|
||||||
if List.307 then
|
if List.307 then
|
||||||
let List.309 : I64 = CallByName List.60 List.78 List.79;
|
let List.309 : I64 = CallByName List.60 List.79 List.80;
|
||||||
let List.308 : [C {}, C I64] = TagId(1) List.309;
|
let List.308 : [C {}, C I64] = TagId(1) List.309;
|
||||||
ret List.308;
|
ret List.308;
|
||||||
else
|
else
|
||||||
|
@ -10,21 +10,21 @@ procedure List.2 (List.78, List.79):
|
||||||
let List.305 : [C {}, C I64] = TagId(0) List.306;
|
let List.305 : [C {}, C I64] = TagId(0) List.306;
|
||||||
ret List.305;
|
ret List.305;
|
||||||
|
|
||||||
procedure List.3 (List.86, List.87, List.88):
|
procedure List.3 (List.87, List.88, List.89):
|
||||||
let List.298 : {List I64, I64} = CallByName List.57 List.86 List.87 List.88;
|
let List.298 : {List I64, I64} = CallByName List.57 List.87 List.88 List.89;
|
||||||
let List.297 : List I64 = StructAtIndex 0 List.298;
|
let List.297 : List I64 = StructAtIndex 0 List.298;
|
||||||
inc List.297;
|
inc List.297;
|
||||||
dec List.298;
|
dec List.298;
|
||||||
ret List.297;
|
ret List.297;
|
||||||
|
|
||||||
procedure List.57 (List.83, List.84, List.85):
|
procedure List.57 (List.84, List.85, List.86):
|
||||||
let List.315 : U64 = CallByName List.6 List.83;
|
let List.315 : U64 = CallByName List.6 List.84;
|
||||||
let List.312 : Int1 = CallByName Num.22 List.84 List.315;
|
let List.312 : Int1 = CallByName Num.22 List.85 List.315;
|
||||||
if List.312 then
|
if List.312 then
|
||||||
let List.313 : {List I64, I64} = CallByName List.61 List.83 List.84 List.85;
|
let List.313 : {List I64, I64} = CallByName List.61 List.84 List.85 List.86;
|
||||||
ret List.313;
|
ret List.313;
|
||||||
else
|
else
|
||||||
let List.311 : {List I64, I64} = Struct {List.83, List.85};
|
let List.311 : {List I64, I64} = Struct {List.84, List.86};
|
||||||
ret List.311;
|
ret List.311;
|
||||||
|
|
||||||
procedure List.6 (#Attr.2):
|
procedure List.6 (#Attr.2):
|
||||||
|
|
|
@ -12,7 +12,7 @@ pub static WILDCARD: &str = "*";
|
||||||
static EMPTY_RECORD: &str = "{}";
|
static EMPTY_RECORD: &str = "{}";
|
||||||
static EMPTY_TAG_UNION: &str = "[]";
|
static EMPTY_TAG_UNION: &str = "[]";
|
||||||
|
|
||||||
/// Rerquirements for parentheses.
|
/// Requirements for parentheses.
|
||||||
///
|
///
|
||||||
/// If we're inside a function (that is, this is either an argument or a return
|
/// If we're inside a function (that is, this is either an argument or a return
|
||||||
/// value), we may need to use parens. Examples:
|
/// value), we may need to use parens. Examples:
|
||||||
|
|
|
@ -1179,3 +1179,16 @@ fn render_nullable_unwrapped_passing_through_alias() {
|
||||||
"Cons (L (Cons (L (Cons (L Nil))))) : DeepList",
|
"Cons (L (Cons (L (Cons (L Nil))))) : DeepList",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn opaque_wrap_function() {
|
||||||
|
expect_success(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
A a := a
|
||||||
|
List.map [1u8, 2u8, 3u8] @A
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
"[1, 2, 3] : List (A U8)",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -7483,14 +7483,14 @@ All branches in an `if` must have the same type!
|
||||||
// and checking it during can. The reason the error appears is because it is parsed as
|
// and checking it during can. The reason the error appears is because it is parsed as
|
||||||
// Apply(Error(OtherModule), [@Age, 21])
|
// Apply(Error(OtherModule), [@Age, 21])
|
||||||
@r###"
|
@r###"
|
||||||
── OPAQUE TYPE NOT APPLIED ─────────────────────────────── /code/proj/Main.roc ─
|
── OPAQUE TYPE NOT DEFINED ─────────────────────────────── /code/proj/Main.roc ─
|
||||||
|
|
||||||
This opaque type is not applied to an argument:
|
The opaque type Age referenced here is not defined:
|
||||||
|
|
||||||
4│ OtherModule.@Age 21
|
4│ OtherModule.@Age 21
|
||||||
^^^^
|
^^^^
|
||||||
|
|
||||||
Note: Opaque types always wrap exactly one argument!
|
Note: It looks like there are no opaque types declared in this scope yet!
|
||||||
|
|
||||||
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
|
── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─
|
||||||
|
|
||||||
|
@ -9551,4 +9551,30 @@ All branches in an `if` must have the same type!
|
||||||
an instance of this opaque type by doing @Age 23.
|
an instance of this opaque type by doing @Age 23.
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test_report!(
|
||||||
|
opaque_wrap_function_mismatch,
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
A := U8
|
||||||
|
List.map [1u16, 2u16, 3u16] @A
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
@r###"
|
||||||
|
── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─
|
||||||
|
|
||||||
|
The 2nd argument to `map` is not what I expect:
|
||||||
|
|
||||||
|
5│ List.map [1u16, 2u16, 3u16] @A
|
||||||
|
^^
|
||||||
|
|
||||||
|
This A opaque wrapping has the type:
|
||||||
|
|
||||||
|
U8 -> A
|
||||||
|
|
||||||
|
But `map` needs the 2nd argument to be:
|
||||||
|
|
||||||
|
U16 -> A
|
||||||
|
"###
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
373
examples/interactive/cli-platform/Path.roc
Normal file
373
examples/interactive/cli-platform/Path.roc
Normal file
|
@ -0,0 +1,373 @@
|
||||||
|
interface Path
|
||||||
|
exposes [
|
||||||
|
Path,
|
||||||
|
PathComponent,
|
||||||
|
WindowsRoot,
|
||||||
|
toComponents,
|
||||||
|
walkComponents,
|
||||||
|
fromStr,
|
||||||
|
fromBytes,
|
||||||
|
]
|
||||||
|
imports []
|
||||||
|
|
||||||
|
## You can canonicalize a [Path] using [Path.canonicalize].
|
||||||
|
##
|
||||||
|
## Comparing canonical paths is often more reliable than comparing raw ones.
|
||||||
|
## For example, `Path.fromStr "foo/bar/../baz" == Path.fromStr "foo/baz"` will return `False`,
|
||||||
|
## because those are different paths even though their canonical equivalents would be equal.
|
||||||
|
##
|
||||||
|
## Also note that canonicalization reads from the file system (in order to resolve symbolic
|
||||||
|
## links, and to convert relative paths into absolute ones). This means that it is not only
|
||||||
|
## a [Task] (which can fail), but also that running [canonicalize] on the same [Path] twice
|
||||||
|
## may give different answers. An example of a way this could happen is if a symbolic link
|
||||||
|
## in the path changed on disk to point somewhere else in between the two [canonicalize] calls.
|
||||||
|
##
|
||||||
|
## Similarly, remember that canonical paths are not guaranteed to refer to a valid file. They
|
||||||
|
## might have referred to one when they were canonicalized, but that file may have moved or
|
||||||
|
## been deleted since the canonical path was created. So you might [canonicalize] a [Path],
|
||||||
|
## and then immediately use that [Path] to read a file from disk, and still get back an error
|
||||||
|
## because something relevant changed on the filesystem between the two operations.
|
||||||
|
##
|
||||||
|
## Also note that different filesystems have different rules for syntactically valid paths.
|
||||||
|
## Suppose you're on a machine with two disks, one formatted as ext4 and another as FAT32.
|
||||||
|
## It's possible to list the contents of a directory on the ext4 disk, and get a [CanPath] which
|
||||||
|
## is valid on that disk, but invalid on the other disk. One way this could happen is if the
|
||||||
|
## directory on the ext4 disk has a filename containing a `:` in it. `:` is allowed in ext4
|
||||||
|
## paths but is considered invalid in FAT32 paths.
|
||||||
|
Path := [
|
||||||
|
# We store these separately for two reasons:
|
||||||
|
# 1. If I'm calling an OS API, passing a path I got from the OS is definitely safe.
|
||||||
|
# However, passing a Path I got from a RocStr might be unsafe; it may contain \0
|
||||||
|
# characters, which would result in the operation happening on a totally different
|
||||||
|
# path. As such, we need to check for \0s and fail without calling the OS API if we
|
||||||
|
# find one in the path.
|
||||||
|
# 2. If I'm converting the Path to a Str, doing that conversion on a Path that was
|
||||||
|
# created from a RocStr needs no further processing. However, if it came from the OS,
|
||||||
|
# then we need to know what charset to assume it had, in order to decode it properly.
|
||||||
|
# These come from the OS (e.g. when reading a directory, calling `canonicalize`,
|
||||||
|
# or reading an environment variable - which, incidentally, are nul-terminated),
|
||||||
|
# so we know they are both nul-terminated and do not contain interior nuls.
|
||||||
|
# As such, they can be passed directly to OS APIs.
|
||||||
|
#
|
||||||
|
# Note that the nul terminator byte is right after the end of the length (into the
|
||||||
|
# unused capacity), so this can both be compared directly to other `List U8`s that
|
||||||
|
# aren't nul-terminated, while also being able to be passed directly to OS APIs.
|
||||||
|
FromOperatingSystem (List U8),
|
||||||
|
|
||||||
|
# These come from userspace (e.g. Path.fromBytes), so they need to be checked for interior
|
||||||
|
# nuls and then nul-terminated before the host can pass them to OS APIs.
|
||||||
|
ArbitraryBytes (List U8),
|
||||||
|
|
||||||
|
# This was created as a RocStr, so it might have interior nul bytes but it's definitely UTF-8.
|
||||||
|
# That means we can `toStr` it trivially, but have to validate before sending it to OS
|
||||||
|
# APIs that expect a nul-terminated `char*`.
|
||||||
|
#
|
||||||
|
# Note that both UNIX and Windows APIs will accept UTF-8, because on Windows the host calls
|
||||||
|
# `_setmbcp(_MB_CP_UTF8);` to set the process's Code Page to UTF-8 before doing anything else.
|
||||||
|
# See https://docs.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page#-a-vs--w-apis
|
||||||
|
# and https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setmbcp?view=msvc-170
|
||||||
|
# for more details on the UTF-8 Code Page in Windows.
|
||||||
|
FromStr Str,
|
||||||
|
]
|
||||||
|
|
||||||
|
## Note that the path may not be valid depending on the filesystem where it is used.
|
||||||
|
## For example, paths containing `:` are valid on ext4 and NTFS filesystems, but not
|
||||||
|
## on FAT ones. So if you have multiple disks on the same machine, but they have
|
||||||
|
## different filesystems, then this path could be valid on one but invalid on another!
|
||||||
|
##
|
||||||
|
## It's safest to assume paths are invalid (even syntactically) until given to an operation
|
||||||
|
## which uses them to open a file. If that operation succeeds, then the path was valid
|
||||||
|
## (at the time). Otherwise, error handling can happen for that operation rather than validating
|
||||||
|
## up front for a false sense of security (given symlinks, parts of a path being renamed, etc.).
|
||||||
|
fromStr : Str -> Path
|
||||||
|
fromStr = \str -> @Path (FromStr str)
|
||||||
|
|
||||||
|
## Not all filesystems use Unicode paths. This function can be used to create a path which
|
||||||
|
## is not valid Unicode (like a Roc [Str] is), but which is valid for a particular filesystem.
|
||||||
|
##
|
||||||
|
## Note that if the list contains any `0` bytes, sending this path to any file operations
|
||||||
|
## (e.g. [File.read] or [WriteStream.openPath]) will fail.
|
||||||
|
fromBytes : List U8 -> Path
|
||||||
|
fromBytes = \bytes -> @Path (ArbitraryBytes bytes)
|
||||||
|
|
||||||
|
## Note that canonicalization reads from the file system (in order to resolve symbolic
|
||||||
|
## links, and to convert relative paths into absolute ones). This means that it is not only
|
||||||
|
## a [Task] (which can fail), but also that running [canonicalize] on the same [Path] twice
|
||||||
|
## may give different answers. An example of a way this could happen is if a symbolic link
|
||||||
|
## in the path changed on disk to point somewhere else in between the two [canonicalize] calls.
|
||||||
|
##
|
||||||
|
## Returns an effect type of `[Metadata, Cwd]` because it can resolve symbolic links
|
||||||
|
## and can access the current working directory by turning a relative path into an
|
||||||
|
## absolute one (which can prepend the absolute path of the current working directory to
|
||||||
|
## the relative path).
|
||||||
|
canonicalize : Path -> Task Path (CanonicalizeErr *) [Metadata, Read [Env]]*
|
||||||
|
|
||||||
|
## Unfortunately, operating system paths do not include information about which charset
|
||||||
|
## they were originally encoded with. It's most common (but not guaranteed) that they will
|
||||||
|
## have been encoded with the same charset as the operating system's curent locale (which
|
||||||
|
## typically does not change after it is set during installation of the OS), so
|
||||||
|
## this should convert a [Path] to a valid string as long as the path was created
|
||||||
|
## with the given [Charset]. (Use [Env.charset] to get the current system charset.)
|
||||||
|
##
|
||||||
|
## For a conversion to [Str] that is lossy but does not return a [Result], see
|
||||||
|
## [displayUtf8].
|
||||||
|
toInner : Path -> [Str Str, Bytes (List U8)]
|
||||||
|
|
||||||
|
## Assumes a path is encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8),
|
||||||
|
## and converts it to a string using [Str.displayUtf8].
|
||||||
|
##
|
||||||
|
## This conversion is lossy because the path may contain invalid UTF-8 bytes. If that happens,
|
||||||
|
## any invalid bytes will be replaced with the [Unicode replacement character](https://unicode.org/glossary/#replacement_character)
|
||||||
|
## instead of returning an error. As such, it's rarely a good idea to use the [Str] returned
|
||||||
|
## by this function for any purpose other than displaying it to a user.
|
||||||
|
##
|
||||||
|
## When you don't know for sure what a path's encoding is, UTF-8 is a popular guess because
|
||||||
|
## it's the default on UNIX and also is the encoding used in Roc strings. This platform also
|
||||||
|
## automatically runs applications under the [UTF-8 code page](https://docs.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page)
|
||||||
|
## on Windows.
|
||||||
|
##
|
||||||
|
## Converting paths to strings can be an unreliable operation, because operating systems
|
||||||
|
## don't record the paths' encodings. This means it's possible for the path to have been
|
||||||
|
## encoded with a different character set than UTF-8 even if UTF-8 is the system default,
|
||||||
|
## which means when [displayUtf8] converts them to a string, the string may include gibberish.
|
||||||
|
## [Here is an example.](https://unix.stackexchange.com/questions/667652/can-a-file-path-be-invalid-utf-8/667863#667863)
|
||||||
|
##
|
||||||
|
## If you happen to know the [Charset] that was used to encode the path, you can use
|
||||||
|
## [toStrUsingCharset] instead of [displayUtf8].
|
||||||
|
displayUtf8 : Path -> Str
|
||||||
|
displayUtf8 = \@Path path ->
|
||||||
|
when path is
|
||||||
|
FromStr str -> str
|
||||||
|
NoInteriorNul bytes | ArbitraryBytes bytes ->
|
||||||
|
Str.displayUtf8 bytes
|
||||||
|
|
||||||
|
isEq : Path, Path -> Bool
|
||||||
|
isEq = \@Path p1, @Path p2 ->
|
||||||
|
when p1 is
|
||||||
|
NoInteriorNul bytes1 | ArbitraryBytes bytes1 ->
|
||||||
|
when p2 is
|
||||||
|
NoInteriorNul bytes2 | ArbitraryBytes bytes2 -> bytes1 == bytes2
|
||||||
|
# We can't know the encoding that was originally used in the path, so we convert
|
||||||
|
# the string to bytes and see if those bytes are equal to the path's bytes.
|
||||||
|
#
|
||||||
|
# This may sound unreliable, but it's how all paths are compared; since the OS
|
||||||
|
# doesn't record which encoding was used to encode the path name, the only
|
||||||
|
# reasonable# definition for path equality is byte-for-byte equality.
|
||||||
|
FromStr str2 -> Str.isEqUtf8 str2 bytes1
|
||||||
|
|
||||||
|
FromStr str1 ->
|
||||||
|
when p2 is
|
||||||
|
NoInteriorNul bytes2 | ArbitraryBytes bytes2 -> Str.isEqUtf8 str1 bytes2
|
||||||
|
FromStr str2 -> str1 == str2
|
||||||
|
|
||||||
|
compare : Path, Path -> [Lt, Eq, Gt]
|
||||||
|
compare = \@Path p1, @Path p2 ->
|
||||||
|
when p1 is
|
||||||
|
NoInteriorNul bytes1 | ArbitraryBytes bytes1 ->
|
||||||
|
when p2 is
|
||||||
|
NoInteriorNul bytes2 | ArbitraryBytes bytes2 -> Ord.compare bytes1 bytes2
|
||||||
|
FromStr str2 -> Str.compareUtf8 str2 bytes1
|
||||||
|
|
||||||
|
FromStr str1 ->
|
||||||
|
when p2 is
|
||||||
|
NoInteriorNul bytes2 | ArbitraryBytes bytes2 -> Ord.compare str1 bytes2
|
||||||
|
FromStr str2 -> str1 == str2
|
||||||
|
|
||||||
|
## ## Path Components
|
||||||
|
PathComponent : [
|
||||||
|
ParentDir, # e.g. ".." on UNIX or Windows
|
||||||
|
CurrentDir, # e.g. "." on UNIX
|
||||||
|
Named Str, # e.g. "stuff" on UNIX
|
||||||
|
DirSep Str, # e.g. "/" on UNIX, "\" or "/" on Windows. Or, sometimes, "¥" on Windows - see
|
||||||
|
# https://docs.microsoft.com/en-us/windows/win32/intl/character-sets-used-in-file-names
|
||||||
|
#
|
||||||
|
# This is included as an option so if you're transforming part of a path,
|
||||||
|
# you can write back whatever separator was originally used.
|
||||||
|
]
|
||||||
|
|
||||||
|
## Note that a root of Slash (`/`) has different meanings on UNIX and on Windows.
|
||||||
|
## * On UNIX, `/` at the beginning of the path refers to the filesystem root, and means the path is absolute.
|
||||||
|
## * On Windows, `/` at the beginning of the path refers to the current disk drive, and means the path is relative.
|
||||||
|
PathRoot : [
|
||||||
|
WindowsSpecificRoot WindowsRoot, # e.g. "C:" on Windows
|
||||||
|
Slash,
|
||||||
|
None,
|
||||||
|
]
|
||||||
|
|
||||||
|
# TODO see https://doc.rust-lang.org/std/path/enum.Prefix.html
|
||||||
|
WindowsRoot : []
|
||||||
|
|
||||||
|
## Returns the root of the path.
|
||||||
|
root : Path -> PathRoot
|
||||||
|
|
||||||
|
components : Path -> { root : PathRoot, components : List PathComponent }
|
||||||
|
|
||||||
|
## Walk over the path's [components].
|
||||||
|
walk :
|
||||||
|
Path,
|
||||||
|
# None means it's a relative path
|
||||||
|
(PathRoot -> state),
|
||||||
|
(state, PathComponent -> state)
|
||||||
|
-> state
|
||||||
|
|
||||||
|
## Returns the path without its last [`component`](#components).
|
||||||
|
##
|
||||||
|
## If the path was empty or contained only a [root](#PathRoot), returns the original path.
|
||||||
|
dropLast : Path -> Path
|
||||||
|
|
||||||
|
# TODO see https://doc.rust-lang.org/std/path/struct.Path.html#method.join for
|
||||||
|
# the definition of the term "adjoin" - should we use that term?
|
||||||
|
append : Path, Path -> Path
|
||||||
|
append = \@Path prefix, @Path suffix ->
|
||||||
|
content =
|
||||||
|
when prefix is
|
||||||
|
NoInteriorNul prefixBytes ->
|
||||||
|
when suffix is
|
||||||
|
NoInteriorNul suffixBytes ->
|
||||||
|
# Neither prefix nor suffix had interior nuls, so the answer won't either
|
||||||
|
List.append prefixBytes suffixBytes
|
||||||
|
|> NoInteriorNul
|
||||||
|
|
||||||
|
ArbitraryBytes suffixBytes ->
|
||||||
|
List.append prefixBytes suffixBytes
|
||||||
|
|> ArbitraryBytes
|
||||||
|
|
||||||
|
FromStr suffixStr ->
|
||||||
|
# Append suffixStr by writing it to the end of prefixBytes
|
||||||
|
Str.writeUtf8 suffixStr prefixBytes (List.len prefixBytes)
|
||||||
|
|> ArbitraryBytes
|
||||||
|
|
||||||
|
ArbitraryBytes prefixBytes ->
|
||||||
|
when suffix is
|
||||||
|
ArbitraryBytes suffixBytes | NoInteriorNul suffixBytes ->
|
||||||
|
List.append prefixBytes suffixBytes
|
||||||
|
|> ArbitraryBytes
|
||||||
|
|
||||||
|
FromStr suffixStr ->
|
||||||
|
# Append suffixStr by writing it to the end of prefixBytes
|
||||||
|
Str.writeUtf8 suffixStr prefixBytes (List.len prefixBytes)
|
||||||
|
|> ArbitraryBytes
|
||||||
|
|
||||||
|
FromStr prefixStr ->
|
||||||
|
when suffix is
|
||||||
|
ArbitraryBytes suffixBytes | NoInteriorNul suffixBytes ->
|
||||||
|
List.append (Str.toUtf8 prefixStr) suffixBytes
|
||||||
|
|> ArbitraryBytes
|
||||||
|
|
||||||
|
FromStr suffixStr ->
|
||||||
|
Str.append prefixStr suffixStr
|
||||||
|
|> FromStr
|
||||||
|
|
||||||
|
@Path content
|
||||||
|
|
||||||
|
appendStr : Path, Str -> Path
|
||||||
|
appendStr = \@Path prefix, suffixStr ->
|
||||||
|
content =
|
||||||
|
when prefix is
|
||||||
|
NoInteriorNul prefixBytes | ArbitraryBytes prefixBytes ->
|
||||||
|
# Append suffixStr by writing it to the end of prefixBytes
|
||||||
|
Str.writeUtf8 suffixStr prefixBytes (List.len prefixBytes)
|
||||||
|
|> ArbitraryBytes
|
||||||
|
|
||||||
|
FromStr prefixStr ->
|
||||||
|
Str.append prefixStr suffixStr
|
||||||
|
|> FromStr
|
||||||
|
|
||||||
|
@Path content
|
||||||
|
|
||||||
|
## Returns `True` if the first path begins with the second.
|
||||||
|
startsWith : Path, Path -> Bool
|
||||||
|
startsWith = \@Path path, @Path prefix ->
|
||||||
|
when path is
|
||||||
|
NoInteriorNul pathBytes | ArbitraryBytes pathBytes ->
|
||||||
|
when prefix is
|
||||||
|
NoInteriorNul prefixBytes | ArbitraryBytes prefixBytes ->
|
||||||
|
List.startsWith pathBytes prefixBytes
|
||||||
|
|
||||||
|
FromStr prefixStr ->
|
||||||
|
strLen = Str.byteCount str
|
||||||
|
|
||||||
|
if strLen == List.len pathBytes then
|
||||||
|
# Grab the first N bytes of the list, where N = byte length of string.
|
||||||
|
bytesPrefix = List.takeAt pathBytes 0 strLen
|
||||||
|
|
||||||
|
# Compare the two for equality.
|
||||||
|
Str.isEqUtf8 prefixStr bytesPrefix
|
||||||
|
else
|
||||||
|
False
|
||||||
|
|
||||||
|
FromStr pathStr ->
|
||||||
|
when prefix is
|
||||||
|
NoInteriorNul prefixBytes | ArbitraryBytes prefixBytes ->
|
||||||
|
Str.startsWithUtf8 pathStr prefixBytes
|
||||||
|
|
||||||
|
FromStr prefixStr ->
|
||||||
|
Str.startsWith pathStr prefixStr
|
||||||
|
|
||||||
|
## Returns `True` if the first path ends with the second.
|
||||||
|
endsWith : Path, Path -> Bool
|
||||||
|
endsWith = \@Path path, @Path prefix ->
|
||||||
|
when path is
|
||||||
|
NoInteriorNul pathBytes | ArbitraryBytes pathBytes ->
|
||||||
|
when suffix is
|
||||||
|
NoInteriorNul suffixBytes | ArbitraryBytes suffixBytes ->
|
||||||
|
List.endsWith pathBytes suffixBytes
|
||||||
|
|
||||||
|
FromStr suffixStr ->
|
||||||
|
strLen = Str.byteCount suffixStr
|
||||||
|
|
||||||
|
if strLen == List.len pathBytes then
|
||||||
|
# Grab the last N bytes of the list, where N = byte length of string.
|
||||||
|
bytesSuffix = List.takeAt pathBytes (strLen - 1) strLen
|
||||||
|
|
||||||
|
# Compare the two for equality.
|
||||||
|
Str.startsWithUtf8 suffixStr bytesSuffix
|
||||||
|
else
|
||||||
|
False
|
||||||
|
|
||||||
|
FromStr pathStr ->
|
||||||
|
when suffix is
|
||||||
|
NoInteriorNul suffixBytes | ArbitraryBytes suffixBytes ->
|
||||||
|
Str.endsWithUtf8 pathStr suffixBytes
|
||||||
|
|
||||||
|
FromStr suffixStr ->
|
||||||
|
Str.endsWith pathStr suffixStr
|
||||||
|
|
||||||
|
# TODO https://doc.rust-lang.org/std/path/struct.Path.html#method.strip_prefix
|
||||||
|
# TODO idea: what if it's File.openRead and File.openWrite? And then e.g. File.metadata,
|
||||||
|
# File.isDir, etc.
|
||||||
|
## If the last component of this path has no `.`, appends `.` followed by the given string.
|
||||||
|
## Otherwise, replaces everything after the last `.` with the given string.
|
||||||
|
##
|
||||||
|
## Examples:
|
||||||
|
##
|
||||||
|
## Path.fromStr "foo/bar/baz" |> Path.withExtension "txt" # foo/bar/baz.txt
|
||||||
|
## Path.fromStr "foo/bar/baz." |> Path.withExtension "txt" # foo/bar/baz.txt
|
||||||
|
## Path.fromStr "foo/bar/baz.xz" |> Path.withExtension "txt" # foo/bar/baz.txt
|
||||||
|
withExtension : Path, Str -> Path
|
||||||
|
withExtension = \@Path path, extension ->
|
||||||
|
when path is
|
||||||
|
NoInteriorNul bytes | ArbitraryBytes bytes ->
|
||||||
|
beforeDot =
|
||||||
|
when List.splitLast '.' is
|
||||||
|
Ok { before } -> before
|
||||||
|
Err NotFound -> list
|
||||||
|
|
||||||
|
beforeDot
|
||||||
|
|> List.reserve (1 + List.len bytes)
|
||||||
|
|> List.append '.'
|
||||||
|
|> List.concat bytes
|
||||||
|
|
||||||
|
FromStr str ->
|
||||||
|
beforeDot =
|
||||||
|
when Str.splitLast str "." is
|
||||||
|
Ok { before } -> before
|
||||||
|
Err NotFound -> str
|
||||||
|
|
||||||
|
beforeDot
|
||||||
|
|> Str.reserve (1 + Str.byteCount str)
|
||||||
|
|> Str.append "."
|
||||||
|
|> Str.concat str
|
||||||
|
|
||||||
|
# NOTE: no withExtensionBytes because it's too narrow. If you really need to get some
|
||||||
|
# non-Unicode in there, do it with
|
|
@ -19,7 +19,9 @@ wget https://github.com/rtfeldman/elm-css/files/8849069/roc-source-code.zip
|
||||||
# TODO: When roc repo is public, download it from nightly builds.
|
# TODO: When roc repo is public, download it from nightly builds.
|
||||||
wget https://github.com/brian-carroll/mock-repl/archive/refs/heads/deploy.zip
|
wget https://github.com/brian-carroll/mock-repl/archive/refs/heads/deploy.zip
|
||||||
unzip deploy.zip
|
unzip deploy.zip
|
||||||
mv mock-repl-deploy/* .
|
# Explicitly list the filenames, failing the build if they're not found
|
||||||
|
mv mock-repl-deploy/roc_repl_wasm.js repl/
|
||||||
|
mv mock-repl-deploy/roc_repl_wasm_bg.wasm repl/
|
||||||
rmdir mock-repl-deploy
|
rmdir mock-repl-deploy
|
||||||
rm deploy.zip
|
rm deploy.zip
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Roc REPL</title>
|
<title>Roc REPL</title>
|
||||||
<link rel="stylesheet" href="./repl.css" />
|
<link rel="stylesheet" href="/repl/repl.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -28,6 +28,6 @@
|
||||||
></textarea>
|
></textarea>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="./repl.js"></script>
|
<script type="module" src="/repl/repl.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -41,7 +41,7 @@ const repl = {
|
||||||
// Initialise
|
// Initialise
|
||||||
repl.elemSourceInput.addEventListener("change", onInputChange);
|
repl.elemSourceInput.addEventListener("change", onInputChange);
|
||||||
repl.elemSourceInput.addEventListener("keyup", onInputKeyup);
|
repl.elemSourceInput.addEventListener("keyup", onInputKeyup);
|
||||||
roc_repl_wasm.default('./roc_repl_wasm_bg.wasm').then((instance) => {
|
roc_repl_wasm.default('/repl/roc_repl_wasm_bg.wasm').then((instance) => {
|
||||||
repl.compiler = instance;
|
repl.compiler = instance;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue