diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index aeb77a803b..0b32ca80b3 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -508,7 +508,7 @@ pub fn types() -> MutMap { Box::new(nat_type()), ); - // bytesToU16 : List U8, Nat -> U16 + // bytesToU16 : List U8, Nat -> Result U16 [ OutOfBounds ] { let position_out_of_bounds = SolvedType::TagUnion( vec![(TagName::Global("OutOfBounds".into()), vec![])], @@ -521,12 +521,18 @@ pub fn types() -> MutMap { ); } - // bytesToU32 : List U8, Nat -> U32 - add_top_level_function_type!( - Symbol::NUM_BYTES_TO_U32, - vec![list_type(u8_type()), nat_type()], - Box::new(u32_type()), - ); + // bytesToU32 : List U8, Nat -> Result U32 [ OutOfBounds ] + { + let position_out_of_bounds = SolvedType::TagUnion( + vec![(TagName::Global("OutOfBounds".into()), vec![])], + Box::new(SolvedType::Wildcard), + ); + add_top_level_function_type!( + Symbol::NUM_BYTES_TO_U32, + vec![list_type(u8_type()), nat_type()], + Box::new(result_type(u32_type(), position_out_of_bounds)), + ); + } // Bool module diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index a645fb917e..c71a0cb2e7 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1090,139 +1090,14 @@ fn num_asin(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } -/// Num.bytesToU16 : List U8, Nat -> U16 +/// Num.bytesToU16 : List U8, Nat -> Result U16 [ OutOfBounds ] fn num_bytes_to_u16(symbol: Symbol, var_store: &mut VarStore) -> Def { - let len_var = var_store.fresh(); - let list_var = var_store.fresh(); - let elem_var = var_store.fresh(); - - let ret_var = var_store.fresh(); - let bool_var = var_store.fresh(); - let add_var = var_store.fresh(); - let cast_var = var_store.fresh(); - - // Perform a bounds check. If it passes, run LowLevel::NumBytesToU16 - let body = If { - cond_var: bool_var, - branch_var: var_store.fresh(), - branches: vec![( - // if-condition - no_region( - // index + 1 < List.len list - RunLowLevel { - op: LowLevel::NumLt, - args: vec![ - ( - len_var, - RunLowLevel { - op: LowLevel::NumAdd, - args: vec![ - (add_var, Var(Symbol::ARG_2)), - ( - add_var, - RunLowLevel { - ret_var: cast_var, - args: vec![(cast_var, Num(var_store.fresh(), 1))], - op: LowLevel::NumCastToNat, - }, - ), - ], - ret_var: add_var, - }, - ), - ( - len_var, - RunLowLevel { - op: LowLevel::ListLen, - args: vec![(list_var, Var(Symbol::ARG_1))], - ret_var: len_var, - }, - ), - ], - ret_var: bool_var, - }, - ), - // then-branch - no_region( - // Ok - tag( - "Ok", - vec![RunLowLevel { - op: LowLevel::NumBytesToU16, - args: vec![ - (list_var, Var(Symbol::ARG_1)), - (len_var, Var(Symbol::ARG_2)), - ], - ret_var: elem_var, - }], - var_store, - ), - ), - )], - final_else: Box::new( - // else-branch - no_region( - // Err - tag( - "Err", - vec![tag("OutOfBounds", Vec::new(), var_store)], - var_store, - ), - ), - ), - }; - - defn( - symbol, - vec![(list_var, Symbol::ARG_1), (len_var, Symbol::ARG_2)], - var_store, - body, - ret_var, - ) - // let list_var = var_store.fresh(); - // let nat_var = var_store.fresh(); - // let ret_var = var_store.fresh(); - - // let body = RunLowLevel { - // op: LowLevel::NumBytesToU16, - // args: vec![ - // (list_var, Var(Symbol::ARG_1)), - // (nat_var, Var(Symbol::ARG_2)), - // ], - // ret_var, - // }; - - // defn( - // symbol, - // vec![(list_var, Symbol::ARG_1), (nat_var, Symbol::ARG_2)], - // var_store, - // body, - // ret_var, - // ) + num_bytes_to(symbol, var_store, 1, LowLevel::NumBytesToU16) } -/// Num.bytesToU32 : List U8, Nat -> U32 +/// Num.bytesToU32 : List U8, Nat -> Result U32 [ OutOfBounds ] fn num_bytes_to_u32(symbol: Symbol, var_store: &mut VarStore) -> Def { - let list_var = var_store.fresh(); - let nat_var = var_store.fresh(); - let ret_var = var_store.fresh(); - - let body = RunLowLevel { - op: LowLevel::NumBytesToU32, - args: vec![ - (list_var, Var(Symbol::ARG_1)), - (nat_var, Var(Symbol::ARG_2)), - ], - ret_var, - }; - - defn( - symbol, - vec![(list_var, Symbol::ARG_1), (nat_var, Symbol::ARG_2)], - var_store, - body, - ret_var, - ) + num_bytes_to(symbol, var_store, 3, LowLevel::NumBytesToU32) } /// Num.bitwiseAnd : Int a, Int a -> Int a @@ -3496,6 +3371,97 @@ fn defn( } } +#[inline(always)] +fn num_bytes_to(symbol: Symbol, var_store: &mut VarStore, offset: i64, low_level: LowLevel) -> Def { + let len_var = var_store.fresh(); + let list_var = var_store.fresh(); + let elem_var = var_store.fresh(); + + let ret_var = var_store.fresh(); + let bool_var = var_store.fresh(); + let add_var = var_store.fresh(); + let cast_var = var_store.fresh(); + + // Perform a bounds check. If it passes, run LowLevel::NumBytesToU16 + let body = If { + cond_var: bool_var, + branch_var: var_store.fresh(), + branches: vec![( + // if-condition + no_region( + // index + 1 < List.len list + RunLowLevel { + op: LowLevel::NumLt, + args: vec![ + ( + len_var, + RunLowLevel { + op: LowLevel::NumAdd, + args: vec![ + (add_var, Var(Symbol::ARG_2)), + ( + add_var, + RunLowLevel { + ret_var: cast_var, + args: vec![(cast_var, Num(var_store.fresh(), offset))], + op: LowLevel::NumCastToNat, + }, + ), + ], + ret_var: add_var, + }, + ), + ( + len_var, + RunLowLevel { + op: LowLevel::ListLen, + args: vec![(list_var, Var(Symbol::ARG_1))], + ret_var: len_var, + }, + ), + ], + ret_var: bool_var, + }, + ), + // then-branch + no_region( + // Ok + tag( + "Ok", + vec![RunLowLevel { + op: low_level, + args: vec![ + (list_var, Var(Symbol::ARG_1)), + (len_var, Var(Symbol::ARG_2)), + ], + ret_var: elem_var, + }], + var_store, + ), + ), + )], + final_else: Box::new( + // else-branch + no_region( + // Err + tag( + "Err", + vec![tag("OutOfBounds", Vec::new(), var_store)], + var_store, + ), + ), + ), + }; + + defn( + symbol, + vec![(list_var, Symbol::ARG_1), (len_var, Symbol::ARG_2)], + var_store, + body, + ret_var, + ) +} + #[inline(always)] fn defn_help( fn_name: Symbol, diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index c2e78941c1..4d3b67cb29 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -1638,7 +1638,7 @@ mod gen_num { "# ), 40, - usize + u16 ); } @@ -1654,7 +1654,7 @@ mod gen_num { "# ), 1, - usize + u16 ); } @@ -1664,23 +1664,46 @@ mod gen_num { indoc!( r#" bytes = Str.toUtf8 "hello" - when Num.bytesToU16 bytes 4 is + when Num.bytesToU32 bytes 0 is Ok v -> v Err OutOfBounds -> 1 "# ), - 1, - usize + 41, + u32 ); } #[test] fn potato_4() { - assert_evals_to!("Num.bytesToU32 [] 1", 41, u32); + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Num.bytesToU32 bytes 234 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 1, + u32 + ); } + #[test] fn potato_5() { - assert_evals_to!("Num.bytesToU32 [] 0", 41, u32); + assert_evals_to!( + indoc!( + r#" + bytes = Str.toUtf8 "hello" + when Num.bytesToU32 bytes 2 is + Ok v -> v + Err OutOfBounds -> 1 + "# + ), + 1, + u32 + ); } }