mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-24 15:03:46 +00:00
Merge remote-tracking branch 'origin/trunk' into assoc-list-dict
This commit is contained in:
commit
5763248b44
41 changed files with 949 additions and 131 deletions
|
@ -121,6 +121,7 @@ comptime {
|
|||
exportStrFn(str.countSegments, "count_segments");
|
||||
exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters");
|
||||
exportStrFn(str.countUtf8Bytes, "count_utf8_bytes");
|
||||
exportStrFn(str.getCapacity, "capacity");
|
||||
exportStrFn(str.startsWith, "starts_with");
|
||||
exportStrFn(str.startsWithScalar, "starts_with_scalar");
|
||||
exportStrFn(str.endsWith, "ends_with");
|
||||
|
|
|
@ -1210,6 +1210,10 @@ pub fn countUtf8Bytes(string: RocStr) callconv(.C) usize {
|
|||
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 {
|
||||
const slice = string.asSlice()[start .. start + length];
|
||||
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_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_CAPACITY: &str = "roc_builtins.str.capacity";
|
||||
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_ENDS_WITH: &str = "roc_builtins.str.ends_with";
|
||||
|
|
|
@ -124,6 +124,7 @@ map_symbol_to_lowlevel_and_arity! {
|
|||
StrAppendScalar; STR_APPEND_SCALAR_UNSAFE; 2,
|
||||
StrGetScalarUnsafe; STR_GET_SCALAR_UNSAFE; 2,
|
||||
StrToNum; STR_TO_NUM; 1,
|
||||
StrGetCapacity; STR_CAPACITY; 1,
|
||||
|
||||
ListLen; LIST_LEN; 1,
|
||||
ListWithCapacity; LIST_WITH_CAPACITY; 1,
|
||||
|
@ -142,6 +143,7 @@ map_symbol_to_lowlevel_and_arity! {
|
|||
ListSublist; LIST_SUBLIST_LOWLEVEL; 3,
|
||||
ListDropAt; LIST_DROP_AT; 2,
|
||||
ListSwap; LIST_SWAP; 3,
|
||||
ListGetCapacity; LIST_CAPACITY; 1,
|
||||
|
||||
NumAdd; NUM_ADD; 2,
|
||||
NumAddWrap; NUM_ADD_WRAP; 2,
|
||||
|
|
|
@ -220,6 +220,9 @@ pub enum Expr {
|
|||
lambda_set_variables: Vec<LambdaSet>,
|
||||
},
|
||||
|
||||
// Opaque as a function, e.g. @Id as a shorthand for \x -> @Id x
|
||||
OpaqueWrapFunction(OpaqueWrapFunctionData),
|
||||
|
||||
// Test
|
||||
Expect {
|
||||
loc_condition: Box<Loc<Expr>>,
|
||||
|
@ -269,6 +272,9 @@ impl Expr {
|
|||
args_count: 0,
|
||||
},
|
||||
&Self::OpaqueRef { name, .. } => Category::OpaqueWrap(name),
|
||||
&Self::OpaqueWrapFunction(OpaqueWrapFunctionData { opaque_name, .. }) => {
|
||||
Category::OpaqueWrap(opaque_name)
|
||||
}
|
||||
Self::Expect { .. } => Category::Expect,
|
||||
|
||||
// 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)]
|
||||
pub struct Field {
|
||||
pub var: Variable,
|
||||
|
@ -835,15 +911,41 @@ pub fn canonicalize_expr<'a>(
|
|||
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
|
||||
// arguments are handled in the Apply branch.
|
||||
let problem = roc_problem::can::RuntimeError::OpaqueNotApplied(Loc::at(
|
||||
region,
|
||||
(*opaque_ref).into(),
|
||||
));
|
||||
env.problem(Problem::RuntimeError(problem.clone()));
|
||||
(RuntimeError(problem), Output::default())
|
||||
// Treat this as a function \payload -> @Opaque payload
|
||||
match scope.lookup_opaque_ref(name, region) {
|
||||
Err(runtime_error) => {
|
||||
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
||||
(RuntimeError(runtime_error), 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) => {
|
||||
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 @ RunLowLevel { .. }
|
||||
| other @ TypedHole { .. }
|
||||
| other @ ForeignCall { .. } => other,
|
||||
| other @ ForeignCall { .. }
|
||||
| other @ OpaqueWrapFunction(_) => other,
|
||||
|
||||
List {
|
||||
elem_var,
|
||||
|
@ -2387,7 +2490,8 @@ fn get_lookup_symbols(expr: &Expr, var_store: &mut VarStore) -> Vec<(Symbol, Var
|
|||
| Expr::SingleQuote(_)
|
||||
| Expr::EmptyRecord
|
||||
| Expr::TypedHole(_)
|
||||
| Expr::RuntimeError(_) => {}
|
||||
| Expr::RuntimeError(_)
|
||||
| Expr::OpaqueWrapFunction(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -962,5 +962,6 @@ fn fix_values_captured_in_closure_expr(
|
|||
let (_, loc_arg) = &mut **argument;
|
||||
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::{
|
||||
abilities::AbilitiesStore,
|
||||
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},
|
||||
};
|
||||
|
||||
|
@ -220,6 +223,7 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
|
|||
ext_var: _,
|
||||
} => visitor.visit_expr(&loc_expr.value, loc_expr.region, *field_var),
|
||||
Expr::Accessor(AccessorData { .. }) => { /* terminal */ }
|
||||
Expr::OpaqueWrapFunction(OpaqueWrapFunctionData { .. }) => { /* terminal */ }
|
||||
Expr::Update {
|
||||
record_var: _,
|
||||
ext_var: _,
|
||||
|
|
|
@ -13,7 +13,7 @@ use roc_can::expected::PExpected;
|
|||
use roc_can::expr::Expr::{self, *};
|
||||
use roc_can::expr::{
|
||||
AccessorData, AnnotatedMark, ClosureData, DeclarationTag, Declarations, DestructureDef, Field,
|
||||
FunctionDef, WhenBranch,
|
||||
FunctionDef, OpaqueWrapFunctionData, WhenBranch,
|
||||
};
|
||||
use roc_can::pattern::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])
|
||||
}
|
||||
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 } => {
|
||||
// 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,
|
||||
};
|
||||
use crate::llvm::build_list::{
|
||||
self, allocate_list, empty_polymorphic_list, list_append_unsafe, list_concat, list_drop_at,
|
||||
list_get_unsafe, list_len, list_map, list_map2, list_map3, list_map4, list_prepend,
|
||||
list_replace_unsafe, list_reserve, list_sort_with, list_sublist, list_swap,
|
||||
self, allocate_list, empty_polymorphic_list, list_append_unsafe, list_capacity, list_concat,
|
||||
list_drop_at, list_get_unsafe, list_len, list_map, list_map2, list_map3, list_map4,
|
||||
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,
|
||||
};
|
||||
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)
|
||||
}
|
||||
StrCountUtf8Bytes => {
|
||||
// Str.countGraphemes : Str -> Nat
|
||||
// Str.countUtf8Bytes : Str -> Nat
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
let string = load_symbol(scope, &args[0]);
|
||||
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 => {
|
||||
// Str.substringUnsafe : Str, Nat, Nat -> Str
|
||||
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)
|
||||
}
|
||||
ListLen => {
|
||||
// List.len : List * -> Int
|
||||
// List.len : List * -> Nat
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
let arg = load_symbol(scope, &args[0]);
|
||||
|
||||
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 => {
|
||||
// List.withCapacity : Nat -> List a
|
||||
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::refcounting::Mode;
|
||||
use inkwell::attributes::{Attribute, AttributeLoc};
|
||||
use inkwell::builder::Builder;
|
||||
use inkwell::context::Context;
|
||||
use inkwell::types::BasicType;
|
||||
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)]
|
||||
pub fn set_from_list<'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")
|
||||
}
|
||||
|
||||
/// List.len : List elem -> Int
|
||||
/// List.len : List * -> Nat
|
||||
pub fn list_len<'ctx>(
|
||||
builder: &Builder<'ctx>,
|
||||
wrapper_struct: StructValue<'ctx>,
|
||||
|
@ -362,6 +362,17 @@ pub fn list_len<'ctx>(
|
|||
.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
|
||||
pub fn list_sort_with<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
|
|
|
@ -243,6 +243,7 @@ impl<'a> LowLevelCall<'a> {
|
|||
StrCountUtf8Bytes => {
|
||||
self.load_args_and_call_zig(backend, bitcode::STR_COUNT_UTF8_BYTES)
|
||||
}
|
||||
StrGetCapacity => self.load_args_and_call_zig(backend, bitcode::STR_CAPACITY),
|
||||
StrToNum => {
|
||||
let number_layout = match self.ret_layout {
|
||||
Layout::Struct { field_layouts, .. } => field_layouts[0],
|
||||
|
@ -308,7 +309,37 @@ impl<'a> LowLevelCall<'a> {
|
|||
let (local_id, offset) =
|
||||
location.local_and_offset(backend.storage.stack_frame_pointer);
|
||||
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"),
|
||||
},
|
||||
|
|
|
@ -29,6 +29,7 @@ pub enum LowLevel {
|
|||
StrReserve,
|
||||
StrAppendScalar,
|
||||
StrGetScalarUnsafe,
|
||||
StrGetCapacity,
|
||||
ListLen,
|
||||
ListWithCapacity,
|
||||
ListReserve,
|
||||
|
@ -46,6 +47,7 @@ pub enum LowLevel {
|
|||
ListDropAt,
|
||||
ListSwap,
|
||||
ListIsUnique,
|
||||
ListGetCapacity,
|
||||
NumAdd,
|
||||
NumAddWrap,
|
||||
NumAddChecked,
|
||||
|
@ -246,7 +248,9 @@ map_symbol_to_lowlevel! {
|
|||
StrAppendScalar <= STR_APPEND_SCALAR_UNSAFE,
|
||||
StrGetScalarUnsafe <= STR_GET_SCALAR_UNSAFE,
|
||||
StrToNum <= STR_TO_NUM,
|
||||
StrGetCapacity <= STR_CAPACITY,
|
||||
ListLen <= LIST_LEN,
|
||||
ListGetCapacity <= LIST_CAPACITY,
|
||||
ListWithCapacity <= LIST_WITH_CAPACITY,
|
||||
ListReserve <= LIST_RESERVE,
|
||||
ListIsUnique <= LIST_IS_UNIQUE,
|
||||
|
|
|
@ -1215,6 +1215,7 @@ define_builtins! {
|
|||
46 STR_WALK_SCALARS_UNTIL: "walkScalarsUntil"
|
||||
47 STR_TO_NUM: "strToNum"
|
||||
48 STR_FROM_UTF8_RANGE_LOWLEVEL: "fromUtf8RangeLowlevel"
|
||||
49 STR_CAPACITY: "capacity"
|
||||
}
|
||||
5 LIST: "List" => {
|
||||
0 LIST_LIST: "List" imported // the List.List type alias
|
||||
|
@ -1285,6 +1286,7 @@ define_builtins! {
|
|||
65 LIST_RESERVE: "reserve"
|
||||
66 LIST_APPEND_UNSAFE: "appendUnsafe"
|
||||
67 LIST_SUBLIST_LOWLEVEL: "sublistLowlevel"
|
||||
68 LIST_CAPACITY: "capacity"
|
||||
}
|
||||
6 RESULT: "Result" => {
|
||||
0 RESULT_RESULT: "Result" // the Result.Result type alias
|
||||
|
@ -1320,6 +1322,7 @@ define_builtins! {
|
|||
14 DICT_REMOVE_ALL: "removeAll" // difference
|
||||
|
||||
15 DICT_WITH_CAPACITY: "withCapacity"
|
||||
16 DICT_CAPACITY: "capacity"
|
||||
}
|
||||
8 SET: "Set" => {
|
||||
0 SET_SET: "Set" // the Set.Set type alias
|
||||
|
@ -1337,6 +1340,7 @@ define_builtins! {
|
|||
12 SET_WALK_USER_FUNCTION: "#walk_user_function"
|
||||
13 SET_CONTAINS: "contains"
|
||||
14 SET_TO_DICT: "toDict"
|
||||
15 SET_CAPACITY: "capacity"
|
||||
}
|
||||
9 BOX: "Box" => {
|
||||
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
|
||||
match op {
|
||||
Unreachable => arena.alloc_slice_copy(&[irrelevant]),
|
||||
ListLen | StrIsEmpty | StrToScalars | StrCountGraphemes | StrCountUtf8Bytes => {
|
||||
arena.alloc_slice_copy(&[borrowed])
|
||||
}
|
||||
ListLen | StrIsEmpty | StrToScalars | StrCountGraphemes | StrCountUtf8Bytes
|
||||
| StrGetCapacity | ListGetCapacity => arena.alloc_slice_copy(&[borrowed]),
|
||||
ListWithCapacity => arena.alloc_slice_copy(&[irrelevant]),
|
||||
ListReplaceUnsafe => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
|
||||
StrGetUnsafe | ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use roc_can::{
|
||||
def::Def,
|
||||
expr::{AccessorData, ClosureData, Expr, Field, WhenBranch},
|
||||
expr::{AccessorData, ClosureData, Expr, Field, OpaqueWrapFunctionData, WhenBranch},
|
||||
};
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use roc_types::{
|
||||
|
@ -543,6 +543,29 @@ fn deep_copy_type_vars_into_expr_help<C: CopyEnv>(
|
|||
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 {
|
||||
loc_condition,
|
||||
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 {
|
||||
record_var,
|
||||
symbol: structure,
|
||||
|
|
|
@ -1809,7 +1809,7 @@ impl<'a> Builtin<'a> {
|
|||
pub const STR_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_LEN: u32 = 1;
|
||||
pub const WRAPPER_CAPACITY: u32 = 2;
|
||||
|
|
|
@ -7341,4 +7341,30 @@ mod solve_expr {
|
|||
"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::expr::Expr::{self, *};
|
||||
use roc_can::expr::{ClosureData, WhenBranch};
|
||||
use roc_can::expr::{ClosureData, OpaqueWrapFunctionData, WhenBranch};
|
||||
use roc_can::pattern::{Pattern, RecordDestruct};
|
||||
|
||||
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)
|
||||
.append(f.text(format!(".{}", field.as_str())))
|
||||
.group(),
|
||||
OpaqueWrapFunction(OpaqueWrapFunctionData { opaque_name, .. }) => {
|
||||
f.text(format!("@{}", opaque_name.as_str(c.interns)))
|
||||
}
|
||||
Accessor(_) => todo!(),
|
||||
Update { .. } => todo!(),
|
||||
Tag { .. } => todo!(),
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
procedure Dict.1 ():
|
||||
let Dict.101 : List {[], []} = Array [];
|
||||
ret Dict.101;
|
||||
let Dict.102 : List {[], []} = Array [];
|
||||
ret Dict.102;
|
||||
|
||||
procedure Dict.7 (Dict.95):
|
||||
let Dict.100 : U64 = CallByName List.6 Dict.95;
|
||||
ret Dict.100;
|
||||
procedure Dict.7 (Dict.96):
|
||||
let Dict.101 : U64 = CallByName List.6 Dict.96;
|
||||
ret Dict.101;
|
||||
|
||||
procedure List.6 (#Attr.2):
|
||||
let List.295 : U64 = lowlevel ListLen #Attr.2;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
procedure List.2 (List.78, List.79):
|
||||
let List.300 : U64 = CallByName List.6 List.78;
|
||||
let List.297 : Int1 = CallByName Num.22 List.79 List.300;
|
||||
procedure List.2 (List.79, List.80):
|
||||
let List.300 : U64 = CallByName List.6 List.79;
|
||||
let List.297 : Int1 = CallByName Num.22 List.80 List.300;
|
||||
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;
|
||||
ret List.298;
|
||||
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.296 : List U8 = CallByName List.65 List.89 List.297;
|
||||
let List.295 : List U8 = CallByName List.66 List.296 List.90;
|
||||
let List.296 : List U8 = CallByName List.65 List.90 List.297;
|
||||
let List.295 : List U8 = CallByName List.66 List.296 List.91;
|
||||
ret List.295;
|
||||
|
||||
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;
|
||||
ret Bool.9;
|
||||
|
||||
procedure List.2 (List.78, List.79):
|
||||
let List.309 : U64 = CallByName List.6 List.78;
|
||||
let List.305 : Int1 = CallByName Num.22 List.79 List.309;
|
||||
procedure List.2 (List.79, List.80):
|
||||
let List.309 : U64 = CallByName List.6 List.79;
|
||||
let List.305 : Int1 = CallByName Num.22 List.80 List.309;
|
||||
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;
|
||||
ret List.306;
|
||||
else
|
||||
|
@ -22,15 +22,15 @@ procedure List.60 (#Attr.2, #Attr.3):
|
|||
let List.308 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
|
||||
ret List.308;
|
||||
|
||||
procedure List.9 (List.206):
|
||||
procedure List.9 (List.207):
|
||||
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.300 : U8 = GetTagId List.295;
|
||||
let List.301 : Int1 = lowlevel Eq List.299 List.300;
|
||||
if List.301 then
|
||||
let List.207 : I64 = UnionAtIndex (Id 1) (Index 0) List.295;
|
||||
let List.296 : [C Int1, C I64] = TagId(1) List.207;
|
||||
let List.208 : I64 = UnionAtIndex (Id 1) (Index 0) List.295;
|
||||
let List.296 : [C Int1, C I64] = TagId(1) List.208;
|
||||
ret List.296;
|
||||
else
|
||||
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;
|
||||
ret Num.257;
|
||||
|
||||
procedure Str.27 (Str.88):
|
||||
let Str.194 : [C Int1, C I64] = CallByName Str.67 Str.88;
|
||||
ret Str.194;
|
||||
procedure Str.27 (Str.89):
|
||||
let Str.195 : [C Int1, C I64] = CallByName Str.68 Str.89;
|
||||
ret Str.195;
|
||||
|
||||
procedure Str.47 (#Attr.2):
|
||||
let Str.202 : {I64, U8} = lowlevel StrToNum #Attr.2;
|
||||
ret Str.202;
|
||||
let Str.203 : {I64, U8} = lowlevel StrToNum #Attr.2;
|
||||
ret Str.203;
|
||||
|
||||
procedure Str.67 (Str.189):
|
||||
let Str.190 : {I64, U8} = CallByName Str.47 Str.189;
|
||||
let Str.200 : U8 = StructAtIndex 1 Str.190;
|
||||
let Str.201 : U8 = 0i64;
|
||||
let Str.197 : Int1 = CallByName Bool.7 Str.200 Str.201;
|
||||
if Str.197 then
|
||||
let Str.199 : I64 = StructAtIndex 0 Str.190;
|
||||
let Str.198 : [C Int1, C I64] = TagId(1) Str.199;
|
||||
ret Str.198;
|
||||
procedure Str.68 (Str.190):
|
||||
let Str.191 : {I64, U8} = CallByName Str.47 Str.190;
|
||||
let Str.201 : U8 = StructAtIndex 1 Str.191;
|
||||
let Str.202 : U8 = 0i64;
|
||||
let Str.198 : Int1 = CallByName Bool.7 Str.201 Str.202;
|
||||
if Str.198 then
|
||||
let Str.200 : I64 = StructAtIndex 0 Str.191;
|
||||
let Str.199 : [C Int1, C I64] = TagId(1) Str.200;
|
||||
ret Str.199;
|
||||
else
|
||||
let Str.196 : Int1 = false;
|
||||
let Str.195 : [C Int1, C I64] = TagId(0) Str.196;
|
||||
ret Str.195;
|
||||
let Str.197 : Int1 = false;
|
||||
let Str.196 : [C Int1, C I64] = TagId(0) Str.197;
|
||||
ret Str.196;
|
||||
|
||||
procedure Test.0 ():
|
||||
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.296 : List I64 = CallByName List.65 List.89 List.297;
|
||||
let List.295 : List I64 = CallByName List.66 List.296 List.90;
|
||||
let List.296 : List I64 = CallByName List.65 List.90 List.297;
|
||||
let List.295 : List I64 = CallByName List.66 List.296 List.91;
|
||||
ret List.295;
|
||||
|
||||
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.296 : List I64 = CallByName List.65 List.89 List.297;
|
||||
let List.295 : List I64 = CallByName List.66 List.296 List.90;
|
||||
let List.296 : List I64 = CallByName List.65 List.90 List.297;
|
||||
let List.295 : List I64 = CallByName List.66 List.296 List.91;
|
||||
ret List.295;
|
||||
|
||||
procedure List.65 (#Attr.2, #Attr.3):
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
procedure List.3 (List.86, List.87, List.88):
|
||||
let List.298 : {List I64, I64} = CallByName List.57 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.87 List.88 List.89;
|
||||
let List.297 : List I64 = StructAtIndex 0 List.298;
|
||||
inc List.297;
|
||||
dec List.298;
|
||||
ret List.297;
|
||||
|
||||
procedure List.57 (List.83, List.84, List.85):
|
||||
let List.303 : U64 = CallByName List.6 List.83;
|
||||
let List.300 : Int1 = CallByName Num.22 List.84 List.303;
|
||||
procedure List.57 (List.84, List.85, List.86):
|
||||
let List.303 : U64 = CallByName List.6 List.84;
|
||||
let List.300 : Int1 = CallByName Num.22 List.85 List.303;
|
||||
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;
|
||||
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;
|
||||
|
||||
procedure List.6 (#Attr.2):
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
procedure List.2 (List.78, List.79):
|
||||
let List.300 : U64 = CallByName List.6 List.78;
|
||||
let List.297 : Int1 = CallByName Num.22 List.79 List.300;
|
||||
procedure List.2 (List.79, List.80):
|
||||
let List.300 : U64 = CallByName List.6 List.79;
|
||||
let List.297 : Int1 = CallByName Num.22 List.80 List.300;
|
||||
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;
|
||||
ret List.298;
|
||||
else
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
procedure List.2 (List.78, List.79):
|
||||
let List.300 : U64 = CallByName List.6 List.78;
|
||||
let List.297 : Int1 = CallByName Num.22 List.79 List.300;
|
||||
procedure List.2 (List.79, List.80):
|
||||
let List.300 : U64 = CallByName List.6 List.79;
|
||||
let List.297 : Int1 = CallByName Num.22 List.80 List.300;
|
||||
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;
|
||||
ret List.298;
|
||||
else
|
||||
|
@ -27,12 +27,12 @@ procedure Num.22 (#Attr.2, #Attr.3):
|
|||
ret Num.257;
|
||||
|
||||
procedure Str.16 (#Attr.2, #Attr.3):
|
||||
let Str.194 : Str = lowlevel StrRepeat #Attr.2 #Attr.3;
|
||||
ret Str.194;
|
||||
let Str.195 : Str = lowlevel StrRepeat #Attr.2 #Attr.3;
|
||||
ret Str.195;
|
||||
|
||||
procedure Str.3 (#Attr.2, #Attr.3):
|
||||
let Str.195 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
|
||||
ret Str.195;
|
||||
let Str.196 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
|
||||
ret Str.196;
|
||||
|
||||
procedure Test.1 ():
|
||||
let Test.21 : Str = "lllllllllllllllllllllooooooooooong";
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
procedure List.2 (List.78, List.79):
|
||||
let List.300 : U64 = CallByName List.6 List.78;
|
||||
let List.297 : Int1 = CallByName Num.22 List.79 List.300;
|
||||
procedure List.2 (List.79, List.80):
|
||||
let List.300 : U64 = CallByName List.6 List.79;
|
||||
let List.297 : Int1 = CallByName Num.22 List.80 List.300;
|
||||
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;
|
||||
ret List.298;
|
||||
else
|
||||
|
@ -29,8 +29,8 @@ procedure Num.22 (#Attr.2, #Attr.3):
|
|||
ret Num.257;
|
||||
|
||||
procedure Str.3 (#Attr.2, #Attr.3):
|
||||
let Str.195 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
|
||||
ret Str.195;
|
||||
let Str.196 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
|
||||
ret Str.196;
|
||||
|
||||
procedure Test.1 ():
|
||||
let Test.21 : Str = "lllllllllllllllllllllooooooooooong";
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
procedure List.3 (List.86, List.87, List.88):
|
||||
let List.296 : {List I64, I64} = CallByName List.57 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.87 List.88 List.89;
|
||||
let List.295 : List I64 = StructAtIndex 0 List.296;
|
||||
inc List.295;
|
||||
dec List.296;
|
||||
ret List.295;
|
||||
|
||||
procedure List.57 (List.83, List.84, List.85):
|
||||
let List.301 : U64 = CallByName List.6 List.83;
|
||||
let List.298 : Int1 = CallByName Num.22 List.84 List.301;
|
||||
procedure List.57 (List.84, List.85, List.86):
|
||||
let List.301 : U64 = CallByName List.6 List.84;
|
||||
let List.298 : Int1 = CallByName Num.22 List.85 List.301;
|
||||
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;
|
||||
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;
|
||||
|
||||
procedure List.6 (#Attr.2):
|
||||
|
|
|
@ -7,9 +7,9 @@ procedure List.28 (#Attr.2, #Attr.3):
|
|||
decref #Attr.2;
|
||||
ret List.297;
|
||||
|
||||
procedure List.54 (List.201):
|
||||
procedure List.54 (List.202):
|
||||
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;
|
||||
|
||||
procedure Num.46 (#Attr.2, #Attr.3):
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
procedure List.2 (List.78, List.79):
|
||||
let List.310 : U64 = CallByName List.6 List.78;
|
||||
let List.307 : Int1 = CallByName Num.22 List.79 List.310;
|
||||
procedure List.2 (List.79, List.80):
|
||||
let List.310 : U64 = CallByName List.6 List.79;
|
||||
let List.307 : Int1 = CallByName Num.22 List.80 List.310;
|
||||
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;
|
||||
ret List.308;
|
||||
else
|
||||
|
@ -10,21 +10,21 @@ procedure List.2 (List.78, List.79):
|
|||
let List.305 : [C {}, C I64] = TagId(0) List.306;
|
||||
ret List.305;
|
||||
|
||||
procedure List.3 (List.86, List.87, List.88):
|
||||
let List.298 : {List I64, I64} = CallByName List.57 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.87 List.88 List.89;
|
||||
let List.297 : List I64 = StructAtIndex 0 List.298;
|
||||
inc List.297;
|
||||
dec List.298;
|
||||
ret List.297;
|
||||
|
||||
procedure List.57 (List.83, List.84, List.85):
|
||||
let List.315 : U64 = CallByName List.6 List.83;
|
||||
let List.312 : Int1 = CallByName Num.22 List.84 List.315;
|
||||
procedure List.57 (List.84, List.85, List.86):
|
||||
let List.315 : U64 = CallByName List.6 List.84;
|
||||
let List.312 : Int1 = CallByName Num.22 List.85 List.315;
|
||||
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;
|
||||
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;
|
||||
|
||||
procedure List.6 (#Attr.2):
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
procedure List.2 (List.78, List.79):
|
||||
let List.310 : U64 = CallByName List.6 List.78;
|
||||
let List.307 : Int1 = CallByName Num.22 List.79 List.310;
|
||||
procedure List.2 (List.79, List.80):
|
||||
let List.310 : U64 = CallByName List.6 List.79;
|
||||
let List.307 : Int1 = CallByName Num.22 List.80 List.310;
|
||||
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;
|
||||
ret List.308;
|
||||
else
|
||||
|
@ -10,21 +10,21 @@ procedure List.2 (List.78, List.79):
|
|||
let List.305 : [C {}, C I64] = TagId(0) List.306;
|
||||
ret List.305;
|
||||
|
||||
procedure List.3 (List.86, List.87, List.88):
|
||||
let List.298 : {List I64, I64} = CallByName List.57 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.87 List.88 List.89;
|
||||
let List.297 : List I64 = StructAtIndex 0 List.298;
|
||||
inc List.297;
|
||||
dec List.298;
|
||||
ret List.297;
|
||||
|
||||
procedure List.57 (List.83, List.84, List.85):
|
||||
let List.315 : U64 = CallByName List.6 List.83;
|
||||
let List.312 : Int1 = CallByName Num.22 List.84 List.315;
|
||||
procedure List.57 (List.84, List.85, List.86):
|
||||
let List.315 : U64 = CallByName List.6 List.84;
|
||||
let List.312 : Int1 = CallByName Num.22 List.85 List.315;
|
||||
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;
|
||||
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;
|
||||
|
||||
procedure List.6 (#Attr.2):
|
||||
|
|
|
@ -12,7 +12,7 @@ pub static WILDCARD: &str = "*";
|
|||
static EMPTY_RECORD: &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
|
||||
/// 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",
|
||||
)
|
||||
}
|
||||
|
||||
#[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
|
||||
// Apply(Error(OtherModule), [@Age, 21])
|
||||
@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
|
||||
^^^^
|
||||
|
||||
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 ─
|
||||
|
||||
|
@ -9551,4 +9551,30 @@ All branches in an `if` must have the same type!
|
|||
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.
|
||||
wget https://github.com/brian-carroll/mock-repl/archive/refs/heads/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
|
||||
rm deploy.zip
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Roc REPL</title>
|
||||
<link rel="stylesheet" href="./repl.css" />
|
||||
<link rel="stylesheet" href="/repl/repl.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -28,6 +28,6 @@
|
|||
></textarea>
|
||||
</section>
|
||||
</div>
|
||||
<script type="module" src="./repl.js"></script>
|
||||
<script type="module" src="/repl/repl.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -41,7 +41,7 @@ const repl = {
|
|||
// Initialise
|
||||
repl.elemSourceInput.addEventListener("change", onInputChange);
|
||||
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;
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue