Merge remote-tracking branch 'origin/trunk' into multi-dep-bugs

This commit is contained in:
Folkert 2020-11-02 15:52:44 +01:00
commit cb0bfa3eb7
50 changed files with 1421 additions and 256 deletions

View file

@ -51,11 +51,6 @@ jobs:
command: clippy command: clippy
args: -- -D warnings args: -- -D warnings
- uses: actions-rs/cargo@v1
name: cargo test
with:
command: test
- uses: actions-rs/cargo@v1 - uses: actions-rs/cargo@v1
name: cargo test -- --ignored name: cargo test -- --ignored
continue-on-error: true continue-on-error: true

View file

@ -21,6 +21,16 @@ fn powInt(base: i64, exp: i64) callconv(.C) i64 {
return math.pow(i64, base, exp); return math.pow(i64, base, exp);
} }
comptime { @export(acos, .{ .name = math_namespace ++ ".acos", .linkage = .Strong }); }
fn acos(num: f64) callconv(.C) f64 {
return math.acos(num);
}
comptime { @export(asin, .{ .name = math_namespace ++ ".asin", .linkage = .Strong }); }
fn asin(num: f64) callconv(.C) f64 {
return math.asin(num);
}
// Str.split // Str.split

View file

@ -17,6 +17,8 @@ pub fn get_bytes() -> Vec<u8> {
buffer buffer
} }
pub const MATH_ASIN: &str = "roc_builtins.math.asin";
pub const MATH_ACOS: &str = "roc_builtins.math.acos";
pub const MATH_ATAN: &str = "roc_builtins.math.atan"; pub const MATH_ATAN: &str = "roc_builtins.math.atan";
pub const MATH_IS_FINITE: &str = "roc_builtins.math.is_finite"; pub const MATH_IS_FINITE: &str = "roc_builtins.math.is_finite";
pub const MATH_POW_INT: &str = "roc_builtins.math.pow_int"; pub const MATH_POW_INT: &str = "roc_builtins.math.pow_int";

View file

@ -350,6 +350,18 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
top_level_function(vec![float_type()], Box::new(float_type())), top_level_function(vec![float_type()], Box::new(float_type())),
); );
// acos : Float -> Float
add_type(
Symbol::NUM_ACOS,
top_level_function(vec![float_type()], Box::new(float_type())),
);
// asin : Float -> Float
add_type(
Symbol::NUM_ASIN,
top_level_function(vec![float_type()], Box::new(float_type())),
);
// Bool module // Bool module
// and : Bool, Bool -> Bool // and : Bool, Bool -> Bool

View file

@ -375,6 +375,18 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
unique_function(vec![float_type(star1)], float_type(star2)) unique_function(vec![float_type(star1)], float_type(star2))
}); });
// acos : Float -> Float
add_type(Symbol::NUM_ACOS, {
let_tvars! { star1, star2 };
unique_function(vec![float_type(star1)], float_type(star2))
});
// asin : Float -> Float
add_type(Symbol::NUM_ASIN, {
let_tvars! { star1, star2 };
unique_function(vec![float_type(star1)], float_type(star2))
});
// Bool module // Bool module
// isEq or (==) : Attr * a, Attr * a -> Attr * Bool // isEq or (==) : Attr * a, Attr * a -> Attr * Bool

View file

@ -98,6 +98,8 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::NUM_POW_INT => num_pow_int, Symbol::NUM_POW_INT => num_pow_int,
Symbol::NUM_FLOOR => num_floor, Symbol::NUM_FLOOR => num_floor,
Symbol::NUM_ATAN => num_atan, Symbol::NUM_ATAN => num_atan,
Symbol::NUM_ACOS => num_acos,
Symbol::NUM_ASIN => num_asin,
} }
} }
@ -781,6 +783,46 @@ fn num_atan(symbol: Symbol, var_store: &mut VarStore) -> Def {
) )
} }
/// Num.acos : Float -> Float
fn num_acos(symbol: Symbol, var_store: &mut VarStore) -> Def {
let arg_float_var = var_store.fresh();
let ret_float_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::NumAcos,
args: vec![(arg_float_var, Var(Symbol::ARG_1))],
ret_var: ret_float_var,
};
defn(
symbol,
vec![(arg_float_var, Symbol::ARG_1)],
var_store,
body,
ret_float_var,
)
}
/// Num.asin : Float -> Float
fn num_asin(symbol: Symbol, var_store: &mut VarStore) -> Def {
let arg_float_var = var_store.fresh();
let ret_float_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::NumAsin,
args: vec![(arg_float_var, Var(Symbol::ARG_1))],
ret_var: ret_float_var,
};
defn(
symbol,
vec![(arg_float_var, Symbol::ARG_1)],
var_store,
body,
ret_float_var,
)
}
/// List.isEmpty : List * -> Bool /// List.isEmpty : List * -> Bool
fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();

View file

@ -1443,6 +1443,8 @@ fn to_pending_def<'a>(
SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) | Nested(sub_def) => { SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) | Nested(sub_def) => {
to_pending_def(env, var_store, sub_def, scope, pattern_type) to_pending_def(env, var_store, sub_def, scope, pattern_type)
} }
NotYetImplemented(s) => todo!("{}", s),
} }
} }

View file

@ -47,6 +47,8 @@ pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> {
Nested(alias @ Alias { .. }) => Nested(alias), Nested(alias @ Alias { .. }) => Nested(alias),
ann @ Annotation(_, _) => Nested(ann), ann @ Annotation(_, _) => Nested(ann),
Nested(ann @ Annotation(_, _)) => Nested(ann), Nested(ann @ Annotation(_, _)) => Nested(ann),
Nested(NotYetImplemented(s)) => todo!("{}", s),
NotYetImplemented(s) => todo!("{}", s),
} }
} }

View file

@ -20,6 +20,7 @@ impl<'a> Formattable<'a> for Def<'a> {
spaces.iter().any(|s| is_comment(s)) || sub_def.is_multiline() spaces.iter().any(|s| is_comment(s)) || sub_def.is_multiline()
} }
Nested(def) => def.is_multiline(), Nested(def) => def.is_multiline(),
NotYetImplemented(s) => todo!("{}", s),
} }
} }
@ -66,6 +67,7 @@ impl<'a> Formattable<'a> for Def<'a> {
fmt_spaces(buf, spaces.iter(), indent); fmt_spaces(buf, spaces.iter(), indent);
} }
Nested(def) => def.format(buf, indent), Nested(def) => def.format(buf, indent),
NotYetImplemented(s) => todo!("{}", s),
} }
} }
} }

View file

