diff --git a/Cargo.lock b/Cargo.lock index 766971cc0d..3cd24a5c1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3374,6 +3374,7 @@ dependencies = [ "roc_module", "roc_parse", "roc_region", + "roc_test_utils", ] [[package]] @@ -3516,9 +3517,7 @@ dependencies = [ name = "roc_parse" version = "0.1.0" dependencies = [ - "ansi_term", "bumpalo", - "diff", "encode_unicode", "indoc", "pretty_assertions", @@ -3527,6 +3526,7 @@ dependencies = [ "roc_collections", "roc_module", "roc_region", + "roc_test_utils", ] [[package]] @@ -3591,6 +3591,10 @@ dependencies = [ name = "roc_std" version = "0.1.0" +[[package]] +name = "roc_test_utils" +version = "0.1.0" + [[package]] name = "roc_types" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 4839856ae9..234708adcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ members = [ "code_markup", "reporting", "roc_std", + "test_utils", "utils", "docs", "linker", diff --git a/Earthfile b/Earthfile index 4ca4ee56f3..ea2adf2fa4 100644 --- a/Earthfile +++ b/Earthfile @@ -47,7 +47,7 @@ install-zig-llvm-valgrind-clippy-rustfmt: copy-dirs: FROM +install-zig-llvm-valgrind-clippy-rustfmt - COPY --dir cli cli_utils compiler docs editor ast code_markup utils reporting roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./ + COPY --dir cli cli_utils compiler docs editor ast code_markup utils test_utils reporting roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./ test-zig: FROM +install-zig-llvm-valgrind-clippy-rustfmt diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index 8b081cd1cf..e1e68c059c 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -94,6 +94,7 @@ comptime { inline for (INTEGERS) |T| { num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow_int."); num.exportDivCeil(T, ROC_BUILTINS ++ "." ++ NUM ++ ".div_ceil."); + num.exportParseInt(T, ROC_BUILTINS ++ "." ++ NUM ++ ".to_int."); } inline for (FLOATS) |T| { diff --git a/compiler/builtins/bitcode/src/num.zig b/compiler/builtins/bitcode/src/num.zig index a3cfdcbb54..26370700bb 100644 --- a/compiler/builtins/bitcode/src/num.zig +++ b/compiler/builtins/bitcode/src/num.zig @@ -2,6 +2,42 @@ const std = @import("std"); const always_inline = std.builtin.CallOptions.Modifier.always_inline; const math = std.math; const RocList = @import("list.zig").RocList; +const RocStr = @import("str.zig").RocStr; + +pub fn NumParseResult(comptime T: type) type { + return extern struct { + errorcode: u8, // 0 indicates success + value: T, + }; +} + +pub fn exportParseInt(comptime T: type, comptime name: []const u8) void { + comptime var f = struct { + fn func(input: T, buf: RocStr) callconv(.C) NumParseResult(T) { + // a radix of 0 will make zig determine the radix from the frefix: + // * A prefix of "0b" implies radix=2, + // * A prefix of "0o" implies radix=8, + // * A prefix of "0x" implies radix=16, + // * Otherwise radix=10 is assumed. + const radix = 0; + if (std.fmt.parseInt(T, buf.asSlice(), radix)) |success| { + return .{ .errorcode = 0, .value = success }; + } else |err| { + return .{ .errorcode = 1, .value = 0 }; + } + } + }.func; + @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); +} + +pub fn exportParseFloat(comptime T: type, comptime name: []const u8) void { + comptime var f = struct { + fn func(input: T, buf: []const u8) callconv(.C) bool { + return std.fmt.parseFloat(T, buf); + } + }.func; + @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); +} pub fn exportPow(comptime T: type, comptime name: []const u8) void { comptime var f = struct { diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index afffd31dd9..33adcf78a4 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -242,6 +242,9 @@ pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with"; pub const STR_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes"; pub const STR_FROM_INT: IntrinsicName = int_intrinsic!("roc_builtins.str.from_int"); pub const STR_FROM_FLOAT: &str = "roc_builtins.str.from_float"; +pub const STR_TO_INT: IntrinsicName = int_intrinsic!("roc_builtins.str.to_int"); +pub const STR_TO_FLOAT: IntrinsicName = float_intrinsic!("roc_builtins.str.to_float"); +pub const STR_TO_DECIMAL: &str = "roc_builtins.str.to_decimal"; pub const STR_EQUAL: &str = "roc_builtins.str.equal"; pub const STR_TO_UTF8: &str = "roc_builtins.str.to_utf8"; pub const STR_FROM_UTF8: &str = "roc_builtins.str.from_utf8"; diff --git a/compiler/fmt/Cargo.toml b/compiler/fmt/Cargo.toml index 6b19b61815..ea17bf7579 100644 --- a/compiler/fmt/Cargo.toml +++ b/compiler/fmt/Cargo.toml @@ -15,3 +15,4 @@ bumpalo = { version = "3.8.0", features = ["collections"] } [dev-dependencies] pretty_assertions = "1.0.0" indoc = "1.0.3" +roc_test_utils = { path = "../../test_utils" } diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index b77f538919..331978c985 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -1098,6 +1098,7 @@ fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool { BinOp::Pizza | BinOp::Assignment | BinOp::HasType | BinOp::Backpassing => false, }) } + Expr::If(_, _) => true, _ => false, } } diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index e2465e98cc..95303128be 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -1,6 +1,4 @@ #[macro_use] -extern crate pretty_assertions; -#[macro_use] extern crate indoc; extern crate bumpalo; extern crate roc_fmt; @@ -14,6 +12,7 @@ mod test_fmt { use roc_fmt::module::fmt_module; use roc_parse::module::{self, module_defs}; use roc_parse::parser::{Parser, State}; + use roc_test_utils::assert_multiline_str_eq; fn expr_formats_to(input: &str, expected: &str) { let arena = Bump::new(); @@ -26,7 +25,7 @@ mod test_fmt { actual.format_with_options(&mut buf, Parens::NotNeeded, Newlines::Yes, 0); - assert_eq!(buf, expected) + assert_multiline_str_eq!(expected, buf.as_str()) } Err(error) => panic!("Unexpected parse failure when parsing this for formatting:\n\n{}\n\nParse error was:\n\n{:?}\n\n", input, error) }; @@ -56,7 +55,7 @@ mod test_fmt { Err(error) => panic!("Unexpected parse failure when parsing this for defs formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error) } - assert_eq!(buf, expected) + assert_multiline_str_eq!(expected, buf.as_str()) } Err(error) => panic!("Unexpected parse failure when parsing this for module header formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error) }; @@ -2311,6 +2310,15 @@ mod test_fmt { )); } + #[test] + fn binop_if() { + expr_formats_same(indoc!( + r#" + 5 * (if x > 0 then 1 else 2) + "# + )); + } + // UNARY OP #[test] diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 70d9c5a32f..e95c903ef1 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -5268,6 +5268,23 @@ fn run_low_level<'a, 'ctx, 'env>( str_ends_with(env, scope, args[0], args[1]) } + StrToNum => { + debug_assert_eq!(args.len(), 1); + + let (string, _string_layout) = load_symbol_and_layout(scope, &args[0]); + + // match on the return layout to figure out which zig builtin we need + let intrinsic = match layout { + Layout::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[*int_width], + Layout::Builtin(Builtin::Float(float_width)) => { + &bitcode::STR_TO_FLOAT[*float_width] + } + Layout::Builtin(Builtin::Decimal) => bitcode::STR_TO_DECIMAL, + _ => unreachable!(), + }; + + call_bitcode_fn(env, &[string], intrinsic) + } StrFromInt => { // Str.fromInt : Int -> Str debug_assert_eq!(args.len(), 1); diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index 4422d5e383..fb0f592f39 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -38,6 +38,7 @@ pub fn decode_low_level<'a>( StrEndsWith => return BuiltinCall(bitcode::STR_ENDS_WITH), StrSplit => return NotImplemented, // needs Array StrCountGraphemes => return NotImplemented, // test needs Array + StrToNum => return NotImplemented, // choose builtin based on storage size StrFromInt => return NotImplemented, // choose builtin based on storage size StrFromUtf8 => return NotImplemented, // needs Array StrTrimLeft => return BuiltinCall(bitcode::STR_TRIM_LEFT), diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index a454724e90..81d32b3ce4 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -13,6 +13,7 @@ pub enum LowLevel { StrEndsWith, StrSplit, StrCountGraphemes, + StrToNum, StrFromInt, StrFromUtf8, StrFromUtf8Range, diff --git a/compiler/parse/Cargo.toml b/compiler/parse/Cargo.toml index d76b482822..0e60203af5 100644 --- a/compiler/parse/Cargo.toml +++ b/compiler/parse/Cargo.toml @@ -17,5 +17,4 @@ pretty_assertions = "1.0.0" indoc = "1.0.3" quickcheck = "1.0.3" quickcheck_macros = "1.0.0" -diff = "0.1.12" -ansi_term = "0.12.1" +roc_test_utils = { path = "../../test_utils" } diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index b9dae1d201..cb831eac57 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -23,6 +23,7 @@ mod test_parse { use roc_parse::parser::{Parser, State, SyntaxError}; use roc_parse::test_helpers::parse_expr_with; use roc_region::all::{Located, Region}; + use roc_test_utils::assert_multiline_str_eq; use std::{f64, i64}; macro_rules! snapshot_tests { @@ -254,10 +255,7 @@ mod test_parse { } else { let expected_result = std::fs::read_to_string(&result_path).unwrap(); - // TODO: do a diff over the "real" content of these strings, rather than - // the debug-formatted content. As is, we get an ugly single-line diff - // from pretty_assertions - assert_eq!(expected_result, actual_result); + assert_multiline_str_eq!(expected_result, actual_result); } } diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml new file mode 100644 index 0000000000..b85fbe29cd --- /dev/null +++ b/test_utils/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "roc_test_utils" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" +description = "Utility functions used all over the code base." + +[dependencies] + +[dev-dependencies] diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs new file mode 100644 index 0000000000..aa15b3ed35 --- /dev/null +++ b/test_utils/src/lib.rs @@ -0,0 +1,15 @@ +#[derive(PartialEq)] +pub struct DebugAsDisplay(pub T); + +impl std::fmt::Debug for DebugAsDisplay { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +#[macro_export] +macro_rules! assert_multiline_str_eq { + ($a:expr, $b:expr) => { + assert_eq!($crate::DebugAsDisplay($a), $crate::DebugAsDisplay($b)) + }; +}