Merge branch 'trunk' into fix-module-formatting

This commit is contained in:
Richard Feldman 2022-02-04 23:03:52 -05:00 committed by GitHub
commit 881bae7267
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
114 changed files with 4260 additions and 1986 deletions

View file

@ -1,4 +1,4 @@
[alias]
test-gen-llvm = "test -p test_gen"
test-gen-dev = "test -p roc_gen_dev -p test_gen --no-default-features --features gen-dev"
test-gen-wasm = "test -p test_gen --no-default-features --features gen-wasm"
test-gen-wasm = "test -p roc_gen_wasm -p test_gen --no-default-features --features gen-wasm"

36
Cargo.lock generated
View file

@ -3204,6 +3204,17 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157"
[[package]]
name = "repl_test"
version = "0.1.0"
dependencies = [
"indoc",
"roc_cli",
"roc_repl_cli",
"roc_test_utils",
"strip-ansi-escapes",
]
[[package]]
name = "rkyv"
version = "0.6.7"
@ -3669,14 +3680,20 @@ version = "0.1.0"
dependencies = [
"bumpalo",
"const_format",
"indoc",
"inkwell 0.1.0",
"libloading 0.7.1",
"roc_build",
"roc_builtins",
"roc_collections",
"roc_gen_llvm",
"roc_load",
"roc_mono",
"roc_parse",
"roc_repl_eval",
"roc_test_utils",
"roc_target",
"roc_types",
"rustyline",
"rustyline-derive",
"strip-ansi-escapes",
"target-lexicon",
]
@ -3685,14 +3702,10 @@ name = "roc_repl_eval"
version = "0.1.0"
dependencies = [
"bumpalo",
"inkwell 0.1.0",
"libloading 0.7.1",
"roc_build",
"roc_builtins",
"roc_can",
"roc_collections",
"roc_fmt",
"roc_gen_llvm",
"roc_load",
"roc_module",
"roc_mono",
@ -3701,7 +3714,14 @@ dependencies = [
"roc_reporting",
"roc_target",
"roc_types",
"target-lexicon",
]
[[package]]
name = "roc_repl_wasm"
version = "0.1.0"
dependencies = [
"roc_parse",
"roc_repl_eval",
]
[[package]]

View file

@ -35,6 +35,8 @@ members = [
"reporting",
"repl_cli",
"repl_eval",
"repl_test",
"repl_wasm",
"roc_std",
"test_utils",
"utils",

View file

@ -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 error_macros utils test_utils reporting repl_cli repl_eval roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
COPY --dir cli cli_utils compiler docs editor ast code_markup error_macros utils test_utils reporting repl_cli repl_eval repl_test repl_wasm roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
test-zig:
FROM +install-zig-llvm-valgrind-clippy-rustfmt

View file

@ -1,6 +1,8 @@
use bumpalo::Bump;
use roc_can::expr::Recursive;
use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int};
use roc_can::expr::{IntValue, Recursive};
use roc_can::num::{
finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult,
};
use roc_can::operator::desugar_expr;
use roc_collections::all::MutSet;
use roc_module::symbol::Symbol;
@ -52,7 +54,7 @@ pub fn expr_to_expr2<'a>(
match parse_expr {
Float(string) => {
match finish_parsing_float(string) {
Ok(float) => {
Ok((float, _bound)) => {
let expr = Expr2::Float {
number: FloatVal::F64(float),
var: env.var_store.fresh(),
@ -73,10 +75,13 @@ pub fn expr_to_expr2<'a>(
}
}
Num(string) => {
match finish_parsing_int(string) {
Ok(int) => {
match finish_parsing_num(string) {
Ok(ParsedNumResult::UnknownNum(int) | ParsedNumResult::Int(int, _)) => {
let expr = Expr2::SmallInt {
number: IntVal::I64(int),
number: IntVal::I64(match int {
IntValue::U128(_) => todo!(),
IntValue::I128(n) => n as i64, // FIXME
}),
var: env.var_store.fresh(),
// TODO non-hardcode
style: IntStyle::Decimal,
@ -85,6 +90,15 @@ pub fn expr_to_expr2<'a>(
(expr, Output::default())
}
Ok(ParsedNumResult::Float(float, _)) => {
let expr = Expr2::Float {
number: FloatVal::F64(float),
var: env.var_store.fresh(),
text: PoolStr::new(string, env.pool),
};
(expr, Output::default())
}
Err((raw, error)) => {
// emit runtime error
let runtime_error = RuntimeError::InvalidInt(
@ -107,9 +121,12 @@ pub fn expr_to_expr2<'a>(
is_negative,
} => {
match finish_parsing_base(string, *base, *is_negative) {
Ok(int) => {
Ok((int, _bound)) => {
let expr = Expr2::SmallInt {
number: IntVal::I64(int),
number: IntVal::I64(match int {
IntValue::U128(_) => todo!(),
IntValue::I128(n) => n as i64, // FIXME
}),
var: env.var_store.fresh(),
// TODO non-hardcode
style: IntStyle::from_base(*base),

View file

@ -3,8 +3,10 @@
#![allow(unused_imports)]
use bumpalo::collections::Vec as BumpVec;
use roc_can::expr::unescape_char;
use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int};
use roc_can::expr::{unescape_char, IntValue};
use roc_can::num::{
finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult,
};
use roc_collections::all::BumpMap;
use roc_module::symbol::{Interns, Symbol};
use roc_parse::ast::{StrLiteral, StrSegment};
@ -183,18 +185,35 @@ pub fn to_pattern2<'a>(
let problem = MalformedPatternProblem::MalformedFloat;
malformed_pattern(env, problem, region)
}
Ok(float) => Pattern2::FloatLiteral(FloatVal::F64(float)),
Ok((float, _bound)) => Pattern2::FloatLiteral(FloatVal::F64(float)),
},
ptype => unsupported_pattern(env, ptype, region),
},
NumLiteral(string) => match pattern_type {
WhenBranch => match finish_parsing_int(string) {
WhenBranch => match finish_parsing_num(string) {
Err(_error) => {
let problem = MalformedPatternProblem::MalformedInt;
malformed_pattern(env, problem, region)
}
Ok(int) => Pattern2::NumLiteral(env.var_store.fresh(), int),
Ok(ParsedNumResult::UnknownNum(int)) => {
Pattern2::NumLiteral(
env.var_store.fresh(),
match int {
IntValue::U128(_) => todo!(),
IntValue::I128(n) => n as i64, // FIXME
},
)
}
Ok(ParsedNumResult::Int(int, _bound)) => {
Pattern2::IntLiteral(IntVal::I64(match int {
IntValue::U128(_) => todo!(),
IntValue::I128(n) => n as i64, // FIXME
}))
}
Ok(ParsedNumResult::Float(int, _bound)) => {
Pattern2::FloatLiteral(FloatVal::F64(int))
}
},
ptype => unsupported_pattern(env, ptype, region),
},
@ -209,7 +228,11 @@ pub fn to_pattern2<'a>(
let problem = MalformedPatternProblem::MalformedBase(*base);
malformed_pattern(env, problem, region)
}
Ok(int) => {
Ok((int, _bound)) => {
let int = match int {
IntValue::U128(_) => todo!(),
IntValue::I128(n) => n as i64, // FIXME
};
if *is_negative {
Pattern2::IntLiteral(IntVal::I64(-int))
} else {

View file

@ -12,8 +12,8 @@ use roc_parse::ast::{
TypeAnnotation, WhenBranch,
};
use roc_parse::header::{
AppHeader, Effects, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName,
PackageEntry, PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
};
use roc_parse::{
ast::{Def, Module},
@ -199,14 +199,6 @@ impl<'a> RemoveSpaces<'a> for Module<'a> {
packages: header.packages.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
provides: header.provides.remove_spaces(arena),
effects: Effects {
spaces_before_effects_keyword: &[],
spaces_after_effects_keyword: &[],
spaces_after_type_name: &[],
effect_shortname: header.effects.effect_shortname.remove_spaces(arena),
effect_type_name: header.effects.effect_type_name.remove_spaces(arena),
entries: header.effects.entries.remove_spaces(arena),
},
before_header: &[],
after_platform_keyword: &[],
before_requires: &[],

View file

@ -4,7 +4,6 @@ platform "examples/multi-module"
packages {}
imports []
provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str
mainForHost = main

View file

@ -4,7 +4,6 @@ platform "examples/multi-dep-thunk"
packages {}
imports []
provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str
mainForHost = main

79
cli_utils/Cargo.lock generated
View file

@ -2475,12 +2475,13 @@ dependencies = [
"roc_builtins",
"roc_can",
"roc_collections",
"roc_error_macros",
"roc_load",
"roc_module",
"roc_parse",
"roc_problem",
"roc_region",
"roc_reporting",
"roc_target",
"roc_types",
"roc_unify",
"snafu",
@ -2510,6 +2511,7 @@ dependencies = [
"roc_reporting",
"roc_solve",
"roc_std",
"roc_target",
"roc_types",
"roc_unify",
"serde_json",
@ -2524,6 +2526,7 @@ dependencies = [
"roc_collections",
"roc_module",
"roc_region",
"roc_target",
"roc_types",
]
@ -2549,31 +2552,24 @@ dependencies = [
"bumpalo",
"clap 3.0.0-beta.5",
"const_format",
"inkwell 0.1.0",
"libloading 0.7.1",
"mimalloc",
"roc_build",
"roc_builtins",
"roc_can",
"roc_collections",
"roc_constrain",
"roc_docs",
"roc_editor",
"roc_error_macros",
"roc_fmt",
"roc_gen_llvm",
"roc_linker",
"roc_load",
"roc_module",
"roc_mono",
"roc_parse",
"roc_problem",
"roc_region",
"roc_repl_cli",
"roc_reporting",
"roc_solve",
"roc_types",
"roc_unify",
"rustyline",
"rustyline-derive",
"roc_target",
"target-lexicon",
"tempfile",
]
@ -2631,6 +2627,7 @@ dependencies = [
"roc_module",
"roc_parse",
"roc_region",
"roc_target",
"roc_types",
"snafu",
]
@ -2680,6 +2677,10 @@ dependencies = [
"winit",
]
[[package]]
name = "roc_error_macros"
version = "0.1.0"
[[package]]
name = "roc_fmt"
version = "0.1.0"
@ -2700,12 +2701,13 @@ dependencies = [
"packed_struct",
"roc_builtins",
"roc_collections",
"roc_error_macros",
"roc_module",
"roc_mono",
"roc_problem",
"roc_region",
"roc_reporting",
"roc_solve",
"roc_target",
"roc_types",
"roc_unify",
"target-lexicon",
@ -2720,10 +2722,11 @@ dependencies = [
"morphic_lib",
"roc_builtins",
"roc_collections",
"roc_error_macros",
"roc_module",
"roc_mono",
"roc_reporting",
"roc_std",
"roc_target",
"target-lexicon",
]
@ -2734,10 +2737,11 @@ dependencies = [
"bumpalo",
"roc_builtins",
"roc_collections",
"roc_error_macros",
"roc_module",
"roc_mono",
"roc_reporting",
"roc_std",
"roc_target",
]
[[package]]
@ -2782,6 +2786,7 @@ dependencies = [
"roc_region",
"roc_reporting",
"roc_solve",
"roc_target",
"roc_types",
"roc_unify",
"ven_pretty",
@ -2815,6 +2820,7 @@ dependencies = [
"roc_region",
"roc_solve",
"roc_std",
"roc_target",
"roc_types",
"roc_unify",
"static_assertions",
@ -2850,6 +2856,44 @@ dependencies = [
"static_assertions",
]
[[package]]
name = "roc_repl_cli"
version = "0.1.0"
dependencies = [
"bumpalo",
"const_format",
"roc_mono",
"roc_parse",
"roc_repl_eval",
"rustyline",
"rustyline-derive",
"target-lexicon",
]
[[package]]
name = "roc_repl_eval"
version = "0.1.0"
dependencies = [
"bumpalo",
"inkwell 0.1.0",
"libloading 0.7.1",
"roc_build",
"roc_builtins",
"roc_can",
"roc_collections",
"roc_fmt",
"roc_gen_llvm",
"roc_load",
"roc_module",
"roc_mono",
"roc_parse",
"roc_region",
"roc_reporting",
"roc_target",
"roc_types",
"target-lexicon",
]
[[package]]
name = "roc_reporting"
version = "0.1.0"
@ -2886,6 +2930,13 @@ dependencies = [
name = "roc_std"
version = "0.1.0"
[[package]]
name = "roc_target"
version = "0.1.0"
dependencies = [
"target-lexicon",
]
[[package]]
name = "roc_types"
version = "0.1.0"

View file

@ -26,7 +26,7 @@ pub fn build(b: *Builder) void {
.default_target = CrossTarget{
.cpu_model = .baseline,
// TODO allow for native target for maximum speed
}
},
});
const i386_target = makeI386Target();
const wasm32_target = makeWasm32Target();

View file

@ -750,7 +750,8 @@ pub fn dictWalk(
const alignment_u32 = alignment.toU32();
// allocate space to write the result of the stepper into
// experimentally aliasing the accum and output pointers is not a good idea
const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment_u32);
// TODO handle alloc failing!
const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment_u32) orelse unreachable;
var b1 = output orelse unreachable;
var b2 = bytes_ptr;

View file

@ -0,0 +1,162 @@
const std = @import("std");
const utils = @import("utils.zig");
const CSlice = utils.CSlice;
const always_inline = std.builtin.CallOptions.Modifier.always_inline;
const Failure = struct {
start_line: u32,
end_line: u32,
start_col: u16,
end_col: u16,
};
// BEGIN FAILURES GLOBALS ///////////////////
var failures_mutex = std.Thread.Mutex{};
var failures: [*]Failure = undefined;
var failure_length: usize = 0;
var failure_capacity: usize = 0;
// END FAILURES GLOBALS /////////////////////
pub fn expectFailed(
start_line: u32,
end_line: u32,
start_col: u16,
end_col: u16,
) void {
const new_failure = Failure{ .start_line = start_line, .end_line = end_line, .start_col = start_col, .end_col = end_col };
// Lock the failures mutex before reading from any of the failures globals,
// and then release the lock once we're done modifying things.
// TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d
// to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig
//
// ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9:
//
// failures_mutex.lock();
// defer failures_mutex.release();
//
// 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆
const held = failures_mutex.acquire();
defer held.release();
// If we don't have enough capacity to add a failure, allocate a new failures pointer.
if (failure_length >= failure_capacity) {
if (failure_capacity > 0) {
// We already had previous failures allocated, so try to realloc in order
// to grow the size in-place without having to memcpy bytes over.
const old_pointer = failures;
const old_bytes = failure_capacity * @sizeOf(Failure);
failure_capacity *= 2;
const new_bytes = failure_capacity * @sizeOf(Failure);
const failures_u8 = @ptrCast([*]u8, @alignCast(@alignOf(Failure), failures));
const raw_pointer = utils.realloc(failures_u8, new_bytes, old_bytes, @alignOf(Failure));
failures = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_pointer));
// If realloc wasn't able to expand in-place (that is, it returned a different pointer),
// then copy the data into the new pointer and dealloc the old one.
if (failures != old_pointer) {
const old_pointer_u8 = @ptrCast([*]u8, old_pointer);
utils.memcpy(@ptrCast([*]u8, failures), old_pointer_u8, old_bytes);
utils.dealloc(old_pointer_u8, @alignOf(Failure));
}
} else {
// We've never had any failures before, so allocate the failures for the first time.
failure_capacity = 10;
const raw_pointer = utils.alloc(failure_capacity * @sizeOf(Failure), @alignOf(Failure));
failures = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_pointer));
}
}
failures[failure_length] = new_failure;
failure_length += 1;
}
pub fn expectFailedC(
start_line: u32,
end_line: u32,
start_col: u16,
end_col: u16,
) callconv(.C) void {
return @call(.{ .modifier = always_inline }, expectFailed, .{ start_line, end_line, start_col, end_col });
}
pub fn getExpectFailures() []Failure {
// TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d
// to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig
//
// ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9:
//
// failures_mutex.lock();
// defer failures_mutex.release();
//
// 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆
const held = failures_mutex.acquire();
defer held.release();
if (failure_length > 0) {
// defensively clone failures, in case someone modifies the originals after the mutex has been released.
const num_bytes = failure_length * @sizeOf(Failure);
// TODO handle the possibility of alloc failing
const raw_clones = utils.alloc(num_bytes, @alignOf(Failure)) orelse unreachable;
utils.memcpy(raw_clones, @ptrCast([*]u8, failures), num_bytes);
const clones = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_clones));
return clones[0..failure_length];
} else {
return failures[0..0];
}
}
pub fn getExpectFailuresC() callconv(.C) CSlice {
var bytes = @ptrCast(*c_void, failures);
return .{ .pointer = bytes, .len = failure_length };
}
pub fn deinitFailures() void {
// TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d
// to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig
//
// ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9:
//
// failures_mutex.lock();
// defer failures_mutex.release();
//
// 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆
const held = failures_mutex.acquire();
defer held.release();
utils.dealloc(@ptrCast([*]u8, failures), @alignOf(Failure));
failure_length = 0;
}
pub fn deinitFailuresC() callconv(.C) void {
return @call(.{ .modifier = always_inline }, deinitFailures, .{});
}
test "expectFailure does something" {
defer deinitFailures();
var fails = getExpectFailures();
try std.testing.expectEqual(fails.len, 0);
expectFailed(1, 2, 3, 4);
fails = getExpectFailures();
try std.testing.expectEqual(fails.len, 1);
utils.dealloc(@ptrCast([*]u8, fails.ptr), @alignOf([*]Failure));
const what_it_should_look_like = Failure{ .start_line = 1, .end_line = 2, .start_col = 3, .end_col = 4 };
fails = getExpectFailures();
try std.testing.expectEqual(fails[0], what_it_should_look_like);
utils.dealloc(@ptrCast([*]u8, fails.ptr), @alignOf([*]Failure));
}

View file

@ -550,7 +550,8 @@ pub fn listKeepResult(
var output = RocList.allocate(alignment, list.len(), list.len() * after_width);
const target_ptr = output.bytes orelse unreachable;
var temporary = @ptrCast([*]u8, utils.alloc(result_width, alignment));
// TODO handle alloc failing!
var temporary = utils.alloc(result_width, alignment) orelse unreachable;
if (data_is_owned) {
inc_n_data(data, size);
@ -614,7 +615,8 @@ pub fn listWalk(
inc_n_data(data, list.len());
}
const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment);
// TODO handle alloc failing!
const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment) orelse unreachable;
var b1 = output orelse unreachable;
var b2 = bytes_ptr;
@ -660,7 +662,8 @@ pub fn listWalkBackwards(
inc_n_data(data, list.len());
}
const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment);
// TODO handle alloc failing!
const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment) orelse unreachable;
var b1 = output orelse unreachable;
var b2 = bytes_ptr;
@ -708,7 +711,8 @@ pub fn listWalkUntil(
return;
}
const bytes_ptr: [*]u8 = utils.alloc(continue_stop_width, alignment);
// TODO handle alloc failing!
const bytes_ptr: [*]u8 = utils.alloc(continue_stop_width, alignment) orelse unreachable;
// NOTE: assumes data bytes are the first bytes in a tag
@memcpy(bytes_ptr, accum orelse unreachable, accum_width);

View file

@ -1,6 +1,7 @@
const std = @import("std");
const math = std.math;
const utils = @import("utils.zig");
const expect = @import("expect.zig");
const ROC_BUILTINS = "roc_builtins";
const NUM = "num";
@ -141,12 +142,14 @@ comptime {
}
// Utils
comptime {
exportUtilsFn(utils.test_panic, "test_panic");
exportUtilsFn(utils.increfC, "incref");
exportUtilsFn(utils.decrefC, "decref");
exportUtilsFn(utils.decrefCheckNullC, "decref_check_null");
exportExpectFn(expect.expectFailedC, "expect_failed");
exportExpectFn(expect.getExpectFailuresC, "get_expect_failures");
exportExpectFn(expect.deinitFailuresC, "deinit_failures");
@export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak });
}
@ -175,6 +178,10 @@ fn exportUtilsFn(comptime func: anytype, comptime func_name: []const u8) void {
exportBuiltinFn(func, "utils." ++ func_name);
}
fn exportExpectFn(comptime func: anytype, comptime func_name: []const u8) void {
exportBuiltinFn(func, "expect." ++ func_name);
}
// Custom panic function, as builtin Zig version errors during LLVM verification
pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn {
const builtin = @import("builtin");

View file

@ -18,14 +18,18 @@ extern fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void;
// Signals to the host that the program has panicked
extern fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void;
// should work just like libc memcpy (we can't assume libc is present)
extern fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
comptime {
const builtin = @import("builtin");
// During tetsts, use the testing allocators to satisfy these functions.
// During tests, use the testing allocators to satisfy these functions.
if (builtin.is_test) {
@export(testing_roc_alloc, .{ .name = "roc_alloc", .linkage = .Strong });
@export(testing_roc_realloc, .{ .name = "roc_realloc", .linkage = .Strong });
@export(testing_roc_dealloc, .{ .name = "roc_dealloc", .linkage = .Strong });
@export(testing_roc_panic, .{ .name = "roc_panic", .linkage = .Strong });
@export(testing_roc_memcpy, .{ .name = "roc_memcpy", .linkage = .Strong });
}
}
@ -53,8 +57,16 @@ fn testing_roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
@panic("Roc panicked");
}
pub fn alloc(size: usize, alignment: u32) [*]u8 {
return @ptrCast([*]u8, @call(.{ .modifier = always_inline }, roc_alloc, .{ size, alignment }));
fn testing_roc_memcpy(dest: *c_void, src: *c_void, bytes: usize) callconv(.C) ?*c_void {
const zig_dest = @ptrCast([*]u8, dest);
const zig_src = @ptrCast([*]u8, src);
@memcpy(zig_dest, zig_src, bytes);
return dest;
}
pub fn alloc(size: usize, alignment: u32) ?[*]u8 {
return @ptrCast(?[*]u8, @call(.{ .modifier = always_inline }, roc_alloc, .{ size, alignment }));
}
pub fn realloc(c_ptr: [*]u8, new_size: usize, old_size: usize, alignment: u32) [*]u8 {
@ -70,6 +82,10 @@ pub fn panic(c_ptr: *c_void, alignment: u32) callconv(.C) void {
return @call(.{ .modifier = always_inline }, roc_panic, .{ c_ptr, alignment });
}
pub fn memcpy(dst: [*]u8, src: [*]u8, size: usize) void {
@call(.{ .modifier = always_inline }, roc_memcpy, .{ dst, src, size });
}
// indirection because otherwise zig creates an alias to the panic function which our LLVM code
// does not know how to deal with
pub fn test_panic(c_ptr: *c_void, alignment: u32) callconv(.C) void {
@ -173,7 +189,8 @@ pub fn allocateWithRefcount(
switch (alignment) {
16 => {
var new_bytes: [*]align(16) u8 = @alignCast(16, alloc(length, alignment));
// TODO handle alloc failing!
var new_bytes: [*]align(16) u8 = @alignCast(16, alloc(length, alignment) orelse unreachable);
var as_usize_array = @ptrCast([*]usize, new_bytes);
as_usize_array[0] = 0;
@ -185,7 +202,8 @@ pub fn allocateWithRefcount(
return first_slot;
},
8 => {
var raw = alloc(length, alignment);
// TODO handle alloc failing!
var raw = alloc(length, alignment) orelse unreachable;
var new_bytes: [*]align(8) u8 = @alignCast(8, raw);
var as_isize_array = @ptrCast([*]isize, new_bytes);
@ -197,7 +215,8 @@ pub fn allocateWithRefcount(
return first_slot;
},
4 => {
var raw = alloc(length, alignment);
// TODO handle alloc failing!
var raw = alloc(length, alignment) orelse unreachable;
var new_bytes: [*]align(@alignOf(isize)) u8 = @alignCast(@alignOf(isize), raw);
var as_isize_array = @ptrCast([*]isize, new_bytes);
@ -217,6 +236,11 @@ pub fn allocateWithRefcount(
}
}
pub const CSlice = extern struct {
pointer: *c_void,
len: usize,
};
pub fn unsafeReallocate(
source_ptr: [*]u8,
alignment: u32,

View file

@ -336,3 +336,6 @@ pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic";
pub const UTILS_INCREF: &str = "roc_builtins.utils.incref";
pub const UTILS_DECREF: &str = "roc_builtins.utils.decref";
pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null";
pub const UTILS_EXPECT_FAILED: &str = "roc_builtins.expect.expect_failed";
pub const UTILS_GET_EXPECT_FAILURES: &str = "roc_builtins.expect.get_expect_failures";
pub const UTILS_DEINIT_FAILURES: &str = "roc_builtins.expect.deinit_failures";

View file

@ -1,6 +1,7 @@
use crate::def::Def;
use crate::expr::{self, ClosureData, Expr::*};
use crate::expr::{self, ClosureData, Expr::*, IntValue};
use crate::expr::{Expr, Field, Recursive};
use crate::num::{FloatWidth, IntWidth, NumWidth, NumericBound};
use crate::pattern::Pattern;
use roc_collections::all::SendMap;
use roc_module::called_via::CalledVia;
@ -793,7 +794,7 @@ fn num_is_zero(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::Eq,
args: vec![
(arg_var, Var(Symbol::ARG_1)),
(arg_var, num(unbound_zero_var, 0)),
(arg_var, num(unbound_zero_var, 0, num_no_bound())),
],
ret_var: bool_var,
};
@ -816,7 +817,7 @@ fn num_is_negative(symbol: Symbol, var_store: &mut VarStore) -> Def {
let body = RunLowLevel {
op: LowLevel::NumGt,
args: vec![
(arg_var, num(unbound_zero_var, 0)),
(arg_var, num(unbound_zero_var, 0, num_no_bound())),
(arg_var, Var(Symbol::ARG_1)),
],
ret_var: bool_var,
@ -841,7 +842,7 @@ fn num_is_positive(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NumGt,
args: vec![
(arg_var, Var(Symbol::ARG_1)),
(arg_var, num(unbound_zero_var, 0)),
(arg_var, num(unbound_zero_var, 0, num_no_bound())),
],
ret_var: bool_var,
};
@ -866,7 +867,7 @@ fn num_is_odd(symbol: Symbol, var_store: &mut VarStore) -> Def {
args: vec![
(
arg_var,
int::<i128>(var_store.fresh(), var_store.fresh(), 1),
int::<i128>(var_store.fresh(), var_store.fresh(), 1, num_no_bound()),
),
(
arg_var,
@ -874,7 +875,7 @@ fn num_is_odd(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NumRemUnchecked,
args: vec![
(arg_var, Var(Symbol::ARG_1)),
(arg_var, num(unbound_two_var, 2)),
(arg_var, num(unbound_two_var, 2, num_no_bound())),
],
ret_var: arg_var,
},
@ -901,14 +902,14 @@ fn num_is_even(symbol: Symbol, var_store: &mut VarStore) -> Def {
let body = RunLowLevel {
op: LowLevel::Eq,
args: vec![
(arg_var, num(arg_num_var, 0)),
(arg_var, num(arg_num_var, 0, num_no_bound())),
(
arg_var,
RunLowLevel {
op: LowLevel::NumRemUnchecked,
args: vec![
(arg_var, Var(Symbol::ARG_1)),
(arg_var, num(arg_num_var, 2)),
(arg_var, num(arg_num_var, 2, num_no_bound())),
],
ret_var: arg_var,
},
@ -962,7 +963,10 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NumGte,
args: vec![
(float_var, Var(Symbol::ARG_1)),
(float_var, float(unbound_zero_var, precision_var, 0.0)),
(
float_var,
float(unbound_zero_var, precision_var, 0.0, num_no_bound()),
),
],
ret_var: bool_var,
}),
@ -1008,7 +1012,10 @@ fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NumGt,
args: vec![
(float_var, Var(Symbol::ARG_1)),
(float_var, float(unbound_zero_var, precision_var, 0.0)),
(
float_var,
float(unbound_zero_var, precision_var, 0.0, num_no_bound()),
),
],
ret_var: bool_var,
}),
@ -1246,92 +1253,182 @@ fn num_int_cast(symbol: Symbol, var_store: &mut VarStore) -> Def {
/// Num.minI8: I8
fn num_min_i8(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i8>(symbol, var_store, i8::MIN)
int_min_or_max::<i8>(
symbol,
var_store,
i8::MIN,
NumericBound::Exact(IntWidth::I8),
)
}
/// Num.maxI8: I8
fn num_max_i8(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i8>(symbol, var_store, i8::MAX)
int_min_or_max::<i8>(
symbol,
var_store,
i8::MAX,
NumericBound::Exact(IntWidth::I8),
)
}
/// Num.minU8: U8
fn num_min_u8(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u8>(symbol, var_store, u8::MIN)
int_min_or_max::<u8>(
symbol,
var_store,
u8::MIN,
NumericBound::Exact(IntWidth::U8),
)
}
/// Num.maxU8: U8
fn num_max_u8(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u8>(symbol, var_store, u8::MAX)
int_min_or_max::<u8>(
symbol,
var_store,
u8::MAX,
NumericBound::Exact(IntWidth::U8),
)
}
/// Num.minI16: I16
fn num_min_i16(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i16>(symbol, var_store, i16::MIN)
int_min_or_max::<i16>(
symbol,
var_store,
i16::MIN,
NumericBound::Exact(IntWidth::I16),
)
}
/// Num.maxI16: I16
fn num_max_i16(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i16>(symbol, var_store, i16::MAX)
int_min_or_max::<i16>(
symbol,
var_store,
i16::MAX,
NumericBound::Exact(IntWidth::I16),
)
}
/// Num.minU16: U16
fn num_min_u16(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u16>(symbol, var_store, u16::MIN)
int_min_or_max::<u16>(
symbol,
var_store,
u16::MIN,
NumericBound::Exact(IntWidth::U16),
)
}
/// Num.maxU16: U16
fn num_max_u16(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u16>(symbol, var_store, u16::MAX)
int_min_or_max::<u16>(
symbol,
var_store,
u16::MAX,
NumericBound::Exact(IntWidth::U16),
)
}
/// Num.minI32: I32
fn num_min_i32(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i32>(symbol, var_store, i32::MIN)
int_min_or_max::<i32>(
symbol,
var_store,
i32::MIN,
NumericBound::Exact(IntWidth::I32),
)
}
/// Num.maxI32: I32
fn num_max_i32(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i32>(symbol, var_store, i32::MAX)
int_min_or_max::<i32>(
symbol,
var_store,
i32::MAX,
NumericBound::Exact(IntWidth::I32),
)
}
/// Num.minU32: U32
fn num_min_u32(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u32>(symbol, var_store, u32::MIN)
int_min_or_max::<u32>(
symbol,
var_store,
u32::MIN,
NumericBound::Exact(IntWidth::U32),
)
}
/// Num.maxU32: U32
fn num_max_u32(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u32>(symbol, var_store, u32::MAX)
int_min_or_max::<u32>(
symbol,
var_store,
u32::MAX,
NumericBound::Exact(IntWidth::U32),
)
}
/// Num.minI64: I64
fn num_min_i64(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i64>(symbol, var_store, i64::MIN)
int_min_or_max::<i64>(
symbol,
var_store,
i64::MIN,
NumericBound::Exact(IntWidth::I64),
)
}
/// Num.maxI64: I64
fn num_max_i64(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i64>(symbol, var_store, i64::MAX)
int_min_or_max::<i64>(
symbol,
var_store,
i64::MAX,
NumericBound::Exact(IntWidth::I64),
)
}
/// Num.minU64: U64
fn num_min_u64(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u64>(symbol, var_store, u64::MIN)
int_min_or_max::<u64>(
symbol,
var_store,
u64::MIN,
NumericBound::Exact(IntWidth::U64),
)
}
/// Num.maxU64: U64
fn num_max_u64(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<u64>(symbol, var_store, u64::MAX)
int_min_or_max::<u64>(
symbol,
var_store,
u64::MAX,
NumericBound::Exact(IntWidth::U64),
)
}
/// Num.minI128: I128
fn num_min_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i128>(symbol, var_store, i128::MIN)
int_min_or_max::<i128>(
symbol,
var_store,
i128::MIN,
NumericBound::Exact(IntWidth::I128),
)
}
/// Num.maxI128: I128
fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
int_min_or_max::<i128>(symbol, var_store, i128::MAX)
int_min_or_max::<i128>(
symbol,
var_store,
i128::MAX,
NumericBound::Exact(IntWidth::I128),
)
}
/// List.isEmpty : List * -> Bool
@ -1344,7 +1441,7 @@ fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
let body = RunLowLevel {
op: LowLevel::Eq,
args: vec![
(len_var, num(unbound_zero_var, 0)),
(len_var, num(unbound_zero_var, 0, num_no_bound())),
(
len_var,
RunLowLevel {
@ -1458,7 +1555,12 @@ fn str_to_num(symbol: Symbol, var_store: &mut VarStore) -> Def {
),
(
errorcode_var,
int::<i128>(errorcode_var, Variable::UNSIGNED8, 0),
int::<i128>(
errorcode_var,
Variable::UNSIGNED8,
0,
NumericBound::Exact(IntWidth::U8),
),
),
],
ret_var: bool_var,
@ -2201,7 +2303,12 @@ fn list_swap(symbol: Symbol, var_store: &mut VarStore) -> Def {
fn list_take_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let len_var = var_store.fresh();
let zero = int::<i128>(len_var, Variable::NATURAL, 0);
let zero = int::<i128>(
len_var,
Variable::NATURAL,
0,
NumericBound::Exact(IntWidth::Nat),
);
let body = RunLowLevel {
op: LowLevel::ListSublist,
@ -2227,7 +2334,12 @@ fn list_take_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let len_var = var_store.fresh();
let zero = int::<i128>(len_var, Variable::NATURAL, 0);
let zero = int::<i128>(
len_var,
Variable::NATURAL,
0,
NumericBound::Exact(IntWidth::Nat),
);
let bool_var = var_store.fresh();
let get_list_len = RunLowLevel {
@ -2337,7 +2449,12 @@ fn list_intersperse(symbol: Symbol, var_store: &mut VarStore) -> Def {
let clos_elem_sym = Symbol::ARG_4;
let int_var = var_store.fresh();
let zero = int::<i128>(int_var, Variable::NATURAL, 0);
let zero = int::<i128>(
int_var,
Variable::NATURAL,
0,
NumericBound::Exact(IntWidth::Nat),
);
// \acc, elem -> acc |> List.append sep |> List.append elem
let clos = Closure(ClosureData {
@ -2417,7 +2534,12 @@ fn list_split(symbol: Symbol, var_store: &mut VarStore) -> Def {
let clos_ret_var = var_store.fresh();
let ret_var = var_store.fresh();
let zero = int::<i128>(index_var, Variable::NATURAL, 0);
let zero = int::<i128>(
index_var,
Variable::NATURAL,
0,
NumericBound::Exact(IntWidth::Nat),
);
let clos = Closure(ClosureData {
function_type: clos_fun_var,
@ -2579,7 +2701,10 @@ fn list_drop_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::ListDropAt,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(index_var, int::<i128>(num_var, num_precision_var, 0)),
(
index_var,
int::<i128>(num_var, num_precision_var, 0, num_no_bound()),
),
],
ret_var: list_var,
};
@ -2676,7 +2801,10 @@ fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
ret_var: len_var,
},
),
(arg_var, int::<i128>(num_var, num_precision_var, 1)),
(
arg_var,
int::<i128>(num_var, num_precision_var, 1, num_no_bound()),
),
],
ret_var: len_var,
},
@ -2874,7 +3002,10 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel {
op: LowLevel::NotEq,
args: vec![
(len_var, int::<i128>(num_var, num_precision_var, 0)),
(
len_var,
int::<i128>(num_var, num_precision_var, 0, num_no_bound()),
),
(
len_var,
RunLowLevel {
@ -2905,7 +3036,15 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::ListGetUnsafe,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(arg_var, int::<i128>(num_var, num_precision_var, 0)),
(
arg_var,
int::<i128>(
num_var,
num_precision_var,
0,
num_no_bound(),
),
),
],
ret_var: list_elem_var,
},
@ -3004,7 +3143,10 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel {
op: LowLevel::NotEq,
args: vec![
(len_var, int::<i128>(num_var, num_precision_var, 0)),
(
len_var,
int::<i128>(num_var, num_precision_var, 0, num_no_bound()),
),
(
len_var,
RunLowLevel {
@ -3035,7 +3177,15 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::ListGetUnsafe,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(arg_var, int::<i128>(num_var, num_precision_var, 0)),
(
arg_var,
int::<i128>(
num_var,
num_precision_var,
0,
num_no_bound(),
),
),
],
ret_var: list_elem_var,
},
@ -3129,7 +3279,10 @@ fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def {
Box::new(function),
vec![
(list_var, Loc::at_zero(Var(Symbol::ARG_1))),
(num_var, Loc::at_zero(num(var_store.fresh(), 0))),
(
num_var,
Loc::at_zero(num(var_store.fresh(), 0, num_no_bound())),
),
(closure_var, Loc::at_zero(Var(Symbol::NUM_ADD))),
],
CalledVia::Space,
@ -3161,7 +3314,10 @@ fn list_product(symbol: Symbol, var_store: &mut VarStore) -> Def {
Box::new(function),
vec![
(list_var, Loc::at_zero(Var(Symbol::ARG_1))),
(num_var, Loc::at_zero(num(var_store.fresh(), 1))),
(
num_var,
Loc::at_zero(num(var_store.fresh(), 1, num_no_bound())),
),
(closure_var, Loc::at_zero(Var(Symbol::NUM_MUL))),
],
CalledVia::Space,
@ -3815,7 +3971,7 @@ fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NotEq,
args: vec![
(num_var, Var(Symbol::ARG_2)),
(num_var, num(unbound_zero_var, 0)),
(num_var, num(unbound_zero_var, 0, num_no_bound())),
],
ret_var: bool_var,
},
@ -3918,7 +4074,10 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::NotEq,
args: vec![
(num_var, Var(Symbol::ARG_2)),
(num_var, float(unbound_zero_var, precision_var, 0.0)),
(
num_var,
float(unbound_zero_var, precision_var, 0.0, num_no_bound()),
),
],
ret_var: bool_var,
},
@ -3983,7 +4142,12 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
(num_var, Var(Symbol::ARG_2)),
(
num_var,
int::<i128>(unbound_zero_var, unbound_zero_precision_var, 0),
int::<i128>(
unbound_zero_var,
unbound_zero_precision_var,
0,
num_no_bound(),
),
),
],
ret_var: bool_var,
@ -4049,7 +4213,12 @@ fn num_div_ceil(symbol: Symbol, var_store: &mut VarStore) -> Def {
(num_var, Var(Symbol::ARG_2)),
(
num_var,
int::<i128>(unbound_zero_var, unbound_zero_precision_var, 0),
int::<i128>(
unbound_zero_var,
unbound_zero_precision_var,
0,
num_no_bound(),
),
),
],
ret_var: bool_var,
@ -4119,7 +4288,10 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel {
op: LowLevel::NotEq,
args: vec![
(len_var, int::<i128>(zero_var, zero_precision_var, 0)),
(
len_var,
int::<i128>(zero_var, zero_precision_var, 0, num_no_bound()),
),
(
len_var,
RunLowLevel {
@ -4143,7 +4315,10 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
op: LowLevel::ListGetUnsafe,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(len_var, int::<i128>(zero_var, zero_precision_var, 0)),
(
len_var,
int::<i128>(zero_var, zero_precision_var, 0, num_no_bound()),
),
],
ret_var: list_elem_var,
},
@ -4200,7 +4375,10 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
RunLowLevel {
op: LowLevel::NotEq,
args: vec![
(len_var, int::<i128>(num_var, num_precision_var, 0)),
(
len_var,
int::<i128>(num_var, num_precision_var, 0, num_no_bound()),
),
(
len_var,
RunLowLevel {
@ -4239,7 +4417,15 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
ret_var: len_var,
},
),
(arg_var, int::<i128>(num_var, num_precision_var, 1)),
(
arg_var,
int::<i128>(
num_var,
num_precision_var,
1,
num_no_bound(),
),
),
],
ret_var: len_var,
},
@ -4867,7 +5053,10 @@ fn num_bytes_to(symbol: Symbol, var_store: &mut VarStore, offset: i64, low_level
add_var,
RunLowLevel {
ret_var: cast_var,
args: vec![(cast_var, num(var_store.fresh(), offset))],
args: vec![(
cast_var,
num(var_store.fresh(), offset, num_no_bound()),
)],
op: LowLevel::NumIntCast,
},
),
@ -4955,13 +5144,18 @@ fn defn_help(
}
#[inline(always)]
fn int_min_or_max<I128>(symbol: Symbol, var_store: &mut VarStore, i: I128) -> Def
fn int_min_or_max<I128>(
symbol: Symbol,
var_store: &mut VarStore,
i: I128,
bound: NumericBound<IntWidth>,
) -> Def
where
I128: Into<i128>,
{
let int_var = var_store.fresh();
let int_precision_var = var_store.fresh();
let body = int::<I128>(int_var, int_precision_var, i);
let body = int::<I128>(int_var, int_precision_var, i, bound);
let std = roc_builtins::std::types();
let solved = std.get(&symbol).unwrap();
@ -4984,21 +5178,53 @@ where
}
}
fn num_no_bound<W: Copy>() -> NumericBound<W> {
NumericBound::None
}
#[inline(always)]
fn int<I128>(num_var: Variable, precision_var: Variable, i: I128) -> Expr
fn int<I128>(
num_var: Variable,
precision_var: Variable,
i: I128,
bound: NumericBound<IntWidth>,
) -> Expr
where
I128: Into<i128>,
{
let ii = i.into();
Int(num_var, precision_var, ii.to_string().into_boxed_str(), ii)
Int(
num_var,
precision_var,
ii.to_string().into_boxed_str(),
IntValue::I128(ii),
bound,
)
}
#[inline(always)]
fn float(num_var: Variable, precision_var: Variable, f: f64) -> Expr {
Float(num_var, precision_var, f.to_string().into_boxed_str(), f)
fn float(
num_var: Variable,
precision_var: Variable,
f: f64,
bound: NumericBound<FloatWidth>,
) -> Expr {
Float(
num_var,
precision_var,
f.to_string().into_boxed_str(),
f,
bound,
)
}
#[inline(always)]
fn num(num_var: Variable, i: i64) -> Expr {
Num(num_var, i.to_string().into_boxed_str(), i)
fn num<I: Into<i128>>(num_var: Variable, i: I, bound: NumericBound<NumWidth>) -> Expr {
let i = i.into();
Num(
num_var,
i.to_string().into_boxed_str(),
IntValue::I128(i),
bound,
)
}

View file

@ -840,9 +840,9 @@ fn pattern_to_vars_by_symbol(
}
}
NumLiteral(_, _, _)
| IntLiteral(_, _, _)
| FloatLiteral(_, _, _)
NumLiteral(..)
| IntLiteral(..)
| FloatLiteral(..)
| StrLiteral(_)
| Underscore
| MalformedPattern(_, _)

View file

@ -1,9 +1,9 @@
use roc_can::annotation::IntroducedVariables;
use roc_can::def::{Declaration, Def};
use roc_can::env::Env;
use roc_can::expr::{ClosureData, Expr, Recursive};
use roc_can::pattern::Pattern;
use roc_can::scope::Scope;
use crate::annotation::IntroducedVariables;
use crate::def::{Declaration, Def};
use crate::env::Env;
use crate::expr::{ClosureData, Expr, Recursive};
use crate::pattern::Pattern;
use crate::scope::Scope;
use roc_collections::all::{MutSet, SendMap};
use roc_module::called_via::CalledVia;
use roc_module::ident::TagName;
@ -220,7 +220,7 @@ fn build_effect_always(
)
};
let def_annotation = roc_can::def::Annotation {
let def_annotation = crate::def::Annotation {
signature,
introduced_variables,
aliases: SendMap::default(),
@ -432,7 +432,7 @@ fn build_effect_map(
)
};
let def_annotation = roc_can::def::Annotation {
let def_annotation = crate::def::Annotation {
signature,
introduced_variables,
aliases: SendMap::default(),
@ -599,7 +599,7 @@ fn build_effect_after(
)
};
let def_annotation = roc_can::def::Annotation {
let def_annotation = crate::def::Annotation {
signature,
introduced_variables,
aliases: SendMap::default(),
@ -852,7 +852,7 @@ fn build_effect_forever(
)
};
let def_annotation = roc_can::def::Annotation {
let def_annotation = crate::def::Annotation {
signature,
introduced_variables,
aliases: SendMap::default(),
@ -1150,7 +1150,7 @@ fn build_effect_loop(
)
};
let def_annotation = roc_can::def::Annotation {
let def_annotation = crate::def::Annotation {
signature,
introduced_variables,
aliases: SendMap::default(),
@ -1334,7 +1334,7 @@ fn build_effect_loop_inner_body(
let step_pattern = applied_tag_pattern(step_tag_name, &[new_state_symbol], var_store);
roc_can::expr::WhenBranch {
crate::expr::WhenBranch {
patterns: vec![Loc::at_zero(step_pattern)],
value: Loc::at_zero(force_thunk2),
guard: None,
@ -1345,7 +1345,7 @@ fn build_effect_loop_inner_body(
let done_tag_name = TagName::Global("Done".into());
let done_pattern = applied_tag_pattern(done_tag_name, &[done_symbol], var_store);
roc_can::expr::WhenBranch {
crate::expr::WhenBranch {
patterns: vec![Loc::at_zero(done_pattern)],
value: Loc::at_zero(Expr::Var(done_symbol)),
guard: None,
@ -1376,7 +1376,7 @@ pub fn build_host_exposed_def(
ident: &str,
effect_tag_name: TagName,
var_store: &mut VarStore,
annotation: roc_can::annotation::Annotation,
annotation: crate::annotation::Annotation,
) -> Def {
let expr_var = var_store.fresh();
let pattern = Pattern::Identifier(symbol);
@ -1520,7 +1520,7 @@ pub fn build_host_exposed_def(
}
};
let def_annotation = roc_can::def::Annotation {
let def_annotation = crate::def::Annotation {
signature: annotation.typ,
introduced_variables: annotation.introduced_variables,
aliases: annotation.aliases,

View file

@ -3,8 +3,8 @@ use crate::builtins::builtin_defs_map;
use crate::def::{can_defs_with_return, Def};
use crate::env::Env;
use crate::num::{
finish_parsing_base, finish_parsing_float, finish_parsing_int, float_expr_from_result,
int_expr_from_result, num_expr_from_result,
finish_parsing_base, finish_parsing_float, finish_parsing_num, float_expr_from_result,
int_expr_from_result, num_expr_from_result, FloatWidth, IntWidth, NumWidth, NumericBound,
};
use crate::pattern::{canonicalize_pattern, Pattern};
use crate::procedure::References;
@ -20,7 +20,7 @@ use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::Alias;
use std::fmt::Debug;
use std::fmt::{Debug, Display};
use std::{char, u32};
#[derive(Clone, Default, Debug, PartialEq)]
@ -46,17 +46,38 @@ impl Output {
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum IntValue {
I128(i128),
U128(u128),
}
impl Display for IntValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
IntValue::I128(n) => Display::fmt(&n, f),
IntValue::U128(n) => Display::fmt(&n, f),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Expr {
// Literals
// Num stores the `a` variable in `Num a`. Not the same as the variable
// stored in Int and Float below, which is strictly for better error messages
Num(Variable, Box<str>, i64),
Num(Variable, Box<str>, IntValue, NumericBound<NumWidth>),
// Int and Float store a variable to generate better error messages
Int(Variable, Variable, Box<str>, i128),
Float(Variable, Variable, Box<str>, f64),
Int(
Variable,
Variable,
Box<str>,
IntValue,
NumericBound<IntWidth>,
),
Float(Variable, Variable, Box<str>, f64, NumericBound<FloatWidth>),
Str(Box<str>),
List {
elem_var: Variable,
@ -208,20 +229,20 @@ pub fn canonicalize_expr<'a>(
use Expr::*;
let (expr, output) = match expr {
ast::Expr::Num(str) => {
&ast::Expr::Num(str) => {
let answer = num_expr_from_result(
var_store,
finish_parsing_int(*str).map(|int| (*str, int)),
finish_parsing_num(str).map(|result| (str, result)),
region,
env,
);
(answer, Output::default())
}
ast::Expr::Float(str) => {
&ast::Expr::Float(str) => {
let answer = float_expr_from_result(
var_store,
finish_parsing_float(str).map(|f| (*str, f)),
finish_parsing_float(str).map(|(f, bound)| (str, f, bound)),
region,
env,
);
@ -790,21 +811,21 @@ pub fn canonicalize_expr<'a>(
(RuntimeError(problem), Output::default())
}
ast::Expr::NonBase10Int {
&ast::Expr::NonBase10Int {
string,
base,
is_negative,
} => {
// the minus sign is added before parsing, to get correct overflow/underflow behavior
let answer = match finish_parsing_base(string, *base, *is_negative) {
Ok(int) => {
let answer = match finish_parsing_base(string, base, is_negative) {
Ok((int, bound)) => {
// Done in this kinda round about way with intermediate variables
// to keep borrowed values around and make this compile
let int_string = int.to_string();
let int_str = int_string.as_str();
int_expr_from_result(var_store, Ok((int_str, int as i128)), region, *base, env)
int_expr_from_result(var_store, Ok((int_str, int, bound)), region, base, env)
}
Err(e) => int_expr_from_result(var_store, Err(e), region, *base, env),
Err(e) => int_expr_from_result(var_store, Err(e), region, base, env),
};
(answer, Output::default())
@ -1226,9 +1247,9 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
match expr {
// Num stores the `a` variable in `Num a`. Not the same as the variable
// stored in Int and Float below, which is strictly for better error messages
other @ Num(_, _, _)
| other @ Int(_, _, _, _)
| other @ Float(_, _, _, _)
other @ Num(..)
| other @ Int(..)
| other @ Float(..)
| other @ Str { .. }
| other @ RuntimeError(_)
| other @ EmptyRecord

View file

@ -5,6 +5,7 @@ pub mod annotation;
pub mod builtins;
pub mod constraint;
pub mod def;
pub mod effect_module;
pub mod env;
pub mod expected;
pub mod expr;

View file

@ -6,15 +6,16 @@ use crate::pattern::Pattern;
use crate::scope::Scope;
use bumpalo::Bump;
use roc_collections::all::{MutMap, MutSet, SendMap};
use roc_module::ident::Ident;
use roc_module::ident::Lowercase;
use roc_module::ident::{Ident, TagName};
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_parse::ast;
use roc_parse::header::HeaderFor;
use roc_parse::pattern::PatternType;
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::Alias;
use roc_types::types::{Alias, Type};
#[derive(Debug)]
pub struct Module {
@ -44,6 +45,7 @@ pub struct ModuleOutput {
pub fn canonicalize_module_defs<'a, F>(
arena: &Bump,
loc_defs: &'a [Loc<ast::Def<'a>>],
header_for: &roc_parse::header::HeaderFor,
home: ModuleId,
module_ids: &ModuleIds,
exposed_ident_ids: IdentIds,
@ -59,12 +61,49 @@ where
{
let mut can_exposed_imports = MutMap::default();
let mut scope = Scope::new(home, var_store);
let mut env = Env::new(home, dep_idents, module_ids, exposed_ident_ids);
let num_deps = dep_idents.len();
for (name, alias) in aliases.into_iter() {
scope.add_alias(name, alias.region, alias.type_variables, alias.typ);
}
let effect_symbol = if let HeaderFor::Hosted { generates, .. } = header_for {
// TODO extract effect name from the header
let name: &str = generates.into();
let effect_symbol = scope
.introduce(
name.into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
Region::zero(),
)
.unwrap();
let effect_tag_name = TagName::Private(effect_symbol);
{
let a_var = var_store.fresh();
let actual = crate::effect_module::build_effect_actual(
effect_tag_name,
Type::Variable(a_var),
var_store,
);
scope.add_alias(
effect_symbol,
Region::zero(),
vec![Loc::at_zero(("a".into(), a_var))],
actual,
);
}
Some(effect_symbol)
} else {
None
};
// Desugar operators (convert them to Apply calls, taking into account
// operator precedence and associativity rules), before doing other canonicalization.
//
@ -82,7 +121,6 @@ where
}));
}
let mut env = Env::new(home, dep_idents, module_ids, exposed_ident_ids);
let mut lookups = Vec::with_capacity(num_deps);
let mut rigid_variables = MutMap::default();
@ -143,7 +181,7 @@ where
}
}
let (defs, scope, output, symbols_introduced) = canonicalize_defs(
let (defs, mut scope, output, symbols_introduced) = canonicalize_defs(
&mut env,
Output::default(),
var_store,
@ -208,7 +246,21 @@ where
(Ok(mut declarations), output) => {
use crate::def::Declaration::*;
for decl in declarations.iter() {
if let Some(effect_symbol) = effect_symbol {
let mut exposed_symbols = MutSet::default();
// NOTE this currently builds all functions, not just the ones that the user requested
crate::effect_module::build_effect_builtins(
&mut env,
&mut scope,
effect_symbol,
var_store,
&mut exposed_symbols,
&mut declarations,
);
}
for decl in declarations.iter_mut() {
match decl {
Declare(def) => {
for (symbol, _) in def.pattern_vars.iter() {
@ -221,6 +273,59 @@ where
exposed_but_not_defined.remove(symbol);
}
}
// Temporary hack: we don't know exactly what symbols are hosted symbols,
// and which are meant to be normal definitions without a body. So for now
// we just assume they are hosted functions (meant to be provided by the platform)
if let Some(effect_symbol) = effect_symbol {
macro_rules! make_hosted_def {
() => {
let symbol = def.pattern_vars.iter().next().unwrap().0;
let ident_id = symbol.ident_id();
let ident =
env.ident_ids.get_name(ident_id).unwrap().to_string();
let def_annotation = def.annotation.clone().unwrap();
let annotation = crate::annotation::Annotation {
typ: def_annotation.signature,
introduced_variables: def_annotation.introduced_variables,
references: Default::default(),
aliases: Default::default(),
};
let hosted_def = crate::effect_module::build_host_exposed_def(
&mut env,
&mut scope,
*symbol,
&ident,
TagName::Private(effect_symbol),
var_store,
annotation,
);
*def = hosted_def;
};
}
match &def.loc_expr.value {
Expr::RuntimeError(RuntimeError::NoImplementationNamed {
..
}) => {
make_hosted_def!();
}
Expr::Closure(closure_data)
if matches!(
closure_data.loc_body.value,
Expr::RuntimeError(
RuntimeError::NoImplementationNamed { .. }
)
) =>
{
make_hosted_def!();
}
_ => {}
}
}
}
DeclareRec(defs) => {
for def in defs {
@ -253,6 +358,18 @@ where
let mut aliases = MutMap::default();
if let Some(effect_symbol) = effect_symbol {
// Remove this from exposed_symbols,
// so that at the end of the process,
// we can see if there were any
// exposed symbols which did not have
// corresponding defs.
exposed_but_not_defined.remove(&effect_symbol);
let hosted_alias = scope.lookup_alias(effect_symbol).unwrap().clone();
aliases.insert(effect_symbol, hosted_alias);
}
for (symbol, alias) in output.aliases {
// Remove this from exposed_symbols,
// so that at the end of the process,
@ -397,9 +514,9 @@ fn fix_values_captured_in_closure_pattern(
}
}
Identifier(_)
| NumLiteral(_, _, _)
| IntLiteral(_, _, _)
| FloatLiteral(_, _, _)
| NumLiteral(..)
| IntLiteral(..)
| FloatLiteral(..)
| StrLiteral(_)
| Underscore
| Shadowed(..)
@ -453,9 +570,9 @@ fn fix_values_captured_in_closure_expr(
fix_values_captured_in_closure_expr(&mut loc_body.value, no_capture_symbols);
}
Num(_, _, _)
| Int(_, _, _, _)
| Float(_, _, _, _)
Num(..)
| Int(..)
| Float(..)
| Str(_)
| Var(_)
| EmptyRecord

View file

@ -1,5 +1,5 @@
use crate::env::Env;
use crate::expr::Expr;
use crate::expr::{Expr, IntValue};
use roc_parse::ast::Base;
use roc_problem::can::Problem;
use roc_problem::can::RuntimeError::*;
@ -7,21 +7,33 @@ use roc_problem::can::{FloatErrorKind, IntErrorKind};
use roc_region::all::Region;
use roc_types::subs::VarStore;
use std::i64;
// TODO use rust's integer parsing again
//
// We're waiting for libcore here, see https://github.com/rust-lang/rust/issues/22639
// There is a nightly API for exposing the parse error.
use std::str;
#[inline(always)]
pub fn num_expr_from_result(
var_store: &mut VarStore,
result: Result<(&str, i64), (&str, IntErrorKind)>,
result: Result<(&str, ParsedNumResult), (&str, IntErrorKind)>,
region: Region,
env: &mut Env,
) -> Expr {
match result {
Ok((str, num)) => Expr::Num(var_store.fresh(), (*str).into(), num),
Ok((str, ParsedNumResult::UnknownNum(num))) => {
Expr::Num(var_store.fresh(), (*str).into(), num, NumericBound::None)
}
Ok((str, ParsedNumResult::Int(num, bound))) => Expr::Int(
var_store.fresh(),
var_store.fresh(),
(*str).into(),
num,
bound,
),
Ok((str, ParsedNumResult::Float(num, bound))) => Expr::Float(
var_store.fresh(),
var_store.fresh(),
(*str).into(),
num,
bound,
),
Err((raw, error)) => {
// (Num *) compiles to Int if it doesn't
// get specialized to something else first,
@ -38,14 +50,20 @@ pub fn num_expr_from_result(
#[inline(always)]
pub fn int_expr_from_result(
var_store: &mut VarStore,
result: Result<(&str, i128), (&str, IntErrorKind)>,
result: Result<(&str, IntValue, NumericBound<IntWidth>), (&str, IntErrorKind)>,
region: Region,
base: Base,
env: &mut Env,
) -> Expr {
// Int stores a variable to generate better error messages
match result {
Ok((str, int)) => Expr::Int(var_store.fresh(), var_store.fresh(), (*str).into(), int),
Ok((str, int, bound)) => Expr::Int(
var_store.fresh(),
var_store.fresh(),
(*str).into(),
int,
bound,
),
Err((raw, error)) => {
let runtime_error = InvalidInt(error, base, region, raw.into());
@ -59,13 +77,19 @@ pub fn int_expr_from_result(
#[inline(always)]
pub fn float_expr_from_result(
var_store: &mut VarStore,
result: Result<(&str, f64), (&str, FloatErrorKind)>,
result: Result<(&str, f64, NumericBound<FloatWidth>), (&str, FloatErrorKind)>,
region: Region,
env: &mut Env,
) -> Expr {
// Float stores a variable to generate better error messages
match result {
Ok((str, float)) => Expr::Float(var_store.fresh(), var_store.fresh(), (*str).into(), float),
Ok((str, float, bound)) => Expr::Float(
var_store.fresh(),
var_store.fresh(),
(*str).into(),
float,
bound,
),
Err((raw, error)) => {
let runtime_error = InvalidFloat(error, region, raw.into());
@ -76,11 +100,32 @@ pub fn float_expr_from_result(
}
}
pub enum ParsedNumResult {
Int(IntValue, NumericBound<IntWidth>),
Float(f64, NumericBound<FloatWidth>),
UnknownNum(IntValue),
}
#[inline(always)]
pub fn finish_parsing_int(raw: &str) -> Result<i64, (&str, IntErrorKind)> {
pub fn finish_parsing_num(raw: &str) -> Result<ParsedNumResult, (&str, IntErrorKind)> {
// Ignore underscores.
let radix = 10;
from_str_radix::<i64>(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e.kind))
let (num, bound) =
from_str_radix(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e))?;
// Let's try to specialize the number
Ok(match bound {
NumericBound::None => ParsedNumResult::UnknownNum(num),
NumericBound::Exact(NumWidth::Int(iw)) => {
ParsedNumResult::Int(num, NumericBound::Exact(iw))
}
NumericBound::Exact(NumWidth::Float(fw)) => {
let num = match num {
IntValue::I128(n) => n as f64,
IntValue::U128(n) => n as f64,
};
ParsedNumResult::Float(num, NumericBound::Exact(fw))
}
})
}
#[inline(always)]
@ -88,7 +133,7 @@ pub fn finish_parsing_base(
raw: &str,
base: Base,
is_negative: bool,
) -> Result<i64, (&str, IntErrorKind)> {
) -> Result<(IntValue, NumericBound<IntWidth>), (&str, IntErrorKind)> {
let radix = match base {
Base::Hex => 16,
Base::Decimal => 10,
@ -98,18 +143,36 @@ pub fn finish_parsing_base(
// Ignore underscores, insert - when negative to get correct underflow/overflow behavior
(if is_negative {
from_str_radix::<i64>(format!("-{}", raw.replace("_", "")).as_str(), radix)
from_str_radix(format!("-{}", raw.replace("_", "")).as_str(), radix)
} else {
from_str_radix::<i64>(raw.replace("_", "").as_str(), radix)
from_str_radix(raw.replace("_", "").as_str(), radix)
})
.map_err(|e| (raw, e.kind))
.and_then(|(n, bound)| {
let bound = match bound {
NumericBound::None => NumericBound::None,
NumericBound::Exact(NumWidth::Int(iw)) => NumericBound::Exact(iw),
NumericBound::Exact(NumWidth::Float(_)) => return Err(IntErrorKind::FloatSuffix),
};
Ok((n, bound))
})
.map_err(|e| (raw, e))
}
#[inline(always)]
pub fn finish_parsing_float(raw: &str) -> Result<f64, (&str, FloatErrorKind)> {
pub fn finish_parsing_float(
raw: &str,
) -> Result<(f64, NumericBound<FloatWidth>), (&str, FloatErrorKind)> {
let (opt_bound, raw_without_suffix) = parse_literal_suffix(raw);
let bound = match opt_bound {
None => NumericBound::None,
Some(NumWidth::Float(fw)) => NumericBound::Exact(fw),
Some(NumWidth::Int(_)) => return Err((raw, FloatErrorKind::IntSuffix)),
};
// Ignore underscores.
match raw.replace("_", "").parse::<f64>() {
Ok(float) if float.is_finite() => Ok(float),
match raw_without_suffix.replace("_", "").parse::<f64>() {
Ok(float) if float.is_finite() => Ok((float, bound)),
Ok(float) => {
if float.is_sign_positive() {
Err((raw, FloatErrorKind::PositiveInfinity))
@ -121,6 +184,35 @@ pub fn finish_parsing_float(raw: &str) -> Result<f64, (&str, FloatErrorKind)> {
}
}
fn parse_literal_suffix(num_str: &str) -> (Option<NumWidth>, &str) {
macro_rules! parse_num_suffix {
($($suffix:expr, $width:expr)*) => {$(
if num_str.ends_with($suffix) {
return (Some($width), num_str.get(0..num_str.len() - $suffix.len()).unwrap());
}
)*}
}
parse_num_suffix! {
"u8", NumWidth::Int(IntWidth::U8)
"u16", NumWidth::Int(IntWidth::U16)
"u32", NumWidth::Int(IntWidth::U32)
"u64", NumWidth::Int(IntWidth::U64)
"u128", NumWidth::Int(IntWidth::U128)
"i8", NumWidth::Int(IntWidth::I8)
"i16", NumWidth::Int(IntWidth::I16)
"i32", NumWidth::Int(IntWidth::I32)
"i64", NumWidth::Int(IntWidth::I64)
"i128", NumWidth::Int(IntWidth::I128)
"nat", NumWidth::Int(IntWidth::Nat)
"dec", NumWidth::Float(FloatWidth::Dec)
"f32", NumWidth::Float(FloatWidth::F32)
"f64", NumWidth::Float(FloatWidth::F64)
}
(None, num_str)
}
/// Integer parsing code taken from the rust libcore,
/// pulled in so we can give custom error messages
///
@ -129,44 +221,11 @@ pub fn finish_parsing_float(raw: &str) -> Result<f64, (&str, FloatErrorKind)> {
/// the LEGAL_DETAILS file in the root directory of this distribution.
///
/// Thanks to the Rust project and its contributors!
trait FromStrRadixHelper: PartialOrd + Copy {
fn min_value() -> Self;
fn max_value() -> Self;
fn from_u32(u: u32) -> Self;
fn checked_mul(&self, other: u32) -> Option<Self>;
fn checked_sub(&self, other: u32) -> Option<Self>;
fn checked_add(&self, other: u32) -> Option<Self>;
}
macro_rules! doit {
($($t:ty)*) => ($(impl FromStrRadixHelper for $t {
#[inline]
fn min_value() -> Self { Self::min_value() }
#[inline]
fn max_value() -> Self { Self::max_value() }
#[inline]
fn from_u32(u: u32) -> Self { u as Self }
#[inline]
fn checked_mul(&self, other: u32) -> Option<Self> {
Self::checked_mul(*self, other as Self)
}
#[inline]
fn checked_sub(&self, other: u32) -> Option<Self> {
Self::checked_sub(*self, other as Self)
}
#[inline]
fn checked_add(&self, other: u32) -> Option<Self> {
Self::checked_add(*self, other as Self)
}
})*)
}
// We only need the i64 implementation, but libcore defines
// doit! { i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize }
doit! { i64 }
fn from_str_radix<T: FromStrRadixHelper>(src: &str, radix: u32) -> Result<T, ParseIntError> {
fn from_str_radix(
src: &str,
radix: u32,
) -> Result<(IntValue, NumericBound<NumWidth>), IntErrorKind> {
use self::IntErrorKind::*;
use self::ParseIntError as PIE;
assert!(
(2..=36).contains(&radix),
@ -174,86 +233,260 @@ fn from_str_radix<T: FromStrRadixHelper>(src: &str, radix: u32) -> Result<T, Par
radix
);
if src.is_empty() {
return Err(PIE { kind: Empty });
}
let (opt_exact_bound, src) = parse_literal_suffix(src);
let is_signed_ty = T::from_u32(0) > T::min_value();
// all valid digits are ascii, so we will just iterate over the utf8 bytes
// and cast them to chars. .to_digit() will safely return None for anything
// other than a valid ascii digit for the given radix, including the first-byte
// of multi-byte sequences
let src = src.as_bytes();
let (is_positive, digits) = match src[0] {
b'+' => (true, &src[1..]),
b'-' if is_signed_ty => (false, &src[1..]),
_ => (true, src),
use std::num::IntErrorKind as StdIEK;
let result = match i128::from_str_radix(src, radix) {
Ok(result) => IntValue::I128(result),
Err(pie) => match pie.kind() {
StdIEK::Empty => return Err(IntErrorKind::Empty),
StdIEK::InvalidDigit => return Err(IntErrorKind::InvalidDigit),
StdIEK::NegOverflow => return Err(IntErrorKind::Underflow),
StdIEK::PosOverflow => {
// try a u128
match u128::from_str_radix(src, radix) {
Ok(result) => IntValue::U128(result),
Err(pie) => match pie.kind() {
StdIEK::InvalidDigit => return Err(IntErrorKind::InvalidDigit),
StdIEK::PosOverflow => return Err(IntErrorKind::Overflow),
StdIEK::Empty | StdIEK::Zero | StdIEK::NegOverflow => unreachable!(),
_ => unreachable!("I thought all possibilities were exhausted, but std::num added a new one")
},
}
}
StdIEK::Zero => unreachable!("Parsed a i128"),
_ => unreachable!(
"I thought all possibilities were exhausted, but std::num added a new one"
),
},
};
if digits.is_empty() {
return Err(PIE { kind: Empty });
}
let (lower_bound, is_negative) = match result {
IntValue::I128(num) => (lower_bound_of_int(num), num <= 0),
IntValue::U128(_) => (IntWidth::U128, false),
};
let mut result = T::from_u32(0);
if is_positive {
// The number is positive
for &c in digits {
let x = match (c as char).to_digit(radix) {
Some(x) => x,
None => return Err(PIE { kind: InvalidDigit }),
};
result = match result.checked_mul(radix) {
Some(result) => result,
None => return Err(PIE { kind: Overflow }),
};
result = match result.checked_add(x) {
Some(result) => result,
None => return Err(PIE { kind: Overflow }),
};
match opt_exact_bound {
None => {
// TODO: use the lower bound
Ok((result, NumericBound::None))
}
Some(bound @ NumWidth::Float(_)) => {
// For now, assume floats can represent all integers
// TODO: this is somewhat incorrect, revisit
Ok((result, NumericBound::Exact(bound)))
}
Some(NumWidth::Int(exact_width)) => {
// We need to check if the exact bound >= lower bound.
if exact_width.is_superset(&lower_bound, is_negative) {
// Great! Use the exact bound.
Ok((result, NumericBound::Exact(NumWidth::Int(exact_width))))
} else {
// This is something like 200i8; the lower bound is u8, which holds strictly more
// ints on the positive side than i8 does. Report an error depending on which side
// of the integers we checked.
let err = if is_negative {
UnderflowsSuffix {
suffix_type: exact_width.type_str(),
min_value: exact_width.min_value(),
}
} else {
OverflowsSuffix {
suffix_type: exact_width.type_str(),
max_value: exact_width.max_value(),
}
};
Err(err)
}
}
}
}
fn lower_bound_of_int(result: i128) -> IntWidth {
use IntWidth::*;
if result >= 0 {
// Positive
let result = result as u128;
if result > U64.max_value() {
I128
} else if result > I64.max_value() {
U64
} else if result > U32.max_value() {
I64
} else if result > I32.max_value() {
U32
} else if result > U16.max_value() {
I32
} else if result > I16.max_value() {
U16
} else if result > U8.max_value() {
I16
} else if result > I8.max_value() {
U8
} else {
I8
}
} else {
// The number is negative
for &c in digits {
let x = match (c as char).to_digit(radix) {
Some(x) => x,
None => return Err(PIE { kind: InvalidDigit }),
};
result = match result.checked_mul(radix) {
Some(result) => result,
None => return Err(PIE { kind: Underflow }),
};
result = match result.checked_sub(x) {
Some(result) => result,
None => return Err(PIE { kind: Underflow }),
};
// Negative
if result < I64.min_value() {
I128
} else if result < I32.min_value() {
I64
} else if result < I16.min_value() {
I32
} else if result < I8.min_value() {
I16
} else {
I8
}
}
Ok(result)
}
/// An error which can be returned when parsing an integer.
///
/// This error is used as the error type for the `from_str_radix()` functions
/// on the primitive integer types, such as [`i8::from_str_radix`].
///
/// # Potential causes
///
/// Among other causes, `ParseIntError` can be thrown because of leading or trailing whitespace
/// in the string e.g., when it is obtained from the standard input.
/// Using the [`str.trim()`] method ensures that no whitespace remains before parsing.
///
/// [`str.trim()`]: ../../std/primitive.str.html#method.trim
/// [`i8::from_str_radix`]: ../../std/primitive.i8.html#method.from_str_radix
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseIntError {
kind: IntErrorKind,
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum IntSign {
Unsigned,
Signed,
}
impl ParseIntError {
/// Outputs the detailed cause of parsing an integer failing.
pub fn kind(&self) -> &IntErrorKind {
&self.kind
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum IntWidth {
U8,
U16,
U32,
U64,
U128,
I8,
I16,
I32,
I64,
I128,
Nat,
}
impl IntWidth {
/// Returns the `IntSign` and bit width of a variant.
fn sign_and_width(&self) -> (IntSign, u32) {
use IntSign::*;
use IntWidth::*;
match self {
U8 => (Unsigned, 8),
U16 => (Unsigned, 16),
U32 => (Unsigned, 32),
U64 => (Unsigned, 64),
U128 => (Unsigned, 128),
I8 => (Signed, 8),
I16 => (Signed, 16),
I32 => (Signed, 32),
I64 => (Signed, 64),
I128 => (Signed, 128),
// TODO: this is platform specific!
Nat => (Unsigned, 64),
}
}
fn type_str(&self) -> &'static str {
use IntWidth::*;
match self {
U8 => "U8",
U16 => "U16",
U32 => "U32",
U64 => "U64",
U128 => "U128",
I8 => "I8",
I16 => "I16",
I32 => "I32",
I64 => "I64",
I128 => "I128",
Nat => "Nat",
}
}
fn max_value(&self) -> u128 {
use IntWidth::*;
match self {
U8 => u8::MAX as u128,
U16 => u16::MAX as u128,
U32 => u32::MAX as u128,
U64 => u64::MAX as u128,
U128 => u128::MAX,
I8 => i8::MAX as u128,
I16 => i16::MAX as u128,
I32 => i32::MAX as u128,
I64 => i64::MAX as u128,
I128 => i128::MAX as u128,
// TODO: this is platform specific!
Nat => u64::MAX as u128,
}
}
fn min_value(&self) -> i128 {
use IntWidth::*;
match self {
U8 | U16 | U32 | U64 | U128 | Nat => 0,
I8 => i8::MIN as i128,
I16 => i16::MIN as i128,
I32 => i32::MIN as i128,
I64 => i64::MIN as i128,
I128 => i128::MIN,
}
}
/// Checks if `self` represents superset of integers that `lower_bound` represents, on a particular
/// side of the integers relative to 0.
///
/// If `is_negative` is true, the negative side is checked; otherwise the positive side is checked.
pub fn is_superset(&self, lower_bound: &Self, is_negative: bool) -> bool {
use IntSign::*;
if is_negative {
match (self.sign_and_width(), lower_bound.sign_and_width()) {
((Signed, us), (Signed, lower_bound)) => us >= lower_bound,
// Unsigned ints can never represent negative numbers; signed (non-zero width)
// ints always can.
((Unsigned, _), (Signed, _)) => false,
((Signed, _), (Unsigned, _)) => true,
// Trivially true; both can only express 0.
((Unsigned, _), (Unsigned, _)) => true,
}
} else {
match (self.sign_and_width(), lower_bound.sign_and_width()) {
((Signed, us), (Signed, lower_bound))
| ((Unsigned, us), (Unsigned, lower_bound)) => us >= lower_bound,
// Unsigned ints with the same bit width as their unsigned counterparts can always
// express 2x more integers on the positive side as unsigned ints.
((Unsigned, us), (Signed, lower_bound)) => us >= lower_bound,
// ...but that means signed int widths can represent less than their unsigned
// counterparts, so the below is true iff the bit width is strictly greater. E.g.
// i16 is a superset of u8, but i16 is not a superset of u16.
((Signed, us), (Unsigned, lower_bound)) => us > lower_bound,
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FloatWidth {
Dec,
F32,
F64,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum NumWidth {
Int(IntWidth),
Float(FloatWidth),
}
/// Describes a bound on the width of a numeric literal.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum NumericBound<W>
where
W: Copy,
{
/// There is no bound on the width.
None,
/// Must have exactly the width `W`.
Exact(W),
}

View file

@ -121,8 +121,8 @@ pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> {
/// then replace the BinOp nodes with Apply nodes. Also drop SpaceBefore and SpaceAfter nodes.
pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc<Expr<'a>> {
match &loc_expr.value {
Float(_)
| Num(_)
Float(..)
| Num(..)
| NonBase10Int { .. }
| Str(_)
| AccessorFunction(_)

View file

@ -1,6 +1,9 @@
use crate::env::Env;
use crate::expr::{canonicalize_expr, unescape_char, Expr, Output};
use crate::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int};
use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output};
use crate::num::{
finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatWidth, IntWidth, NumWidth,
NumericBound, ParsedNumResult,
};
use crate::scope::Scope;
use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol;
@ -9,6 +12,7 @@ use roc_parse::pattern::PatternType;
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
/// A pattern, including possible problems (e.g. shadowing) so that
/// codegen can generate a runtime error if this pattern is reached.
#[derive(Clone, Debug, PartialEq)]
@ -25,9 +29,15 @@ pub enum Pattern {
ext_var: Variable,
destructs: Vec<Loc<RecordDestruct>>,
},
IntLiteral(Variable, Box<str>, i64),
NumLiteral(Variable, Box<str>, i64),
FloatLiteral(Variable, Box<str>, f64),
NumLiteral(Variable, Box<str>, IntValue, NumericBound<NumWidth>),
IntLiteral(
Variable,
Variable,
Box<str>,
IntValue,
NumericBound<IntWidth>,
),
FloatLiteral(Variable, Variable, Box<str>, f64, NumericBound<FloatWidth>),
StrLiteral(Box<str>),
Underscore,
@ -85,9 +95,9 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
}
}
NumLiteral(_, _, _)
| IntLiteral(_, _, _)
| FloatLiteral(_, _, _)
NumLiteral(..)
| IntLiteral(..)
| FloatLiteral(..)
| StrLiteral(_)
| Underscore
| MalformedPattern(_, _)
@ -184,13 +194,19 @@ pub fn canonicalize_pattern<'a>(
}
}
FloatLiteral(str) => match pattern_type {
&FloatLiteral(str) => match pattern_type {
WhenBranch => match finish_parsing_float(str) {
Err(_error) => {
let problem = MalformedPatternProblem::MalformedFloat;
malformed_pattern(env, problem, region)
}
Ok(float) => Pattern::FloatLiteral(var_store.fresh(), (*str).into(), float),
Ok((float, bound)) => Pattern::FloatLiteral(
var_store.fresh(),
var_store.fresh(),
(str).into(),
float,
bound,
),
},
ptype => unsupported_pattern(env, ptype, region),
},
@ -200,32 +216,58 @@ pub fn canonicalize_pattern<'a>(
TopLevelDef | DefExpr => bad_underscore(env, region),
},
NumLiteral(str) => match pattern_type {
WhenBranch => match finish_parsing_int(str) {
&NumLiteral(str) => match pattern_type {
WhenBranch => match finish_parsing_num(str) {
Err(_error) => {
let problem = MalformedPatternProblem::MalformedInt;
malformed_pattern(env, problem, region)
}
Ok(int) => Pattern::NumLiteral(var_store.fresh(), (*str).into(), int),
Ok(ParsedNumResult::UnknownNum(int)) => {
Pattern::NumLiteral(var_store.fresh(), (str).into(), int, NumericBound::None)
}
Ok(ParsedNumResult::Int(int, bound)) => Pattern::IntLiteral(
var_store.fresh(),
var_store.fresh(),
(str).into(),
int,
bound,
),
Ok(ParsedNumResult::Float(float, bound)) => Pattern::FloatLiteral(
var_store.fresh(),
var_store.fresh(),
(str).into(),
float,
bound,
),
},
ptype => unsupported_pattern(env, ptype, region),
},
NonBase10Literal {
&NonBase10Literal {
string,
base,
is_negative,
} => match pattern_type {
WhenBranch => match finish_parsing_base(string, *base, *is_negative) {
WhenBranch => match finish_parsing_base(string, base, is_negative) {
Err(_error) => {
let problem = MalformedPatternProblem::MalformedBase(*base);
let problem = MalformedPatternProblem::MalformedBase(base);
malformed_pattern(env, problem, region)
}
Ok(int) => {
let sign_str = if *is_negative { "-" } else { "" };
Ok((IntValue::U128(_), _)) if is_negative => {
// Can't negate a u128; that doesn't fit in any integer literal type we support.
let problem = MalformedPatternProblem::MalformedInt;
malformed_pattern(env, problem, region)
}
Ok((int, bound)) => {
let sign_str = if is_negative { "-" } else { "" };
let int_str = format!("{}{}", sign_str, int.to_string()).into_boxed_str();
let i = if *is_negative { -int } else { int };
Pattern::IntLiteral(var_store.fresh(), int_str, i)
let i = match int {
// Safety: this is fine because I128::MAX = |I128::MIN| - 1
IntValue::I128(n) if is_negative => IntValue::I128(-n),
IntValue::I128(n) => IntValue::I128(n),
IntValue::U128(_) => unreachable!(),
};
Pattern::IntLiteral(var_store.fresh(), var_store.fresh(), int_str, i, bound)
}
},
ptype => unsupported_pattern(env, ptype, region),
@ -473,9 +515,9 @@ fn add_bindings_from_patterns(
answer.push((*symbol, *region));
}
}
NumLiteral(_, _, _)
| IntLiteral(_, _, _)
| FloatLiteral(_, _, _)
NumLiteral(..)
| IntLiteral(..)
| FloatLiteral(..)
| StrLiteral(_)
| Underscore
| MalformedPattern(_, _)

View file

@ -15,7 +15,7 @@ mod test_can {
use crate::helpers::{can_expr_with, test_home, CanExprOut};
use bumpalo::Bump;
use roc_can::expr::Expr::{self, *};
use roc_can::expr::{ClosureData, Recursive};
use roc_can::expr::{ClosureData, IntValue, Recursive};
use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError};
use roc_region::all::{Position, Region};
use std::{f64, i64};
@ -32,7 +32,7 @@ mod test_can {
let actual_out = can_expr_with(&arena, test_home(), input);
match actual_out.loc_expr.value {
Expr::Float(_, _, _, actual) => {
Expr::Float(_, _, _, actual, _) => {
assert_eq!(expected, actual);
}
actual => {
@ -46,8 +46,8 @@ mod test_can {
let actual_out = can_expr_with(&arena, test_home(), input);
match actual_out.loc_expr.value {
Expr::Int(_, _, _, actual) => {
assert_eq!(expected, actual);
Expr::Int(_, _, _, actual, _) => {
assert_eq!(IntValue::I128(expected), actual);
}
actual => {
panic!("Expected an Int *, but got: {:?}", actual);
@ -55,13 +55,13 @@ mod test_can {
}
}
fn assert_can_num(input: &str, expected: i64) {
fn assert_can_num(input: &str, expected: i128) {
let arena = Bump::new();
let actual_out = can_expr_with(&arena, test_home(), input);
match actual_out.loc_expr.value {
Expr::Num(_, _, actual) => {
assert_eq!(expected, actual);
Expr::Num(_, _, actual, _) => {
assert_eq!(IntValue::I128(expected), actual);
}
actual => {
panic!("Expected a Num, but got: {:?}", actual);
@ -79,7 +79,7 @@ mod test_can {
fn int_too_large() {
use roc_parse::ast::Base;
let string = (i64::MAX as i128 + 1).to_string();
let string = "340_282_366_920_938_463_463_374_607_431_768_211_456".to_string();
assert_can(
&string.clone(),
@ -96,7 +96,7 @@ mod test_can {
fn int_too_small() {
use roc_parse::ast::Base;
let string = (i64::MIN as i128 - 1).to_string();
let string = "-170_141_183_460_469_231_731_687_303_715_884_105_729".to_string();
assert_can(
&string.clone(),
@ -186,12 +186,12 @@ mod test_can {
#[test]
fn num_max() {
assert_can_num(&(i64::MAX.to_string()), i64::MAX);
assert_can_num(&(i64::MAX.to_string()), i64::MAX.into());
}
#[test]
fn num_min() {
assert_can_num(&(i64::MIN.to_string()), i64::MIN);
assert_can_num(&(i64::MIN.to_string()), i64::MIN.into());
}
#[test]

View file

@ -1,6 +1,7 @@
use roc_can::constraint::Constraint::{self, *};
use roc_can::constraint::LetConstraint;
use roc_can::expected::Expected::{self, *};
use roc_can::num::{FloatWidth, IntWidth, NumWidth, NumericBound};
use roc_collections::all::SendMap;
use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol;
@ -10,28 +11,49 @@ use roc_types::types::Category;
use roc_types::types::Reason;
use roc_types::types::Type::{self, *};
pub fn add_numeric_bound_constr(
constrs: &mut Vec<Constraint>,
num_type: Type,
bound: impl TypedNumericBound,
region: Region,
category: Category,
) {
if let Some(typ) = bound.concrete_num_type() {
constrs.push(Eq(
num_type,
Expected::ForReason(Reason::NumericLiteralSuffix, typ, region),
category,
region,
));
}
}
#[inline(always)]
pub fn int_literal(
num_var: Variable,
precision_var: Variable,
expected: Expected<Type>,
region: Region,
bound: NumericBound<IntWidth>,
) -> Constraint {
let num_type = Variable(num_var);
let reason = Reason::IntLiteral;
exists(
vec![num_var],
And(vec![
Eq(
num_type.clone(),
ForReason(reason, num_int(Type::Variable(precision_var)), region),
Category::Int,
region,
),
Eq(num_type, expected, Category::Int, region),
]),
)
let mut constrs = Vec::with_capacity(3);
// Always add the bound first; this improves the resolved type quality in case it's an alias
// like "U8".
add_numeric_bound_constr(&mut constrs, num_type.clone(), bound, region, Category::Num);
constrs.extend(vec![
Eq(
num_type.clone(),
ForReason(reason, num_int(Type::Variable(precision_var)), region),
Category::Int,
region,
),
Eq(num_type, expected, Category::Int, region),
]);
exists(vec![num_var], And(constrs))
}
#[inline(always)]
@ -40,22 +62,46 @@ pub fn float_literal(
precision_var: Variable,
expected: Expected<Type>,
region: Region,
bound: NumericBound<FloatWidth>,
) -> Constraint {
let num_type = Variable(num_var);
let reason = Reason::FloatLiteral;
exists(
vec![num_var, precision_var],
And(vec![
Eq(
num_type.clone(),
ForReason(reason, num_float(Type::Variable(precision_var)), region),
Category::Float,
region,
),
Eq(num_type, expected, Category::Float, region),
]),
)
let mut constrs = Vec::with_capacity(3);
add_numeric_bound_constr(
&mut constrs,
num_type.clone(),
bound,
region,
Category::Float,
);
constrs.extend(vec![
Eq(
num_type.clone(),
ForReason(reason, num_float(Type::Variable(precision_var)), region),
Category::Float,
region,
),
Eq(num_type, expected, Category::Float, region),
]);
exists(vec![num_var, precision_var], And(constrs))
}
#[inline(always)]
pub fn num_literal(
num_var: Variable,
expected: Expected<Type>,
region: Region,
bound: NumericBound<NumWidth>,
) -> Constraint {
let num_type = crate::builtins::num_num(Type::Variable(num_var));
let mut constrs = Vec::with_capacity(3);
add_numeric_bound_constr(&mut constrs, num_type.clone(), bound, region, Category::Num);
constrs.extend(vec![Eq(num_type, expected, Category::Num, region)]);
exists(vec![num_var], And(constrs))
}
#[inline(always)]
@ -148,6 +194,57 @@ pub fn num_int(range: Type) -> Type {
)
}
macro_rules! num_types {
// Represent
// num_u8 ~ U8 : Num Integer Unsigned8 = @Num (@Integer (@Unsigned8))
// int_u8 ~ Integer Unsigned8 = @Integer (@Unsigned8)
//
// num_f32 ~ F32 : Num FloaingPoint Binary32 = @Num (@FloaingPoint (@Binary32))
// float_f32 ~ FloatingPoint Binary32 = @FloatingPoint (@Binary32)
// and so on, for all numeric types.
($($num_fn:ident, $sub_fn:ident, $num_type:ident, $alias:path, $inner_alias:path, $inner_private_tag:path)*) => {
$(
#[inline(always)]
fn $sub_fn() -> Type {
builtin_alias(
$inner_alias,
vec![],
Box::new(Type::TagUnion(
vec![(TagName::Private($inner_private_tag), vec![])],
Box::new(Type::EmptyTagUnion)
)),
)
}
#[inline(always)]
fn $num_fn() -> Type {
builtin_alias(
$alias,
vec![],
Box::new($num_type($sub_fn()))
)
}
)*
}
}
num_types! {
num_u8, int_u8, num_int, Symbol::NUM_U8, Symbol::NUM_UNSIGNED8, Symbol::NUM_AT_UNSIGNED8
num_u16, int_u16, num_int, Symbol::NUM_U16, Symbol::NUM_UNSIGNED16, Symbol::NUM_AT_UNSIGNED16
num_u32, int_u32, num_int, Symbol::NUM_U32, Symbol::NUM_UNSIGNED32, Symbol::NUM_AT_UNSIGNED32
num_u64, int_u64, num_int, Symbol::NUM_U64, Symbol::NUM_UNSIGNED64, Symbol::NUM_AT_UNSIGNED64
num_u128, int_u128, num_int, Symbol::NUM_U128, Symbol::NUM_UNSIGNED128, Symbol::NUM_AT_UNSIGNED128
num_i8, int_i8, num_int, Symbol::NUM_I8, Symbol::NUM_SIGNED8, Symbol::NUM_AT_SIGNED8
num_i16, int_i16, num_int, Symbol::NUM_I16, Symbol::NUM_SIGNED16, Symbol::NUM_AT_SIGNED16
num_i32, int_i32, num_int, Symbol::NUM_I32, Symbol::NUM_SIGNED32, Symbol::NUM_AT_SIGNED32
num_i64, int_i64, num_int, Symbol::NUM_I64, Symbol::NUM_SIGNED64, Symbol::NUM_AT_SIGNED64
num_i128, int_i128, num_int, Symbol::NUM_I128, Symbol::NUM_SIGNED128, Symbol::NUM_AT_SIGNED128
num_nat, int_nat, num_int, Symbol::NUM_NAT, Symbol::NUM_NATURAL, Symbol::NUM_AT_NATURAL
num_dec, float_dec, num_float, Symbol::NUM_DEC, Symbol::NUM_DECIMAL, Symbol::NUM_AT_DECIMAL
num_f32, float_f32, num_float, Symbol::NUM_F32, Symbol::NUM_BINARY32, Symbol::NUM_AT_BINARY32
num_f64, float_f64, num_float, Symbol::NUM_F64, Symbol::NUM_BINARY64, Symbol::NUM_AT_BINARY64
}
#[inline(always)]
pub fn num_signed64() -> Type {
let alias_content = Type::TagUnion(
@ -188,3 +285,55 @@ pub fn num_num(typ: Type) -> Type {
Box::new(alias_content),
)
}
pub trait TypedNumericBound {
/// Get a concrete type for this number, if one exists.
/// Returns `None` e.g. if the bound is open, like `Int *`.
fn concrete_num_type(&self) -> Option<Type>;
}
impl TypedNumericBound for NumericBound<IntWidth> {
fn concrete_num_type(&self) -> Option<Type> {
match self {
NumericBound::None => None,
NumericBound::Exact(w) => Some(match w {
IntWidth::U8 => num_u8(),
IntWidth::U16 => num_u16(),
IntWidth::U32 => num_u32(),
IntWidth::U64 => num_u64(),
IntWidth::U128 => num_u128(),
IntWidth::I8 => num_i8(),
IntWidth::I16 => num_i16(),
IntWidth::I32 => num_i32(),
IntWidth::I64 => num_i64(),
IntWidth::I128 => num_i128(),
IntWidth::Nat => num_nat(),
}),
}
}
}
impl TypedNumericBound for NumericBound<FloatWidth> {
fn concrete_num_type(&self) -> Option<Type> {
match self {
NumericBound::None => None,
NumericBound::Exact(w) => Some(match w {
FloatWidth::Dec => num_dec(),
FloatWidth::F32 => num_f32(),
FloatWidth::F64 => num_f64(),
}),
}
}
}
impl TypedNumericBound for NumericBound<NumWidth> {
fn concrete_num_type(&self) -> Option<Type> {
match self {
NumericBound::None => None,
NumericBound::Exact(NumWidth::Int(iw)) => NumericBound::Exact(*iw).concrete_num_type(),
NumericBound::Exact(NumWidth::Float(fw)) => {
NumericBound::Exact(*fw).concrete_num_type()
}
}
}
}

View file

@ -1,4 +1,6 @@
use crate::builtins::{empty_list_type, float_literal, int_literal, list_type, str_type};
use crate::builtins::{
empty_list_type, float_literal, int_literal, list_type, num_literal, str_type,
};
use crate::pattern::{constrain_pattern, PatternState};
use roc_can::annotation::IntroducedVariables;
use roc_can::constraint::Constraint::{self, *};
@ -96,17 +98,11 @@ pub fn constrain_expr(
expected: Expected<Type>,
) -> Constraint {
match expr {
Int(var, precision, _, _) => int_literal(*var, *precision, expected, region),
Num(var, _, _) => exists(
vec![*var],
Eq(
crate::builtins::num_num(Type::Variable(*var)),
expected,
Category::Num,
region,
),
),
Float(var, precision, _, _) => float_literal(*var, *precision, expected, region),
&Int(var, precision, _, _, bound) => int_literal(var, precision, expected, region, bound),
&Num(var, _, _, bound) => num_literal(var, expected, region, bound),
&Float(var, precision, _, _, bound) => {
float_literal(var, precision, expected, region, bound)
}
EmptyRecord => constrain_empty_record(region, expected),
Expr::Record { record_var, fields } => {
if fields.is_empty() {

View file

@ -55,9 +55,9 @@ fn headers_from_annotation_help(
Underscore
| MalformedPattern(_, _)
| UnsupportedPattern(_)
| NumLiteral(_, _, _)
| IntLiteral(_, _, _)
| FloatLiteral(_, _, _)
| NumLiteral(..)
| IntLiteral(..)
| FloatLiteral(..)
| StrLiteral(_) => true,
RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() {
@ -178,31 +178,83 @@ pub fn constrain_pattern(
);
}
NumLiteral(var, _, _) => {
state.vars.push(*var);
&NumLiteral(var, _, _, bound) => {
state.vars.push(var);
let num_type = builtins::num_num(Type::Variable(var));
builtins::add_numeric_bound_constr(
&mut state.constraints,
num_type.clone(),
bound,
region,
Category::Num,
);
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Num,
builtins::num_num(Type::Variable(*var)),
num_type,
expected,
));
}
IntLiteral(precision_var, _, _) => {
&IntLiteral(num_var, precision_var, _, _, bound) => {
// First constraint on the free num var; this improves the resolved type quality in
// case the bound is an alias.
builtins::add_numeric_bound_constr(
&mut state.constraints,
Type::Variable(num_var),
bound,
region,
Category::Int,
);
// Link the free num var with the int var and our expectation.
let int_type = builtins::num_int(Type::Variable(precision_var));
state.constraints.push(Constraint::Eq(
Type::Variable(num_var),
Expected::NoExpectation(int_type),
Category::Int,
region,
));
// Also constrain the pattern against the num var, again to reuse aliases if they're present.
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Int,
builtins::num_int(Type::Variable(*precision_var)),
Type::Variable(num_var),
expected,
));
}
FloatLiteral(precision_var, _, _) => {
&FloatLiteral(num_var, precision_var, _, _, bound) => {
// First constraint on the free num var; this improves the resolved type quality in
// case the bound is an alias.
builtins::add_numeric_bound_constr(
&mut state.constraints,
Type::Variable(num_var),
bound,
region,
Category::Float,
);
// Link the free num var with the float var and our expectation.
let float_type = builtins::num_float(Type::Variable(precision_var));
state.constraints.push(Constraint::Eq(
Type::Variable(num_var),
Expected::NoExpectation(float_type),
Category::Float,
region,
));
// Also constrain the pattern against the num var, again to reuse aliases if they're present.
state.constraints.push(Constraint::Pattern(
region,
PatternCategory::Float,
builtins::num_float(Type::Variable(*precision_var)),
Type::Variable(num_var),
expected,
));
}

View file

@ -46,7 +46,9 @@ impl<'a> Formattable for Def<'a> {
indent + INDENT,
);
} else {
buf.push_str(" : ");
buf.spaces(1);
buf.push_str(":");
buf.spaces(1);
loc_annotation.format_with_options(
buf,
Parens::NotNeeded,

View file

@ -27,8 +27,8 @@ impl<'a> Formattable for Expr<'a> {
}
// These expressions never have newlines
Float(_)
| Num(_)
Float(..)
| Num(..)
| NonBase10Int { .. }
| Access(_, _)
| AccessorFunction(_)
@ -196,17 +196,25 @@ impl<'a> Formattable for Expr<'a> {
buf.push(')');
}
}
Num(string) | Float(string) | GlobalTag(string) | PrivateTag(string) => {
&Num(string) => {
buf.indent(indent);
buf.push_str(string);
}
&Float(string) => {
buf.indent(indent);
buf.push_str(string);
}
GlobalTag(string) | PrivateTag(string) => {
buf.indent(indent);
buf.push_str(string)
}
NonBase10Int {
&NonBase10Int {
base,
string,
is_negative,
} => {
buf.indent(indent);
if *is_negative {
if is_negative {
buf.push('-');
}

View file

@ -5,8 +5,8 @@ use crate::spaces::{fmt_default_spaces, fmt_spaces, INDENT};
use crate::Buf;
use roc_parse::ast::{Collection, Module, Spaced};
use roc_parse::header::{
AppHeader, Effects, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName,
PackageEntry, PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
};
use roc_parse::ident::UppercaseIdent;
use roc_region::all::Loc;
@ -170,8 +170,6 @@ pub fn fmt_platform_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a PlatformHe
buf.push_str("provides");
fmt_default_spaces(buf, header.after_provides, indent);
fmt_provides(buf, header.provides, None, indent);
fmt_effects(buf, &header.effects, indent);
}
fn fmt_requires<'a, 'buf>(buf: &mut Buf<'buf>, requires: &PlatformRequires<'a>, indent: u16) {
@ -183,29 +181,6 @@ fn fmt_requires<'a, 'buf>(buf: &mut Buf<'buf>, requires: &PlatformRequires<'a>,
buf.push_str(" }");
}
fn fmt_effects<'a, 'buf>(buf: &mut Buf<'buf>, effects: &Effects<'a>, indent: u16) {
fmt_default_spaces(buf, effects.spaces_before_effects_keyword, indent);
buf.indent(indent);
buf.push_str("effects");
fmt_default_spaces(buf, effects.spaces_after_effects_keyword, indent);
buf.indent(indent);
buf.push_str(effects.effect_shortname);
buf.push('.');
buf.push_str(effects.effect_type_name);
fmt_default_spaces(buf, effects.spaces_after_type_name, indent);
fmt_collection(
buf,
indent + INDENT,
'{',
'}',
effects.entries,
Newlines::No,
)
}
impl<'a> Formattable for TypedIdent<'a> {
fn is_multiline(&self) -> bool {
false

View file

@ -31,9 +31,9 @@ impl<'a> Formattable for Pattern<'a> {
| Pattern::GlobalTag(_)
| Pattern::PrivateTag(_)
| Pattern::Apply(_, _)
| Pattern::NumLiteral(_)
| Pattern::NumLiteral(..)
| Pattern::NonBase10Literal { .. }
| Pattern::FloatLiteral(_)
| Pattern::FloatLiteral(..)
| Pattern::StrLiteral(_)
| Pattern::Underscore(_)
| Pattern::Malformed(_)
@ -116,17 +116,17 @@ impl<'a> Formattable for Pattern<'a> {
loc_pattern.format(buf, indent);
}
NumLiteral(string) => {
&NumLiteral(string) => {
buf.indent(indent);
buf.push_str(string);
}
NonBase10Literal {
&NonBase10Literal {
base,
string,
is_negative,
} => {
buf.indent(indent);
if *is_negative {
if is_negative {
buf.push('-');
}
@ -139,7 +139,7 @@ impl<'a> Formattable for Pattern<'a> {
buf.push_str(string);
}
FloatLiteral(string) => {
&FloatLiteral(string) => {
buf.indent(indent);
buf.push_str(string);
}

View file

@ -2675,13 +2675,7 @@ mod test_fmt {
exposes [] \
packages {} \
imports [ Task.{ Task } ] \
provides [ mainForHost ] \
effects fx.Effect \
{ \
putLine : Str -> Effect {}, \
putInt : I64 -> Effect {}, \
getInt : Effect { value : I64, errorCode : [ A, B ], isError : Bool } \
}",
provides [ mainForHost ]",
);
}

View file

@ -771,11 +771,13 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
env.context.bool_type().const_int(*int as u64, false).into()
}
Layout::Builtin(Builtin::Int(int_width)) => {
int_with_precision(env, *int as i128, *int_width).into()
int_with_precision(env, *int, *int_width).into()
}
_ => panic!("Invalid layout for int literal = {:?}", layout),
},
U128(int) => const_u128(env, *int).into(),
Float(float) => match layout {
Layout::Builtin(Builtin::Float(float_width)) => {
float_with_precision(env, *float, *float_width)
@ -3073,6 +3075,19 @@ fn const_i128<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, value: i128) -> IntValu
.const_int_arbitrary_precision(&[a, b])
}
fn const_u128<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, value: u128) -> IntValue<'ctx> {
// truncate the lower 64 bits
let value = value as u128;
let a = value as u64;
// get the upper 64 bits
let b = (value >> 64) as u64;
env.context
.i128_type()
.const_int_arbitrary_precision(&[a, b])
}
fn build_switch_ir<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
@ -5284,11 +5299,6 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
}
}
// TODO: Fix me! I should be different in tests vs. user code!
fn expect_failed() {
panic!("An expectation failed!");
}
#[allow(clippy::too_many_arguments)]
fn run_low_level<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
@ -5299,7 +5309,6 @@ fn run_low_level<'a, 'ctx, 'env>(
op: LowLevel,
args: &[Symbol],
update_mode: UpdateMode,
// expect_failed: *const (),
) -> BasicValueEnum<'ctx> {
use LowLevel::*;
@ -6063,21 +6072,28 @@ fn run_low_level<'a, 'ctx, 'env>(
match env.target_info.ptr_width() {
roc_target::PtrWidth::Bytes8 => {
let fn_ptr_type = context
.void_type()
.fn_type(&[], false)
.ptr_type(AddressSpace::Generic);
let fn_addr = env
.ptr_int()
.const_int(expect_failed as *const () as u64, false);
let func: PointerValue<'ctx> = bd.build_int_to_ptr(
fn_addr,
fn_ptr_type,
"cast_expect_failed_addr_to_ptr",
);
let func = env
.module
.get_function(bitcode::UTILS_EXPECT_FAILED)
.unwrap();
// TODO get the actual line info instead of
// hardcoding as zero!
let callable = CallableValue::try_from(func).unwrap();
let start_line = context.i32_type().const_int(0, false);
let end_line = context.i32_type().const_int(0, false);
let start_col = context.i16_type().const_int(0, false);
let end_col = context.i16_type().const_int(0, false);
bd.build_call(callable, &[], "call_expect_failed");
bd.build_call(
callable,
&[
start_line.into(),
end_line.into(),
start_col.into(),
end_col.into(),
],
"call_expect_failed",
);
bd.build_unconditional_branch(then_block);
}

View file

@ -42,6 +42,43 @@ pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) {
}
}
// roc_memcpy
{
// The type of this function (but not the implementation) should have
// already been defined by the builtins, which rely on it.
let fn_val = module.get_function("roc_memcpy").unwrap();
let mut params = fn_val.get_param_iter();
let dest_arg = params.next().unwrap();
let dest_alignment = 1;
let src_arg = params.next().unwrap();
let src_alignment = 1;
let bytes_arg = params.next().unwrap();
debug_assert!(params.next().is_none());
// Add a basic block for the entry point
let entry = ctx.append_basic_block(fn_val, "entry");
builder.position_at_end(entry);
// Call libc memcpy()
let _retval = builder
.build_memcpy(
dest_arg.into_pointer_value(),
dest_alignment,
src_arg.into_pointer_value(),
src_alignment,
bytes_arg.into_int_value(),
)
.unwrap();
builder.build_return(None);
if cfg!(debug_assertions) {
crate::llvm::build::verify_fn(fn_val);
}
}
// roc_realloc
{
let libc_realloc_val = {

View file

@ -38,10 +38,23 @@ macro_rules! run_jit_function {
}};
($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr) => {{
run_jit_function!($lib, $main_fn_name, $ty, $transform, $errors, &[])
}};
($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr, $expect_failures:expr) => {{
use inkwell::context::Context;
use roc_builtins::bitcode;
use roc_gen_llvm::run_roc::RocCallResult;
use std::mem::MaybeUninit;
#[derive(Debug, Copy, Clone)]
#[repr(C)]
struct Failure {
start_line: u32,
end_line: u32,
start_col: u16,
end_col: u16,
}
unsafe {
let main: libloading::Symbol<unsafe extern "C" fn(*mut RocCallResult<$ty>) -> ()> =
$lib.get($main_fn_name.as_bytes())
@ -49,11 +62,50 @@ macro_rules! run_jit_function {
.ok_or(format!("Unable to JIT compile `{}`", $main_fn_name))
.expect("errored");
let mut result = MaybeUninit::uninit();
#[repr(C)]
struct Failures {
failures: *const Failure,
count: usize,
}
main(result.as_mut_ptr());
impl Drop for Failures {
fn drop(&mut self) {
use std::alloc::{dealloc, Layout};
use std::mem;
match result.assume_init().into() {
unsafe {
let layout = Layout::from_size_align_unchecked(
mem::size_of::<Failure>(),
mem::align_of::<Failure>(),
);
dealloc(self.failures as *mut u8, layout);
}
}
}
let get_expect_failures: libloading::Symbol<unsafe extern "C" fn() -> Failures> = $lib
.get(bitcode::UTILS_GET_EXPECT_FAILURES.as_bytes())
.ok()
.ok_or(format!(
"Unable to JIT compile `{}`",
bitcode::UTILS_GET_EXPECT_FAILURES
))
.expect("errored");
let mut main_result = MaybeUninit::uninit();
main(main_result.as_mut_ptr());
let failures = get_expect_failures();
if failures.count > 0 {
// TODO tell the user about the failures!
let failures =
unsafe { core::slice::from_raw_parts(failures.failures, failures.count) };
panic!("Failed with {} failures. Failures: ", failures.len());
}
match main_result.assume_init().into() {
Ok(success) => {
// only if there are no exceptions thrown, check for errors
assert!($errors.is_empty(), "Encountered errors:\n{}", $errors);

View file

@ -13,7 +13,7 @@ use roc_constrain::module::{
constrain_imports, pre_constrain_imports, ConstrainableImports, Import,
};
use roc_constrain::module::{constrain_module, ExposedModuleTypes, SubsByModule};
use roc_module::ident::{Ident, ModuleName, QualifiedModuleName, TagName};
use roc_module::ident::{Ident, ModuleName, QualifiedModuleName};
use roc_module::symbol::{
IdentIds, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds, PackageQualified,
Symbol,
@ -23,9 +23,9 @@ use roc_mono::ir::{
UpdateModeIds,
};
use roc_mono::layout::{Layout, LayoutCache, LayoutProblem};
use roc_parse::ast::{self, ExtractSpaces, Spaced, StrLiteral, TypeAnnotation};
use roc_parse::header::PackageName;
use roc_parse::ast::{self, ExtractSpaces, Spaced, StrLiteral};
use roc_parse::header::{ExposedName, ImportsEntry, PackageEntry, PlatformHeader, To, TypedIdent};
use roc_parse::header::{HeaderFor, ModuleNameEnum, PackageName};
use roc_parse::ident::UppercaseIdent;
use roc_parse::module::module_defs;
use roc_parse::parser::{FileError, Parser, SyntaxError};
@ -35,7 +35,7 @@ use roc_solve::solve;
use roc_target::TargetInfo;
use roc_types::solved_types::Solved;
use roc_types::subs::{Subs, VarStore, Variable};
use roc_types::types::{Alias, Type};
use roc_types::types::Alias;
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::collections::{HashMap, HashSet};
use std::io;
@ -165,37 +165,6 @@ impl<'a> Dependencies<'a> {
output
}
pub fn add_effect_module(
&mut self,
module_id: ModuleId,
dependencies: &MutSet<ModuleId>,
goal_phase: Phase,
) -> MutSet<(ModuleId, Phase)> {
// add dependencies for self
// phase i + 1 of a file always depends on phase i being completed
{
let mut i = 2;
// platform modules should only start at CanonicalizeAndConstrain
debug_assert!(PHASES[i] == Phase::CanonicalizeAndConstrain);
while PHASES[i] < goal_phase {
self.add_dependency_help(module_id, module_id, PHASES[i + 1], PHASES[i]);
i += 1;
}
}
self.add_to_status(module_id, goal_phase);
let mut output = MutSet::default();
// all the dependencies can be loaded
for dep in dependencies {
output.insert((*dep, Phase::LoadHeader));
}
output
}
fn add_to_status(&mut self, module_id: ModuleId, goal_phase: Phase) {
for phase in PHASES.iter() {
if *phase > goal_phase {
@ -674,24 +643,7 @@ struct ModuleHeader<'a> {
exposed_imports: MutMap<Ident, (Symbol, Region)>,
parse_state: roc_parse::state::State<'a>,
module_timing: ModuleTiming,
}
#[derive(Debug)]
enum HeaderFor<'a> {
App {
to_platform: To<'a>,
},
PkgConfig {
/// usually `pf`
config_shorthand: &'a str,
/// the type scheme of the main function (required by the platform)
/// (currently unused)
#[allow(dead_code)]
platform_main_type: TypedIdent<'a>,
/// provided symbol to host (commonly `mainForHost`)
main_for_host: Symbol,
},
Interface,
header_for: HeaderFor<'a>,
}
#[derive(Debug)]
@ -774,7 +726,6 @@ impl<'a> MonomorphizedModule<'a> {
#[derive(Debug)]
struct ParsedModule<'a> {
module_id: ModuleId,
module_name: ModuleNameEnum<'a>,
module_path: PathBuf,
src: &'a str,
module_timing: ModuleTiming,
@ -783,6 +734,8 @@ struct ParsedModule<'a> {
exposed_ident_ids: IdentIds,
exposed_imports: MutMap<Ident, (Symbol, Region)>,
parsed_defs: &'a [Loc<roc_parse::ast::Def<'a>>],
module_name: ModuleNameEnum<'a>,
header_for: HeaderFor<'a>,
}
/// A message sent out _from_ a worker thread,
@ -790,19 +743,13 @@ struct ParsedModule<'a> {
#[derive(Debug)]
enum Msg<'a> {
Many(Vec<Msg<'a>>),
Header(ModuleHeader<'a>, HeaderFor<'a>),
Header(ModuleHeader<'a>),
Parsed(ParsedModule<'a>),
CanonicalizedAndConstrained {
constrained_module: ConstrainedModule,
canonicalization_problems: Vec<roc_problem::can::Problem>,
module_docs: Option<ModuleDocumentation>,
},
MadeEffectModule {
type_shortname: &'a str,
constrained_module: ConstrainedModule,
canonicalization_problems: Vec<roc_problem::can::Problem>,
module_docs: ModuleDocumentation,
},
SolvedTypes {
module_id: ModuleId,
ident_ids: IdentIds,
@ -861,6 +808,7 @@ enum PlatformPath<'a> {
NotSpecified,
Valid(To<'a>),
RootIsInterface,
RootIsHosted,
RootIsPkgConfig,
}
@ -1695,7 +1643,7 @@ fn update<'a>(
Ok(state)
}
Header(header, header_extra) => {
Header(header) => {
use HeaderFor::*;
log!("loaded header for {:?}", header.module_id);
@ -1712,13 +1660,13 @@ fn update<'a>(
if let PkgConfig {
config_shorthand, ..
} = header_extra
} = header.header_for
{
work.extend(state.dependencies.notify_package(config_shorthand));
}
}
match header_extra {
match header.header_for {
App { to_platform } => {
debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified));
state.platform_path = PlatformPath::Valid(to_platform);
@ -1742,6 +1690,12 @@ fn update<'a>(
state.platform_path = PlatformPath::RootIsInterface;
}
}
Hosted { .. } => {
if header.is_root_module {
debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified));
state.platform_path = PlatformPath::RootIsHosted;
}
}
}
// store an ID to name mapping, so we know the file to read when fetching dependencies' headers
@ -1863,57 +1817,6 @@ fn update<'a>(
Ok(state)
}
MadeEffectModule {
constrained_module,
canonicalization_problems,
module_docs,
type_shortname,
} => {
let module_id = constrained_module.module.module_id;
log!("made effect module for {:?}", module_id);
state
.module_cache
.can_problems
.insert(module_id, canonicalization_problems);
state
.module_cache
.documentation
.insert(module_id, module_docs);
state
.module_cache
.aliases
.insert(module_id, constrained_module.module.aliases.clone());
state
.module_cache
.constrained
.insert(module_id, constrained_module);
let mut work = state.dependencies.add_effect_module(
module_id,
&MutSet::default(),
state.goal_phase,
);
work.extend(state.dependencies.notify_package(type_shortname));
work.extend(state.dependencies.notify(module_id, Phase::LoadHeader));
work.extend(state.dependencies.notify(module_id, Phase::Parse));
work.extend(
state
.dependencies
.notify(module_id, Phase::CanonicalizeAndConstrain),
);
start_tasks(arena, &mut state, work, injector, worker_listeners)?;
Ok(state)
}
SolvedTypes {
module_id,
ident_ids,
@ -2366,14 +2269,10 @@ fn load_pkg_config<'a>(
// Insert the first entries for this module's timings
let mut pkg_module_timing = ModuleTiming::new(module_start_time);
let mut effect_module_timing = ModuleTiming::new(module_start_time);
pkg_module_timing.read_roc_file = file_io_duration;
pkg_module_timing.parse_header = parse_header_duration;
effect_module_timing.read_roc_file = file_io_duration;
effect_module_timing.parse_header = parse_header_duration;
match parsed {
Ok((ast::Module::Interface { header }, _parse_state)) => {
Err(LoadingProblem::UnexpectedHeader(format!(
@ -2396,23 +2295,13 @@ fn load_pkg_config<'a>(
filename,
parser_state,
module_ids.clone(),
ident_ids_by_module.clone(),
ident_ids_by_module,
&header,
pkg_module_timing,
)
.1;
let effects_module_msg = fabricate_effects_module(
arena,
header.effects.effect_shortname,
module_ids,
ident_ids_by_module,
header,
effect_module_timing,
)
.1;
Ok(Msg::Many(vec![effects_module_msg, pkg_config_module_msg]))
Ok(pkg_config_module_msg)
}
Ok((ast::Module::Hosted { header }, _parse_state)) => {
Err(LoadingProblem::UnexpectedHeader(format!(
@ -2551,7 +2440,33 @@ fn parse_header<'a>(
packages: &[],
exposes: unspace(arena, header.exposes.items),
imports: unspace(arena, header.imports.items),
to_platform: None,
extra: HeaderFor::Interface,
};
Ok(send_header(
info,
parse_state,
module_ids,
ident_ids_by_module,
module_timing,
))
}
Ok((ast::Module::Hosted { header }, parse_state)) => {
let info = HeaderInfo {
loc_name: Loc {
region: header.name.region,
value: ModuleNameEnum::Hosted(header.name.value),
},
filename,
is_root_module,
opt_shorthand,
packages: &[],
exposes: unspace(arena, header.exposes.items),
imports: unspace(arena, header.imports.items),
extra: HeaderFor::Hosted {
generates: header.generates,
generates_with: unspace(arena, header.generates_with.items),
},
};
Ok(send_header(
@ -2593,7 +2508,9 @@ fn parse_header<'a>(
packages,
exposes,
imports: unspace(arena, header.imports.items),
to_platform: Some(header.to.value),
extra: HeaderFor::App {
to_platform: header.to.value,
},
};
let (module_id, app_module_header_msg) = send_header(
@ -2661,37 +2578,13 @@ fn parse_header<'a>(
To::NewPackage(_package_name) => Ok((module_id, app_module_header_msg)),
}
}
Ok((ast::Module::Platform { header }, _parse_state)) => Ok(fabricate_effects_module(
arena,
"",
module_ids,
ident_ids_by_module,
header,
module_timing,
)),
Ok((ast::Module::Hosted { header }, parse_state)) => {
let info = HeaderInfo {
loc_name: Loc {
region: header.name.region,
value: ModuleNameEnum::Hosted(header.name.value),
},
filename,
is_root_module,
opt_shorthand,
packages: &[],
exposes: unspace(arena, header.exposes.items),
imports: unspace(arena, header.imports.items),
to_platform: None,
};
Ok(send_header(
info,
parse_state,
module_ids,
ident_ids_by_module,
module_timing,
))
Ok((ast::Module::Platform { header }, _parse_state)) => {
Err(LoadingProblem::UnexpectedHeader(format!(
"got an unexpected platform header\n{:?}",
header
)))
}
Err(fail) => Err(LoadingProblem::ParsingFailed(
fail.map_problem(SyntaxError::Header)
.into_file_error(filename),
@ -2760,15 +2653,6 @@ fn load_from_str<'a>(
)
}
#[derive(Debug)]
enum ModuleNameEnum<'a> {
/// A filename
App(StrLiteral<'a>),
Interface(roc_parse::header::ModuleName<'a>),
Hosted(roc_parse::header::ModuleName<'a>),
PkgConfig,
}
#[derive(Debug)]
struct HeaderInfo<'a> {
loc_name: Loc<ModuleNameEnum<'a>>,
@ -2778,7 +2662,7 @@ struct HeaderInfo<'a> {
packages: &'a [Loc<PackageEntry<'a>>],
exposes: &'a [Loc<ExposedName<'a>>],
imports: &'a [Loc<ImportsEntry<'a>>],
to_platform: Option<To<'a>>,
extra: HeaderFor<'a>,
}
#[allow(clippy::too_many_arguments)]
@ -2799,7 +2683,7 @@ fn send_header<'a>(
packages,
exposes,
imports,
to_platform,
extra,
} = info;
let declared_name: ModuleName = match &loc_name.value {
@ -2936,11 +2820,6 @@ fn send_header<'a>(
// We always need to send these, even if deps is empty,
// because the coordinator thread needs to receive this message
// to decrement its "pending" count.
let extra = match to_platform {
Some(to_platform) => HeaderFor::App { to_platform },
None => HeaderFor::Interface,
};
let mut package_qualified_imported_modules = MutSet::default();
for (pq_module_name, module_id) in &deps_by_name {
match pq_module_name {
@ -2957,24 +2836,22 @@ fn send_header<'a>(
(
home,
Msg::Header(
ModuleHeader {
module_id: home,
module_path: filename,
is_root_module,
exposed_ident_ids: ident_ids,
module_name: loc_name.value,
packages: package_entries,
imported_modules,
package_qualified_imported_modules,
deps_by_name,
exposes: exposed,
parse_state,
exposed_imports: scope,
module_timing,
},
extra,
),
Msg::Header(ModuleHeader {
module_id: home,
module_path: filename,
is_root_module,
exposed_ident_ids: ident_ids,
module_name: loc_name.value,
packages: package_entries,
imported_modules,
package_qualified_imported_modules,
deps_by_name,
exposes: exposed,
parse_state,
exposed_imports: scope,
module_timing,
header_for: extra,
}),
)
}
@ -3023,7 +2900,6 @@ fn send_header_two<'a>(
HashMap::with_capacity_and_hasher(num_exposes, default_hasher());
// add standard imports
// TODO add Effect by default
imported_modules.insert(app_module_id, Region::zero());
deps_by_name.insert(
PQModuleName::Unqualified(ModuleName::APP.into()),
@ -3198,24 +3074,22 @@ fn send_header_two<'a>(
(
home,
Msg::Header(
ModuleHeader {
module_id: home,
module_path: filename,
is_root_module,
exposed_ident_ids: ident_ids,
module_name,
packages: package_entries,
imported_modules,
package_qualified_imported_modules,
deps_by_name,
exposes: exposed,
parse_state,
exposed_imports: scope,
module_timing,
},
extra,
),
Msg::Header(ModuleHeader {
module_id: home,
module_path: filename,
is_root_module,
exposed_ident_ids: ident_ids,
module_name,
packages: package_entries,
imported_modules,
package_qualified_imported_modules,
deps_by_name,
exposes: exposed,
parse_state,
exposed_imports: scope,
module_timing,
header_for: extra,
}),
)
}
@ -3383,272 +3257,6 @@ fn fabricate_pkg_config_module<'a>(
)
}
#[allow(clippy::too_many_arguments)]
fn fabricate_effects_module<'a>(
arena: &'a Bump,
shorthand: &'a str,
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
header: PlatformHeader<'a>,
module_timing: ModuleTiming,
) -> (ModuleId, Msg<'a>) {
let num_exposes = header.provides.len() + 1;
let mut exposed: Vec<Symbol> = Vec::with_capacity(num_exposes);
let effects = header.effects;
let module_id: ModuleId;
let effect_entries = unpack_exposes_entries(arena, effects.entries.items);
let name = effects.effect_type_name;
let declared_name: ModuleName = name.into();
let hardcoded_effect_symbols = {
let mut functions: Vec<_> = crate::effect_module::BUILTIN_EFFECT_FUNCTIONS
.iter()
.map(|x| x.0)
.collect();
functions.push(name);
functions
};
{
let mut module_ids = (*module_ids).lock();
for exposed in header.exposes.iter() {
let module_name = exposed.value.extract_spaces().item;
module_ids.get_or_insert(&PQModuleName::Qualified(
shorthand,
module_name.as_str().into(),
));
}
}
let exposed_ident_ids = {
// Lock just long enough to perform the minimal operations necessary.
let mut module_ids = (*module_ids).lock();
let mut ident_ids_by_module = (*ident_ids_by_module).lock();
let name = PQModuleName::Qualified(shorthand, declared_name);
module_id = module_ids.get_or_insert(&name);
// Ensure this module has an entry in the exposed_ident_ids map.
ident_ids_by_module
.entry(module_id)
.or_insert_with(IdentIds::default);
let ident_ids = ident_ids_by_module.get_mut(&module_id).unwrap();
// Generate IdentIds entries for all values this module exposes.
// This way, when we encounter them in Defs later, they already
// have an IdentIds entry.
//
// We must *not* add them to scope yet, or else the Defs will
// incorrectly think they're shadowing them!
for (loc_exposed, _) in effect_entries.iter() {
// Use get_or_insert here because the ident_ids may already
// created an IdentId for this, when it was imported exposed
// in a dependent module.
//
// For example, if module A has [ B.{ foo } ], then
// when we get here for B, `foo` will already have
// an IdentId. We must reuse that!
let ident_id = ident_ids.get_or_insert(&loc_exposed.value.into());
let symbol = Symbol::new(module_id, ident_id);
exposed.push(symbol);
}
for hardcoded in hardcoded_effect_symbols {
// Use get_or_insert here because the ident_ids may already
// created an IdentId for this, when it was imported exposed
// in a dependent module.
//
// For example, if module A has [ B.{ foo } ], then
// when we get here for B, `foo` will already have
// an IdentId. We must reuse that!
let ident_id = ident_ids.get_or_insert(&hardcoded.into());
let symbol = Symbol::new(module_id, ident_id);
exposed.push(symbol);
}
if cfg!(debug_assertions) {
module_id.register_debug_idents(ident_ids);
}
ident_ids.clone()
};
// a platform module has no dependencies, hence empty
let dep_idents: MutMap<ModuleId, IdentIds> = IdentIds::exposed_builtins(0);
let mut var_store = VarStore::default();
let module_ids = { (*module_ids).lock().clone() }.into_module_ids();
let mut scope = roc_can::scope::Scope::new(module_id, &mut var_store);
let mut can_env =
roc_can::env::Env::new(module_id, &dep_idents, &module_ids, exposed_ident_ids);
let effect_symbol = scope
.introduce(
name.into(),
&can_env.exposed_ident_ids,
&mut can_env.ident_ids,
Region::zero(),
)
.unwrap();
let effect_tag_name = TagName::Private(effect_symbol);
let mut aliases = MutMap::default();
let alias = {
let a_var = var_store.fresh();
let actual = crate::effect_module::build_effect_actual(
effect_tag_name,
Type::Variable(a_var),
&mut var_store,
);
scope.add_alias(
effect_symbol,
Region::zero(),
vec![Loc::at_zero(("a".into(), a_var))],
actual,
);
scope.lookup_alias(effect_symbol).unwrap().clone()
};
aliases.insert(effect_symbol, alias);
let mut declarations = Vec::new();
let exposed_symbols: MutSet<Symbol> = {
let mut exposed_symbols = MutSet::default();
{
for (ident, ann) in effect_entries {
let symbol = {
scope
.introduce(
ident.value.into(),
&can_env.exposed_ident_ids,
&mut can_env.ident_ids,
Region::zero(),
)
.unwrap()
};
let annotation = roc_can::annotation::canonicalize_annotation(
&mut can_env,
&mut scope,
&ann.value,
Region::zero(),
&mut var_store,
);
let def = crate::effect_module::build_host_exposed_def(
&mut can_env,
&mut scope,
symbol,
ident.value,
TagName::Private(effect_symbol),
&mut var_store,
annotation,
);
exposed_symbols.insert(symbol);
declarations.push(Declaration::Declare(def));
}
}
// define Effect.after, Effect.map etc.
crate::effect_module::build_effect_builtins(
&mut can_env,
&mut scope,
effect_symbol,
&mut var_store,
&mut exposed_symbols,
&mut declarations,
);
exposed_symbols
};
use roc_can::module::ModuleOutput;
let module_output = ModuleOutput {
aliases,
rigid_variables: MutMap::default(),
declarations,
exposed_imports: MutMap::default(),
lookups: Vec::new(),
problems: can_env.problems,
ident_ids: can_env.ident_ids,
references: MutSet::default(),
scope,
};
let constraint = constrain_module(&module_output.declarations, module_id);
let module = Module {
module_id,
exposed_imports: module_output.exposed_imports,
exposed_symbols,
references: module_output.references,
aliases: module_output.aliases,
rigid_variables: module_output.rigid_variables,
};
let imported_modules = MutMap::default();
// Should a effect module ever have a ModuleDocumentation?
let module_docs = ModuleDocumentation {
name: String::from(name),
entries: Vec::new(),
scope: module_output.scope,
};
let constrained_module = ConstrainedModule {
module,
declarations: module_output.declarations,
imported_modules,
var_store,
constraint,
ident_ids: module_output.ident_ids,
dep_idents,
module_timing,
};
(
module_id,
Msg::MadeEffectModule {
type_shortname: effects.effect_shortname,
constrained_module,
canonicalization_problems: module_output.problems,
module_docs,
},
)
}
fn unpack_exposes_entries<'a>(
arena: &'a Bump,
entries: &'a [Loc<Spaced<'a, TypedIdent<'a>>>],
) -> bumpalo::collections::Vec<'a, (Loc<&'a str>, Loc<TypeAnnotation<'a>>)> {
use bumpalo::collections::Vec;
let iter = entries.iter().map(|entry| {
let entry: TypedIdent<'a> = entry.value.extract_spaces().item;
(entry.ident, entry.ann)
});
Vec::from_iter_in(iter, arena)
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::unnecessary_wraps)]
fn canonicalize_and_constrain<'a, F>(
@ -3668,6 +3276,7 @@ where
let ParsedModule {
module_id,
module_name,
header_for,
exposed_ident_ids,
parsed_defs,
exposed_imports,
@ -3680,6 +3289,7 @@ where
let canonicalized = canonicalize_module_defs(
arena,
parsed_defs,
&header_for,
module_id,
module_ids,
exposed_ident_ids,
@ -3702,12 +3312,14 @@ where
ModuleNameEnum::PkgConfig => None,
ModuleNameEnum::App(_) => None,
ModuleNameEnum::Interface(name) | ModuleNameEnum::Hosted(name) => {
Some(crate::docs::generate_module_docs(
let docs = crate::docs::generate_module_docs(
module_output.scope,
name.as_str().into(),
&module_output.ident_ids,
parsed_defs,
))
);
Some(docs)
}
};
@ -3784,6 +3396,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, Loadi
exposed_ident_ids,
exposed_imports,
module_path,
header_for,
..
} = header;
@ -3798,6 +3411,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, Loadi
exposed_ident_ids,
exposed_imports,
parsed_defs,
header_for,
};
Ok(Msg::Parsed(parsed))
@ -4430,7 +4044,23 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin
}
RootIsInterface => {
let doc = alloc.stack(vec![
alloc.reflow(r"The input file is a interface file, but only app modules can be ran."),
alloc.reflow(r"The input file is an interface module, but only app modules can be ran."),
alloc.concat(vec![
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "),
alloc.reflow(r"but won't output any executable."),
])
]);
Report {
filename: "UNKNOWN.roc".into(),
doc,
title: "NO PLATFORM".to_string(),
severity: Severity::RuntimeError,
}
}
RootIsHosted => {
let doc = alloc.stack(vec![
alloc.reflow(r"The input file is a hosted module, but only app modules can be ran."),
alloc.concat(vec![
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "),
alloc.reflow(r"but won't output any executable."),

View file

@ -2,5 +2,4 @@
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod docs;
pub mod effect_module;
pub mod file;

View file

@ -1638,7 +1638,9 @@ fn literal_spec(
match literal {
Str(_) => new_static_string(builder, block),
Int(_) | Float(_) | Decimal(_) | Bool(_) | Byte(_) => builder.add_make_tuple(block, &[]),
Int(_) | U128(_) | Float(_) | Decimal(_) | Bool(_) | Byte(_) => {
builder.add_make_tuple(block, &[])
}
}
}

View file

@ -87,6 +87,7 @@ enum Test<'a> {
arguments: Vec<(Pattern<'a>, Layout<'a>)>,
},
IsInt(i128, IntWidth),
IsU128(u128),
IsFloat(u64, FloatWidth),
IsDecimal(RocDec),
IsStr(Box<str>),
@ -136,6 +137,10 @@ impl<'a> Hash for Test<'a> {
state.write_u8(6);
v.0.hash(state);
}
IsU128(v) => {
state.write_u8(7);
v.hash(state);
}
}
}
}
@ -311,6 +316,7 @@ fn tests_are_complete_help(last_test: &Test, number_of_tests: usize) -> bool {
Test::IsByte { num_alts, .. } => number_of_tests == *num_alts,
Test::IsBit(_) => number_of_tests == 2,
Test::IsInt(_, _) => false,
Test::IsU128(_) => false,
Test::IsFloat(_, _) => false,
Test::IsDecimal(_) => false,
Test::IsStr(_) => false,
@ -565,6 +571,7 @@ fn test_at_path<'a>(
num_alts: union.alternatives.len(),
},
IntLiteral(v, precision) => IsInt(*v, *precision),
U128Literal(v) => IsU128(*v),
FloatLiteral(v, precision) => IsFloat(*v, *precision),
DecimalLiteral(v) => IsDecimal(*v),
StrLiteral(v) => IsStr(v.clone()),
@ -823,6 +830,18 @@ fn to_relevant_branch_help<'a>(
_ => None,
},
U128Literal(int) => match test {
IsU128(is_int) if int == *is_int => {
start.extend(end);
Some(Branch {
goal: branch.goal,
guard: branch.guard.clone(),
patterns: start,
})
}
_ => None,
},
FloatLiteral(float, p1) => match test {
IsFloat(test_float, p2) if float == *test_float => {
debug_assert_eq!(p1, *p2);
@ -934,6 +953,7 @@ fn needs_tests(pattern: &Pattern) -> bool {
| BitLiteral { .. }
| EnumLiteral { .. }
| IntLiteral(_, _)
| U128Literal(_)
| FloatLiteral(_, _)
| DecimalLiteral(_)
| StrLiteral(_) => true,
@ -1305,6 +1325,14 @@ fn test_to_equality<'a>(
(stores, lhs_symbol, rhs_symbol, None)
}
Test::IsU128(test_int) => {
let lhs = Expr::Literal(Literal::U128(test_int));
let lhs_symbol = env.unique_symbol();
stores.push((lhs_symbol, Layout::int_width(IntWidth::U128), lhs));
(stores, lhs_symbol, rhs_symbol, None)
}
Test::IsFloat(test_int, precision) => {
// TODO maybe we can actually use i64 comparison here?
let test_float = f64::from_bits(test_int as u64);

View file

@ -54,6 +54,7 @@ pub enum Pattern {
#[derive(Clone, Debug, PartialEq)]
pub enum Literal {
Int(i128),
U128(u128),
Bit(bool),
Byte(u8),
Float(u64),
@ -66,6 +67,7 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern {
match pattern {
IntLiteral(v, _) => Literal(Literal::Int(*v)),
U128Literal(v) => Literal(Literal::U128(*v)),
FloatLiteral(v, _) => Literal(Literal::Float(*v)),
DecimalLiteral(v) => Literal(Literal::Decimal(*v)),
StrLiteral(v) => Literal(Literal::Str(v.clone())),

View file

@ -8,7 +8,7 @@ use crate::layout::{
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_can::expr::ClosureData;
use roc_can::expr::{ClosureData, IntValue};
use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap};
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel;
@ -1278,6 +1278,7 @@ impl ModifyRc {
pub enum Literal<'a> {
// Literals
Int(i128),
U128(u128),
Float(f64),
Decimal(RocDec),
Str(&'a str),
@ -1524,6 +1525,7 @@ impl<'a> Literal<'a> {
match self {
Int(lit) => alloc.text(format!("{}i64", lit)),
U128(lit) => alloc.text(format!("{}u128", lit)),
Float(lit) => alloc.text(format!("{}f64", lit)),
// TODO: Add proper Dec.to_str
Decimal(lit) => alloc.text(format!("{}Dec", lit.0)),
@ -2020,7 +2022,7 @@ fn pattern_to_when<'a>(
(symbol, Loc::at_zero(wrapped_body))
}
IntLiteral(_, _, _) | NumLiteral(_, _, _) | FloatLiteral(_, _, _) | StrLiteral(_) => {
IntLiteral(..) | NumLiteral(..) | FloatLiteral(..) | StrLiteral(_) => {
// These patters are refutable, and thus should never occur outside a `when` expression
// They should have been replaced with `UnsupportedPattern` during canonicalization
unreachable!("refutable pattern {:?} where irrefutable pattern is expected. This should never happen!", pattern.value)
@ -3009,14 +3011,17 @@ fn try_make_literal<'a>(
use roc_can::expr::Expr::*;
match can_expr {
Int(_, precision, _, int) => {
Int(_, precision, _, int, _bound) => {
match num_argument_to_int_or_float(env.subs, env.target_info, *precision, false) {
IntOrFloat::Int(_) => Some(Literal::Int(*int)),
IntOrFloat::Int(_) => Some(match *int {
IntValue::I128(n) => Literal::Int(n),
IntValue::U128(n) => Literal::U128(n),
}),
_ => unreachable!("unexpected float precision for integer"),
}
}
Float(_, precision, float_str, float) => {
Float(_, precision, float_str, float, _bound) => {
match num_argument_to_int_or_float(env.subs, env.target_info, *precision, true) {
IntOrFloat::Float(_) => Some(Literal::Float(*float)),
IntOrFloat::DecimalFloatType => {
@ -3036,11 +3041,17 @@ fn try_make_literal<'a>(
// TODO investigate lifetime trouble
// Str(string) => Some(Literal::Str(env.arena.alloc(string))),
Num(var, num_str, num) => {
Num(var, num_str, num, _bound) => {
// first figure out what kind of number this is
match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) {
IntOrFloat::Int(_) => Some(Literal::Int((*num).into())),
IntOrFloat::Float(_) => Some(Literal::Float(*num as f64)),
IntOrFloat::Int(_) => Some(match *num {
IntValue::I128(n) => Literal::Int(n),
IntValue::U128(n) => Literal::U128(n),
}),
IntOrFloat::Float(_) => Some(match *num {
IntValue::I128(n) => Literal::Float(n as f64),
IntValue::U128(n) => Literal::Float(n as f64),
}),
IntOrFloat::DecimalFloatType => {
let dec = match RocDec::from_str(num_str) {
Some(d) => d,
@ -3072,11 +3083,14 @@ pub fn with_hole<'a>(
let arena = env.arena;
match can_expr {
Int(_, precision, _, int) => {
Int(_, precision, _, int, _bound) => {
match num_argument_to_int_or_float(env.subs, env.target_info, precision, false) {
IntOrFloat::Int(precision) => Stmt::Let(
assigned,
Expr::Literal(Literal::Int(int)),
Expr::Literal(match int {
IntValue::I128(n) => Literal::Int(n),
IntValue::U128(n) => Literal::U128(n),
}),
Layout::Builtin(Builtin::Int(precision)),
hole,
),
@ -3084,7 +3098,7 @@ pub fn with_hole<'a>(
}
}
Float(_, precision, float_str, float) => {
Float(_, precision, float_str, float, _bound) => {
match num_argument_to_int_or_float(env.subs, env.target_info, precision, true) {
IntOrFloat::Float(precision) => Stmt::Let(
assigned,
@ -3115,18 +3129,24 @@ pub fn with_hole<'a>(
hole,
),
Num(var, num_str, num) => {
Num(var, num_str, num, _bound) => {
// first figure out what kind of number this is
match num_argument_to_int_or_float(env.subs, env.target_info, var, false) {
IntOrFloat::Int(precision) => Stmt::Let(
assigned,
Expr::Literal(Literal::Int(num.into())),
Expr::Literal(match num {
IntValue::I128(n) => Literal::Int(n),
IntValue::U128(n) => Literal::U128(n),
}),
Layout::int_width(precision),
hole,
),
IntOrFloat::Float(precision) => Stmt::Let(
assigned,
Expr::Literal(Literal::Float(num as f64)),
Expr::Literal(match num {
IntValue::I128(n) => Literal::Float(n as f64),
IntValue::U128(n) => Literal::Float(n as f64),
}),
Layout::float_width(precision),
hole,
),
@ -6211,6 +6231,7 @@ fn store_pattern_help<'a>(
return StorePattern::NotProductive(stmt);
}
IntLiteral(_, _)
| U128Literal(_)
| FloatLiteral(_, _)
| DecimalLiteral(_)
| EnumLiteral { .. }
@ -7583,6 +7604,7 @@ fn call_specialized_proc<'a>(
pub enum Pattern<'a> {
Identifier(Symbol),
Underscore,
U128Literal(u128),
IntLiteral(i128, IntWidth),
FloatLiteral(u64, FloatWidth),
DecimalLiteral(RocDec),
@ -7662,9 +7684,15 @@ fn from_can_pattern_help<'a>(
match can_pattern {
Underscore => Ok(Pattern::Underscore),
Identifier(symbol) => Ok(Pattern::Identifier(*symbol)),
IntLiteral(var, _, int) => {
match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) {
IntOrFloat::Int(precision) => Ok(Pattern::IntLiteral(*int as i128, precision)),
IntLiteral(_, precision_var, _, int, _bound) => {
match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, false) {
IntOrFloat::Int(precision) => {
let int = match *int {
IntValue::I128(n) => Pattern::IntLiteral(n, precision),
IntValue::U128(n) => Pattern::U128Literal(n),
};
Ok(int)
}
other => {
panic!(
"Invalid precision for int pattern: {:?} has {:?}",
@ -7673,11 +7701,11 @@ fn from_can_pattern_help<'a>(
}
}
}
FloatLiteral(var, float_str, float) => {
FloatLiteral(_, precision_var, float_str, float, _bound) => {
// TODO: Can I reuse num_argument_to_int_or_float here if I pass in true?
match num_argument_to_int_or_float(env.subs, env.target_info, *var, true) {
match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, true) {
IntOrFloat::Int(_) => {
panic!("Invalid precision for float pattern {:?}", var)
panic!("Invalid precision for float pattern {:?}", precision_var)
}
IntOrFloat::Float(precision) => {
Ok(Pattern::FloatLiteral(f64::to_bits(*float), precision))
@ -7704,10 +7732,20 @@ fn from_can_pattern_help<'a>(
// TODO preserve malformed problem information here?
Err(RuntimeError::UnsupportedPattern(*region))
}
NumLiteral(var, num_str, num) => {
NumLiteral(var, num_str, num, _bound) => {
match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) {
IntOrFloat::Int(precision) => Ok(Pattern::IntLiteral(*num as i128, precision)),
IntOrFloat::Float(precision) => Ok(Pattern::FloatLiteral(*num as u64, precision)),
IntOrFloat::Int(precision) => Ok(match num {
IntValue::I128(num) => Pattern::IntLiteral(*num, precision),
IntValue::U128(num) => Pattern::U128Literal(*num),
}),
IntOrFloat::Float(precision) => {
// TODO: this may be lossy
let num = match *num {
IntValue::I128(n) => f64::to_bits(n as f64),
IntValue::U128(n) => f64::to_bits(n as f64),
};
Ok(Pattern::FloatLiteral(num, precision))
}
IntOrFloat::DecimalFloatType => {
let dec = match RocDec::from_str(num_str) {
Some(d) => d,

View file

@ -514,7 +514,7 @@ fn numeric_negate_expression<'a, T>(
let start = state.pos();
let region = Region::new(start, expr.region.end());
let new_expr = match &expr.value {
let new_expr = match expr.value {
Expr::Num(string) => {
let new_string =
unsafe { std::str::from_utf8_unchecked(&state.bytes()[..string.len() + 1]) };
@ -536,7 +536,7 @@ fn numeric_negate_expression<'a, T>(
Expr::NonBase10Int {
is_negative: !is_negative,
string,
base: *base,
base,
}
}
_ => Expr::UnaryOp(arena.alloc(expr), Loc::at(loc_op.region, UnaryOp::Negate)),
@ -1450,8 +1450,8 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
Ok(Pattern::RecordDestructure(patterns))
}
Expr::Float(string) => Ok(Pattern::FloatLiteral(string)),
Expr::Num(string) => Ok(Pattern::NumLiteral(string)),
&Expr::Float(string) => Ok(Pattern::FloatLiteral(string)),
&Expr::Num(string) => Ok(Pattern::NumLiteral(string)),
Expr::NonBase10Int {
string,
base,

View file

@ -8,6 +8,28 @@ use crate::string_literal;
use bumpalo::collections::Vec;
use roc_region::all::Loc;
#[derive(Debug)]
pub enum HeaderFor<'a> {
App {
to_platform: To<'a>,
},
Hosted {
generates: UppercaseIdent<'a>,
generates_with: &'a [Loc<ExposedName<'a>>],
},
PkgConfig {
/// usually `pf`
config_shorthand: &'a str,
/// the type scheme of the main function (required by the platform)
/// (currently unused)
#[allow(dead_code)]
platform_main_type: TypedIdent<'a>,
/// provided symbol to host (commonly `mainForHost`)
main_for_host: roc_module::symbol::Symbol,
},
Interface,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub enum Version<'a> {
Exact(&'a str),
@ -47,6 +69,15 @@ impl<'a> ModuleName<'a> {
}
}
#[derive(Debug)]
pub enum ModuleNameEnum<'a> {
/// A filename
App(StrLiteral<'a>),
Interface(ModuleName<'a>),
Hosted(ModuleName<'a>),
PkgConfig,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub struct ExposedName<'a>(&'a str);
@ -162,7 +193,6 @@ pub struct PlatformHeader<'a> {
pub packages: Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>,
pub imports: Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>,
pub provides: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
pub effects: Effects<'a>,
// Potential comments and newlines - these will typically all be empty.
pub before_header: &'a [CommentOrNewline<'a>],
@ -179,17 +209,6 @@ pub struct PlatformHeader<'a> {
pub after_provides: &'a [CommentOrNewline<'a>],
}
/// e.g. fx.Effects
#[derive(Clone, Debug, PartialEq)]
pub struct Effects<'a> {
pub spaces_before_effects_keyword: &'a [CommentOrNewline<'a>],
pub spaces_after_effects_keyword: &'a [CommentOrNewline<'a>],
pub spaces_after_type_name: &'a [CommentOrNewline<'a>],
pub effect_shortname: &'a str,
pub effect_type_name: &'a str,
pub entries: Collection<'a, Loc<Spaced<'a, TypedIdent<'a>>>>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ImportsEntry<'a> {
/// e.g. `Task` or `Task.{ Task, after }`

View file

@ -22,6 +22,12 @@ impl<'a> From<UppercaseIdent<'a>> for &'a str {
}
}
impl<'a> From<&'a UppercaseIdent<'a>> for &'a str {
fn from(ident: &'a UppercaseIdent<'a>) -> Self {
ident.0
}
}
/// The parser accepts all of these in any position where any one of them could
/// appear. This way, canonicalization can give more helpful error messages like
/// "you can't redefine this tag!" if you wrote `Foo = ...` or

View file

@ -1,15 +1,13 @@
use crate::ast::{Collection, CommentOrNewline, Def, Module, Spaced};
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::header::{
package_entry, package_name, AppHeader, Effects, ExposedName, HostedHeader, ImportsEntry,
package_entry, package_name, AppHeader, ExposedName, HostedHeader, ImportsEntry,
InterfaceHeader, ModuleName, PackageEntry, PlatformHeader, PlatformRequires, To, TypedIdent,
};
use crate::ident::{
self, lowercase_ident, unqualified_ident, uppercase, uppercase_ident, UppercaseIdent,
};
use crate::ident::{self, lowercase_ident, unqualified_ident, uppercase, UppercaseIdent};
use crate::parser::Progress::{self, *};
use crate::parser::{
backtrackable, optional, specialize, specialize_region, word1, EEffects, EExposes, EGenerates,
backtrackable, optional, specialize, specialize_region, word1, EExposes, EGenerates,
EGeneratesWith, EHeader, EImports, EPackages, EProvides, ERequires, ETypedIdent, Parser,
SourceError, SyntaxError,
};
@ -323,8 +321,6 @@ fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> {
let (_, ((before_provides, after_provides), (provides, _provides_type)), state) =
specialize(EHeader::Provides, provides_without_to()).parse(arena, state)?;
let (_, effects, state) = specialize(EHeader::Effects, effects()).parse(arena, state)?;
let header = PlatformHeader {
name,
requires,
@ -332,7 +328,6 @@ fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> {
packages: packages.entries,
imports,
provides,
effects,
before_header: &[] as &[_],
after_platform_keyword,
before_requires,
@ -822,63 +817,6 @@ fn imports<'a>() -> impl Parser<
)
}
#[inline(always)]
fn effects<'a>() -> impl Parser<'a, Effects<'a>, EEffects<'a>> {
move |arena, state| {
let min_indent = 1;
let (_, (spaces_before_effects_keyword, spaces_after_effects_keyword), state) =
spaces_around_keyword(
min_indent,
"effects",
EEffects::Effects,
EEffects::Space,
EEffects::IndentEffects,
EEffects::IndentListStart,
)
.parse(arena, state)?;
// e.g. `fx.`
let (_, type_shortname, state) = skip_second!(
specialize(|_, pos| EEffects::Shorthand(pos), lowercase_ident()),
word1(b'.', EEffects::ShorthandDot)
)
.parse(arena, state)?;
// the type name, e.g. Effects
let (_, (type_name, spaces_after_type_name), state) = and!(
specialize(|_, pos| EEffects::TypeName(pos), uppercase_ident()),
space0_e(min_indent, EEffects::Space, EEffects::IndentListStart)
)
.parse(arena, state)?;
let (_, entries, state) = collection_trailing_sep_e!(
word1(b'{', EEffects::ListStart),
specialize(EEffects::TypedIdent, loc!(typed_ident())),
word1(b',', EEffects::ListEnd),
word1(b'}', EEffects::ListEnd),
min_indent,
EEffects::Open,
EEffects::Space,
EEffects::IndentListEnd,
Spaced::SpaceBefore
)
.parse(arena, state)?;
Ok((
MadeProgress,
Effects {
spaces_before_effects_keyword,
spaces_after_effects_keyword,
spaces_after_type_name,
effect_shortname: type_shortname,
effect_type_name: type_name,
entries,
},
state,
))
}
}
#[inline(always)]
fn typed_ident<'a>() -> impl Parser<'a, Spaced<'a, TypedIdent<'a>>, ETypedIdent<'a>> {
// e.g.

View file

@ -71,7 +71,6 @@ pub enum EHeader<'a> {
Imports(EImports, Position),
Requires(ERequires<'a>, Position),
Packages(EPackages<'a>, Position),
Effects(EEffects<'a>, Position),
Generates(EGenerates, Position),
GeneratesWith(EGeneratesWith, Position),
@ -167,22 +166,6 @@ pub enum EPackageEntry<'a> {
Space(BadInputError, Position),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EEffects<'a> {
Space(BadInputError, Position),
Effects(Position),
Open(Position),
IndentEffects(Position),
ListStart(Position),
ListEnd(Position),
IndentListStart(Position),
IndentListEnd(Position),
TypedIdent(ETypedIdent<'a>, Position),
ShorthandDot(Position),
Shorthand(Position),
TypeName(Position),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EImports {
Open(Position),

View file

@ -18,14 +18,6 @@ Platform {
packages: [],
imports: [],
provides: [],
effects: Effects {
spaces_before_effects_keyword: [],
spaces_after_effects_keyword: [],
spaces_after_type_name: [],
effect_shortname: "fx",
effect_type_name: "Blah",
entries: [],
},
before_header: [],
after_platform_keyword: [],
before_requires: [],

View file

@ -1 +1 @@
platform "rtfeldman/blah" requires {} { main : {} } exposes [] packages {} imports [] provides [] effects fx.Blah {}
platform "rtfeldman/blah" requires {} { main : {} } exposes [] packages {} imports [] provides []

View file

@ -43,105 +43,6 @@ Platform {
"mainForHost",
),
],
effects: Effects {
spaces_before_effects_keyword: [
Newline,
],
spaces_after_effects_keyword: [],
spaces_after_type_name: [
Newline,
],
effect_shortname: "fx",
effect_type_name: "Effect",
entries: [
@208-228 SpaceBefore(
TypedIdent {
ident: @208-215 "getLine",
spaces_before_colon: [],
ann: @218-228 Apply(
"",
"Effect",
[
@225-228 Apply(
"",
"Str",
[],
),
],
),
},
[
Newline,
],
),
@242-268 SpaceBefore(
TypedIdent {
ident: @242-249 "putLine",
spaces_before_colon: [],
ann: @259-268 Function(
[
@252-255 Apply(
"",
"Str",
[],
),
],
@259-268 Apply(
"",
"Effect",
[
@266-268 Record {
fields: [],
ext: None,
},
],
),
),
},
[
Newline,
],
),
@282-318 SpaceBefore(
SpaceAfter(
TypedIdent {
ident: @282-294 "twoArguments",
spaces_before_colon: [],
ann: @309-318 Function(
[
@297-300 Apply(
"",
"Int",
[],
),
@302-305 Apply(
"",
"Int",
[],
),
],
@309-318 Apply(
"",
"Effect",
[
@316-318 Record {
fields: [],
ext: None,
},
],
),
),
},
[
Newline,
],
),
[
Newline,
],
),
],
},
before_header: [],
after_platform_keyword: [],
before_requires: [

View file

@ -4,9 +4,3 @@ platform "examples/cli"
packages {}
imports [ Task.{ Task } ]
provides [ mainForHost ]
effects fx.Effect
{
getLine : Effect Str,
putLine : Str -> Effect {},
twoArguments : Int, Int -> Effect {}
}

View file

@ -34,16 +34,6 @@ Platform {
"mainForHost",
),
],
effects: Effects {
spaces_before_effects_keyword: [
Newline,
],
spaces_after_effects_keyword: [],
spaces_after_type_name: [],
effect_shortname: "fx",
effect_type_name: "Effect",
entries: [],
},
before_header: [],
after_platform_keyword: [],
before_requires: [

View file

@ -4,4 +4,3 @@ platform "foo/barbaz"
packages { foo: "./foo" }
imports []
provides [ mainForHost ]
effects fx.Effect {}

View file

@ -0,0 +1,451 @@
Record(
Collection {
items: [
@4-15 SpaceBefore(
RequiredValue(
@4-6 "u8",
[],
@10-15 Num(
"123u8",
),
),
[
Newline,
],
),
@19-31 SpaceBefore(
RequiredValue(
@19-22 "u16",
[],
@25-31 Num(
"123u16",
),
),
[
Newline,
],
),
@35-47 SpaceBefore(
RequiredValue(
@35-38 "u32",
[],
@41-47 Num(
"123u32",
),
),
[
Newline,
],
),
@51-63 SpaceBefore(
RequiredValue(
@51-54 "u64",
[],
@57-63 Num(
"123u64",
),
),
[
Newline,
],
),
@67-80 SpaceBefore(
RequiredValue(
@67-71 "u128",
[],
@73-80 Num(
"123u128",
),
),
[
Newline,
],
),
@84-95 SpaceBefore(
RequiredValue(
@84-86 "i8",
[],
@90-95 Num(
"123i8",
),
),
[
Newline,
],
),
@99-111 SpaceBefore(
RequiredValue(
@99-102 "i16",
[],
@105-111 Num(
"123i16",
),
),
[
Newline,
],
),
@115-127 SpaceBefore(
RequiredValue(
@115-118 "i32",
[],
@121-127 Num(
"123i32",
),
),
[
Newline,
],
),
@131-143 SpaceBefore(
RequiredValue(
@131-134 "i64",
[],
@137-143 Num(
"123i64",
),
),
[
Newline,
],
),
@147-160 SpaceBefore(
RequiredValue(
@147-151 "i128",
[],
@153-160 Num(
"123i128",
),
),
[
Newline,
],
),
@164-176 SpaceBefore(
RequiredValue(
@164-167 "nat",
[],
@170-176 Num(
"123nat",
),
),
[
Newline,
],
),
@180-192 SpaceBefore(
RequiredValue(
@180-183 "dec",
[],
@186-192 Num(
"123dec",
),
),
[
Newline,
],
),
@196-211 SpaceBefore(
RequiredValue(
@196-201 "u8Neg",
[],
@205-211 Num(
"-123u8",
),
),
[
Newline,
],
),
@215-231 SpaceBefore(
RequiredValue(
@215-221 "u16Neg",
[],
@224-231 Num(
"-123u16",
),
),
[
Newline,
],
),
@235-251 SpaceBefore(
RequiredValue(
@235-241 "u32Neg",
[],
@244-251 Num(
"-123u32",
),
),
[
Newline,
],
),
@255-271 SpaceBefore(
RequiredValue(
@255-261 "u64Neg",
[],
@264-271 Num(
"-123u64",
),
),
[
Newline,
],
),
@275-292 SpaceBefore(
RequiredValue(
@275-282 "u128Neg",
[],
@284-292 Num(
"-123u128",
),
),
[
Newline,
],
),
@296-311 SpaceBefore(
RequiredValue(
@296-301 "i8Neg",
[],
@305-311 Num(
"-123i8",
),
),
[
Newline,
],
),
@315-331 SpaceBefore(
RequiredValue(
@315-321 "i16Neg",
[],
@324-331 Num(
"-123i16",
),
),
[
Newline,
],
),
@335-351 SpaceBefore(
RequiredValue(
@335-341 "i32Neg",
[],
@344-351 Num(
"-123i32",
),
),
[
Newline,
],
),
@355-371 SpaceBefore(
RequiredValue(
@355-361 "i64Neg",
[],
@364-371 Num(
"-123i64",
),
),
[
Newline,
],
),
@375-392 SpaceBefore(
RequiredValue(
@375-382 "i128Neg",
[],
@384-392 Num(
"-123i128",
),
),
[
Newline,
],
),
@396-412 SpaceBefore(
RequiredValue(
@396-402 "natNeg",
[],
@405-412 Num(
"-123nat",
),
),
[
Newline,
],
),
@416-432 SpaceBefore(
RequiredValue(
@416-422 "decNeg",
[],
@425-432 Num(
"-123dec",
),
),
[
Newline,
],
),
@436-452 SpaceBefore(
RequiredValue(
@436-441 "u8Bin",
[],
@445-452 NonBase10Int {
string: "101u8",
base: Binary,
is_negative: false,
},
),
[
Newline,
],
),
@456-473 SpaceBefore(
RequiredValue(
@456-462 "u16Bin",
[],
@465-473 NonBase10Int {
string: "101u16",
base: Binary,
is_negative: false,
},
),
[
Newline,
],
),
@477-494 SpaceBefore(
RequiredValue(
@477-483 "u32Bin",
[],
@486-494 NonBase10Int {
string: "101u32",
base: Binary,
is_negative: false,
},
),
[
Newline,
],
),
@498-515 SpaceBefore(
RequiredValue(
@498-504 "u64Bin",
[],
@507-515 NonBase10Int {
string: "101u64",
base: Binary,
is_negative: false,
},
),
[
Newline,
],
),
@519-537 SpaceBefore(
RequiredValue(
@519-526 "u128Bin",
[],
@528-537 NonBase10Int {
string: "101u128",
base: Binary,
is_negative: false,
},
),
[
Newline,
],
),
@541-557 SpaceBefore(
RequiredValue(
@541-546 "i8Bin",
[],
@550-557 NonBase10Int {
string: "101i8",
base: Binary,
is_negative: false,
},
),
[
Newline,
],
),
@561-578 SpaceBefore(
RequiredValue(
@561-567 "i16Bin",
[],
@570-578 NonBase10Int {
string: "101i16",
base: Binary,
is_negative: false,
},
),
[
Newline,
],
),
@582-599 SpaceBefore(
RequiredValue(
@582-588 "i32Bin",
[],
@591-599 NonBase10Int {
string: "101i32",
base: Binary,
is_negative: false,
},
),
[
Newline,
],
),
@603-620 SpaceBefore(
RequiredValue(
@603-609 "i64Bin",
[],
@612-620 NonBase10Int {
string: "101i64",
base: Binary,
is_negative: false,
},
),
[
Newline,
],
),
@624-642 SpaceBefore(
RequiredValue(
@624-631 "i128Bin",
[],
@633-642 NonBase10Int {
string: "101i128",
base: Binary,
is_negative: false,
},
),
[
Newline,
],
),
@646-663 SpaceBefore(
RequiredValue(
@646-652 "natBin",
[],
@655-663 NonBase10Int {
string: "101nat",
base: Binary,
is_negative: false,
},
),
[
Newline,
],
),
],
final_comments: [
Newline,
],
},
)

View file

@ -0,0 +1,37 @@
{
u8: 123u8,
u16: 123u16,
u32: 123u32,
u64: 123u64,
u128: 123u128,
i8: 123i8,
i16: 123i16,
i32: 123i32,
i64: 123i64,
i128: 123i128,
nat: 123nat,
dec: 123dec,
u8Neg: -123u8,
u16Neg: -123u16,
u32Neg: -123u32,
u64Neg: -123u64,
u128Neg: -123u128,
i8Neg: -123i8,
i16Neg: -123i16,
i32Neg: -123i32,
i64Neg: -123i64,
i128Neg: -123i128,
natNeg: -123nat,
decNeg: -123dec,
u8Bin: 0b101u8,
u16Bin: 0b101u16,
u32Bin: 0b101u32,
u64Bin: 0b101u64,
u128Bin: 0b101u128,
i8Bin: 0b101i8,
i16Bin: 0b101i16,
i32Bin: 0b101i32,
i64Bin: 0b101i64,
i128Bin: 0b101i128,
natBin: 0b101nat,
}

View file

@ -41,16 +41,6 @@ Platform {
"mainForHost",
),
],
effects: Effects {
spaces_before_effects_keyword: [
Newline,
],
spaces_after_effects_keyword: [],
spaces_after_type_name: [],
effect_shortname: "fx",
effect_type_name: "Effect",
entries: [],
},
before_header: [],
after_platform_keyword: [],
before_requires: [

View file

@ -4,7 +4,6 @@ platform "test/types"
packages {}
imports []
provides [ mainForHost ]
effects fx.Effect {}
mainForHost : App Flags Model
mainForHost = main

View file

@ -182,6 +182,7 @@ mod test_parse {
pass/newline_singleton_list.expr,
pass/nonempty_platform_header.header,
pass/not_docs.expr,
pass/number_literal_suffixes.expr,
pass/one_backpassing.expr,
pass/one_char_string.expr,
pass/one_def.expr,

View file

@ -107,6 +107,18 @@ pub enum IntErrorKind {
Overflow,
/// Integer is too small to store in target integer type.
Underflow,
/// This is an integer, but it has a float numeric suffix.
FloatSuffix,
/// The integer literal overflows the width of the suffix associated with it.
OverflowsSuffix {
suffix_type: &'static str,
max_value: u128,
},
/// The integer literal underflows the width of the suffix associated with it.
UnderflowsSuffix {
suffix_type: &'static str,
min_value: i128,
},
}
/// Enum to store the various types of errors that can cause parsing a float to fail.
@ -118,6 +130,8 @@ pub enum FloatErrorKind {
NegativeInfinity,
/// the literal is too large for f64
PositiveInfinity,
/// This is a float, but it has an integer numeric suffix.
IntSuffix,
}
#[derive(Clone, Debug, PartialEq)]

View file

@ -5044,4 +5044,156 @@ mod solve_expr {
"[ Email Str ] -> Bool",
)
}
#[test]
fn numeric_literal_suffixes() {
infer_eq_without_problem(
indoc!(
r#"
{
u8: 123u8,
u16: 123u16,
u32: 123u32,
u64: 123u64,
u128: 123u128,
i8: 123i8,
i16: 123i16,
i32: 123i32,
i64: 123i64,
i128: 123i128,
nat: 123nat,
bu8: 0b11u8,
bu16: 0b11u16,
bu32: 0b11u32,
bu64: 0b11u64,
bu128: 0b11u128,
bi8: 0b11i8,
bi16: 0b11i16,
bi32: 0b11i32,
bi64: 0b11i64,
bi128: 0b11i128,
bnat: 0b11nat,
dec: 123.0dec,
f32: 123.0f32,
f64: 123.0f64,
fdec: 123dec,
ff32: 123f32,
ff64: 123f64,
}
"#
),
r#"{ bi128 : I128, bi16 : I16, bi32 : I32, bi64 : I64, bi8 : I8, bnat : Nat, bu128 : U128, bu16 : U16, bu32 : U32, bu64 : U64, bu8 : U8, dec : Dec, f32 : F32, f64 : F64, fdec : Dec, ff32 : F32, ff64 : F64, i128 : I128, i16 : I16, i32 : I32, i64 : I64, i8 : I8, nat : Nat, u128 : U128, u16 : U16, u32 : U32, u64 : U64, u8 : U8 }"#,
)
}
#[test]
fn numeric_literal_suffixes_in_pattern() {
infer_eq_without_problem(
indoc!(
r#"
{
u8: (\n ->
when n is
123u8 -> n),
u16: (\n ->
when n is
123u16 -> n),
u32: (\n ->
when n is
123u32 -> n),
u64: (\n ->
when n is
123u64 -> n),
u128: (\n ->
when n is
123u128 -> n),
i8: (\n ->
when n is
123i8 -> n),
i16: (\n ->
when n is
123i16 -> n),
i32: (\n ->
when n is
123i32 -> n),
i64: (\n ->
when n is
123i64 -> n),
i128: (\n ->
when n is
123i128 -> n),
nat: (\n ->
when n is
123nat -> n),
bu8: (\n ->
when n is
0b11u8 -> n),
bu16: (\n ->
when n is
0b11u16 -> n),
bu32: (\n ->
when n is
0b11u32 -> n),
bu64: (\n ->
when n is
0b11u64 -> n),
bu128: (\n ->
when n is
0b11u128 -> n),
bi8: (\n ->
when n is
0b11i8 -> n),
bi16: (\n ->
when n is
0b11i16 -> n),
bi32: (\n ->
when n is
0b11i32 -> n),
bi64: (\n ->
when n is
0b11i64 -> n),
bi128: (\n ->
when n is
0b11i128 -> n),
bnat: (\n ->
when n is
0b11nat -> n),
dec: (\n ->
when n is
123.0dec -> n),
f32: (\n ->
when n is
123.0f32 -> n),
f64: (\n ->
when n is
123.0f64 -> n),
fdec: (\n ->
when n is
123dec -> n),
ff32: (\n ->
when n is
123f32 -> n),
ff64: (\n ->
when n is
123f64 -> n),
}
"#
),
r#"{ bi128 : I128 -> I128, bi16 : I16 -> I16, bi32 : I32 -> I32, bi64 : I64 -> I64, bi8 : I8 -> I8, bnat : Nat -> Nat, bu128 : U128 -> U128, bu16 : U16 -> U16, bu32 : U32 -> U32, bu64 : U64 -> U64, bu8 : U8 -> U8, dec : Dec -> Dec, f32 : F32 -> F32, f64 : F64 -> F64, fdec : Dec -> Dec, ff32 : F32 -> F32, ff64 : F64 -> F64, i128 : I128 -> I128, i16 : I16 -> I16, i32 : I32 -> I32, i64 : I64 -> I64, i8 : I8 -> I8, nat : Nat -> Nat, u128 : U128 -> U128, u16 : U16 -> U16, u32 : U32 -> U32, u64 : U64 -> U64, u8 : U8 -> U8 }"#,
)
}
}

View file

@ -1,6 +1,8 @@
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_evals_to;
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_expect_failed;
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_llvm_evals_to;
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_non_opt_evals_to;
@ -8,6 +10,8 @@ use crate::helpers::llvm::assert_non_opt_evals_to;
#[cfg(feature = "gen-dev")]
use crate::helpers::dev::assert_evals_to;
// #[cfg(feature = "gen-dev")]
// use crate::helpers::dev::assert_expect_failed;
// #[cfg(feature = "gen-dev")]
// use crate::helpers::dev::assert_evals_to as assert_llvm_evals_to;
// #[cfg(feature = "gen-dev")]
// use crate::helpers::dev::assert_evals_to as assert_non_opt_evals_to;
@ -15,6 +19,8 @@ use crate::helpers::dev::assert_evals_to;
#[cfg(feature = "gen-wasm")]
use crate::helpers::wasm::assert_evals_to;
// #[cfg(feature = "gen-wasm")]
// use crate::helpers::dev::assert_expect_failed;
// #[cfg(feature = "gen-wasm")]
// use crate::helpers::wasm::assert_evals_to as assert_llvm_evals_to;
// #[cfg(feature = "gen-wasm")]
// use crate::helpers::wasm::assert_evals_to as assert_non_opt_evals_to;
@ -2485,9 +2491,9 @@ fn call_invalid_layout() {
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[should_panic(expected = "An expectation failed!")]
#[should_panic(expected = "Failed with 1 failures. Failures: ")]
fn expect_fail() {
assert_evals_to!(
assert_expect_failed!(
indoc!(
r#"
expect 1 == 2

View file

@ -257,5 +257,25 @@ macro_rules! assert_evals_to {
};
}
#[allow(unused_macros)]
macro_rules! assert_expect_failed {
($src:expr, $expected:expr, $ty:ty, $failures:expr) => {{
use bumpalo::Bump;
use roc_gen_dev::run_jit_function_raw;
let stdlib = roc_builtins::std::standard_stdlib();
let arena = Bump::new();
let (main_fn_name, errors, lib) =
$crate::helpers::dev::helper(&arena, $src, stdlib, true, true);
let transform = |success| {
let expected = $expected;
assert_eq!(&success, &expected);
};
run_jit_function_raw!(lib, main_fn_name, $ty, transform, errors);
}};
}
#[allow(unused_imports)]
pub(crate) use assert_evals_to;
pub(crate) use assert_expect_failed;

View file

@ -192,7 +192,11 @@ fn create_llvm_module<'a>(
for function in FunctionIterator::from_module(module) {
let name = function.get_name().to_str().unwrap();
if name.starts_with("roc_builtins") {
function.set_linkage(Linkage::Internal);
if name.starts_with("roc_builtins.expect") {
function.set_linkage(Linkage::External);
} else {
function.set_linkage(Linkage::Internal);
}
}
if name.starts_with("roc_builtins.dict") {
@ -601,6 +605,46 @@ macro_rules! assert_evals_to {
};
}
#[allow(unused_macros)]
macro_rules! assert_expect_failed {
($src:expr, $expected:expr, $ty:ty) => {
use bumpalo::Bump;
use inkwell::context::Context;
use roc_gen_llvm::run_jit_function;
let arena = Bump::new();
let context = Context::create();
// NOTE the stdlib must be in the arena; just taking a reference will segfault
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
let is_gen_test = true;
let (main_fn_name, errors, lib) =
$crate::helpers::llvm::helper(&arena, $src, stdlib, is_gen_test, false, &context);
let transform = |success| {
let expected = $expected;
assert_eq!(&success, &expected, "LLVM test failed");
};
run_jit_function!(lib, main_fn_name, $ty, transform, errors)
};
($src:expr, $expected:expr, $ty:ty) => {
$crate::helpers::llvm::assert_llvm_evals_to!(
$src,
$expected,
$ty,
$crate::helpers::llvm::identity,
false
);
};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
$crate::helpers::llvm::assert_llvm_evals_to!($src, $expected, $ty, $transform, false);
};
}
macro_rules! expect_runtime_error_panic {
($src:expr) => {{
#[cfg(feature = "wasm-cli-run")]
@ -651,6 +695,8 @@ macro_rules! assert_non_opt_evals_to {
#[allow(unused_imports)]
pub(crate) use assert_evals_to;
#[allow(unused_imports)]
pub(crate) use assert_expect_failed;
#[allow(unused_imports)]
pub(crate) use assert_llvm_evals_to;
#[allow(unused_imports)]
pub(crate) use assert_non_opt_evals_to;

View file

@ -24,6 +24,11 @@ pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
libc::malloc(size)
}
#[no_mangle]
pub unsafe fn roc_memcpy(dest: *mut c_void, src: *const c_void, bytes: usize) -> *mut c_void {
libc::memcpy(dest, src, bytes)
}
#[no_mangle]
pub unsafe fn roc_realloc(
c_ptr: *mut c_void,

View file

@ -1,4 +1,4 @@
use crate::subs::{Content, FlatType, GetSubsSlice, Subs, UnionTags, Variable};
use crate::subs::{AliasVariables, Content, FlatType, GetSubsSlice, Subs, UnionTags, Variable};
use crate::types::{name_type_var, RecordField};
use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Lowercase, TagName};
@ -289,6 +289,17 @@ pub fn content_to_string(
buf
}
pub fn get_single_arg<'a>(subs: &'a Subs, args: &'a AliasVariables) -> &'a Content {
debug_assert_eq!(args.len(), 1);
let arg_var_index = args
.into_iter()
.next()
.expect("Num was not applied to a type argument!");
let arg_var = subs[arg_var_index];
subs.get_content_without_compacting(arg_var)
}
fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, parens: Parens) {
use crate::subs::Content::*;
@ -306,18 +317,19 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
match *symbol {
Symbol::NUM_NUM => {
debug_assert_eq!(args.len(), 1);
let arg_var_index = args
.into_iter()
.next()
.expect("Num was not applied to a type argument!");
let arg_var = subs[arg_var_index];
let content = subs.get_content_without_compacting(arg_var);
match &content {
Alias(nested, _, _) => match *nested {
Symbol::NUM_INTEGER => buf.push_str("I64"),
let content = get_single_arg(subs, args);
match *content {
Alias(nested, args, _actual) => match nested {
Symbol::NUM_INTEGER => {
write_integer(
env,
get_single_arg(subs, &args),
subs,
buf,
parens,
false,
);
}
Symbol::NUM_FLOATINGPOINT => buf.push_str("F64"),
_ => write_parens!(write_parens, buf, {
@ -333,6 +345,33 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
}
}
Symbol::NUM_INT => {
let content = get_single_arg(subs, args);
write_integer(env, content, subs, buf, parens, write_parens)
}
Symbol::NUM_FLOAT => {
debug_assert_eq!(args.len(), 1);
let arg_var_index = args
.into_iter()
.next()
.expect("Num was not applied to a type argument!");
let arg_var = subs[arg_var_index];
let content = subs.get_content_without_compacting(arg_var);
match content {
Alias(Symbol::NUM_BINARY32, _, _) => buf.push_str("F32"),
Alias(Symbol::NUM_BINARY64, _, _) => buf.push_str("F64"),
Alias(Symbol::NUM_DECIMAL, _, _) => buf.push_str("Dec"),
_ => write_parens!(write_parens, buf, {
buf.push_str("Float ");
write_content(env, content, subs, buf, parens);
}),
}
}
_ => write_parens!(write_parens, buf, {
write_symbol(env, *symbol, buf);
@ -362,6 +401,51 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
}
}
fn write_integer(
env: &Env,
content: &Content,
subs: &Subs,
buf: &mut String,
parens: Parens,
write_parens: bool,
) {
use crate::subs::Content::*;
macro_rules! derive_num_writes {
($($lit:expr, $tag:path)*) => {
write_parens!(
write_parens,
buf,
match content {
$(
&Alias($tag, _, _) => {
buf.push_str($lit)
},
)*
actual => {
buf.push_str("Int ");
write_content(env, actual, subs, buf, parens);
}
}
)
}
}
derive_num_writes! {
"U8", Symbol::NUM_UNSIGNED8
"U16", Symbol::NUM_UNSIGNED16
"U32", Symbol::NUM_UNSIGNED32
"U64", Symbol::NUM_UNSIGNED64
"U128", Symbol::NUM_UNSIGNED128
"I8", Symbol::NUM_SIGNED8
"I16", Symbol::NUM_SIGNED16
"I32", Symbol::NUM_SIGNED32
"I64", Symbol::NUM_SIGNED64
"I128", Symbol::NUM_SIGNED128
"Nat", Symbol::NUM_NATURAL
}
}
enum ExtContent<'a> {
Empty,
Content(Variable, &'a Content),

View file

@ -1626,6 +1626,25 @@ pub enum FlatType {
EmptyTagUnion,
}
impl FlatType {
pub fn get_singleton_tag_union<'a>(&'a self, subs: &'a Subs) -> Option<&'a TagName> {
match self {
Self::TagUnion(tags, ext) => {
let tags = tags.unsorted_tags_and_ext(subs, *ext).0.tags;
if tags.len() != 1 {
return None;
}
let (tag_name, vars) = tags[0];
if !vars.is_empty() {
return None;
}
Some(tag_name)
}
_ => None,
}
}
}
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum Builtin {
Str,

View file

@ -1186,6 +1186,7 @@ pub enum Reason {
RecordUpdateValue(Lowercase),
RecordUpdateKeys(Symbol, SendMap<Lowercase, Region>),
RecordDefaultField(Lowercase),
NumericLiteralSuffix,
}
#[derive(PartialEq, Debug, Clone)]

View file

@ -33,7 +33,6 @@ platform "test-platform"
packages {}
imports []
provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str
mainForHost = main

View file

@ -0,0 +1,10 @@
hosted Effect
exposes [ Effect, after, map, always, forever, loop, putLine, putInt, getInt ]
imports []
generates Effect with [ after, map, always, forever, loop ]
putLine : Str -> Effect {}
putInt : I64 -> Effect {}
getInt : Effect { value : I64, errorCode : [ A, B ], isError : Bool }

View file

@ -4,12 +4,6 @@ platform "folkertdev/foo"
packages {}
imports [ Task.{ Task } ]
provides [ mainForHost ]
effects fx.Effect
{
putLine : Str -> Effect {},
putInt : I64 -> Effect {},
getInt : Effect { value : I64, errorCode : [ A, B ], isError : Bool },
}
mainForHost : Task {} [] as Fx
mainForHost = main

View file

@ -1,6 +1,6 @@
interface Task
exposes [ Task, succeed, fail, after, map, putLine, putInt, getInt, forever, loop ]
imports [ fx.Effect ]
imports [ pf.Effect ]
Task ok err : Effect.Effect (Result ok err)

View file

@ -0,0 +1,8 @@
hosted Effect
exposes [ Effect, after, map, always, forever, loop, putLine, getLine ]
imports []
generates Effect with [ after, map, always, forever, loop ]
putLine : Str -> Effect {}
getLine : Effect Str

View file

@ -4,7 +4,6 @@ platform "examples/cli"
packages {}
imports [ Task.{ Task } ]
provides [ mainForHost ]
effects fx.Effect { putLine : Str -> Effect {}, getLine : Effect Str }
mainForHost : Task {} [] as Fx
mainForHost = main

View file

@ -1,6 +1,6 @@
interface Stdin
exposes [ line ]
imports [ fx.Effect, Task ]
imports [ pf.Effect, Task ]
line : Task.Task Str *
line = Effect.after Effect.getLine Task.succeed# TODO FIXME Effect.getLine should suffice

View file

@ -1,6 +1,6 @@
interface Stdout
exposes [ line ]
imports [ fx.Effect, Task.{ Task } ]
imports [ pf.Effect, Task.{ Task } ]
# line : Str -> Task.Task {} *
# line = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {})

View file

@ -1,6 +1,6 @@
interface Task
exposes [ Task, succeed, fail, await, map, onFail, attempt, forever, loop ]
imports [ fx.Effect ]
imports [ pf.Effect ]
Task ok err : Effect.Effect (Result ok err)

View file

@ -1,6 +1,6 @@
app "effect-example"
packages { pf: "thing/platform-dir" }
imports [ fx.Effect ]
imports [ pf.Effect ]
provides [ main ] to pf
main : Effect.Effect {}

View file

@ -0,0 +1,8 @@
hosted Effect
exposes [ Effect, after, map, always, forever, putLine, getLine ]
imports []
generates Effect with [ after, map, always, forever ]
putLine : Str -> Effect {}
getLine : Effect Str

View file

@ -2,13 +2,8 @@ platform "roc-examples/cli"
requires {} { main : Effect {} }
exposes []
packages {}
imports [ fx.Effect ]
imports [ pf.Effect ]
provides [ mainForHost ]
effects fx.Effect
{
putLine : Str -> Effect {},
getLine : Effect Str,
}
mainForHost : Effect.Effect {} as Fx
mainForHost = main

View file

@ -0,0 +1,22 @@
hosted Effect
exposes [ Effect, after, map, always, forever, loop, openFile, closeFile, withFileOpen, getFileLine, getFileBytes, putLine, putRaw, getLine, getChar ]
imports []
generates Effect with [ after, map, always, forever, loop ]
openFile : Str -> Effect U64
closeFile : U64 -> Effect {}
withFileOpen : Str, (U64 -> Effect (Result ok err)) -> Effect {}
getFileLine : U64 -> Effect Str
getFileBytes : U64 -> Effect (List U8)
putLine : Str -> Effect {}
putRaw : Str -> Effect {}
getLine : Effect Str
getChar : Effect U8

View file

@ -1,6 +1,6 @@
interface File
exposes [ line, Handle, withOpen, chunk ]
imports [ fx.Effect, Task.{ Task } ]
imports [ pf.Effect, Task.{ Task } ]
Handle : [ @Handle U64 ]
@ -24,4 +24,4 @@ withOpen = \path, callback ->
handle <- Task.await (open path)
result <- Task.attempt (callback handle)
{ } <- Task.await (close handle)
Task.fromResult result
Task.fromResult result

View file

@ -4,19 +4,6 @@ platform "examples/cli"
packages {}
imports [ Task.{ Task } ]
provides [ mainForHost ]
effects fx.Effect
{
openFile : Str -> Effect U64,
closeFile : U64 -> Effect {},
withFileOpen : Str, (U64 -> Effect (Result ok err)) -> Effect {},
getFileLine : U64 -> Effect Str,
getFileBytes : U64 -> Effect (List U8),
putLine : Str -> Effect {},
putRaw : Str -> Effect {},
# Is there a limit to the number of effect, uncomment the next line and it crashes
# getLine : Effect Str,
getChar : Effect U8,
}
mainForHost : Str -> Task {} [] as Fx
mainForHost = \file -> main file

View file

@ -1,6 +1,6 @@
interface Stdin
exposes [ char ]
imports [ fx.Effect, Task ]
imports [ pf.Effect, Task ]
# line : Task.Task Str *
# line = Effect.after Effect.getLine Task.succeed # TODO FIXME Effect.getLine should suffice

View file

@ -1,9 +1,9 @@
interface Stdout
exposes [ line, raw ]
imports [ fx.Effect, Task.{ Task } ]
imports [ pf.Effect, Task.{ Task } ]
line : Str -> Task {} *
line = \str -> Effect.map (Effect.putLine str) (\_ -> Ok {})
raw : Str -> Task {} *
raw = \str -> Effect.map (Effect.putRaw str) (\_ -> Ok {})
raw = \str -> Effect.map (Effect.putRaw str) (\_ -> Ok {})

View file

@ -1,6 +1,6 @@
interface Task
exposes [ Task, succeed, fail, await, map, onFail, attempt, fromResult, loop ]
imports [ fx.Effect ]
imports [ pf.Effect ]
Task ok err : Effect.Effect (Result ok err)

View file

@ -4,7 +4,6 @@ platform "examples/add"
packages {}
imports []
provides [ mainForHost ]
effects fx.Effect {}
mainForHost : I64 -> I64
mainForHost = \a -> main a

View file

@ -4,7 +4,6 @@ platform "examples/hello-world"
packages {}
imports []
provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str
mainForHost = main

View file

@ -4,7 +4,6 @@ platform "examples/hello-swift"
packages {}
imports []
provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str
mainForHost = main

View file

@ -4,7 +4,6 @@ platform "examples/hello-world"
packages {}
imports []
provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str
mainForHost = main

View file

@ -4,7 +4,6 @@ platform "examples/hello-world"
packages {}
imports []
provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str
mainForHost = main

View file

@ -4,7 +4,6 @@ platform "examples/hello-world"
packages {}
imports []
provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str
mainForHost = main

View file

@ -4,7 +4,6 @@ platform "examples/quicksort"
packages {}
imports []
provides [ mainForHost ]
effects fx.Effect {}
mainForHost : List I64 -> List I64
mainForHost = \list -> quicksort list

View file

@ -4,7 +4,6 @@ platform "folkertdev/foo"
packages {}
imports []
provides [ mainForHost ]
effects fx.Effect {}
mainForHost : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View }
mainForHost = main

View file

@ -8,18 +8,22 @@ version = "0.1.0"
[dependencies]
bumpalo = {version = "3.8.0", features = ["collections"]}
const_format = "0.2.22"
inkwell = {path = "../vendor/inkwell"}
libloading = {version = "0.7.1"}
rustyline = {git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1"}
rustyline-derive = {git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1"}
target-lexicon = "0.12.2"
roc_build = {path = "../compiler/build"}
roc_collections = {path = "../compiler/collections"}
roc_gen_llvm = {path = "../compiler/gen_llvm"}
roc_load = {path = "../compiler/load"}
roc_mono = {path = "../compiler/mono"}
roc_parse = {path = "../compiler/parse"}
roc_repl_eval = {path = "../repl_eval"}
[dev-dependencies]
indoc = "1.0.3"
roc_test_utils = {path = "../test_utils"}
strip-ansi-escapes = "0.1.1"
roc_target = {path = "../compiler/roc_target"}
roc_types = {path = "../compiler/types"}
roc_builtins = {path = "../compiler/builtins"}
[lib]
name = "roc_repl_cli"

View file

@ -1,15 +1,27 @@
use bumpalo::Bump;
use const_format::concatcp;
use inkwell::context::Context;
use libloading::Library;
use rustyline::highlight::{Highlighter, PromptInfo};
use rustyline::validate::{self, ValidationContext, ValidationResult, Validator};
use rustyline_derive::{Completer, Helper, Hinter};
use std::borrow::Cow;
use std::io;
use target_lexicon::Triple;
use roc_build::link::module_to_dylib;
use roc_collections::all::MutSet;
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type};
use roc_load::file::MonomorphizedModule;
use roc_mono::ir::OptLevel;
use roc_parse::ast::Expr;
use roc_parse::parser::{EExpr, ELambda, SyntaxError};
use roc_repl_eval::gen::{gen_and_eval, ReplOutput};
#[cfg(test)]
mod tests;
use roc_repl_eval::eval::jit_to_ast;
use roc_repl_eval::gen::{compile_to_mono, format_answer, ReplOutput};
use roc_repl_eval::ReplApp;
use roc_target::TargetInfo;
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
const BLUE: &str = "\u{001b}[36m";
const PINK: &str = "\u{001b}[35m";
@ -105,6 +117,210 @@ impl Validator for InputValidator {
}
}
struct CliReplApp {
lib: Library,
}
macro_rules! deref_number {
($name: ident, $t: ty) => {
fn $name(&self, addr: usize) -> $t {
let ptr = addr as *const _;
unsafe { *ptr }
}
};
}
impl ReplApp for CliReplApp {
deref_number!(deref_bool, bool);
deref_number!(deref_u8, u8);
deref_number!(deref_u16, u16);
deref_number!(deref_u32, u32);
deref_number!(deref_u64, u64);
deref_number!(deref_u128, u128);
deref_number!(deref_usize, usize);
deref_number!(deref_i8, i8);
deref_number!(deref_i16, i16);
deref_number!(deref_i32, i32);
deref_number!(deref_i64, i64);
deref_number!(deref_i128, i128);
deref_number!(deref_isize, isize);
deref_number!(deref_f32, f32);
deref_number!(deref_f64, f64);
fn deref_str(&self, addr: usize) -> &str {
unsafe { *(addr as *const &'static str) }
}
/// Run user code that returns a type with a `Builtin` layout
/// Size of the return value is statically determined from its Rust type
fn call_function<'a, T: Sized, F: Fn(T) -> Expr<'a>>(
&self,
main_fn_name: &str,
transform: F,
) -> Expr<'a> {
run_jit_function!(self.lib, main_fn_name, T, transform)
}
/// Run user code that returns a struct or union, whose size is provided as an argument
fn call_function_dynamic_size<T: Sized, F: Fn(usize) -> T>(
&self,
main_fn_name: &str,
bytes: usize,
transform: F,
) -> T {
run_jit_function_dynamic_type!(self.lib, main_fn_name, bytes, transform)
}
}
fn gen_and_eval_llvm<'a>(
src: &str,
target: Triple,
opt_level: OptLevel,
) -> Result<ReplOutput, SyntaxError<'a>> {
let arena = Bump::new();
let target_info = TargetInfo::from(&target);
let loaded = match compile_to_mono(&arena, src, target_info) {
Ok(x) => x,
Err(prob_strings) => {
return Ok(ReplOutput::Problems(prob_strings));
}
};
let MonomorphizedModule {
procedures,
entry_point,
interns,
exposed_to_host,
mut subs,
module_id: home,
..
} = loaded;
let context = Context::create();
let builder = context.create_builder();
let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins(
&target, &context, "",
));
debug_assert_eq!(exposed_to_host.values.len(), 1);
let (main_fn_symbol, main_fn_var) = exposed_to_host.values.iter().next().unwrap();
let main_fn_symbol = *main_fn_symbol;
let main_fn_var = *main_fn_var;
// pretty-print the expr type string for later.
name_all_type_vars(main_fn_var, &mut subs);
let content = subs.get_content_without_compacting(main_fn_var);
let expr_type_str = content_to_string(content, &subs, home, &interns);
let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
Some(layout) => *layout,
None => {
return Ok(ReplOutput::NoProblems {
expr: "<function>".to_string(),
expr_type: expr_type_str,
});
}
};
let module = arena.alloc(module);
let (module_pass, function_pass) =
roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level);
let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module);
// Compile and add all the Procs before adding main
let env = roc_gen_llvm::llvm::build::Env {
arena: &arena,
builder: &builder,
dibuilder: &dibuilder,
compile_unit: &compile_unit,
context: &context,
interns,
module,
target_info,
is_gen_test: true, // so roc_panic is generated
// important! we don't want any procedures to get the C calling convention
exposed_to_host: MutSet::default(),
};
// Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no
// platform to provide them.
add_default_roc_externs(&env);
let (main_fn_name, main_fn) = roc_gen_llvm::llvm::build::build_procedures_return_main(
&env,
opt_level,
procedures,
entry_point,
);
env.dibuilder.finalize();
// we don't use the debug info, and it causes weird errors.
module.strip_debug_info();
// Uncomment this to see the module's un-optimized LLVM instruction output:
// env.module.print_to_stderr();
if main_fn.verify(true) {
function_pass.run_on(&main_fn);
} else {
panic!("Main function {} failed LLVM verification in build. Uncomment things nearby to see more details.", main_fn_name);
}
module_pass.run_on(env.module);
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
// Verify the module
if let Err(errors) = env.module.verify() {
panic!(
"Errors defining module:\n{}\n\nUncomment things nearby to see more details.",
errors.to_string()
);
}
let lib = module_to_dylib(env.module, &target, opt_level)
.expect("Error loading compiled dylib for test");
let app = CliReplApp { lib };
let res_answer = jit_to_ast(
&arena,
&app,
main_fn_name,
main_fn_layout,
content,
&env.interns,
home,
&subs,
target_info,
);
let formatted = format_answer(&arena, res_answer, expr_type_str);
Ok(formatted)
}
fn eval_and_format<'a>(src: &str) -> Result<String, SyntaxError<'a>> {
let format_output = |output| match output {
ReplOutput::NoProblems { expr, expr_type } => {
format!("\n{} {}:{} {}", expr, PINK, END_COL, expr_type)
}
ReplOutput::Problems(lines) => format!("\n{}\n", lines.join("\n\n")),
};
gen_and_eval_llvm(src, Triple::host(), OptLevel::Normal).map(format_output)
}
fn report_parse_error(fail: SyntaxError) {
println!("TODO Gracefully report parse error in repl: {:?}", fail);
}
pub fn main() -> io::Result<()> {
use rustyline::error::ReadlineError;
use rustyline::Editor;
@ -222,19 +438,3 @@ pub fn main() -> io::Result<()> {
Ok(())
}
fn report_parse_error(fail: SyntaxError) {
println!("TODO Gracefully report parse error in repl: {:?}", fail);
}
fn eval_and_format<'a>(src: &str) -> Result<String, SyntaxError<'a>> {
use roc_mono::ir::OptLevel;
use target_lexicon::Triple;
gen_and_eval(src.as_bytes(), Triple::host(), OptLevel::Normal).map(|output| match output {
ReplOutput::NoProblems { expr, expr_type } => {
format!("\n{} {}:{} {}", expr, PINK, END_COL, expr_type)
}
ReplOutput::Problems(lines) => format!("\n{}\n", lines.join("\n\n")),
})
}

View file

@ -5,22 +5,13 @@ version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["llvm"]
llvm = ["inkwell", "libloading", "roc_gen_llvm", "roc_build/llvm"]
[dependencies]
bumpalo = {version = "3.8.0", features = ["collections"]}
inkwell = {path = "../vendor/inkwell", optional = true}
libloading = {version = "0.7.1", optional = true}
target-lexicon = "0.12.2"
roc_build = {path = "../compiler/build", default-features = false}
roc_builtins = {path = "../compiler/builtins"}
roc_can = {path = "../compiler/can"}
roc_collections = {path = "../compiler/collections"}
roc_fmt = {path = "../compiler/fmt"}
roc_gen_llvm = {path = "../compiler/gen_llvm", optional = true}
roc_load = {path = "../compiler/load"}
roc_module = {path = "../compiler/module"}
roc_mono = {path = "../compiler/mono"}

View file

@ -1,159 +0,0 @@
use std::mem::size_of;
pub trait AppMemory {
fn deref_bool(&self, addr: usize) -> bool;
fn deref_u8(&self, addr: usize) -> u8;
fn deref_u16(&self, addr: usize) -> u16;
fn deref_u32(&self, addr: usize) -> u32;
fn deref_u64(&self, addr: usize) -> u64;
fn deref_u128(&self, addr: usize) -> u128;
fn deref_usize(&self, addr: usize) -> usize;
fn deref_i8(&self, addr: usize) -> i8;
fn deref_i16(&self, addr: usize) -> i16;
fn deref_i32(&self, addr: usize) -> i32;
fn deref_i64(&self, addr: usize) -> i64;
fn deref_i128(&self, addr: usize) -> i128;
fn deref_isize(&self, addr: usize) -> isize;
fn deref_f32(&self, addr: usize) -> f32;
fn deref_f64(&self, addr: usize) -> f64;
fn deref_str(&self, addr: usize) -> &str;
}
/// A block of app memory in the same address space as the compiler
pub struct AppMemoryInternal;
macro_rules! internal_number_type {
($name: ident, $t: ty) => {
fn $name(&self, addr: usize) -> $t {
let ptr = addr as *const _;
unsafe { *ptr }
}
};
}
impl AppMemory for AppMemoryInternal {
internal_number_type!(deref_bool, bool);
internal_number_type!(deref_u8, u8);
internal_number_type!(deref_u16, u16);
internal_number_type!(deref_u32, u32);
internal_number_type!(deref_u64, u64);
internal_number_type!(deref_u128, u128);
internal_number_type!(deref_usize, usize);
internal_number_type!(deref_i8, i8);
internal_number_type!(deref_i16, i16);
internal_number_type!(deref_i32, i32);
internal_number_type!(deref_i64, i64);
internal_number_type!(deref_i128, i128);
internal_number_type!(deref_isize, isize);
internal_number_type!(deref_f32, f32);
internal_number_type!(deref_f64, f64);
fn deref_str(&self, addr: usize) -> &str {
unsafe { *(addr as *const &'static str) }
}
}
/// A block of app memory copied from an exteral address space outside the compiler
/// (e.g. compiler and app are in separate Wasm modules)
pub struct AppMemoryExternal<'a> {
bytes: &'a [u8],
}
macro_rules! external_number_type {
($name: ident, $t: ty) => {
fn $name(&self, address: usize) -> $t {
const N: usize = size_of::<$t>();
let mut array = [0; N];
array.copy_from_slice(&self.bytes[address..][..N]);
<$t>::from_le_bytes(array)
}
};
}
impl<'a> AppMemory for AppMemoryExternal<'a> {
fn deref_bool(&self, address: usize) -> bool {
self.bytes[address] != 0
}
external_number_type!(deref_u8, u8);
external_number_type!(deref_u16, u16);
external_number_type!(deref_u32, u32);
external_number_type!(deref_u64, u64);
external_number_type!(deref_u128, u128);
external_number_type!(deref_usize, usize);
external_number_type!(deref_i8, i8);
external_number_type!(deref_i16, i16);
external_number_type!(deref_i32, i32);
external_number_type!(deref_i64, i64);
external_number_type!(deref_i128, i128);
external_number_type!(deref_isize, isize);
external_number_type!(deref_f32, f32);
external_number_type!(deref_f64, f64);
fn deref_str(&self, addr: usize) -> &str {
let elems_addr = self.deref_usize(addr);
let len = self.deref_usize(addr + size_of::<usize>());
let bytes = &self.bytes[elems_addr..][..len];
std::str::from_utf8(bytes).unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn internal_u8() {
let value: u8 = 123;
let ptr = &value as *const u8;
let addr = ptr as usize;
let memory = AppMemoryInternal;
let recovered: u8 = memory.deref_u8(addr);
assert_eq!(value, recovered);
}
#[test]
fn external_u8() {
let value: u8 = 123;
let memory = AppMemoryExternal {
bytes: &[0, 0, value, 0, 0],
};
let addr = 2;
let recovered: u8 = memory.deref_u8(addr);
assert_eq!(value, recovered);
}
#[test]
fn internal_i64() {
let value: i64 = -123 << 33;
let ptr = &value as *const i64;
let addr = ptr as usize;
let memory = AppMemoryInternal;
let recovered: i64 = memory.deref_i64(addr);
assert_eq!(value, recovered);
}
#[test]
fn external_i64() {
let value: i64 = -1 << 33;
let memory = AppMemoryExternal {
bytes: &[
0, 0, //
0, 0, 0, 0, 0xfe, 0xff, 0xff, 0xff, //
0, 0,
],
};
let addr = 2;
let recovered: i64 = memory.deref_i64(addr);
assert_eq!(value, recovered);
}
}

View file

@ -16,18 +16,12 @@ use roc_region::all::{Loc, Region};
use roc_target::TargetInfo;
use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable};
#[cfg(feature = "llvm")]
use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type};
use crate::ReplApp;
#[cfg(feature = "llvm")]
type AppExecutable = libloading::Library;
use super::app_memory::AppMemory;
struct Env<'a, 'env, M> {
struct Env<'a, 'env, A> {
arena: &'a Bump,
subs: &'env Subs,
app_memory: &'a M,
app: &'a A,
target_info: TargetInfo,
interns: &'env Interns,
home: ModuleId,
@ -46,9 +40,9 @@ pub enum ToAstProblem {
/// we get to a struct or tag, we know what the labels are and can turn them
/// back into the appropriate user-facing literals.
#[allow(clippy::too_many_arguments)]
pub unsafe fn jit_to_ast<'a, M: AppMemory>(
pub fn jit_to_ast<'a, A: ReplApp>(
arena: &'a Bump,
app: AppExecutable,
app: &'a A,
main_fn_name: &str,
layout: ProcLayout<'a>,
content: &'a Content,
@ -56,12 +50,11 @@ pub unsafe fn jit_to_ast<'a, M: AppMemory>(
home: ModuleId,
subs: &'a Subs,
target_info: TargetInfo,
app_memory: &'a M,
) -> Result<Expr<'a>, ToAstProblem> {
let env = Env {
arena,
subs,
app_memory,
app,
target_info,
interns,
home,
@ -73,7 +66,7 @@ pub unsafe fn jit_to_ast<'a, M: AppMemory>(
result,
} => {
// this is a thunk
jit_to_ast_help(&env, app, main_fn_name, &result, content)
jit_to_ast_help(&env, main_fn_name, &result, content)
}
_ => Err(ToAstProblem::FunctionLayout),
}
@ -93,8 +86,8 @@ enum NewtypeKind<'a> {
///
/// The returned list of newtype containers is ordered by increasing depth. As an example,
/// `A ({b : C 123})` will have the unrolled list `[Tag(A), RecordField(b), Tag(C)]`.
fn unroll_newtypes<'a, M: AppMemory>(
env: &Env<'a, 'a, M>,
fn unroll_newtypes<'a, A: ReplApp>(
env: &Env<'a, 'a, A>,
mut content: &'a Content,
) -> (Vec<'a, NewtypeKind<'a>>, &'a Content) {
let mut newtype_containers = Vec::with_capacity_in(1, env.arena);
@ -127,8 +120,8 @@ fn unroll_newtypes<'a, M: AppMemory>(
}
}
fn apply_newtypes<'a, M: AppMemory>(
env: &Env<'a, '_, M>,
fn apply_newtypes<'a, A: ReplApp>(
env: &Env<'a, '_, A>,
newtype_containers: Vec<'a, NewtypeKind<'a>>,
mut expr: Expr<'a>,
) -> Expr<'a> {
@ -155,15 +148,15 @@ fn apply_newtypes<'a, M: AppMemory>(
expr
}
fn unroll_aliases<'a, M: AppMemory>(env: &Env<'a, 'a, M>, mut content: &'a Content) -> &'a Content {
fn unroll_aliases<'a, A: ReplApp>(env: &Env<'a, 'a, A>, mut content: &'a Content) -> &'a Content {
while let Content::Alias(_, _, real) = content {
content = env.subs.get_content_without_compacting(*real);
}
content
}
fn unroll_recursion_var<'a, M: AppMemory>(
env: &Env<'a, 'a, M>,
fn unroll_recursion_var<'a, A: ReplApp>(
env: &Env<'a, 'a, A>,
mut content: &'a Content,
) -> &'a Content {
while let Content::RecursionVar { structure, .. } = content {
@ -172,8 +165,8 @@ fn unroll_recursion_var<'a, M: AppMemory>(
content
}
fn get_tags_vars_and_variant<'a, M: AppMemory>(
env: &Env<'a, '_, M>,
fn get_tags_vars_and_variant<'a, A: ReplApp>(
env: &Env<'a, '_, A>,
tags: &UnionTags,
opt_rec_var: Option<Variable>,
) -> (MutMap<TagName, std::vec::Vec<Variable>>, UnionVariant<'a>) {
@ -190,8 +183,8 @@ fn get_tags_vars_and_variant<'a, M: AppMemory>(
(vars_of_tag, union_variant)
}
fn expr_of_tag<'a, M: AppMemory>(
env: &Env<'a, 'a, M>,
fn expr_of_tag<'a, A: ReplApp>(
env: &Env<'a, 'a, A>,
data_addr: usize,
tag_name: &TagName,
arg_layouts: &'a [Layout<'a>],
@ -213,8 +206,8 @@ fn expr_of_tag<'a, M: AppMemory>(
/// Gets the tag ID of a union variant, assuming that the tag ID is stored alongside (after) the
/// tag data. The caller is expected to check that the tag ID is indeed stored this way.
fn tag_id_from_data<'a, M: AppMemory>(
env: &Env<'a, 'a, M>,
fn tag_id_from_data<'a, A: ReplApp>(
env: &Env<'a, 'a, A>,
union_layout: UnionLayout,
data_addr: usize,
) -> i64 {
@ -224,13 +217,13 @@ fn tag_id_from_data<'a, M: AppMemory>(
let tag_id_addr = data_addr + offset as usize;
match union_layout.tag_id_builtin() {
Builtin::Bool => env.app_memory.deref_bool(tag_id_addr) as i64,
Builtin::Int(IntWidth::U8) => env.app_memory.deref_u8(tag_id_addr) as i64,
Builtin::Int(IntWidth::U16) => env.app_memory.deref_u16(tag_id_addr) as i64,
Builtin::Bool => env.app.deref_bool(tag_id_addr) as i64,
Builtin::Int(IntWidth::U8) => env.app.deref_u8(tag_id_addr) as i64,
Builtin::Int(IntWidth::U16) => env.app.deref_u16(tag_id_addr) as i64,
Builtin::Int(IntWidth::U64) => {
// used by non-recursive unions at the
// moment, remove if that is no longer the case
env.app_memory.deref_i64(tag_id_addr)
env.app.deref_i64(tag_id_addr)
}
_ => unreachable!("invalid tag id layout"),
}
@ -240,13 +233,13 @@ fn tag_id_from_data<'a, M: AppMemory>(
/// pointer to the data of the union variant). Returns
/// - the tag ID
/// - the address of the data of the union variant, unmasked if the pointer held the tag ID
fn tag_id_from_recursive_ptr<'a, M: AppMemory>(
env: &Env<'a, 'a, M>,
fn tag_id_from_recursive_ptr<'a, A: ReplApp>(
env: &Env<'a, 'a, A>,
union_layout: UnionLayout,
rec_addr: usize,
) -> (i64, usize) {
let tag_in_ptr = union_layout.stores_tag_id_in_pointer(env.target_info);
let addr_with_id = env.app_memory.deref_usize(rec_addr);
let addr_with_id = env.app.deref_usize(rec_addr);
if tag_in_ptr {
let (_, tag_id_mask) = UnionLayout::tag_id_pointer_bits_and_mask(env.target_info);
@ -264,9 +257,8 @@ const OPAQUE_FUNCTION: Expr = Expr::Var {
ident: "<function>",
};
fn jit_to_ast_help<'a, M: AppMemory>(
env: &Env<'a, 'a, M>,
app: AppExecutable,
fn jit_to_ast_help<'a, A: ReplApp>(
env: &Env<'a, 'a, A>,
main_fn_name: &str,
layout: &Layout<'a>,
content: &'a Content,
@ -274,26 +266,25 @@ fn jit_to_ast_help<'a, M: AppMemory>(
let (newtype_containers, content) = unroll_newtypes(env, content);
let content = unroll_aliases(env, content);
let result = match layout {
Layout::Builtin(Builtin::Bool) => Ok(run_jit_function!(app, main_fn_name, bool, |num| {
bool_to_ast(env, num, content)
})),
Layout::Builtin(Builtin::Bool) => Ok(env
.app
.call_function(main_fn_name, |num: bool| bool_to_ast(env, num, content))),
Layout::Builtin(Builtin::Int(int_width)) => {
use IntWidth::*;
macro_rules! helper {
($ty:ty) => {
run_jit_function!(app, main_fn_name, $ty, |num| num_to_ast(
env,
number_literal_to_ast(env.arena, num),
content
))
env.app.call_function(main_fn_name, |num: $ty| {
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
})
};
}
let result = match int_width {
U8 | I8 => {
// NOTE: this is does not handle 8-bit numbers yet
run_jit_function!(app, main_fn_name, u8, |num| byte_to_ast(env, num, content))
// NOTE: `helper!` does not handle 8-bit numbers yet
env.app
.call_function(main_fn_name, |num: u8| byte_to_ast(env, num, content))
}
U16 => helper!(u16),
U32 => helper!(u32),
@ -312,11 +303,9 @@ fn jit_to_ast_help<'a, M: AppMemory>(
macro_rules! helper {
($ty:ty) => {
run_jit_function!(app, main_fn_name, $ty, |num| num_to_ast(
env,
number_literal_to_ast(env.arena, num),
content
))
env.app.call_function(main_fn_name, |num: $ty| {
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
})
};
}
@ -328,18 +317,16 @@ fn jit_to_ast_help<'a, M: AppMemory>(
Ok(result)
}
Layout::Builtin(Builtin::Str) => Ok(run_jit_function!(
app,
main_fn_name,
&'static str,
|string: &'static str| { str_to_ast(env.arena, env.arena.alloc(string)) }
)),
Layout::Builtin(Builtin::List(elem_layout)) => Ok(run_jit_function!(
app,
main_fn_name,
(usize, usize),
|(addr, len): (usize, usize)| { list_to_ast(env, addr, len, elem_layout, content) }
)),
Layout::Builtin(Builtin::Str) => {
Ok(env.app.call_function(main_fn_name, |string: &'static str| {
str_to_ast(env.arena, env.arena.alloc(string))
}))
}
Layout::Builtin(Builtin::List(elem_layout)) => Ok(env
.app
.call_function(main_fn_name, |(addr, len): (usize, usize)| {
list_to_ast(env, addr, len, elem_layout, content)
})),
Layout::Builtin(other) => {
todo!("add support for rendering builtin {:?} to the REPL", other)
}
@ -395,37 +382,30 @@ fn jit_to_ast_help<'a, M: AppMemory>(
let result_stack_size = layout.stack_size(env.target_info);
run_jit_function_dynamic_type!(
app,
env.app.call_function_dynamic_size(
main_fn_name,
result_stack_size as usize,
|bytes_addr: usize| { struct_addr_to_ast(bytes_addr as usize) }
struct_addr_to_ast,
)
}
Layout::Union(UnionLayout::NonRecursive(_)) => {
let size = layout.stack_size(env.target_info);
Ok(run_jit_function_dynamic_type!(
app,
main_fn_name,
size as usize,
|addr: usize| {
Ok(env
.app
.call_function_dynamic_size(main_fn_name, size as usize, |addr: usize| {
addr_to_ast(env, addr, layout, WhenRecursive::Unreachable, content)
}
))
}))
}
Layout::Union(UnionLayout::Recursive(_))
| Layout::Union(UnionLayout::NonNullableUnwrapped(_))
| Layout::Union(UnionLayout::NullableUnwrapped { .. })
| Layout::Union(UnionLayout::NullableWrapped { .. }) => {
let size = layout.stack_size(env.target_info);
Ok(run_jit_function_dynamic_type!(
app,
main_fn_name,
size as usize,
|addr: usize| {
Ok(env
.app
.call_function_dynamic_size(main_fn_name, size as usize, |addr: usize| {
addr_to_ast(env, addr, layout, WhenRecursive::Loop(*layout), content)
}
))
}))
}
Layout::RecursivePointer => {
unreachable!("RecursivePointers can only be inside structures")
@ -435,7 +415,7 @@ fn jit_to_ast_help<'a, M: AppMemory>(
result.map(|e| apply_newtypes(env, newtype_containers, e))
}
fn tag_name_to_expr<'a, M: AppMemory>(env: &Env<'a, '_, M>, tag_name: &TagName) -> Expr<'a> {
fn tag_name_to_expr<'a, A: ReplApp>(env: &Env<'a, '_, A>, tag_name: &TagName) -> Expr<'a> {
match tag_name {
TagName::Global(_) => Expr::GlobalTag(
env.arena
@ -457,8 +437,8 @@ enum WhenRecursive<'a> {
Loop(Layout<'a>),
}
fn addr_to_ast<'a, M: AppMemory>(
env: &Env<'a, 'a, M>,
fn addr_to_ast<'a, A: ReplApp>(
env: &Env<'a, 'a, A>,
addr: usize,
layout: &Layout<'a>,
when_recursive: WhenRecursive<'a>,
@ -466,7 +446,7 @@ fn addr_to_ast<'a, M: AppMemory>(
) -> Expr<'a> {
macro_rules! helper {
($method: ident, $ty: ty) => {{
let num: $ty = env.app_memory.$method(addr);
let num: $ty = env.app.$method(addr);
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
}};
@ -480,7 +460,7 @@ fn addr_to_ast<'a, M: AppMemory>(
(_, Layout::Builtin(Builtin::Bool)) => {
// TODO: bits are not as expected here.
// num is always false at the moment.
let num: bool = env.app_memory.deref_bool(addr);
let num: bool = env.app.deref_bool(addr);
bool_to_ast(env, num, content)
}
@ -510,13 +490,13 @@ fn addr_to_ast<'a, M: AppMemory>(
}
}
(_, Layout::Builtin(Builtin::List(elem_layout))) => {
let elem_addr = env.app_memory.deref_usize(addr);
let len = env.app_memory.deref_usize(addr + env.target_info.ptr_width() as usize);
let elem_addr = env.app.deref_usize(addr);
let len = env.app.deref_usize(addr + env.target_info.ptr_width() as usize);
list_to_ast(env, elem_addr, len, elem_layout, content)
}
(_, Layout::Builtin(Builtin::Str)) => {
let arena_str = env.app_memory.deref_str(addr);
let arena_str = env.app.deref_str(addr);
str_to_ast(env.arena, arena_str)
}
@ -636,7 +616,7 @@ fn addr_to_ast<'a, M: AppMemory>(
_ => unreachable!("any other variant would have a different layout"),
};
let data_addr = env.app_memory.deref_usize(addr);
let data_addr = env.app.deref_usize(addr);
expr_of_tag(
env,
@ -666,7 +646,7 @@ fn addr_to_ast<'a, M: AppMemory>(
_ => unreachable!("any other variant would have a different layout"),
};
let data_addr = env.app_memory.deref_usize(addr);
let data_addr = env.app.deref_usize(addr);
if data_addr == 0 {
tag_name_to_expr(env, &nullable_name)
} else {
@ -697,7 +677,7 @@ fn addr_to_ast<'a, M: AppMemory>(
_ => unreachable!("any other variant would have a different layout"),
};
let data_addr = env.app_memory.deref_usize(addr);
let data_addr = env.app.deref_usize(addr);
if data_addr == 0 {
tag_name_to_expr(env, &nullable_name)
} else {
@ -726,8 +706,8 @@ fn addr_to_ast<'a, M: AppMemory>(
apply_newtypes(env, newtype_containers, expr)
}
fn list_to_ast<'a, M: AppMemory>(
env: &Env<'a, 'a, M>,
fn list_to_ast<'a, A: ReplApp>(
env: &Env<'a, 'a, A>,
addr: usize,
len: usize,
elem_layout: &Layout<'a>,
@ -775,8 +755,8 @@ fn list_to_ast<'a, M: AppMemory>(
Expr::List(Collection::with_items(output))
}
fn single_tag_union_to_ast<'a, M: AppMemory>(
env: &Env<'a, 'a, M>,
fn single_tag_union_to_ast<'a, A: ReplApp>(
env: &Env<'a, 'a, A>,
addr: usize,
field_layouts: &'a [Layout<'a>],
tag_name: &TagName,
@ -801,8 +781,8 @@ fn single_tag_union_to_ast<'a, M: AppMemory>(
Expr::Apply(loc_tag_expr, output, CalledVia::Space)
}
fn sequence_of_expr<'a, I, M: AppMemory>(
env: &Env<'a, 'a, M>,
fn sequence_of_expr<'a, I, A: ReplApp>(
env: &Env<'a, 'a, A>,
addr: usize,
sequence: I,
when_recursive: WhenRecursive<'a>,
@ -832,8 +812,8 @@ where
output
}
fn struct_to_ast<'a, M: AppMemory>(
env: &Env<'a, 'a, M>,
fn struct_to_ast<'a, A: ReplApp>(
env: &Env<'a, 'a, A>,
addr: usize,
field_layouts: &'a [Layout<'a>],
record_fields: RecordFields,
@ -949,7 +929,7 @@ fn unpack_two_element_tag_union(
(tag_name1, payload_vars1, tag_name2, payload_vars2)
}
fn bool_to_ast<'a, M: AppMemory>(env: &Env<'a, '_, M>, value: bool, content: &Content) -> Expr<'a> {
fn bool_to_ast<'a, A: ReplApp>(env: &Env<'a, '_, A>, value: bool, content: &Content) -> Expr<'a> {
use Content::*;
let arena = env.arena;
@ -1027,7 +1007,7 @@ fn bool_to_ast<'a, M: AppMemory>(env: &Env<'a, '_, M>, value: bool, content: &Co
}
}
fn byte_to_ast<'a, M: AppMemory>(env: &Env<'a, '_, M>, value: u8, content: &Content) -> Expr<'a> {
fn byte_to_ast<'a, A: ReplApp>(env: &Env<'a, '_, A>, value: u8, content: &Content) -> Expr<'a> {
use Content::*;
let arena = env.arena;
@ -1119,8 +1099,8 @@ fn byte_to_ast<'a, M: AppMemory>(env: &Env<'a, '_, M>, value: u8, content: &Cont
}
}
fn num_to_ast<'a, M: AppMemory>(
env: &Env<'a, '_, M>,
fn num_to_ast<'a, A: ReplApp>(
env: &Env<'a, '_, A>,
num_expr: Expr<'a>,
content: &Content,
) -> Expr<'a> {
@ -1199,15 +1179,15 @@ fn number_literal_to_ast<T: std::fmt::Display>(arena: &Bump, num: T) -> Expr<'_>
}
#[cfg(target_endian = "little")]
#[cfg(target_pointer_width = "64")]
/// TODO implement this for 32-bit and big-endian targets. NOTE: As of this writing,
/// we don't have big-endian small strings implemented yet!
/// NOTE: As of this writing, we don't have big-endian small strings implemented yet!
fn str_to_ast<'a>(arena: &'a Bump, string: &'a str) -> Expr<'a> {
let bytes: [u8; 16] = unsafe { std::mem::transmute::<&'a str, [u8; 16]>(string) };
let is_small = (bytes[15] & 0b1000_0000) != 0;
const STR_SIZE: usize = 2 * std::mem::size_of::<usize>();
let bytes: [u8; STR_SIZE] = unsafe { std::mem::transmute(string) };
let is_small = (bytes[STR_SIZE - 1] & 0b1000_0000) != 0;
if is_small {
let len = (bytes[15] & 0b0111_1111) as usize;
let len = (bytes[STR_SIZE - 1] & 0b0111_1111) as usize;
let mut string = bumpalo::collections::String::with_capacity_in(len, arena);
for byte in bytes.iter().take(len) {

Some files were not shown because too many files have changed in this diff Show more