@ -1842,6 +1842,252 @@ pub fn create_entry_block_alloca<'a, 'ctx>(
builder.build_alloca(basic_type, name) builder.build_alloca(basic_type, name)
} }
fn expose_function_to_host<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
roc_function: FunctionValue<'ctx>,
) {
use inkwell::types::BasicType;
let roc_wrapper_function = make_exception_catching_wrapper(env, roc_function);
let roc_function_type = roc_wrapper_function.get_type();
// STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}`
let mut argument_types = roc_function_type.get_param_types();
let return_type = roc_function_type.get_return_type().unwrap();
let output_type = return_type.ptr_type(AddressSpace::Generic);
argument_types.push(output_type.into());
let c_function_type = env.context.void_type().fn_type(&argument_types, false);
let c_function_name: String = format!("{}_exposed", roc_function.get_name().to_str().unwrap());
let c_function = env.module.add_function(
c_function_name.as_str(),
c_function_type,
Some(Linkage::External),
);
// STEP 2: build the exposed function's body
let builder = env.builder;
let context = env.context;
let entry = context.append_basic_block(c_function, "entry");
builder.position_at_end(entry);
// drop the final argument, which is the pointer we write the result into
let args = c_function.get_params();
let output_arg_index = args.len() - 1;
let args = &args[..args.len() - 1];
debug_assert_eq!(args.len(), roc_function.get_params().len());
debug_assert_eq!(args.len(), roc_wrapper_function.get_params().len());
let call_wrapped = builder.build_call(roc_wrapper_function, args, "call_wrapped_function");
call_wrapped.set_call_convention(FAST_CALL_CONV);
let call_result = call_wrapped.try_as_basic_value().left().unwrap();
let output_arg = c_function
.get_nth_param(output_arg_index as u32)
.unwrap()
.into_pointer_value();
builder.build_store(output_arg, call_result);
builder.build_return(None);
// STEP 3: build a {} -> u64 function that gives the size of the return type
let size_function_type = env.context.i64_type().fn_type(&[], false);
let size_function_name: String = format!("{}_size", roc_function.get_name().to_str().unwrap());
let size_function = env.module.add_function(
size_function_name.as_str(),
size_function_type,
Some(Linkage::External),
);
let entry = context.append_basic_block(size_function, "entry");
builder.position_at_end(entry);
let size: BasicValueEnum = return_type.size_of().unwrap().into();
builder.build_return(Some(&size));
}
fn make_exception_catching_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
roc_function: FunctionValue<'ctx>,
) -> FunctionValue<'ctx> {
// build the C calling convention wrapper
let context = env.context;
let builder = env.builder;
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
let roc_function_type = roc_function.get_type();
let argument_types = roc_function_type.get_param_types();
let wrapper_function_name = format!("{}_catcher", roc_function.get_name().to_str().unwrap());
let wrapper_return_type = context.struct_type(
&[
context.i64_type().into(),
roc_function_type.get_return_type().unwrap(),
],
false,
);
let wrapper_function_type = wrapper_return_type.fn_type(&argument_types, false);
// Add main to the module.
let wrapper_function =
env.module
.add_function(&wrapper_function_name, wrapper_function_type, None);
// our exposed main function adheres to the C calling convention
wrapper_function.set_call_conventions(FAST_CALL_CONV);
// Add main's body
let basic_block = context.append_basic_block(wrapper_function, "entry");
let then_block = context.append_basic_block(wrapper_function, "then_block");
let catch_block = context.append_basic_block(wrapper_function, "catch_block");
let cont_block = context.append_basic_block(wrapper_function, "cont_block");
builder.position_at_end(basic_block);
let result_alloca = builder.build_alloca(wrapper_return_type, "result");
// invoke instead of call, so that we can catch any exeptions thrown in Roc code
let arguments = wrapper_function.get_params();
let call_result = {
let call = builder.build_invoke(
roc_function,
&arguments,
then_block,
catch_block,
"call_roc_function",
);
call.set_call_convention(FAST_CALL_CONV);
call.try_as_basic_value().left().unwrap()
};
// exception handling
{
builder.position_at_end(catch_block);
let landing_pad_type = {
let exception_ptr = context.i8_type().ptr_type(AddressSpace::Generic).into();
let selector_value = context.i32_type().into();
context.struct_type(&[exception_ptr, selector_value], false)
};
let info = builder
.build_catch_all_landing_pad(
&landing_pad_type,
&BasicValueEnum::IntValue(context.i8_type().const_zero()),
context.i8_type().ptr_type(AddressSpace::Generic),
"main_landing_pad",
)
.into_struct_value();
let exception_ptr = builder
.build_extract_value(info, 0, "exception_ptr")
.unwrap();
let thrown = cxa_begin_catch(env, exception_ptr);
let error_msg = {
let exception_type = u8_ptr;
let ptr = builder.build_bitcast(
thrown,
exception_type.ptr_type(AddressSpace::Generic),
"cast",
);
builder.build_load(ptr.into_pointer_value(), "error_msg")
};
let return_type = context.struct_type(&[context.i64_type().into(), u8_ptr.into()], false);
let return_value = {
let v1 = return_type.const_zero();
// flag is non-zero, indicating failure
let flag = context.i64_type().const_int(1, false);
let v2 = builder
.build_insert_value(v1, flag, 0, "set_error")
.unwrap();
let v3 = builder
.build_insert_value(v2, error_msg, 1, "set_exception")
.unwrap();
v3
};
// bitcast result alloca so we can store our concrete type { flag, error_msg } in there
let result_alloca_bitcast = builder
.build_bitcast(
result_alloca,
return_type.ptr_type(AddressSpace::Generic),
"result_alloca_bitcast",
)
.into_pointer_value();
// store our return value
builder.build_store(result_alloca_bitcast, return_value);
cxa_end_catch(env);
builder.build_unconditional_branch(cont_block);
}
{
builder.position_at_end(then_block);
let return_value = {
let v1 = wrapper_return_type.const_zero();
let v2 = builder
.build_insert_value(v1, context.i64_type().const_zero(), 0, "set_no_error")
.unwrap();
let v3 = builder
.build_insert_value(v2, call_result, 1, "set_call_result")
.unwrap();
v3
};
let ptr = builder.build_bitcast(
result_alloca,
wrapper_return_type.ptr_type(AddressSpace::Generic),
"name",
);
builder.build_store(ptr.into_pointer_value(), return_value);
builder.build_unconditional_branch(cont_block);
}
{
builder.position_at_end(cont_block);
let result = builder.build_load(result_alloca, "result");
builder.build_return(Some(&result));
}
// MUST set the personality at the very end;
// doing it earlier can cause the personality to be ignored
let personality_func = get_gxx_personality_v0(env);
wrapper_function.set_personality_function(personality_func);
wrapper_function
}
pub fn build_proc_header<'a, 'ctx, 'env>( pub fn build_proc_header<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
@ -1871,19 +2117,79 @@ pub fn build_proc_header<'a, 'ctx, 'env>(
.module .module
.add_function(fn_name.as_str(), fn_type, Some(Linkage::Private)); .add_function(fn_name.as_str(), fn_type, Some(Linkage::Private));
if env.exposed_to_host.contains(&symbol) {
// If this is an external-facing function, it'll use the C calling convention
// and external linkage.
fn_val.set_linkage(Linkage::External);
fn_val.set_call_conventions(C_CALL_CONV);
} else {
// If it's an internal-only function, it should use the fast calling conention.
fn_val.set_call_conventions(FAST_CALL_CONV); fn_val.set_call_conventions(FAST_CALL_CONV);
if env.exposed_to_host.contains(&symbol) {
expose_function_to_host(env, fn_val);
} }
fn_val fn_val
} }
#[allow(dead_code)]
pub fn build_closure_caller<'a, 'ctx, 'env>(
env: &'a Env<'a, 'ctx, 'env>,
closure_function: FunctionValue<'ctx>,
) {
let context = env.context;
let builder = env.builder;
// asuming the closure has type `a, b, closure_data -> c`
// change that into `a, b, *const closure_data, *mut output -> ()`
// a function `a, b, closure_data -> RocCallResult<c>`
let wrapped_function = make_exception_catching_wrapper(env, closure_function);
let closure_function_type = closure_function.get_type();
let wrapped_function_type = wrapped_function.get_type();
let mut arguments = closure_function_type.get_param_types();
// require that the closure data is passed by reference
let closure_data_type = arguments.pop().unwrap();
let closure_data_ptr_type = get_ptr_type(&closure_data_type, AddressSpace::Generic);
arguments.push(closure_data_ptr_type.into());
// require that a pointer is passed in to write the result into
let output_type = get_ptr_type(
&wrapped_function_type.get_return_type().unwrap(),
AddressSpace::Generic,
);
arguments.push(output_type.into());
let caller_function_type = env.context.void_type().fn_type(&arguments, false);
let caller_function_name: String =
format!("{}_caller", closure_function.get_name().to_str().unwrap());
let caller_function = env.module.add_function(
caller_function_name.as_str(),
caller_function_type,
Some(Linkage::External),
);
caller_function.set_call_conventions(C_CALL_CONV);
let entry = context.append_basic_block(caller_function, "entry");
builder.position_at_end(entry);
let mut parameters = caller_function.get_params();
let output = parameters.pop().unwrap();
let closure_data_ptr = parameters.pop().unwrap();
let closure_data =
builder.build_load(closure_data_ptr.into_pointer_value(), "load_closure_data");
parameters.push(closure_data);
let call = builder.build_call(wrapped_function, &parameters, "call_wrapped_function");
call.set_call_convention(FAST_CALL_CONV);
let result = call.try_as_basic_value().left().unwrap();
builder.build_store(output.into_pointer_value(), result);
builder.build_return(None);
}
pub fn build_proc<'a, 'ctx, 'env>( pub fn build_proc<'a, 'ctx, 'env>(
env: &'a Env<'a, 'ctx, 'env>, env: &'a Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
@ -1907,6 +2213,10 @@ pub fn build_proc<'a, 'ctx, 'env>(
// the closure argument (if any) comes in as an opaque sequence of bytes. // the closure argument (if any) comes in as an opaque sequence of bytes.
// we need to cast that to the specific closure data layout that the body expects // we need to cast that to the specific closure data layout that the body expects
let value = if let Symbol::ARG_CLOSURE = *arg_symbol { let value = if let Symbol::ARG_CLOSURE = *arg_symbol {
// generate a caller function (to be used by the host)
// build_closure_caller(env, fn_val);
// builder.position_at_end(entry);
// blindly trust that there is a layout available for the closure data // blindly trust that there is a layout available for the closure data
let layout = proc.closure_data_layout.clone().unwrap(); let layout = proc.closure_data_layout.clone().unwrap();
@ -2172,7 +2482,7 @@ fn run_low_level<'a, 'ctx, 'env>(
list_join(env, inplace, parent, list, outer_list_layout) list_join(env, inplace, parent, list, outer_list_layout)
} }
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumCeiling | NumFloor NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumSin | NumCos | NumCeiling | NumFloor
| NumToFloat | NumIsFinite | NumAtan => { | NumToFloat | NumIsFinite | NumAtan | NumAcos | NumAsin => {
debug_assert_eq!(args.len(), 1); debug_assert_eq!(args.len(), 1);
let (arg, arg_layout) = load_symbol_and_layout(env, scope, &args[0]); let (arg, arg_layout) = load_symbol_and_layout(env, scope, &args[0]);
@ -2737,6 +3047,8 @@ fn build_float_unary_op<'a, 'ctx, 'env>(
), ),
NumIsFinite => call_bitcode_fn(NumIsFinite, env, &[arg.into()], &bitcode::MATH_IS_FINITE), NumIsFinite => call_bitcode_fn(NumIsFinite, env, &[arg.into()], &bitcode::MATH_IS_FINITE),
NumAtan => call_bitcode_fn(NumAtan, env, &[arg.into()], &bitcode::MATH_ATAN), NumAtan => call_bitcode_fn(NumAtan, env, &[arg.into()], &bitcode::MATH_ATAN),
NumAcos => call_bitcode_fn(NumAcos, env, &[arg.into()], &bitcode::MATH_ACOS),
NumAsin => call_bitcode_fn(NumAsin, env, &[arg.into()], &bitcode::MATH_ASIN),
_ => { _ => {
unreachable!("Unrecognized int unary operation: {:?}", op); unreachable!("Unrecognized int unary operation: {:?}", op);
} }
@ -2918,7 +3230,7 @@ fn cxa_rethrow_exception<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValu
} }
fn get_gxx_personality_v0<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> FunctionValue<'ctx> { fn get_gxx_personality_v0<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> FunctionValue<'ctx> {
let name = "__cxa_rethrow"; let name = "__gxx_personality_v0";
let module = env.module; let module = env.module;
let context = env.context; let context = env.context;

View file

@ -101,6 +101,8 @@ fn generate_module_doc<'a>(
} => (acc, None), } => (acc, None),
Body(_, _) | Nested(_) => (acc, None), Body(_, _) | Nested(_) => (acc, None),
NotYetImplemented(s) => todo!("{}", s),
} }
} }

View file

@ -44,6 +44,8 @@ pub enum LowLevel {
NumFloor, NumFloor,
NumIsFinite, NumIsFinite,
NumAtan, NumAtan,
NumAcos,
NumAsin,
Eq, Eq,
NotEq, NotEq,
And, And,

View file

@ -652,6 +652,8 @@ define_builtins! {
42 NUM_ADD_WRAP: "addWrap" 42 NUM_ADD_WRAP: "addWrap"
43 NUM_ADD_CHECKED: "addChecked" 43 NUM_ADD_CHECKED: "addChecked"
44 NUM_ATAN: "atan" 44 NUM_ATAN: "atan"
45 NUM_ACOS: "acos"
46 NUM_ASIN: "asin"
} }
2 BOOL: "Bool" => { 2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias

View file

@ -527,6 +527,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
| NumPowInt => arena.alloc_slice_copy(&[irrelevant, irrelevant]), | NumPowInt => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor
| NumToFloat | Not | NumIsFinite | NumAtan => arena.alloc_slice_copy(&[irrelevant]), | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin => {
arena.alloc_slice_copy(&[irrelevant])
}
} }
} }

4
compiler/parse/fuzz/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
target
corpus
artifacts

182
compiler/parse/fuzz/Cargo.lock generated Normal file
View file

@ -0,0 +1,182 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "arbitrary"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db55d72333851e17d572bec876e390cd3b11eb1ef53ae821dd9f3b653d2b4569"
[[package]]
name = "bitmaps"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
dependencies = [
"typenum",
]
[[package]]
name = "bumpalo"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
[[package]]
name = "cc"
version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d"
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "im"
version = "14.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "696059c87b83c5a258817ecd67c3af915e3ed141891fc35a1e79908801cf0ce7"
dependencies = [
"bitmaps",
"rand_core 0.5.1",
"rand_xoshiro",
"sized-chunks",
"typenum",
"version_check",
]
[[package]]
name = "im-rc"
version = "14.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "303f7e6256d546e01979071417432425f15c1891fb309a5f2d724ee908fabd6e"
dependencies = [
"bitmaps",
"rand_core 0.5.1",
"rand_xoshiro",
"sized-chunks",
"typenum",
"version_check",
]
[[package]]
name = "inlinable_string"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb6ee2a7da03bfc3b66ca47c92c2e392fcc053ea040a85561749b026f7aad09a"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libfuzzer-sys"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee8c42ab62f43795ed77a965ed07994c5584cdc94fd0ebf14b22ac1524077acc"
dependencies = [
"arbitrary",
"cc",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
[[package]]
name = "rand_xoshiro"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9fcdd2e881d02f1d9390ae47ad8e5696a9e4be7b547a1da2afbc61973217004"
dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "roc_collections"
version = "0.1.0"
dependencies = [
"bumpalo",
"im",
"im-rc",
"wyhash",
]
[[package]]
name = "roc_module"
version = "0.1.0"
dependencies = [
"bumpalo",
"inlinable_string",
"lazy_static",
"roc_collections",
"roc_region",
]
[[package]]
name = "roc_parse"
version = "0.1.0"
dependencies = [
"bumpalo",
"encode_unicode",
"inlinable_string",
"roc_collections",
"roc_module",
"roc_region",
]
[[package]]
name = "roc_parse-fuzz"
version = "0.0.0"
dependencies = [
"bumpalo",
"libfuzzer-sys",
"roc_parse",
]
[[package]]
name = "roc_region"
version = "0.1.0"
[[package]]
name = "sized-chunks"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d59044ea371ad781ff976f7b06480b9f0180e834eda94114f2afb4afc12b7718"
dependencies = [
"bitmaps",
"typenum",
]
[[package]]
name = "typenum"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
[[package]]
name = "version_check"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
[[package]]
name = "wyhash"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "782a50f48ac4336916227cd199c61c7b42f38d0ad705421b49eb12c74c53ae00"
dependencies = [
"rand_core 0.4.2",
]

View file

@ -0,0 +1,39 @@
[package]
name = "roc_parse-fuzz"
version = "0.0.0"
authors = ["Automatically generated"]
publish = false
edition = "2018"
[package.metadata]
cargo-fuzz = true
[dependencies]
libfuzzer-sys = "0.3"
bumpalo = { version = "3.2", features = ["collections"] }
[dependencies.roc_parse]
path = ".."
# Prevent this from interfering with workspaces
[workspace]
members = ["."]
[[bin]]
name = "fuzz_expr"
path = "fuzz_targets/fuzz_expr.rs"
test = false
doc = false
[[bin]]
name = "fuzz_defs"
path = "fuzz_targets/fuzz_defs.rs"
test = false
doc = false
[[bin]]
name = "fuzz_header"
path = "fuzz_targets/fuzz_header.rs"
test = false
doc = false

View file

@ -0,0 +1,11 @@
To setup fuzzing you will need to install cargo-fuzz and run with rust nightly:
```
$ cargo install cargo-fuzz
$ cargo +nightly fuzz run -j<cores> <target> -- -dict=dict.txt
```
The different targets can be found by running `cargo fuzz list`.
When a bug is found, it will be reported with commands to run it again and look for a minimized version.
If you are going to file a bug, please minimize the input before filing the bug.

View file

@ -0,0 +1,36 @@
"if"
"then"
"else"
"when"
"as"
"is"
"expect"
"app"
"platform"
"provides"
"requires"
"exposes"
"imports"
"effects"
"interface"
"|>"
"=="
"!="
"&&"
"||"
"+"
"*"
"-"
"//"
"/"
"<="
"<"
">="
">"
"^"
"%%"
"%"
"->"

View file

@ -0,0 +1,11 @@
#![no_main]
use bumpalo::Bump;
use libfuzzer_sys::fuzz_target;
use roc_parse::test_helpers::parse_defs_with;
fuzz_target!(|data: &[u8]| {
if let Ok(input) = std::str::from_utf8(data) {
let arena = Bump::new();
let _actual = parse_defs_with(&arena, input.trim());
}
});

View file

@ -0,0 +1,11 @@
#![no_main]
use bumpalo::Bump;
use libfuzzer_sys::fuzz_target;
use roc_parse::test_helpers::parse_expr_with;
fuzz_target!(|data: &[u8]| {
if let Ok(input) = std::str::from_utf8(data) {
let arena = Bump::new();
let _actual = parse_expr_with(&arena, input.trim());
}
});

View file

@ -0,0 +1,11 @@
#![no_main]
use bumpalo::Bump;
use libfuzzer_sys::fuzz_target;
use roc_parse::test_helpers::parse_header_with;
fuzz_target!(|data: &[u8]| {
if let Ok(input) = std::str::from_utf8(data) {
let arena = Bump::new();
let _actual = parse_header_with(&arena, input.trim());
}
});

View file

@ -277,6 +277,8 @@ pub enum Def<'a> {
/// This is used only to avoid cloning when reordering expressions (e.g. in desugar()). /// This is used only to avoid cloning when reordering expressions (e.g. in desugar()).
/// It lets us take a (&Def) and create a plain (Def) from it. /// It lets us take a (&Def) and create a plain (Def) from it.
Nested(&'a Def<'a>), Nested(&'a Def<'a>),
NotYetImplemented(&'static str),
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]

View file

@ -561,7 +561,7 @@ fn annotation_or_alias<'a>(
ann: loc_ann, ann: loc_ann,
}, },
Apply(_, _) => { Apply(_, _) => {
panic!("TODO gracefully handle invalid Apply in type annotation"); Def::NotYetImplemented("TODO gracefully handle invalid Apply in type annotation")
} }
SpaceAfter(value, spaces_before) => Def::SpaceAfter( SpaceAfter(value, spaces_before) => Def::SpaceAfter(
arena.alloc(annotation_or_alias(arena, value, region, loc_ann)), arena.alloc(annotation_or_alias(arena, value, region, loc_ann)),
@ -574,19 +574,19 @@ fn annotation_or_alias<'a>(
Nested(value) => annotation_or_alias(arena, value, region, loc_ann), Nested(value) => annotation_or_alias(arena, value, region, loc_ann),
PrivateTag(_) => { PrivateTag(_) => {
panic!("TODO gracefully handle trying to use a private tag as an annotation."); Def::NotYetImplemented("TODO gracefully handle trying to use a private tag as an annotation.")
} }
QualifiedIdentifier { .. } => { QualifiedIdentifier { .. } => {
panic!("TODO gracefully handle trying to annotate a qualified identifier, e.g. `Foo.bar : ...`"); Def::NotYetImplemented("TODO gracefully handle trying to annotate a qualified identifier, e.g. `Foo.bar : ...`")
} }
NumLiteral(_) | NonBase10Literal { .. } | FloatLiteral(_) | StrLiteral(_) => { NumLiteral(_) | NonBase10Literal { .. } | FloatLiteral(_) | StrLiteral(_) => {
panic!("TODO gracefully handle trying to annotate a litera"); Def::NotYetImplemented("TODO gracefully handle trying to annotate a litera")
} }
Underscore => { Underscore => {
panic!("TODO gracefully handle trying to give a type annotation to an undrscore"); Def::NotYetImplemented("TODO gracefully handle trying to give a type annotation to an undrscore")
} }
Malformed(_) => { Malformed(_) => {
panic!("TODO translate a malformed pattern into a malformed annotation"); Def::NotYetImplemented("TODO translate a malformed pattern into a malformed annotation")
} }
Identifier(ident) => { Identifier(ident) => {
// This is a regular Annotation // This is a regular Annotation
@ -633,7 +633,13 @@ fn parse_def_expr<'a>(
)) ))
// `<` because '=' should be same indent (or greater) as the entire def-expr // `<` because '=' should be same indent (or greater) as the entire def-expr
} else if equals_sign_indent < def_start_col { } else if equals_sign_indent < def_start_col {
todo!("TODO the = in this declaration seems outdented. equals_sign_indent was {} and def_start_col was {}", equals_sign_indent, def_start_col); Err((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented(format!("TODO the = in this declaration seems outdented. equals_sign_indent was {} and def_start_col was {}", equals_sign_indent, def_start_col)),
},
state,
))
} else { } else {
// Indented more beyond the original indent of the entire def-expr. // Indented more beyond the original indent of the entire def-expr.
let indented_more = def_start_col + 1; let indented_more = def_start_col + 1;
@ -720,7 +726,15 @@ fn parse_def_signature<'a>(
)) ))
// `<` because ':' should be same indent or greater // `<` because ':' should be same indent or greater
} else if colon_indent < original_indent { } else if colon_indent < original_indent {
panic!("TODO the : in this declaration seems outdented"); Err((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented(
"TODO the : in this declaration seems outdented".to_string(),
),
},
state,
))
} else { } else {
// Indented more beyond the original indent. // Indented more beyond the original indent.
let indented_more = original_indent + 1; let indented_more = original_indent + 1;
@ -1069,11 +1083,12 @@ fn loc_ident_pattern<'a>(min_indent: u16) -> impl Parser<'a, Located<Pattern<'a>
} else { } else {
format!("{}.{}", module_name, parts.join(".")) format!("{}.{}", module_name, parts.join("."))
}; };
Ok(( Ok((
Located { Located {
region: loc_ident.region, region: loc_ident.region,
value: Pattern::Malformed(arena.alloc(malformed_str)), value: Pattern::Malformed(
String::from_str_in(&malformed_str, &arena).into_bump_str(),
),
}, },
state, state,
)) ))
@ -1120,7 +1135,15 @@ mod when {
), ),
move |arena, state, (case_indent, loc_condition)| { move |arena, state, (case_indent, loc_condition)| {
if case_indent < min_indent { if case_indent < min_indent {
panic!("TODO case wasn't indented enough"); return Err((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented(
"TODO case wasn't indented enough".to_string(),
),
},
state,
));
} }
// Everything in the branches must be indented at least as much as the case itself. // Everything in the branches must be indented at least as much as the case itself.
@ -1178,9 +1201,15 @@ mod when {
if alternatives_indented_correctly(&loc_patterns, original_indent) { if alternatives_indented_correctly(&loc_patterns, original_indent) {
Ok(((loc_patterns, loc_guard), state)) Ok(((loc_patterns, loc_guard), state))
} else { } else {
panic!( Err((
"TODO additional branch didn't have same indentation as first branch" Fail {
); attempting: state.attempting,
reason: FailReason::NotYetImplemented(
"TODO additional branch didn't have same indentation as first branch".to_string(),
),
},
state,
))
} }
}, },
), ),
@ -1490,10 +1519,16 @@ fn ident_etc<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
}); });
} }
Err(malformed) => { Err(malformed) => {
panic!( return Err((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented(format!(
"TODO early return malformed pattern {:?}", "TODO early return malformed pattern {:?}",
malformed malformed
); )),
},
state,
));
} }
} }
} }

View file

@ -24,4 +24,5 @@ pub mod number_literal;
pub mod pattern; pub mod pattern;
pub mod problems; pub mod problems;
pub mod string_literal; pub mod string_literal;
pub mod test_helpers;
pub mod type_annotation; pub mod type_annotation;

View file

@ -224,6 +224,7 @@ pub enum FailReason {
BadUtf8, BadUtf8,
ReservedKeyword(Region), ReservedKeyword(Region),
ArgumentsBeforeEquals(Region), ArgumentsBeforeEquals(Region),
NotYetImplemented(String),
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]

View file

@ -1,8 +1,8 @@
use crate::ast::{Attempting, EscapedChar, StrLiteral, StrSegment}; use crate::ast::{Attempting, EscapedChar, StrLiteral, StrSegment};
use crate::expr; use crate::expr;
use crate::parser::{ use crate::parser::{
allocated, ascii_char, ascii_hex_digits, loc, parse_utf8, unexpected, unexpected_eof, allocated, ascii_char, ascii_hex_digits, loc, parse_utf8, unexpected, unexpected_eof, Fail,
ParseResult, Parser, State, FailReason, ParseResult, Parser, State,
}; };
use bumpalo::collections::vec::Vec; use bumpalo::collections::vec::Vec;
use bumpalo::Bump; use bumpalo::Bump;
@ -279,7 +279,16 @@ where
// lines.push(line); // lines.push(line);
// Ok((StrLiteral::Block(lines.into_bump_slice()), state)) // Ok((StrLiteral::Block(lines.into_bump_slice()), state))
todo!("TODO parse this line in a block string: {:?}", line); Err((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented(format!(
"TODO parse this line in a block string: {:?}",
line
)),
},
state,
))
} }
Err(reason) => state.fail(reason), Err(reason) => state.fail(reason),
}; };

View file

@ -0,0 +1,45 @@
use crate::ast::{self, Attempting};
use crate::blankspace::space0_before;
use crate::expr::expr;
use crate::module::{header, module_defs};
use crate::parser::{loc, Fail, Parser, State};
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_region::all::Located;
#[allow(dead_code)]
pub fn parse_expr_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> {
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
}
#[allow(dead_code)]
pub fn parse_header_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Module<'a>, Fail> {
let state = State::new(input.trim().as_bytes(), Attempting::Module);
let answer = header().parse(arena, state);
answer
.map(|(loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail)
}
#[allow(dead_code)]
pub fn parse_defs_with<'a>(
arena: &'a Bump,
input: &'a str,
) -> Result<Vec<'a, Located<ast::Def<'a>>>, Fail> {
let state = State::new(input.trim().as_bytes(), Attempting::Module);
let answer = module_defs().parse(arena, state);
answer
.map(|(loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail)
}
#[allow(dead_code)]
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
let state = State::new(input.trim().as_bytes(), Attempting::Module);
let parser = space0_before(loc(expr(0)), 0);
let answer = parser.parse(&arena, state);
answer
.map(|(loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail)
}

View file

@ -4,8 +4,8 @@ use crate::expr::{global_tag, private_tag};
use crate::ident::join_module_parts; use crate::ident::join_module_parts;
use crate::keyword; use crate::keyword;
use crate::parser::{ use crate::parser::{
allocated, ascii_char, ascii_string, not, optional, peek_utf8_char, unexpected, Either, allocated, ascii_char, ascii_string, not, optional, peek_utf8_char, unexpected, Either, Fail,
ParseResult, Parser, State, FailReason, ParseResult, Parser, State,
}; };
use bumpalo::collections::string::String; use bumpalo::collections::string::String;
use bumpalo::collections::vec::Vec; use bumpalo::collections::vec::Vec;
@ -239,7 +239,13 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
Ok((first, state)) Ok((first, state))
} else { } else {
// e.g. `Int,Int` without an arrow and return type // e.g. `Int,Int` without an arrow and return type
panic!("Invalid function signature") Err((
Fail {
attempting: state.attempting,
reason: FailReason::NotYetImplemented("TODO: Decide the correct error to return for 'Invalid function signature'".to_string()),
},
state,
))
} }
} }
} }

View file

@ -1,23 +0,0 @@
extern crate bumpalo;
use self::bumpalo::Bump;
use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before;
use roc_parse::parser::{loc, Fail, Parser, State};
use roc_region::all::Located;
#[allow(dead_code)]
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> {
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
}
#[allow(dead_code)]
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
let state = State::new(input.trim().as_bytes(), Attempting::Module);
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
let answer = parser.parse(&arena, state);
answer
.map(|(loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail)
}

View file

@ -11,11 +11,8 @@ extern crate quickcheck_macros;
extern crate roc_module; extern crate roc_module;
extern crate roc_parse; extern crate roc_parse;
mod helpers;
#[cfg(test)] #[cfg(test)]
mod test_parse { mod test_parse {
use crate::helpers::parse_with;
use bumpalo::collections::vec::Vec; use bumpalo::collections::vec::Vec;
use bumpalo::{self, Bump}; use bumpalo::{self, Bump};
use roc_module::operator::BinOp::*; use roc_module::operator::BinOp::*;
@ -33,19 +30,20 @@ mod test_parse {
use roc_parse::header::ModuleName; use roc_parse::header::ModuleName;
use roc_parse::module::{interface_header, module_defs}; use roc_parse::module::{interface_header, module_defs};
use roc_parse::parser::{Fail, FailReason, Parser, State}; use roc_parse::parser::{Fail, FailReason, Parser, State};
use roc_parse::test_helpers::parse_expr_with;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use std::{f64, i64}; use std::{f64, i64};
fn assert_parses_to<'a>(input: &'a str, expected_expr: Expr<'a>) { fn assert_parses_to<'a>(input: &'a str, expected_expr: Expr<'a>) {
let arena = Bump::new(); let arena = Bump::new();
let actual = parse_with(&arena, input.trim()); let actual = parse_expr_with(&arena, input.trim());
assert_eq!(Ok(expected_expr), actual); assert_eq!(Ok(expected_expr), actual);
} }
fn assert_parsing_fails<'a>(input: &'a str, reason: FailReason, attempting: Attempting) { fn assert_parsing_fails<'a>(input: &'a str, reason: FailReason, attempting: Attempting) {
let arena = Bump::new(); let arena = Bump::new();
let actual = parse_with(&arena, input); let actual = parse_expr_with(&arena, input);
let expected_fail = Fail { reason, attempting }; let expected_fail = Fail { reason, attempting };
assert_eq!(Err(expected_fail), actual); assert_eq!(Err(expected_fail), actual);
@ -53,7 +51,7 @@ mod test_parse {
fn assert_segments<E: Fn(&Bump) -> Vec<'_, ast::StrSegment<'_>>>(input: &str, to_expected: E) { fn assert_segments<E: Fn(&Bump) -> Vec<'_, ast::StrSegment<'_>>>(input: &str, to_expected: E) {
let arena = Bump::new(); let arena = Bump::new();
let actual = parse_with(&arena, arena.alloc(input)); let actual = parse_expr_with(&arena, arena.alloc(input));
let expected_slice = to_expected(&arena); let expected_slice = to_expected(&arena);
let expected_expr = Expr::Str(Line(&expected_slice)); let expected_expr = Expr::Str(Line(&expected_slice));
@ -77,7 +75,7 @@ mod test_parse {
("\\t", EscapedChar::Tab), ("\\t", EscapedChar::Tab),
("\\\"", EscapedChar::Quote), ("\\\"", EscapedChar::Quote),
] { ] {
let actual = parse_with(&arena, arena.alloc(to_input(string))); let actual = parse_expr_with(&arena, arena.alloc(to_input(string)));
let expected_slice = to_expected(*escaped, &arena); let expected_slice = to_expected(*escaped, &arena);
let expected_expr = Expr::Str(Line(&expected_slice)); let expected_expr = Expr::Str(Line(&expected_slice));
@ -423,7 +421,7 @@ mod test_parse {
fields: &[], fields: &[],
update: None, update: None,
}; };
let actual = parse_with(&arena, "{}"); let actual = parse_expr_with(&arena, "{}");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -455,7 +453,7 @@ mod test_parse {
fields, fields,
}; };
let actual = parse_with(&arena, "{ Foo.Bar.baz & x: 5, y: 0 }"); let actual = parse_expr_with(&arena, "{ Foo.Bar.baz & x: 5, y: 0 }");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -470,7 +468,7 @@ mod test_parse {
Located::new(0, 0, 2, 3, Num("2")), Located::new(0, 0, 2, 3, Num("2")),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "1+2"); let actual = parse_expr_with(&arena, "1+2");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -484,7 +482,7 @@ mod test_parse {
Located::new(0, 0, 2, 3, Num("2")), Located::new(0, 0, 2, 3, Num("2")),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "1-2"); let actual = parse_expr_with(&arena, "1-2");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -498,7 +496,7 @@ mod test_parse {
Located::new(0, 0, 7, 8, Num("2")), Located::new(0, 0, 7, 8, Num("2")),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "1 + 2"); let actual = parse_expr_with(&arena, "1 + 2");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -512,7 +510,7 @@ mod test_parse {
Located::new(0, 0, 7, 8, Num("2")), Located::new(0, 0, 7, 8, Num("2")),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "1 - 2"); let actual = parse_expr_with(&arena, "1 - 2");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -535,7 +533,7 @@ mod test_parse {
Located::new(0, 0, 4, 5, Num("2")), Located::new(0, 0, 4, 5, Num("2")),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "x + 2"); let actual = parse_expr_with(&arena, "x + 2");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -557,7 +555,7 @@ mod test_parse {
Located::new(0, 0, 4, 5, Num("2")), Located::new(0, 0, 4, 5, Num("2")),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "x - 2"); let actual = parse_expr_with(&arena, "x - 2");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -572,7 +570,7 @@ mod test_parse {
Located::new(1, 1, 2, 3, Num("4")), Located::new(1, 1, 2, 3, Num("4")),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "3 \n+ 4"); let actual = parse_expr_with(&arena, "3 \n+ 4");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -587,7 +585,7 @@ mod test_parse {
Located::new(1, 1, 2, 3, Num("4")), Located::new(1, 1, 2, 3, Num("4")),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "3 \n- 4"); let actual = parse_expr_with(&arena, "3 \n- 4");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -602,7 +600,7 @@ mod test_parse {
Located::new(1, 1, 2, 3, spaced_int), Located::new(1, 1, 2, 3, spaced_int),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "3 *\n 4"); let actual = parse_expr_with(&arena, "3 *\n 4");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -617,7 +615,7 @@ mod test_parse {
Located::new(1, 1, 2, 3, spaced_int), Located::new(1, 1, 2, 3, spaced_int),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "3 -\n 4"); let actual = parse_expr_with(&arena, "3 -\n 4");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -632,7 +630,7 @@ mod test_parse {
Located::new(1, 1, 2, 3, Num("4")), Located::new(1, 1, 2, 3, Num("4")),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "3 # 2 × 2\n+ 4"); let actual = parse_expr_with(&arena, "3 # 2 × 2\n+ 4");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -647,7 +645,7 @@ mod test_parse {
Located::new(1, 1, 2, 3, Num("4")), Located::new(1, 1, 2, 3, Num("4")),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "3 # test!\n+ 4"); let actual = parse_expr_with(&arena, "3 # test!\n+ 4");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -662,7 +660,7 @@ mod test_parse {
Located::new(1, 1, 1, 3, spaced_int), Located::new(1, 1, 1, 3, spaced_int),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "12 * # test!\n 92"); let actual = parse_expr_with(&arena, "12 * # test!\n 92");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -678,7 +676,7 @@ mod test_parse {
Located::new(3, 3, 2, 3, spaced_int2), Located::new(3, 3, 2, 3, spaced_int2),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "3 \n+ \n\n 4"); let actual = parse_expr_with(&arena, "3 \n+ \n\n 4");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -702,7 +700,7 @@ mod test_parse {
Located::new(0, 0, 3, 4, var2), Located::new(0, 0, 3, 4, var2),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "x- y"); let actual = parse_expr_with(&arena, "x- y");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -716,7 +714,7 @@ mod test_parse {
Located::new(0, 0, 4, 5, Num("5")), Located::new(0, 0, 4, 5, Num("5")),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "-12-5"); let actual = parse_expr_with(&arena, "-12-5");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -730,7 +728,7 @@ mod test_parse {
Located::new(0, 0, 3, 5, Num("11")), Located::new(0, 0, 3, 5, Num("11")),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "10*11"); let actual = parse_expr_with(&arena, "10*11");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -749,7 +747,7 @@ mod test_parse {
Located::new(0, 0, 3, 9, BinOp(inner)), Located::new(0, 0, 3, 9, BinOp(inner)),
)); ));
let expected = BinOp(outer); let expected = BinOp(outer);
let actual = parse_with(&arena, "31*42+534"); let actual = parse_expr_with(&arena, "31*42+534");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -771,7 +769,7 @@ mod test_parse {
Located::new(0, 0, 3, 4, var2), Located::new(0, 0, 3, 4, var2),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "x==y"); let actual = parse_expr_with(&arena, "x==y");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -793,7 +791,7 @@ mod test_parse {
Located::new(0, 0, 5, 6, var2), Located::new(0, 0, 5, 6, var2),
)); ));
let expected = BinOp(tuple); let expected = BinOp(tuple);
let actual = parse_with(&arena, "x == y"); let actual = parse_expr_with(&arena, "x == y");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -807,7 +805,7 @@ mod test_parse {
module_name: "", module_name: "",
ident: "whee", ident: "whee",
}; };
let actual = parse_with(&arena, "whee"); let actual = parse_expr_with(&arena, "whee");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -819,7 +817,7 @@ mod test_parse {
module_name: "", module_name: "",
ident: "whee", ident: "whee",
})); }));
let actual = parse_with(&arena, "(whee)"); let actual = parse_expr_with(&arena, "(whee)");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -831,7 +829,7 @@ mod test_parse {
module_name: "One.Two", module_name: "One.Two",
ident: "whee", ident: "whee",
}; };
let actual = parse_with(&arena, "One.Two.whee"); let actual = parse_expr_with(&arena, "One.Two.whee");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -842,7 +840,7 @@ mod test_parse {
fn basic_global_tag() { fn basic_global_tag() {
let arena = Bump::new(); let arena = Bump::new();
let expected = Expr::GlobalTag("Whee"); let expected = Expr::GlobalTag("Whee");
let actual = parse_with(&arena, "Whee"); let actual = parse_expr_with(&arena, "Whee");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -851,7 +849,7 @@ mod test_parse {
fn basic_private_tag() { fn basic_private_tag() {
let arena = Bump::new(); let arena = Bump::new();
let expected = Expr::PrivateTag("@Whee"); let expected = Expr::PrivateTag("@Whee");
let actual = parse_with(&arena, "@Whee"); let actual = parse_expr_with(&arena, "@Whee");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -867,7 +865,7 @@ mod test_parse {
args, args,
CalledVia::Space, CalledVia::Space,
); );
let actual = parse_with(&arena, "@Whee 12 34"); let actual = parse_expr_with(&arena, "@Whee 12 34");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -883,7 +881,7 @@ mod test_parse {
args, args,
CalledVia::Space, CalledVia::Space,
); );
let actual = parse_with(&arena, "Whee 12 34"); let actual = parse_expr_with(&arena, "Whee 12 34");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -901,7 +899,7 @@ mod test_parse {
args, args,
CalledVia::Space, CalledVia::Space,
); );
let actual = parse_with(&arena, "Whee (12) (34)"); let actual = parse_expr_with(&arena, "Whee (12) (34)");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -910,7 +908,7 @@ mod test_parse {
fn qualified_global_tag() { fn qualified_global_tag() {
let arena = Bump::new(); let arena = Bump::new();
let expected = Expr::MalformedIdent("One.Two.Whee"); let expected = Expr::MalformedIdent("One.Two.Whee");
let actual = parse_with(&arena, "One.Two.Whee"); let actual = parse_expr_with(&arena, "One.Two.Whee");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -920,7 +918,7 @@ mod test_parse {
// fn qualified_private_tag() { // fn qualified_private_tag() {
// let arena = Bump::new(); // let arena = Bump::new();
// let expected = Expr::MalformedIdent("One.Two.@Whee"); // let expected = Expr::MalformedIdent("One.Two.@Whee");
// let actual = parse_with(&arena, "One.Two.@Whee"); // let actual = parse_expr_with(&arena, "One.Two.@Whee");
// assert_eq!(Ok(expected), actual); // assert_eq!(Ok(expected), actual);
// } // }
@ -931,7 +929,7 @@ mod test_parse {
let pattern = Located::new(0, 0, 1, 6, Pattern::GlobalTag("Thing")); let pattern = Located::new(0, 0, 1, 6, Pattern::GlobalTag("Thing"));
let patterns = &[pattern]; let patterns = &[pattern];
let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 10, 12, Num("42")))); let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 10, 12, Num("42"))));
let actual = parse_with(&arena, "\\Thing -> 42"); let actual = parse_expr_with(&arena, "\\Thing -> 42");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -940,7 +938,7 @@ mod test_parse {
fn private_qualified_tag() { fn private_qualified_tag() {
let arena = Bump::new(); let arena = Bump::new();
let expected = Expr::MalformedIdent("@One.Two.Whee"); let expected = Expr::MalformedIdent("@One.Two.Whee");
let actual = parse_with(&arena, "@One.Two.Whee"); let actual = parse_expr_with(&arena, "@One.Two.Whee");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -952,7 +950,7 @@ mod test_parse {
let arena = Bump::new(); let arena = Bump::new();
let elems = &[]; let elems = &[];
let expected = List(elems); let expected = List(elems);
let actual = parse_with(&arena, "[]"); let actual = parse_expr_with(&arena, "[]");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -963,7 +961,7 @@ mod test_parse {
let arena = Bump::new(); let arena = Bump::new();
let elems = &[]; let elems = &[];
let expected = List(elems); let expected = List(elems);
let actual = parse_with(&arena, "[ ]"); let actual = parse_expr_with(&arena, "[ ]");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -973,7 +971,7 @@ mod test_parse {
let arena = Bump::new(); let arena = Bump::new();
let elems = &[&*arena.alloc(Located::new(0, 0, 1, 2, Num("1")))]; let elems = &[&*arena.alloc(Located::new(0, 0, 1, 2, Num("1")))];
let expected = List(elems); let expected = List(elems);
let actual = parse_with(&arena, "[1]"); let actual = parse_expr_with(&arena, "[1]");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -983,7 +981,7 @@ mod test_parse {
let arena = Bump::new(); let arena = Bump::new();
let elems = &[&*arena.alloc(Located::new(0, 0, 2, 3, Num("1")))]; let elems = &[&*arena.alloc(Located::new(0, 0, 2, 3, Num("1")))];
let expected = List(elems); let expected = List(elems);
let actual = parse_with(&arena, "[ 1 ]"); let actual = parse_expr_with(&arena, "[ 1 ]");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -998,7 +996,7 @@ mod test_parse {
ident: "rec", ident: "rec",
}; };
let expected = Access(arena.alloc(var), "field"); let expected = Access(arena.alloc(var), "field");
let actual = parse_with(&arena, "rec.field"); let actual = parse_expr_with(&arena, "rec.field");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1011,7 +1009,7 @@ mod test_parse {
ident: "rec", ident: "rec",
})); }));
let expected = Access(arena.alloc(paren_var), "field"); let expected = Access(arena.alloc(paren_var), "field");
let actual = parse_with(&arena, "(rec).field"); let actual = parse_expr_with(&arena, "(rec).field");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1024,7 +1022,7 @@ mod test_parse {
ident: "rec", ident: "rec",
})); }));
let expected = Access(arena.alloc(paren_var), "field"); let expected = Access(arena.alloc(paren_var), "field");
let actual = parse_with(&arena, "(One.Two.rec).field"); let actual = parse_expr_with(&arena, "(One.Two.rec).field");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1040,7 +1038,7 @@ mod test_parse {
arena.alloc(Access(arena.alloc(Access(arena.alloc(var), "abc")), "def")), arena.alloc(Access(arena.alloc(Access(arena.alloc(var), "abc")), "def")),
"ghi", "ghi",
); );
let actual = parse_with(&arena, "rec.abc.def.ghi"); let actual = parse_expr_with(&arena, "rec.abc.def.ghi");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1056,7 +1054,7 @@ mod test_parse {
arena.alloc(Access(arena.alloc(Access(arena.alloc(var), "abc")), "def")), arena.alloc(Access(arena.alloc(Access(arena.alloc(var), "abc")), "def")),
"ghi", "ghi",
); );
let actual = parse_with(&arena, "One.Two.rec.abc.def.ghi"); let actual = parse_expr_with(&arena, "One.Two.rec.abc.def.ghi");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1077,7 +1075,7 @@ mod test_parse {
args, args,
CalledVia::Space, CalledVia::Space,
); );
let actual = parse_with(&arena, "whee 1"); let actual = parse_expr_with(&arena, "whee 1");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1102,7 +1100,7 @@ mod test_parse {
args, args,
CalledVia::Space, CalledVia::Space,
); );
let actual = parse_with(&arena, "whee 12 34"); let actual = parse_expr_with(&arena, "whee 12 34");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1155,7 +1153,7 @@ mod test_parse {
args, args,
CalledVia::Space, CalledVia::Space,
); );
let actual = parse_with(&arena, "a b c d"); let actual = parse_expr_with(&arena, "a b c d");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1174,7 +1172,7 @@ mod test_parse {
args, args,
CalledVia::Space, CalledVia::Space,
); );
let actual = parse_with(&arena, "(whee) 1"); let actual = parse_expr_with(&arena, "(whee) 1");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1191,7 +1189,7 @@ mod test_parse {
}; };
let loc_arg1_expr = Located::new(0, 0, 1, 4, arg1_expr); let loc_arg1_expr = Located::new(0, 0, 1, 4, arg1_expr);
let expected = UnaryOp(arena.alloc(loc_arg1_expr), loc_op); let expected = UnaryOp(arena.alloc(loc_arg1_expr), loc_op);
let actual = parse_with(&arena, "-foo"); let actual = parse_expr_with(&arena, "-foo");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1206,7 +1204,7 @@ mod test_parse {
}; };
let loc_arg1_expr = Located::new(0, 0, 1, 5, arg1_expr); let loc_arg1_expr = Located::new(0, 0, 1, 5, arg1_expr);
let expected = UnaryOp(arena.alloc(loc_arg1_expr), loc_op); let expected = UnaryOp(arena.alloc(loc_arg1_expr), loc_op);
let actual = parse_with(&arena, "!blah"); let actual = parse_expr_with(&arena, "!blah");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1242,7 +1240,7 @@ mod test_parse {
CalledVia::Space, CalledVia::Space,
); );
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 13, apply_expr)), loc_op); let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 13, apply_expr)), loc_op);
let actual = parse_with(&arena, "-whee 12 foo"); let actual = parse_expr_with(&arena, "-whee 12 foo");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1278,7 +1276,7 @@ mod test_parse {
CalledVia::Space, CalledVia::Space,
); );
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 13, apply_expr)), loc_op); let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 13, apply_expr)), loc_op);
let actual = parse_with(&arena, "!whee 12 foo"); let actual = parse_expr_with(&arena, "!whee 12 foo");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1314,7 +1312,7 @@ mod test_parse {
CalledVia::Space, CalledVia::Space,
))); )));
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 15, apply_expr)), loc_op); let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 15, apply_expr)), loc_op);
let actual = parse_with(&arena, "-(whee 12 foo)"); let actual = parse_expr_with(&arena, "-(whee 12 foo)");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1350,7 +1348,7 @@ mod test_parse {
CalledVia::Space, CalledVia::Space,
))); )));
let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 15, apply_expr)), loc_op); let expected = UnaryOp(arena.alloc(Located::new(0, 0, 1, 15, apply_expr)), loc_op);
let actual = parse_with(&arena, "!(whee 12 foo)"); let actual = parse_expr_with(&arena, "!(whee 12 foo)");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1377,7 +1375,7 @@ mod test_parse {
args, args,
CalledVia::Space, CalledVia::Space,
); );
let actual = parse_with(&arena, "whee 12 -foo"); let actual = parse_expr_with(&arena, "whee 12 -foo");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1394,7 +1392,7 @@ mod test_parse {
let access = Access(arena.alloc(var), "field"); let access = Access(arena.alloc(var), "field");
let loc_access = Located::new(0, 0, 1, 11, access); let loc_access = Located::new(0, 0, 1, 11, access);
let expected = UnaryOp(arena.alloc(loc_access), loc_op); let expected = UnaryOp(arena.alloc(loc_access), loc_op);
let actual = parse_with(&arena, "-rec1.field"); let actual = parse_expr_with(&arena, "-rec1.field");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1407,7 +1405,7 @@ mod test_parse {
let pattern = Located::new(0, 0, 1, 2, Identifier("a")); let pattern = Located::new(0, 0, 1, 2, Identifier("a"));
let patterns = &[pattern]; let patterns = &[pattern];
let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 6, 8, Num("42")))); let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 6, 8, Num("42"))));
let actual = parse_with(&arena, "\\a -> 42"); let actual = parse_expr_with(&arena, "\\a -> 42");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1418,7 +1416,7 @@ mod test_parse {
let pattern = Located::new(0, 0, 1, 2, Underscore); let pattern = Located::new(0, 0, 1, 2, Underscore);
let patterns = &[pattern]; let patterns = &[pattern];
let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 6, 8, Num("42")))); let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 6, 8, Num("42"))));
let actual = parse_with(&arena, "\\_ -> 42"); let actual = parse_expr_with(&arena, "\\_ -> 42");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1429,7 +1427,7 @@ mod test_parse {
// underscore in an argument name, it would parse as three arguments // underscore in an argument name, it would parse as three arguments
// (and would ignore the underscore as if it had been blank space). // (and would ignore the underscore as if it had been blank space).
let arena = Bump::new(); let arena = Bump::new();
let actual = parse_with(&arena, "\\the_answer -> 42"); let actual = parse_expr_with(&arena, "\\the_answer -> 42");
assert_eq!(Ok(MalformedClosure), actual); assert_eq!(Ok(MalformedClosure), actual);
} }
@ -1441,7 +1439,7 @@ mod test_parse {
let arg2 = Located::new(0, 0, 4, 5, Identifier("b")); let arg2 = Located::new(0, 0, 4, 5, Identifier("b"));
let patterns = &[arg1, arg2]; let patterns = &[arg1, arg2];
let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 9, 11, Num("42")))); let expected = Closure(patterns, arena.alloc(Located::new(0, 0, 9, 11, Num("42"))));
let actual = parse_with(&arena, "\\a, b -> 42"); let actual = parse_expr_with(&arena, "\\a, b -> 42");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1457,7 +1455,7 @@ mod test_parse {
arena.alloc(patterns), arena.alloc(patterns),
arena.alloc(Located::new(0, 0, 12, 14, Num("42"))), arena.alloc(Located::new(0, 0, 12, 14, Num("42"))),
); );
let actual = parse_with(&arena, "\\a, b, c -> 42"); let actual = parse_expr_with(&arena, "\\a, b, c -> 42");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -1472,7 +1470,7 @@ mod test_parse {
arena.alloc(patterns), arena.alloc(patterns),
arena.alloc(Located::new(0, 0, 9, 11, Num("42"))), arena.alloc(Located::new(0, 0, 9, 11, Num("42"))),
); );
let actual = parse_with(&arena, "\\_, _ -> 42"); let actual = parse_expr_with(&arena, "\\_, _ -> 42");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
} }
@ -2040,7 +2038,7 @@ mod test_parse {
}; };
let loc_cond = Located::new(0, 0, 5, 6, var); let loc_cond = Located::new(0, 0, 5, 6, var);
let expected = Expr::When(arena.alloc(loc_cond), branches); let expected = Expr::When(arena.alloc(loc_cond), branches);
let actual = parse_with( let actual = parse_expr_with(
&arena, &arena,
indoc!( indoc!(
r#" r#"
@ -2084,7 +2082,7 @@ mod test_parse {
}; };
let loc_cond = Located::new(0, 0, 5, 6, var); let loc_cond = Located::new(0, 0, 5, 6, var);
let expected = Expr::When(arena.alloc(loc_cond), branches); let expected = Expr::When(arena.alloc(loc_cond), branches);
let actual = parse_with( let actual = parse_expr_with(
&arena, &arena,
indoc!( indoc!(
r#" r#"
@ -2133,7 +2131,7 @@ mod test_parse {
}; };
let loc_cond = Located::new(0, 0, 5, 6, var); let loc_cond = Located::new(0, 0, 5, 6, var);
let expected = Expr::When(arena.alloc(loc_cond), branches); let expected = Expr::When(arena.alloc(loc_cond), branches);
let actual = parse_with( let actual = parse_expr_with(
&arena, &arena,
indoc!( indoc!(
r#" r#"
@ -2183,7 +2181,7 @@ mod test_parse {
}; };
let loc_cond = Located::new(0, 0, 5, 6, var); let loc_cond = Located::new(0, 0, 5, 6, var);
let expected = Expr::When(arena.alloc(loc_cond), branches); let expected = Expr::When(arena.alloc(loc_cond), branches);
let actual = parse_with( let actual = parse_expr_with(
&arena, &arena,
indoc!( indoc!(
r#" r#"
@ -2490,7 +2488,7 @@ mod test_parse {
}; };
let loc_cond = Located::new(0, 0, 5, 6, var); let loc_cond = Located::new(0, 0, 5, 6, var);
let expected = Expr::When(arena.alloc(loc_cond), branches); let expected = Expr::When(arena.alloc(loc_cond), branches);
let actual = parse_with( let actual = parse_expr_with(
&arena, &arena,
indoc!( indoc!(
r#" r#"
@ -2535,7 +2533,7 @@ mod test_parse {
}; };
let loc_cond = Located::new(0, 0, 5, 6, var); let loc_cond = Located::new(0, 0, 5, 6, var);
let expected = Expr::When(arena.alloc(loc_cond), branches); let expected = Expr::When(arena.alloc(loc_cond), branches);
let actual = parse_with( let actual = parse_expr_with(
&arena, &arena,
indoc!( indoc!(
r#" r#"

View file

@ -0,0 +1,8 @@
app Closure provides [ closure ] imports []
closure : {} -> Int
closure =
x = 42
\{} -> x

23
examples/closure/platform/Cargo.lock generated Normal file
View file

@ -0,0 +1,23 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "host"
version = "0.1.0"
dependencies = [
"roc_std 0.1.0",
]
[[package]]
name = "libc"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "roc_std"
version = "0.1.0"
dependencies = [
"libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)",
]
[metadata]
"checksum libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)" = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"

View file

@ -0,0 +1,13 @@
[package]
name = "host"
version = "0.1.0"
authors = ["Richard Feldman <oss@rtfeldman.com>"]
edition = "2018"
[lib]
crate-type = ["staticlib"]
[dependencies]
roc_std = { path = "../../../roc_std" }
[workspace]

View file

@ -0,0 +1,7 @@
#include <stdio.h>
extern int rust_main();
int main() {
return rust_main();
}

View file

@ -0,0 +1,102 @@
use std::alloc::Layout;
use std::ffi::CString;
use std::mem::MaybeUninit;
use std::os::raw::c_char;
use std::time::SystemTime;
use RocCallResult::*;
extern "C" {
#[link_name = "closure_1_exposed"]
fn make_closure(output: *mut u8) -> ();
// #[link_name = "0_1_caller"]
// fn call_closure_0(unit: (), closure_data: *const u8, output: *mut u8) -> ();
#[link_name = "closure_1_size"]
fn closure_size() -> i64;
}
#[no_mangle]
pub fn rust_main() -> isize {
println!("Running Roc closure");
let start_time = SystemTime::now();
let size = unsafe { closure_size() } as usize;
let layout = Layout::array::<u8>(size).unwrap();
let roc_closure = unsafe {
let buffer = std::alloc::alloc(layout);
make_closure(buffer);
type CLOSURE_DATA = i64;
let output = &*(buffer as *mut RocCallResult<(fn(CLOSURE_DATA) -> i64, CLOSURE_DATA)>);
match output.into() {
Ok((function_pointer, closure_data)) => {
std::alloc::dealloc(buffer, layout);
move || function_pointer(closure_data)
}
Err(msg) => {
std::alloc::dealloc(buffer, layout);
panic!("Roc failed with message: {}", msg);
}
}
};
let answer = roc_closure();
let end_time = SystemTime::now();
let duration = end_time.duration_since(start_time).unwrap();
println!(
"Roc closure took {:.4} ms to compute this answer: {:?}",
duration.as_secs_f64() * 1000.0,
// truncate the answer, so stdout is not swamped
answer
);
// Exit code
0
}
#[repr(u64)]
pub enum RocCallResult<T> {
Success(T),
Failure(*mut c_char),
}
impl<T: Sized> Into<Result<T, String>> for RocCallResult<T> {
fn into(self) -> Result<T, String> {
match self {
Success(value) => Ok(value),
Failure(failure) => Err({
let raw = unsafe { CString::from_raw(failure) };
let result = format!("{:?}", raw);
// make sure rust does not try to free the Roc string
std::mem::forget(raw);
result
}),
}
}
}
impl<T: Sized + Copy> Into<Result<T, String>> for &RocCallResult<T> {
fn into(self) -> Result<T, String> {
match self {
Success(value) => Ok(*value),
Failure(failure) => Err({
let raw = unsafe { CString::from_raw(*failure) };
let result = format!("{:?}", raw);
// make sure rust does not try to free the Roc string
std::mem::forget(raw);
result
}),
}
}
}

View file

@ -1,17 +1,27 @@
use roc_std::RocCallResult;
use roc_std::RocStr; use roc_std::RocStr;
use std::str; use std::str;
extern "C" { extern "C" {
#[link_name = "main_1"] #[link_name = "main_1_exposed"]
fn main() -> RocStr; fn say_hello(output: &mut RocCallResult<RocStr>) -> ();
} }
#[no_mangle] #[no_mangle]
pub fn rust_main() -> isize { pub fn rust_main() -> isize {
println!( let answer = unsafe {
"Roc says: {}", use std::mem::MaybeUninit;
str::from_utf8(unsafe { main().as_slice() }).unwrap() let mut output: MaybeUninit<RocCallResult<RocStr>> = MaybeUninit::uninit();
);
say_hello(&mut *output.as_mut_ptr());
match output.assume_init().into() {
Ok(value) => value,
Err(msg) => panic!("roc failed with message {}", msg),
}
};
println!("Roc says: {}", str::from_utf8(answer.as_slice()).unwrap());
// Exit code // Exit code
0 0

View file

@ -1,9 +1,10 @@
use roc_std::RocCallResult;
use roc_std::RocList; use roc_std::RocList;
use std::time::SystemTime; use std::time::SystemTime;
extern "C" { extern "C" {
#[link_name = "quicksort_1"] #[link_name = "quicksort_1_exposed"]
fn quicksort(list: RocList<i64>) -> RocList<i64>; fn quicksort(list: RocList<i64>, output: &mut RocCallResult<RocList<i64>>) -> ();
} }
const NUM_NUMS: usize = 100; const NUM_NUMS: usize = 100;
@ -24,7 +25,17 @@ pub fn rust_main() -> isize {
println!("Running Roc quicksort on {} numbers...", nums.len()); println!("Running Roc quicksort on {} numbers...", nums.len());
let start_time = SystemTime::now(); let start_time = SystemTime::now();
let answer = unsafe { quicksort(nums) }; let answer = unsafe {
use std::mem::MaybeUninit;
let mut output = MaybeUninit::uninit();
quicksort(nums, &mut *output.as_mut_ptr());
match output.assume_init().into() {
Ok(value) => value,
Err(msg) => panic!("roc failed with message: {}", msg),
}
};
let end_time = SystemTime::now(); let end_time = SystemTime::now();
let duration = end_time.duration_since(start_time).unwrap(); let duration = end_time.duration_since(start_time).unwrap();

View file

@ -1,9 +1,10 @@
use roc_std::RocCallResult;
use roc_std::RocList; use roc_std::RocList;
use std::time::SystemTime; use std::time::SystemTime;
extern "C" { extern "C" {
#[link_name = "quicksort_1"] #[link_name = "quicksort_1_exposed"]
fn quicksort(list: RocList<i64>) -> RocList<i64>; fn quicksort(list: RocList<i64>, output: &mut RocCallResult<RocList<i64>>) -> ();
} }
const NUM_NUMS: usize = 100; const NUM_NUMS: usize = 100;
@ -24,7 +25,18 @@ pub fn rust_main() -> isize {
println!("Running Roc quicksort on {} numbers...", nums.len()); println!("Running Roc quicksort on {} numbers...", nums.len());
let start_time = SystemTime::now(); let start_time = SystemTime::now();
let answer = unsafe { quicksort(nums) }; let answer = unsafe {
use std::mem::MaybeUninit;
let mut output = MaybeUninit::uninit();
quicksort(nums, &mut *output.as_mut_ptr());
match output.assume_init().into() {
Ok(value) => value,
Err(msg) => panic!("roc failed with message {}", msg),
}
};
let end_time = SystemTime::now(); let end_time = SystemTime::now();
let duration = end_time.duration_since(start_time).unwrap(); let duration = end_time.duration_since(start_time).unwrap();

View file

@ -0,0 +1,23 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "host"
version = "0.1.0"
dependencies = [
"roc_std 0.1.0",
]
[[package]]
name = "libc"
version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "roc_std"
version = "0.1.0"
dependencies = [
"libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)",
]
[metadata]
"checksum libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)" = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"

View file

@ -0,0 +1,13 @@
[package]
name = "host"
version = "0.1.0"
authors = ["Richard Feldman <oss@rtfeldman.com>"]
edition = "2018"
[lib]
crate-type = ["staticlib"]
[dependencies]
roc_std = { path = "../../../roc_std" }
[workspace]

View file

@ -1,49 +0,0 @@
# Rebuilding the host from source
Here are the current steps to rebuild this host. These
steps can likely be moved into a `build.rs` script after
turning `host.rs` into a `cargo` project, but that hasn't
been attempted yet.
## Compile the Rust and C sources
Currently this host has both a `host.rs` and a `host.c`.
This is only because we haven't figured out a way to convince
Rust to emit a `.o` file that doesn't define a `main` entrypoint,
but which is capable of being linked into one later.
As a workaround, we have `host.rs` expose a function called
`rust_main` instead of `main`, and all `host.c` does is provide
an actual `main` which imports and then calls `rust_main` from
the compiled `host.rs`. It's not the most elegant workaround,
but [asking on `users.rust-lang.org`](https://users.rust-lang.org/t/error-when-compiling-linking-with-o-files/49635/4)
didn't turn up any nicer approaches. Maybe they're out there though!
To make this workaround happen, we need to compile both `host.rs`
and `host.c`. First, `cd` into `platform/host/src/` and then run:
```
$ clang -c host.c -o c_host.o
$ rustc host.rs -o rust_host.o
```
Now we should have `c_host.o` and `rust_host.o` in the curent directory.
## Link together the `.o` files
Next, combine `c_host.o` and `rust_host.o` into `host.o` using `ld -r` like so:
```
$ ld -r c_host.o rust_host.o -o host.o
```
Move `host.o` into the appropriate `platform/` subdirectory
based on your architecture and operating system. For example,
on macOS, you'd move `host.o` into the `platform/host/x86_64-unknown-darwin10/` directory.
## All done!
Congratulations! You now have an updated host.
It's now fine to delete `c_host.o` and `rust_host.o`,
since they were only needed to produce `host.o`.

View file

@ -1,12 +0,0 @@
#!/bin/bash
# compile c_host.o and rust_host.o
clang -c host.c -o c_host.o
rustc host.rs -o rust_host.o
# link them together into host.o
ld -r c_host.o rust_host.o -o host.o
# clean up
rm -f c_host.o
rm -f rust_host.o

View file

@ -1,47 +0,0 @@
#![crate_type = "staticlib"]
use std::time::SystemTime;
extern "C" {
#[allow(improper_ctypes)]
#[link_name = "quicksort_1"]
fn quicksort(list: Box<[i64]>) -> Box<[i64]>;
}
const NUM_NUMS: usize = 10_000;
#[no_mangle]
pub fn rust_main() -> isize {
let nums: Box<[i64]> = {
let mut nums = Vec::with_capacity(NUM_NUMS);
for index in 0..nums.capacity() {
let num = index as i64 % 123;
nums.push(num);
}
nums.into()
};
println!("Running Roc quicksort on {} numbers...", nums.len());
let start_time = SystemTime::now();
let answer = unsafe { quicksort(nums) };
let end_time = SystemTime::now();
let duration = end_time.duration_since(start_time).unwrap();
println!(
"Roc quicksort took {:.4} ms to compute this answer: {:?}",
duration.as_secs_f64() * 1000.0,
// truncate the answer, so stdout is not swamped
&answer[0..20]
);
// the pointer is to the first _element_ of the list,
// but the refcount precedes it. Thus calling free() on
// this pointer would segfault/cause badness. Therefore, we
// leak it for now
Box::leak(answer);
// Exit code
0
}

View file

@ -0,0 +1,52 @@
use roc_std::RocCallResult;
use roc_std::RocList;
use std::time::SystemTime;
extern "C" {
#[link_name = "quicksort_1_exposed"]
fn quicksort(list: RocList<i64>, output: &mut RocCallResult<RocList<i64>>) -> ();
}
const NUM_NUMS: usize = 100;
#[no_mangle]
pub fn rust_main() -> isize {
let nums: RocList<i64> = {
let mut nums = Vec::with_capacity(NUM_NUMS);
for index in 0..nums.capacity() {
let num = index as i64 % 12;
nums.push(num);
}
RocList::from_slice(&nums)
};
println!("Running Roc quicksort on {} numbers...", nums.len());
let start_time = SystemTime::now();
let answer = unsafe {
use std::mem::MaybeUninit;
let mut output = MaybeUninit::uninit();
quicksort(nums, &mut *output.as_mut_ptr());
match output.assume_init().into() {
Ok(value) => value,
Err(msg) => panic!("roc failed with message {}", msg),
}
};
let end_time = SystemTime::now();
let duration = end_time.duration_since(start_time).unwrap();
println!(
"Roc quicksort took {:.4} ms to compute this answer: {:?}",
duration.as_secs_f64() * 1000.0,
// truncate the answer, so stdout is not swamped
&answer.as_slice()[0..20]
);
// Exit code
0
}

2
roc_std/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*.o
*.a

48
roc_std/build.rs Normal file
View file

@ -0,0 +1,48 @@
// Adapted from https://github.com/TheDan64/scoped_alloca
// by Daniel Kolsoi, licensed under the Apache License 2.0
// Thank you, Dan!
use std::env;
use std::fs::create_dir;
use std::path::Path;
use std::process::Command;
fn main() {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let cargo_dir = Path::new(manifest_dir.as_str());
let lib_dir = cargo_dir.join("lib");
let alloca_c = cargo_dir.join("src/alloca.c");
let alloca_o = lib_dir.join("alloca.o");
let liballoca_a = lib_dir.join("liballoca.a");
println!("cargo:rustc-link-search=native={}", lib_dir.display());
// No need to recompile alloca static lib every time. We could
// add a feature flag to do so if needed, though
if liballoca_a.is_file() {
return;
}
if !lib_dir.is_dir() {
create_dir(&lib_dir).unwrap();
}
let clang_output = Command::new("clang")
.arg("-c")
.arg(alloca_c)
.arg("-o")
.arg(&alloca_o)
.output()
.expect("Could not execute clang");
assert!(clang_output.status.success(), "{:?}", clang_output);
let ar_output = Command::new("ar")
.arg("-q")
.arg(liballoca_a)
.arg(alloca_o)
.output()
.expect("Could not execute ar");
assert!(ar_output.status.success(), "{:?}", ar_output);
}

9
roc_std/src/alloca.c Normal file
View file

@ -0,0 +1,9 @@
#include <alloca.h>
// From https://github.com/TheDan64/scoped_alloca
// by Daniel Kolsoi, licensed under the Apache License 2.0
// Thank you, Dan!
void *c_alloca(size_t bytes) {
return alloca(bytes);
}

124
roc_std/src/alloca.rs Normal file
View file

@ -0,0 +1,124 @@
// Adapted from https://github.com/TheDan64/scoped_alloca
// by Daniel Kolsoi, licensed under the Apache License 2.0
// Thank you, Dan!
use libc::{c_void, size_t};
#[link(name = "alloca")]
extern "C" {
#[no_mangle]
fn c_alloca(_: size_t) -> *mut c_void;
}
/// This calls C's `alloca` function to allocate some bytes on the stack,
/// then provides those bytes to the given callback function.
///
/// It provides a `&mut c_void` to reflect the lifetime of that memory
/// (it only lives for the duration of the callback), which you'll probably
/// want to cast to something else. To cast `ptr` to the type you want,
/// use `&mut *(ptr as *mut _ as *mut _)` - for example:
///
/// ```rust,ignore
/// with_stack_bytes(size_of::<Foo>(), |ptr| {
/// let foo: &mut Foo = &mut *(ptr as *mut _ as *mut _);
/// ```
///
/// Yes, both `as *mut _` casts are necessary! The first one casts it to
/// a `*mut c_void` and the second one casts it to the pointer type you want.
/// Finally, the `&mut *` converts it from a pointer to a mutable reference.
///
/// # Safety
///
/// This function is `#[inline(always)]`, which means it will allocate
/// the bytes on the stack of whoever calls this function.
///
/// Naturally, if you give this a large number of bytes, it may cause
/// stack overflows, so be careful!
///
/// Due to how Rust FFI works with inlining, in debug builds this actually
/// allocates on the heap (using `malloc`) and then deallocates the memory
/// (using `free`) before the callback returns. In debug builds, this can lead
/// to memory leaks if the callback panics - but release builds should be fine,
/// because they only ever allocate on the stack.
///
#[inline(always)]
pub unsafe fn with_stack_bytes<F, R>(bytes: usize, callback: F) -> R
where
F: FnOnce(&mut c_void) -> R,
{
let ptr = malloc_or_alloca(bytes);
let answer = callback(&mut *ptr);
free_or_noop(ptr);
answer
}
#[cfg(debug_assertions)]
#[inline(always)]
unsafe fn malloc_or_alloca(bytes: usize) -> *mut c_void {
libc::malloc(bytes)
}
#[cfg(not(debug_assertions))]
#[inline(always)]
unsafe fn malloc_or_alloca(bytes: usize) -> *mut c_void {
c_alloca(bytes)
}
#[cfg(debug_assertions)]
#[inline(always)]
unsafe fn free_or_noop(ptr: *mut c_void) {
libc::free(ptr)
}
#[cfg(not(debug_assertions))]
#[inline(always)]
fn free_or_noop(_ptr: *mut c_void) {
// In release builds, we'll have used alloca,
// so there's nothing to free.
}
#[cfg(test)]
mod tests {
use super::with_stack_bytes;
use core::mem::size_of;
#[repr(C)]
#[derive(Debug, PartialEq)]
struct TestStruct {
x: u8,
y: u16,
z: u64,
}
#[test]
fn test_alloca() {
let test_struct = TestStruct {
x: 123,
y: 4567,
z: 89012345678,
};
let sum: u64 = unsafe {
with_stack_bytes(size_of::<TestStruct>(), |ptr| {
// Surprisingly, both casts are necessary; the first one
// turns it from &mut c_void to *mut c_void, and the second
// one turns it into *mut TestStruct
let new_ts: &mut TestStruct = &mut *(ptr as *mut _ as *mut _);
new_ts.x = test_struct.x;
new_ts.y = test_struct.y;
new_ts.z = test_struct.z;
assert_eq!(new_ts, &test_struct);
new_ts.x as u64 + new_ts.y as u64 + new_ts.z as u64
})
};
assert_eq!(
sum,
test_struct.x as u64 + test_struct.y as u64 + test_struct.z as u64
);
}
}

View file

@ -2,6 +2,8 @@
#![no_std] #![no_std]
use core::fmt; use core::fmt;
pub mod alloca;
// A list of C functions that are being imported // A list of C functions that are being imported
extern "C" { extern "C" {
pub fn printf(format: *const u8, ...) -> i32; pub fn printf(format: *const u8, ...) -> i32;
@ -426,3 +428,39 @@ impl Drop for RocStr {
} }
} }
} }
#[allow(non_camel_case_types)]
type c_char = u8;
#[repr(u64)]
pub enum RocCallResult<T> {
Success(T),
Failure(*mut c_char),
}
impl<T: Sized> Into<Result<T, &'static str>> for RocCallResult<T> {
fn into(self) -> Result<T, &'static str> {
use RocCallResult::*;
match self {
Success(value) => Ok(value),
Failure(failure) => Err({
let msg = unsafe {
let mut null_byte_index = 0;
loop {
if *failure.offset(null_byte_index) == 0 {
break;
}
null_byte_index += 1;
}
let bytes = core::slice::from_raw_parts(failure, null_byte_index as usize);
core::str::from_utf8_unchecked(bytes)
};
msg
}),
}
}
}