mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-21 15:59:20 +03:00
Merge remote-tracking branch 'origin/trunk' into decimal-literals
This commit is contained in:
commit
5529841d68
2
.github/workflows/spellcheck.yml
vendored
2
.github/workflows/spellcheck.yml
vendored
@ -8,7 +8,7 @@ env:
|
||||
jobs:
|
||||
spell-check:
|
||||
name: spell check
|
||||
runs-on: [self-hosted]
|
||||
runs-on: [self-hosted, linux]
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
2
.github/workflows/www.yml
vendored
2
.github/workflows/www.yml
vendored
@ -9,7 +9,7 @@ on:
|
||||
jobs:
|
||||
deploy:
|
||||
name: 'Deploy to Netlify'
|
||||
runs-on: [self-hosted]
|
||||
runs-on: [self-hosted, linux]
|
||||
steps:
|
||||
- uses: jsmrcaga/action-netlify-deploy@v1.6.0
|
||||
with:
|
||||
|
5
AUTHORS
5
AUTHORS
@ -51,3 +51,8 @@ Eric Newbury <enewbury@users.noreply.github.com>
|
||||
Ayaz Hafiz <ayaz.hafiz.1@gmail.com>
|
||||
Johannes Maas <github@j-maas.de>
|
||||
Takeshi Sato <doublequotation@gmail.com>
|
||||
Joost Baas <joost@joostbaas.eu>
|
||||
Callum Dunster <cdunster@users.noreply.github.com>
|
||||
Martin Stewart <MartinSStewart@gmail.com>
|
||||
James Hegedus <jthegedus@hey.com>
|
||||
Cristiano Piemontese <cristiano.piemontese@vidiemme.it>
|
||||
|
@ -94,13 +94,7 @@ Install nix:
|
||||
|
||||
`curl -L https://nixos.org/nix/install | sh`
|
||||
|
||||
If you're on MacOS and using a OS version >= 10.15:
|
||||
|
||||
`sh <(curl -L https://nixos.org/nix/install) --darwin-use-unencrypted-nix-store-volume`
|
||||
|
||||
You may prefer to setup up the volume manually by following nix documentation.
|
||||
|
||||
> You may need to restart your terminal
|
||||
You will need to start a fresh terminal session to use nix.
|
||||
|
||||
### Usage
|
||||
|
||||
|
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -3467,6 +3467,7 @@ dependencies = [
|
||||
"roc_std",
|
||||
"roc_types",
|
||||
"roc_unify",
|
||||
"static_assertions",
|
||||
"ven_graph",
|
||||
"ven_pretty",
|
||||
]
|
||||
@ -3475,7 +3476,9 @@ dependencies = [
|
||||
name = "roc_parse"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"bumpalo",
|
||||
"diff",
|
||||
"encode_unicode",
|
||||
"indoc",
|
||||
"pretty_assertions",
|
||||
@ -3551,6 +3554,7 @@ version = "0.1.0"
|
||||
name = "roc_types"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"roc_collections",
|
||||
"roc_module",
|
||||
"roc_region",
|
||||
|
@ -12,7 +12,6 @@ members = [
|
||||
"compiler/constrain",
|
||||
"compiler/unify",
|
||||
"compiler/solve",
|
||||
"compiler/reporting",
|
||||
"compiler/fmt",
|
||||
"compiler/mono",
|
||||
"compiler/test_mono",
|
||||
@ -31,6 +30,7 @@ members = [
|
||||
"ast",
|
||||
"cli",
|
||||
"code_markup",
|
||||
"reporting",
|
||||
"roc_std",
|
||||
"utils",
|
||||
"docs",
|
||||
|
12
Earthfile
12
Earthfile
@ -47,7 +47,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
|
||||
|
||||
copy-dirs:
|
||||
FROM +install-zig-llvm-valgrind-clippy-rustfmt
|
||||
COPY --dir cli cli_utils compiler docs editor ast code_markup utils roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
|
||||
COPY --dir cli cli_utils compiler docs editor ast code_markup utils reporting roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
|
||||
|
||||
test-zig:
|
||||
FROM +install-zig-llvm-valgrind-clippy-rustfmt
|
||||
@ -79,17 +79,17 @@ test-rust:
|
||||
# not pre-compiling the host can cause race conditions
|
||||
RUN echo "4" | cargo run --release examples/benchmarks/NQueens.roc
|
||||
RUN --mount=type=cache,target=$SCCACHE_DIR \
|
||||
cargo test --release --features with_sound --workspace && sccache --show-stats
|
||||
cargo test --locked --release --features with_sound --workspace && sccache --show-stats
|
||||
# test the dev and wasm backend: they require an explicit feature flag.
|
||||
RUN --mount=type=cache,target=$SCCACHE_DIR \
|
||||
cargo test --release --package test_gen --no-default-features --features gen-dev && sccache --show-stats
|
||||
cargo test --locked --release --package test_gen --no-default-features --features gen-dev && sccache --show-stats
|
||||
# gen-wasm has some multithreading problems to do with the wasmer runtime. Run it single-threaded as a separate job
|
||||
RUN --mount=type=cache,target=$SCCACHE_DIR \
|
||||
cargo test --release --package test_gen --no-default-features --features gen-wasm -- --test-threads=1 && sccache --show-stats
|
||||
cargo test --locked --release --package test_gen --no-default-features --features gen-wasm -- --test-threads=1 && sccache --show-stats
|
||||
# run i386 (32-bit linux) cli tests
|
||||
RUN echo "4" | cargo run --release --features="target-x86" -- --backend=x86_32 examples/benchmarks/NQueens.roc
|
||||
RUN echo "4" | cargo run --locked --release --features="target-x86" -- --backend=x86_32 examples/benchmarks/NQueens.roc
|
||||
RUN --mount=type=cache,target=$SCCACHE_DIR \
|
||||
cargo test --release --features with_sound --test cli_run i386 --features="i386-cli-run" && sccache --show-stats
|
||||
cargo test --locked --release --features with_sound --test cli_run i386 --features="i386-cli-run" && sccache --show-stats
|
||||
|
||||
verify-no-git-changes:
|
||||
FROM +test-rust
|
||||
|
@ -20,6 +20,7 @@ use crate::{
|
||||
expr2::{ClosureExtra, Expr2, ExprId, WhenBranch},
|
||||
record_field::RecordField,
|
||||
},
|
||||
fun_def::FunctionDef,
|
||||
pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct},
|
||||
types::{Type2, TypeId},
|
||||
val_def::ValueDef,
|
||||
@ -276,7 +277,7 @@ pub fn constrain_expr<'a>(
|
||||
expr_id: expr_node_id,
|
||||
closure_var,
|
||||
fn_var,
|
||||
..
|
||||
called_via,
|
||||
} => {
|
||||
// The expression that evaluates to the function being called, e.g. `foo` in
|
||||
// (foo) bar baz
|
||||
@ -348,7 +349,7 @@ pub fn constrain_expr<'a>(
|
||||
region,
|
||||
);
|
||||
|
||||
let category = Category::CallResult(opt_symbol);
|
||||
let category = Category::CallResult(opt_symbol, *called_via);
|
||||
|
||||
let mut and_constraints = BumpVec::with_capacity_in(4, arena);
|
||||
|
||||
@ -818,6 +819,126 @@ pub fn constrain_expr<'a>(
|
||||
}
|
||||
}
|
||||
}
|
||||
// In an expression like
|
||||
// id = \x -> x
|
||||
//
|
||||
// id 1
|
||||
// The `def_id` refers to the definition `id = \x -> x`,
|
||||
// and the body refers to `id 1`.
|
||||
Expr2::LetFunction {
|
||||
def_id,
|
||||
body_id,
|
||||
body_var: _,
|
||||
} => {
|
||||
let body = env.pool.get(*body_id);
|
||||
let body_con = constrain_expr(arena, env, body, expected.shallow_clone(), region);
|
||||
|
||||
let function_def = env.pool.get(*def_id);
|
||||
|
||||
let (name, arguments, body_id, rigid_vars, args_constrs) = match function_def {
|
||||
FunctionDef::WithAnnotation {
|
||||
name,
|
||||
arguments,
|
||||
body_id,
|
||||
rigids,
|
||||
return_type: _,
|
||||
} => {
|
||||
// The annotation gives us arguments with proper Type2s, but the constraints we
|
||||
// generate below args bound to type variables. Create fresh ones and bind them
|
||||
// to the types we already know.
|
||||
let mut args_constrs = BumpVec::with_capacity_in(arguments.len(), arena);
|
||||
let args_vars = PoolVec::with_capacity(arguments.len() as u32, env.pool);
|
||||
for (arg_ty_node_id, arg_var_node_id) in
|
||||
arguments.iter_node_ids().zip(args_vars.iter_node_ids())
|
||||
{
|
||||
let (ty, pattern) = env.pool.get(arg_ty_node_id);
|
||||
let arg_var = env.var_store.fresh();
|
||||
let ty = env.pool.get(*ty);
|
||||
args_constrs.push(Eq(
|
||||
Type2::Variable(arg_var),
|
||||
Expected::NoExpectation(ty.shallow_clone()),
|
||||
Category::Storage(std::file!(), std::line!()),
|
||||
// TODO: should be the actual region of the argument
|
||||
region,
|
||||
));
|
||||
env.pool[arg_var_node_id] = (arg_var, *pattern);
|
||||
}
|
||||
|
||||
let rigids = env.pool.get(*rigids);
|
||||
let rigid_vars: BumpVec<Variable> =
|
||||
BumpVec::from_iter_in(rigids.names.iter(env.pool).map(|&(_, v)| v), arena);
|
||||
|
||||
(name, args_vars, body_id, rigid_vars, args_constrs)
|
||||
}
|
||||
FunctionDef::NoAnnotation {
|
||||
name,
|
||||
arguments,
|
||||
body_id,
|
||||
return_var: _,
|
||||
} => {
|
||||
(
|
||||
name,
|
||||
arguments.shallow_clone(),
|
||||
body_id,
|
||||
BumpVec::new_in(arena), // The function is unannotated, so there are no rigid type vars
|
||||
BumpVec::new_in(arena), // No extra constraints to generate for arguments
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// A function definition is equivalent to a named value definition, where the
|
||||
// value is a closure. So, we create a closure definition in correspondence
|
||||
// with the function definition, generate type constraints for it, and demand
|
||||
// that type of the function is just the type of the resolved closure.
|
||||
let fn_var = env.var_store.fresh();
|
||||
let fn_ty = Type2::Variable(fn_var);
|
||||
|
||||
let extra = ClosureExtra {
|
||||
return_type: env.var_store.fresh(),
|
||||
captured_symbols: PoolVec::empty(env.pool),
|
||||
closure_type: env.var_store.fresh(),
|
||||
closure_ext_var: env.var_store.fresh(),
|
||||
};
|
||||
let clos = Expr2::Closure {
|
||||
args: arguments.shallow_clone(),
|
||||
uniq_symbol: *name,
|
||||
body_id: *body_id,
|
||||
function_type: env.var_store.fresh(),
|
||||
extra: env.pool.add(extra),
|
||||
recursive: roc_can::expr::Recursive::Recursive,
|
||||
};
|
||||
let clos_con = constrain_expr(
|
||||
arena,
|
||||
env,
|
||||
&clos,
|
||||
Expected::NoExpectation(fn_ty.shallow_clone()),
|
||||
region,
|
||||
);
|
||||
|
||||
// This is the `foo` part in `foo = \...`. We want to bind the name of the
|
||||
// function with its type, whose constraints we generated above.
|
||||
let mut def_pattern_state = PatternState2 {
|
||||
headers: BumpMap::new_in(arena),
|
||||
vars: BumpVec::new_in(arena),
|
||||
constraints: args_constrs,
|
||||
};
|
||||
def_pattern_state.headers.insert(*name, fn_ty);
|
||||
def_pattern_state.vars.push(fn_var);
|
||||
|
||||
Let(arena.alloc(LetConstraint {
|
||||
rigid_vars,
|
||||
flex_vars: def_pattern_state.vars,
|
||||
def_types: def_pattern_state.headers, // Binding function name -> its type
|
||||
defs_constraint: Let(arena.alloc(LetConstraint {
|
||||
rigid_vars: BumpVec::new_in(arena), // always empty
|
||||
flex_vars: BumpVec::new_in(arena), // empty, because our functions have no arguments
|
||||
def_types: BumpMap::new_in(arena), // empty, because our functions have no arguments
|
||||
defs_constraint: And(def_pattern_state.constraints),
|
||||
ret_constraint: clos_con,
|
||||
})),
|
||||
ret_constraint: body_con,
|
||||
}))
|
||||
}
|
||||
Expr2::Update {
|
||||
symbol,
|
||||
updates,
|
||||
@ -1031,7 +1152,6 @@ pub fn constrain_expr<'a>(
|
||||
exists(arena, vars, And(and_constraints))
|
||||
}
|
||||
Expr2::LetRec { .. } => todo!(),
|
||||
Expr2::LetFunction { .. } => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1765,7 +1885,7 @@ pub mod test_constrain {
|
||||
use roc_parse::parser::SyntaxError;
|
||||
use roc_region::all::Region;
|
||||
use roc_types::{
|
||||
pretty_print::content_to_string,
|
||||
pretty_print::{content_to_string, name_all_type_vars},
|
||||
solved_types::Solved,
|
||||
subs::{Subs, VarStore, Variable},
|
||||
};
|
||||
@ -1799,7 +1919,7 @@ pub mod test_constrain {
|
||||
aliases,
|
||||
};
|
||||
|
||||
let mut subs = Subs::new(var_store);
|
||||
let mut subs = Subs::new_from_varstore(var_store);
|
||||
|
||||
for (var, name) in rigid_variables {
|
||||
subs.rigid_var(var, name);
|
||||
@ -1845,7 +1965,7 @@ pub mod test_constrain {
|
||||
let expr2_result = str_to_expr2(&code_arena, actual, &mut env, &mut scope, region);
|
||||
|
||||
match expr2_result {
|
||||
Ok((expr, _)) => {
|
||||
Ok((expr, output)) => {
|
||||
let constraint = constrain_expr(
|
||||
&code_arena,
|
||||
&mut env,
|
||||
@ -1865,17 +1985,22 @@ pub mod test_constrain {
|
||||
let mut var_store = VarStore::default();
|
||||
std::mem::swap(ref_var_store, &mut var_store);
|
||||
|
||||
let rigids = output.introduced_variables.name_by_var;
|
||||
|
||||
let (mut solved, _, _) = run_solve(
|
||||
&code_arena,
|
||||
pool,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
rigids,
|
||||
constraint,
|
||||
var_store,
|
||||
);
|
||||
|
||||
let subs = solved.inner_mut();
|
||||
|
||||
// name type vars
|
||||
name_all_type_vars(var, subs);
|
||||
|
||||
let content = subs.get_content_without_compacting(var);
|
||||
|
||||
// Connect the ModuleId to it's IdentIds
|
||||
@ -2132,6 +2257,102 @@ pub mod test_constrain {
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dual_arity_lambda() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\a, b -> Pair a b
|
||||
"#
|
||||
),
|
||||
"a, b -> [ Pair a b ]*",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn anonymous_identity() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
(\a -> a) 3.14
|
||||
"#
|
||||
),
|
||||
"Float *",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn identity_of_identity() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
(\val -> val) (\val -> val)
|
||||
"#
|
||||
),
|
||||
"a -> a",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn identity_function() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\val -> val
|
||||
"#
|
||||
),
|
||||
"a -> a",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_function() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\f, x -> f x
|
||||
"#
|
||||
),
|
||||
"(a -> b), a -> b",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flip_function() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\f -> (\a, b -> f b a)
|
||||
"#
|
||||
),
|
||||
"(a, b -> c) -> (b, a -> c)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn always_function() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\val -> \_ -> val
|
||||
"#
|
||||
),
|
||||
"a -> (* -> a)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pass_a_function() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\f -> f {}
|
||||
"#
|
||||
),
|
||||
"({} -> a) -> a",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constrain_closure() {
|
||||
infer_eq(
|
||||
@ -2145,4 +2366,130 @@ pub mod test_constrain {
|
||||
"{}* -> Num *",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recursive_identity() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
identity = \val -> val
|
||||
|
||||
identity
|
||||
"#
|
||||
),
|
||||
"a -> a",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn use_apply() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
identity = \a -> a
|
||||
apply = \f, x -> f x
|
||||
|
||||
apply identity 5
|
||||
"#
|
||||
),
|
||||
"Num *",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_let_function() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
curryPair = \a ->
|
||||
getB = \b -> Pair a b
|
||||
getB
|
||||
|
||||
curryPair
|
||||
"#
|
||||
),
|
||||
"a -> (b -> [ Pair a b ]*)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_with_bound_var() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
fn = \rec ->
|
||||
x = rec.x
|
||||
|
||||
rec
|
||||
|
||||
fn
|
||||
"#
|
||||
),
|
||||
"{ x : a }b -> { x : a }b",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn using_type_signature() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
bar : custom -> custom
|
||||
bar = \x -> x
|
||||
|
||||
bar
|
||||
"#
|
||||
),
|
||||
"custom -> custom",
|
||||
);
|
||||
}
|
||||
|
||||
#[ignore = "Currently panics at 'Invalid Cycle', ast/src/lang/core/def/def.rs:1212:21"]
|
||||
#[test]
|
||||
fn using_type_signature2() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
id1 : tya -> tya
|
||||
id1 = \x -> x
|
||||
|
||||
id2 : tyb -> tyb
|
||||
id2 = id1
|
||||
|
||||
id2
|
||||
"#
|
||||
),
|
||||
"tyb -> tyb",
|
||||
);
|
||||
}
|
||||
|
||||
#[ignore = "Implement annotation-only decls"]
|
||||
#[test]
|
||||
fn type_signature_without_body() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
foo: Str -> {}
|
||||
|
||||
foo "hi"
|
||||
"#
|
||||
),
|
||||
"{}",
|
||||
);
|
||||
}
|
||||
|
||||
#[ignore = "Implement annotation-only decls"]
|
||||
#[test]
|
||||
fn type_signature_without_body_rigid() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
foo : Num * -> custom
|
||||
|
||||
foo 2
|
||||
"#
|
||||
),
|
||||
"custom",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,11 @@ use crate::{
|
||||
rigids::Rigids,
|
||||
scope::Scope,
|
||||
},
|
||||
mem_pool::{pool::Pool, pool_vec::PoolVec, shallow_clone::ShallowClone},
|
||||
mem_pool::{
|
||||
pool::{NodeId, Pool},
|
||||
pool_vec::PoolVec,
|
||||
shallow_clone::ShallowClone,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -316,7 +320,7 @@ fn from_pending_alias<'a>(
|
||||
}
|
||||
|
||||
for loc_lowercase in vars {
|
||||
if !named_rigids.contains_key(loc_lowercase.value.as_str()) {
|
||||
if !named_rigids.contains_key(&loc_lowercase.value) {
|
||||
env.problem(Problem::PhantomTypeArgument {
|
||||
alias: symbol,
|
||||
variable_region: loc_lowercase.region,
|
||||
@ -454,6 +458,10 @@ fn canonicalize_pending_def<'a>(
|
||||
output.references.referenced_aliases.insert(symbol);
|
||||
}
|
||||
|
||||
// Ensure rigid type vars and their names are known in the output.
|
||||
for (name, &var) in named_rigids.iter() {
|
||||
output.introduced_variables.insert_named(name.clone(), var);
|
||||
}
|
||||
let rigids = Rigids::new(named_rigids, unnamed_rigids, env.pool);
|
||||
|
||||
// bookkeeping for tail-call detection. If we're assigning to an
|
||||
@ -521,7 +529,7 @@ fn canonicalize_pending_def<'a>(
|
||||
// parent commit for the bug this fixed!
|
||||
let refs = References::new();
|
||||
|
||||
let arguments: PoolVec<(PatternId, Type2)> =
|
||||
let arguments: PoolVec<(NodeId<Type2>, PatternId)> =
|
||||
PoolVec::with_capacity(closure_args.len() as u32, env.pool);
|
||||
|
||||
let return_type: TypeId;
|
||||
@ -558,7 +566,8 @@ fn canonicalize_pending_def<'a>(
|
||||
for (node_id, ((_, pattern_id), typ)) in
|
||||
arguments.iter_node_ids().zip(it.into_iter())
|
||||
{
|
||||
env.pool[node_id] = (pattern_id, typ);
|
||||
let typ = env.pool.add(typ);
|
||||
env.pool[node_id] = (typ, pattern_id);
|
||||
}
|
||||
|
||||
return_type = return_type_id;
|
||||
@ -689,14 +698,14 @@ fn canonicalize_pending_def<'a>(
|
||||
// parent commit for the bug this fixed!
|
||||
let refs = References::new();
|
||||
|
||||
let arguments: PoolVec<(PatternId, Variable)> =
|
||||
let arguments: PoolVec<(Variable, PatternId)> =
|
||||
PoolVec::with_capacity(closure_args.len() as u32, env.pool);
|
||||
|
||||
let it: Vec<_> = closure_args.iter(env.pool).map(|(x, y)| (*x, *y)).collect();
|
||||
|
||||
for (node_id, (_, pattern_id)) in arguments.iter_node_ids().zip(it.into_iter())
|
||||
{
|
||||
env.pool[node_id] = (pattern_id, env.var_store.fresh());
|
||||
env.pool[node_id] = (env.var_store.fresh(), pattern_id);
|
||||
}
|
||||
|
||||
let function_def = FunctionDef::NoAnnotation {
|
||||
|
@ -6,8 +6,8 @@ use crate::{
|
||||
mem_pool::{pool::NodeId, pool_str::PoolStr, pool_vec::PoolVec},
|
||||
};
|
||||
use roc_can::expr::Recursive;
|
||||
use roc_module::called_via::CalledVia;
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::operator::CalledVia;
|
||||
use roc_module::symbol::Symbol;
|
||||
|
||||
use super::record_field::RecordField;
|
||||
|
@ -132,7 +132,7 @@ pub fn expr_to_expr2<'a>(
|
||||
|
||||
Str(literal) => flatten_str_literal(env, scope, literal),
|
||||
|
||||
List { items, .. } => {
|
||||
List(items) => {
|
||||
let mut output = Output::default();
|
||||
let output_ref = &mut output;
|
||||
|
||||
@ -185,13 +185,12 @@ pub fn expr_to_expr2<'a>(
|
||||
RecordUpdate {
|
||||
fields,
|
||||
update: loc_update,
|
||||
final_comments: _,
|
||||
} => {
|
||||
let (can_update, update_out) =
|
||||
expr_to_expr2(env, scope, &loc_update.value, loc_update.region);
|
||||
|
||||
if let Expr2::Var(symbol) = &can_update {
|
||||
match canonicalize_fields(env, scope, fields) {
|
||||
match canonicalize_fields(env, scope, fields.items) {
|
||||
Ok((can_fields, mut output)) => {
|
||||
output.references.union_mut(update_out.references);
|
||||
|
||||
@ -236,14 +235,11 @@ pub fn expr_to_expr2<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
Record {
|
||||
fields,
|
||||
final_comments: _,
|
||||
} => {
|
||||
Record(fields) => {
|
||||
if fields.is_empty() {
|
||||
(Expr2::EmptyRecord, Output::default())
|
||||
} else {
|
||||
match canonicalize_fields(env, scope, fields) {
|
||||
match canonicalize_fields(env, scope, fields.items) {
|
||||
Ok((can_fields, output)) => (
|
||||
Expr2::Record {
|
||||
record_var: env.var_store.fresh(),
|
||||
|
@ -14,15 +14,15 @@ use super::{
|
||||
#[derive(Debug)]
|
||||
pub enum FunctionDef {
|
||||
WithAnnotation {
|
||||
name: Symbol, // 8B
|
||||
arguments: PoolVec<(PatternId, Type2)>, // 8B
|
||||
rigids: NodeId<Rigids>, // 4B
|
||||
return_type: TypeId, // 4B
|
||||
body_id: ExprId, // 4B
|
||||
name: Symbol, // 8B
|
||||
arguments: PoolVec<(NodeId<Type2>, PatternId)>, // 8B
|
||||
rigids: NodeId<Rigids>, // 4B
|
||||
return_type: TypeId, // 4B
|
||||
body_id: ExprId, // 4B
|
||||
},
|
||||
NoAnnotation {
|
||||
name: Symbol, // 8B
|
||||
arguments: PoolVec<(PatternId, Variable)>, // 8B
|
||||
arguments: PoolVec<(Variable, PatternId)>, // 8B
|
||||
return_var: Variable, // 4B
|
||||
body_id: ExprId, // 4B
|
||||
},
|
||||
|
@ -2,7 +2,7 @@ pub mod ast;
|
||||
mod declaration;
|
||||
pub mod def;
|
||||
pub mod expr;
|
||||
mod fun_def;
|
||||
pub mod fun_def;
|
||||
pub mod header;
|
||||
pub mod pattern;
|
||||
pub mod str;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use roc_module::{operator::CalledVia, symbol::Symbol};
|
||||
use roc_module::{called_via::CalledVia, symbol::Symbol};
|
||||
use roc_parse::ast::StrLiteral;
|
||||
|
||||
use crate::{
|
||||
|
@ -3,7 +3,7 @@
|
||||
#![allow(unused_imports)]
|
||||
// use roc_can::expr::Output;
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_module::ident::{Ident, TagName};
|
||||
use roc_module::ident::{Ident, Lowercase, TagName};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::{Located, Region};
|
||||
use roc_types::types::{Problem, RecordField};
|
||||
@ -20,29 +20,34 @@ pub type TypeId = NodeId<Type2>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Type2 {
|
||||
Variable(Variable),
|
||||
Variable(Variable), // 4B
|
||||
|
||||
Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 12B + 4B
|
||||
AsAlias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 12B + 4B
|
||||
Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad
|
||||
AsAlias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad
|
||||
|
||||
// 32B
|
||||
// 24B
|
||||
HostExposedAlias {
|
||||
name: Symbol, // 8B
|
||||
arguments: PoolVec<(PoolStr, TypeId)>, // 12B
|
||||
arguments: PoolVec<(PoolStr, TypeId)>, // 8B
|
||||
actual_var: Variable, // 4B
|
||||
actual: TypeId, // 4B
|
||||
},
|
||||
EmptyTagUnion,
|
||||
TagUnion(PoolVec<(TagName, PoolVec<Type2>)>, TypeId), // 16B = 12B + 4B
|
||||
RecursiveTagUnion(Variable, PoolVec<(TagName, PoolVec<Type2>)>, TypeId), // 20B = 4B + 12B + 4B
|
||||
TagUnion(PoolVec<(TagName, PoolVec<Type2>)>, TypeId), // 12B = 8B + 4B
|
||||
RecursiveTagUnion(Variable, PoolVec<(TagName, PoolVec<Type2>)>, TypeId), // 16B = 4B + 8B + 4B
|
||||
|
||||
EmptyRec,
|
||||
Record(PoolVec<(PoolStr, RecordField<TypeId>)>, TypeId), // 16B = 12B + 4B
|
||||
Record(PoolVec<(PoolStr, RecordField<TypeId>)>, TypeId), // 12B = 8B + 4B
|
||||
|
||||
Function(PoolVec<Type2>, TypeId, TypeId), // 20B = 12B + 4B + 4B
|
||||
Apply(Symbol, PoolVec<Type2>), // 20B = 8B + 12B
|
||||
Function(PoolVec<Type2>, TypeId, TypeId), // 16B = 8B + 4B + 4B
|
||||
Apply(Symbol, PoolVec<Type2>), // 16B = 8B + 8B
|
||||
|
||||
Erroneous(Problem2),
|
||||
Erroneous(Problem2), // 24B
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type2_size() {
|
||||
assert_eq!(std::mem::size_of::<Type2>(), 32); // 24B + pad
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -171,9 +176,9 @@ pub enum Signature {
|
||||
},
|
||||
}
|
||||
|
||||
pub enum Annotation2<'a> {
|
||||
pub enum Annotation2 {
|
||||
Annotation {
|
||||
named_rigids: MutMap<&'a str, Variable>,
|
||||
named_rigids: MutMap<Lowercase, Variable>,
|
||||
unnamed_rigids: MutSet<Variable>,
|
||||
symbols: MutSet<Symbol>,
|
||||
signature: Signature,
|
||||
@ -186,7 +191,7 @@ pub fn to_annotation2<'a>(
|
||||
scope: &mut Scope,
|
||||
annotation: &'a roc_parse::ast::TypeAnnotation<'a>,
|
||||
region: Region,
|
||||
) -> Annotation2<'a> {
|
||||
) -> Annotation2 {
|
||||
let mut references = References::default();
|
||||
|
||||
let annotation = to_type2(env, scope, &mut references, annotation, region);
|
||||
@ -240,11 +245,7 @@ pub fn to_annotation2<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
fn shallow_dealias<'a>(
|
||||
env: &mut Env,
|
||||
references: References<'a>,
|
||||
annotation: Type2,
|
||||
) -> Annotation2<'a> {
|
||||
fn shallow_dealias<'a>(env: &mut Env, references: References, annotation: Type2) -> Annotation2 {
|
||||
let References {
|
||||
named,
|
||||
unnamed,
|
||||
@ -288,8 +289,8 @@ fn shallow_dealias<'a>(
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct References<'a> {
|
||||
named: MutMap<&'a str, Variable>,
|
||||
pub struct References {
|
||||
named: MutMap<Lowercase, Variable>,
|
||||
unnamed: MutSet<Variable>,
|
||||
hidden: MutSet<Variable>,
|
||||
symbols: MutSet<Symbol>,
|
||||
@ -298,7 +299,7 @@ pub struct References<'a> {
|
||||
pub fn to_type_id<'a>(
|
||||
env: &mut Env,
|
||||
scope: &mut Scope,
|
||||
rigids: &mut References<'a>,
|
||||
rigids: &mut References,
|
||||
annotation: &roc_parse::ast::TypeAnnotation<'a>,
|
||||
region: Region,
|
||||
) -> TypeId {
|
||||
@ -310,7 +311,7 @@ pub fn to_type_id<'a>(
|
||||
pub fn as_type_id<'a>(
|
||||
env: &mut Env,
|
||||
scope: &mut Scope,
|
||||
rigids: &mut References<'a>,
|
||||
rigids: &mut References,
|
||||
type_id: TypeId,
|
||||
annotation: &roc_parse::ast::TypeAnnotation<'a>,
|
||||
region: Region,
|
||||
@ -324,7 +325,7 @@ pub fn as_type_id<'a>(
|
||||
pub fn to_type2<'a>(
|
||||
env: &mut Env,
|
||||
scope: &mut Scope,
|
||||
references: &mut References<'a>,
|
||||
references: &mut References,
|
||||
annotation: &roc_parse::ast::TypeAnnotation<'a>,
|
||||
region: Region,
|
||||
) -> Type2 {
|
||||
@ -375,8 +376,9 @@ pub fn to_type2<'a>(
|
||||
Type2::Function(arguments, closure_type_id, return_type_id)
|
||||
}
|
||||
BoundVariable(v) => {
|
||||
// a rigid type variable
|
||||
match references.named.get(v) {
|
||||
// A rigid type variable. The parser should have already ensured that the name is indeed a lowercase.
|
||||
let v = Lowercase::from(*v);
|
||||
match references.named.get(&v) {
|
||||
Some(var) => Type2::Variable(*var),
|
||||
None => {
|
||||
let var = env.var_store.fresh();
|
||||
@ -387,6 +389,9 @@ pub fn to_type2<'a>(
|
||||
}
|
||||
}
|
||||
}
|
||||
Inferred => {
|
||||
unimplemented!();
|
||||
}
|
||||
Wildcard | Malformed(_) => {
|
||||
let var = env.var_store.fresh();
|
||||
|
||||
@ -401,7 +406,7 @@ pub fn to_type2<'a>(
|
||||
let field_types = PoolVec::with_capacity(field_types_map.len() as u32, env.pool);
|
||||
|
||||
for (node_id, (label, field)) in field_types.iter_node_ids().zip(field_types_map) {
|
||||
let poolstr = PoolStr::new(label, env.pool);
|
||||
let poolstr = PoolStr::new(label.as_str(), env.pool);
|
||||
|
||||
let rec_field = match field {
|
||||
RecordField::Optional(_) => {
|
||||
@ -428,7 +433,7 @@ pub fn to_type2<'a>(
|
||||
Type2::Record(field_types, ext_type)
|
||||
}
|
||||
TagUnion { tags, ext, .. } => {
|
||||
let tag_types_vec = can_tags(env, scope, references, tags, region);
|
||||
let tag_types_vec = can_tags(env, scope, references, tags.items, region);
|
||||
|
||||
let tag_types = PoolVec::with_capacity(tag_types_vec.len() as u32, env.pool);
|
||||
|
||||
@ -480,10 +485,10 @@ pub fn to_type2<'a>(
|
||||
{
|
||||
match loc_var.value {
|
||||
BoundVariable(ident) => {
|
||||
let var_name = ident;
|
||||
let var_name = Lowercase::from(ident);
|
||||
|
||||
if let Some(var) = references.named.get(&var_name) {
|
||||
let poolstr = PoolStr::new(var_name, env.pool);
|
||||
let poolstr = PoolStr::new(var_name.as_str(), env.pool);
|
||||
|
||||
let type_id = env.pool.add(Type2::Variable(*var));
|
||||
env.pool[var_id] = (poolstr.shallow_clone(), type_id);
|
||||
@ -494,7 +499,7 @@ pub fn to_type2<'a>(
|
||||
let var = env.var_store.fresh();
|
||||
|
||||
references.named.insert(var_name.clone(), var);
|
||||
let poolstr = PoolStr::new(var_name, env.pool);
|
||||
let poolstr = PoolStr::new(var_name.as_str(), env.pool);
|
||||
|
||||
let type_id = env.pool.add(Type2::Variable(var));
|
||||
env.pool[var_id] = (poolstr.shallow_clone(), type_id);
|
||||
@ -576,10 +581,10 @@ pub fn to_type2<'a>(
|
||||
fn can_assigned_fields<'a>(
|
||||
env: &mut Env,
|
||||
scope: &mut Scope,
|
||||
rigids: &mut References<'a>,
|
||||
rigids: &mut References,
|
||||
fields: &&[Located<roc_parse::ast::AssignedField<'a, roc_parse::ast::TypeAnnotation<'a>>>],
|
||||
region: Region,
|
||||
) -> MutMap<&'a str, RecordField<Type2>> {
|
||||
) -> MutMap<Lowercase, RecordField<Type2>> {
|
||||
use roc_parse::ast::AssignedField::*;
|
||||
use roc_types::types::RecordField::*;
|
||||
|
||||
@ -602,8 +607,8 @@ fn can_assigned_fields<'a>(
|
||||
let field_type =
|
||||
to_type2(env, scope, rigids, &annotation.value, annotation.region);
|
||||
|
||||
let label = field_name.value;
|
||||
field_types.insert(label, Required(field_type));
|
||||
let label = Lowercase::from(field_name.value);
|
||||
field_types.insert(label.clone(), Required(field_type));
|
||||
|
||||
break 'inner label;
|
||||
}
|
||||
@ -611,20 +616,20 @@ fn can_assigned_fields<'a>(
|
||||
let field_type =
|
||||
to_type2(env, scope, rigids, &annotation.value, annotation.region);
|
||||
|
||||
let label = field_name.value;
|
||||
let label = Lowercase::from(field_name.value);
|
||||
field_types.insert(label.clone(), Optional(field_type));
|
||||
|
||||
break 'inner label;
|
||||
}
|
||||
LabelOnly(loc_field_name) => {
|
||||
// Interpret { a, b } as { a : a, b : b }
|
||||
let field_name = loc_field_name.value;
|
||||
let field_name = Lowercase::from(loc_field_name.value);
|
||||
let field_type = {
|
||||
if let Some(var) = rigids.named.get(&field_name) {
|
||||
Type2::Variable(*var)
|
||||
} else {
|
||||
let field_var = env.var_store.fresh();
|
||||
rigids.named.insert(field_name, field_var);
|
||||
rigids.named.insert(field_name.clone(), field_var);
|
||||
Type2::Variable(field_var)
|
||||
}
|
||||
};
|
||||
@ -664,7 +669,7 @@ fn can_assigned_fields<'a>(
|
||||
fn can_tags<'a>(
|
||||
env: &mut Env,
|
||||
scope: &mut Scope,
|
||||
rigids: &mut References<'a>,
|
||||
rigids: &mut References,
|
||||
tags: &'a [Located<roc_parse::ast::Tag<'a>>],
|
||||
region: Region,
|
||||
) -> Vec<(TagName, PoolVec<Type2>)> {
|
||||
@ -748,7 +753,7 @@ enum TypeApply {
|
||||
fn to_type_apply<'a>(
|
||||
env: &mut Env,
|
||||
scope: &mut Scope,
|
||||
rigids: &mut References<'a>,
|
||||
rigids: &mut References,
|
||||
module_name: &str,
|
||||
ident: &str,
|
||||
type_arguments: &[Located<roc_parse::ast::TypeAnnotation<'a>>],
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::mem_pool::pool::{NodeId, Pool};
|
||||
use bumpalo::{collections::Vec as BumpVec, Bump};
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_module::ident::{Ident, ModuleName};
|
||||
use roc_module::ident::{Ident, Lowercase, ModuleName};
|
||||
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
|
||||
use roc_problem::can::{Problem, RuntimeError};
|
||||
use roc_region::all::{Located, Region};
|
||||
@ -134,23 +134,37 @@ impl<'a> Env<'a> {
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
match self
|
||||
.dep_idents
|
||||
.get(&module_id)
|
||||
.and_then(|exposed_ids| exposed_ids.get_id(&ident))
|
||||
{
|
||||
Some(ident_id) => {
|
||||
let symbol = Symbol::new(module_id, *ident_id);
|
||||
match self.dep_idents.get(&module_id) {
|
||||
Some(exposed_ids) => match exposed_ids.get_id(&ident) {
|
||||
Some(ident_id) => {
|
||||
let symbol = Symbol::new(module_id, *ident_id);
|
||||
|
||||
self.qualified_lookups.insert(symbol);
|
||||
self.qualified_lookups.insert(symbol);
|
||||
|
||||
Ok(symbol)
|
||||
Ok(symbol)
|
||||
}
|
||||
None => {
|
||||
let exposed_values = exposed_ids
|
||||
.idents()
|
||||
.filter(|(_, ident)| {
|
||||
ident.as_ref().starts_with(|c: char| c.is_lowercase())
|
||||
})
|
||||
.map(|(_, ident)| Lowercase::from(ident.as_ref()))
|
||||
.collect();
|
||||
Err(RuntimeError::ValueNotExposed {
|
||||
module_name,
|
||||
ident,
|
||||
region,
|
||||
exposed_values,
|
||||
})
|
||||
}
|
||||
},
|
||||
None => {
|
||||
panic!(
|
||||
"Module {} exists, but is not recorded in dep_idents",
|
||||
module_name
|
||||
)
|
||||
}
|
||||
None => Err(RuntimeError::ValueNotExposed {
|
||||
module_name,
|
||||
ident,
|
||||
region,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use crate::mem_pool::{
|
||||
pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone,
|
||||
};
|
||||
use roc_collections::all::WyHash;
|
||||
use roc_module::ident::Lowercase;
|
||||
use roc_types::subs::Variable;
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -18,7 +19,7 @@ pub struct Rigids {
|
||||
#[allow(clippy::needless_collect)]
|
||||
impl Rigids {
|
||||
pub fn new(
|
||||
named: HashMap<&str, Variable, BuildHasherDefault<WyHash>>,
|
||||
named: HashMap<Lowercase, Variable, BuildHasherDefault<WyHash>>,
|
||||
unnamed: HashSet<Variable, BuildHasherDefault<WyHash>>,
|
||||
pool: &mut Pool,
|
||||
) -> Self {
|
||||
@ -26,7 +27,7 @@ impl Rigids {
|
||||
|
||||
let mut temp_names = Vec::new();
|
||||
|
||||
temp_names.extend(named.iter().map(|(name, var)| (Some(*name), *var)));
|
||||
temp_names.extend(named.iter().map(|(name, var)| (Some(name.as_str()), *var)));
|
||||
|
||||
temp_names.extend(unnamed.iter().map(|var| (None, *var)));
|
||||
|
||||
|
@ -61,7 +61,7 @@ roc_load = { path = "../compiler/load" }
|
||||
roc_gen_llvm = { path = "../compiler/gen_llvm", optional = true }
|
||||
roc_build = { path = "../compiler/build", default-features = false }
|
||||
roc_fmt = { path = "../compiler/fmt" }
|
||||
roc_reporting = { path = "../compiler/reporting" }
|
||||
roc_reporting = { path = "../reporting" }
|
||||
roc_editor = { path = "../editor", optional = true }
|
||||
roc_linker = { path = "../linker" }
|
||||
clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] }
|
||||
|
@ -55,6 +55,7 @@ pub fn build_file<'a>(
|
||||
link_type: LinkType,
|
||||
surgically_link: bool,
|
||||
precompiled: bool,
|
||||
target_valgrind: bool,
|
||||
) -> Result<BuiltFile, LoadingProblem<'a>> {
|
||||
let compilation_start = SystemTime::now();
|
||||
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
|
||||
@ -116,6 +117,7 @@ pub fn build_file<'a>(
|
||||
.keys()
|
||||
.map(|x| x.as_str(&loaded.interns).to_string())
|
||||
.collect(),
|
||||
target_valgrind,
|
||||
);
|
||||
|
||||
// TODO try to move as much of this linking as possible to the precompiled
|
||||
@ -247,7 +249,7 @@ pub fn build_file<'a>(
|
||||
link(
|
||||
target,
|
||||
binary_path.clone(),
|
||||
&inputs,
|
||||
&inputs,
|
||||
link_type
|
||||
)
|
||||
.map_err(|_| {
|
||||
@ -280,6 +282,7 @@ pub fn build_file<'a>(
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn spawn_rebuild_thread(
|
||||
opt_level: OptLevel,
|
||||
surgically_link: bool,
|
||||
@ -288,9 +291,12 @@ fn spawn_rebuild_thread(
|
||||
binary_path: PathBuf,
|
||||
target: &Triple,
|
||||
exported_symbols: Vec<String>,
|
||||
target_valgrind: bool,
|
||||
) -> std::thread::JoinHandle<u128> {
|
||||
let thread_local_target = target.clone();
|
||||
std::thread::spawn(move || {
|
||||
print!("🔨 Rebuilding host... ");
|
||||
|
||||
let rebuild_host_start = SystemTime::now();
|
||||
if !precompiled {
|
||||
if surgically_link {
|
||||
@ -299,6 +305,7 @@ fn spawn_rebuild_thread(
|
||||
&thread_local_target,
|
||||
host_input_path.as_path(),
|
||||
exported_symbols,
|
||||
target_valgrind,
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
@ -307,6 +314,7 @@ fn spawn_rebuild_thread(
|
||||
&thread_local_target,
|
||||
host_input_path.as_path(),
|
||||
None,
|
||||
target_valgrind,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -316,6 +324,9 @@ fn spawn_rebuild_thread(
|
||||
std::fs::copy(prehost, binary_path.as_path()).unwrap();
|
||||
}
|
||||
let rebuild_host_end = rebuild_host_start.elapsed().unwrap();
|
||||
|
||||
println!("Done!");
|
||||
|
||||
rebuild_host_end.as_millis()
|
||||
})
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ pub const FLAG_BACKEND: &str = "backend";
|
||||
pub const FLAG_TIME: &str = "time";
|
||||
pub const FLAG_LINK: &str = "roc-linker";
|
||||
pub const FLAG_PRECOMPILED: &str = "precompiled-host";
|
||||
pub const FLAG_VALGRIND: &str = "valgrind";
|
||||
pub const ROC_FILE: &str = "ROC_FILE";
|
||||
pub const BACKEND: &str = "BACKEND";
|
||||
pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES";
|
||||
@ -100,6 +101,12 @@ pub fn build_app<'a>() -> App<'a> {
|
||||
.about("Assumes the host has been precompiled and skips recompiling the host.")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(FLAG_VALGRIND)
|
||||
.long(FLAG_VALGRIND)
|
||||
.about("Some assembly instructions are not supported by valgrind, this flag prevents those from being output when building the host.")
|
||||
.required(false),
|
||||
)
|
||||
)
|
||||
.subcommand(App::new(CMD_REPL)
|
||||
.about("Launch the interactive Read Eval Print Loop (REPL)")
|
||||
@ -258,6 +265,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
|
||||
};
|
||||
let surgically_link = matches.is_present(FLAG_LINK);
|
||||
let precompiled = matches.is_present(FLAG_PRECOMPILED);
|
||||
|
||||
if surgically_link && !roc_linker::supported(&link_type, &target) {
|
||||
panic!(
|
||||
"Link type, {:?}, with target, {}, not supported by roc linker",
|
||||
@ -287,6 +295,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
|
||||
});
|
||||
|
||||
let src_dir = path.parent().unwrap().canonicalize().unwrap();
|
||||
let target_valgrind = matches.is_present(FLAG_VALGRIND);
|
||||
let res_binary_path = build_file(
|
||||
&arena,
|
||||
&target,
|
||||
@ -298,6 +307,7 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
|
||||
link_type,
|
||||
surgically_link,
|
||||
precompiled,
|
||||
target_valgrind,
|
||||
);
|
||||
|
||||
match res_binary_path {
|
||||
|
@ -2,12 +2,12 @@ use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use libloading::Library;
|
||||
use roc_gen_llvm::{run_jit_function, run_jit_function_dynamic_type};
|
||||
use roc_module::called_via::CalledVia;
|
||||
use roc_module::ident::TagName;
|
||||
use roc_module::operator::CalledVia;
|
||||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||
use roc_mono::ir::ProcLayout;
|
||||
use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant};
|
||||
use roc_parse::ast::{AssignedField, Expr, StrLiteral};
|
||||
use roc_parse::ast::{AssignedField, Collection, Expr, StrLiteral};
|
||||
use roc_region::all::{Located, Region};
|
||||
use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable};
|
||||
|
||||
@ -133,10 +133,7 @@ fn jit_to_ast_help<'a>(
|
||||
),
|
||||
Layout::Builtin(Builtin::EmptyList) => {
|
||||
Ok(run_jit_function!(lib, main_fn_name, &'static str, |_| {
|
||||
Expr::List {
|
||||
items: &[],
|
||||
final_comments: &[],
|
||||
}
|
||||
Expr::List(Collection::empty())
|
||||
}))
|
||||
}
|
||||
Layout::Builtin(Builtin::List(elem_layout)) => Ok(run_jit_function!(
|
||||
@ -431,10 +428,7 @@ fn ptr_to_ast<'a>(
|
||||
|
||||
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
|
||||
}
|
||||
Layout::Builtin(Builtin::EmptyList) => Expr::List {
|
||||
items: &[],
|
||||
final_comments: &[],
|
||||
},
|
||||
Layout::Builtin(Builtin::EmptyList) => Expr::List(Collection::empty()),
|
||||
Layout::Builtin(Builtin::List(elem_layout)) => {
|
||||
// Turn the (ptr, len) wrapper struct into actual ptr and len values.
|
||||
let len = unsafe { *(ptr.offset(env.ptr_bytes as isize) as *const usize) };
|
||||
@ -522,10 +516,7 @@ fn list_to_ast<'a>(
|
||||
|
||||
let output = output.into_bump_slice();
|
||||
|
||||
Expr::List {
|
||||
items: output,
|
||||
final_comments: &[],
|
||||
}
|
||||
Expr::List(Collection::with_items(output))
|
||||
}
|
||||
|
||||
fn single_tag_union_to_ast<'a>(
|
||||
@ -621,10 +612,7 @@ fn struct_to_ast<'a>(
|
||||
|
||||
let output = env.arena.alloc([loc_field]);
|
||||
|
||||
Expr::Record {
|
||||
fields: output,
|
||||
final_comments: &[],
|
||||
}
|
||||
Expr::Record(Collection::with_items(output))
|
||||
} else {
|
||||
debug_assert_eq!(sorted_fields.len(), field_layouts.len());
|
||||
|
||||
@ -658,10 +646,7 @@ fn struct_to_ast<'a>(
|
||||
|
||||
let output = output.into_bump_slice();
|
||||
|
||||
Expr::Record {
|
||||
fields: output,
|
||||
final_comments: &[],
|
||||
}
|
||||
Expr::Record(Collection::with_items(output))
|
||||
}
|
||||
}
|
||||
|
||||
@ -735,10 +720,7 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a
|
||||
region: Region::zero(),
|
||||
};
|
||||
|
||||
Expr::Record {
|
||||
fields: arena.alloc([loc_assigned_field]),
|
||||
final_comments: arena.alloc([]),
|
||||
}
|
||||
Expr::Record(Collection::with_items(arena.alloc([loc_assigned_field])))
|
||||
}
|
||||
FlatType::TagUnion(tags, _) if tags.len() == 1 => {
|
||||
let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags);
|
||||
@ -850,10 +832,7 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a>
|
||||
region: Region::zero(),
|
||||
};
|
||||
|
||||
Expr::Record {
|
||||
fields: arena.alloc([loc_assigned_field]),
|
||||
final_comments: &[],
|
||||
}
|
||||
Expr::Record(Collection::with_items(arena.alloc([loc_assigned_field])))
|
||||
}
|
||||
FlatType::TagUnion(tags, _) if tags.len() == 1 => {
|
||||
let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags);
|
||||
@ -972,10 +951,7 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E
|
||||
region: Region::zero(),
|
||||
};
|
||||
|
||||
Expr::Record {
|
||||
fields: arena.alloc([loc_assigned_field]),
|
||||
final_comments: arena.alloc([]),
|
||||
}
|
||||
Expr::Record(Collection::with_items(arena.alloc([loc_assigned_field])))
|
||||
}
|
||||
FlatType::TagUnion(tags, _) => {
|
||||
// This was a single-tag union that got unwrapped at runtime.
|
||||
|
@ -218,8 +218,8 @@ pub fn gen_and_eval<'a>(
|
||||
// Verify the module
|
||||
if let Err(errors) = env.module.verify() {
|
||||
panic!(
|
||||
"Errors defining module: {}\n\nUncomment things nearby to see more details.",
|
||||
errors
|
||||
"Errors defining module:\n{}\n\nUncomment things nearby to see more details.",
|
||||
errors.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,14 @@ mod cli_run {
|
||||
expected_ending: &str,
|
||||
use_valgrind: bool,
|
||||
) {
|
||||
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat());
|
||||
let mut all_flags = vec![];
|
||||
all_flags.extend_from_slice(flags);
|
||||
|
||||
if use_valgrind {
|
||||
all_flags.extend_from_slice(&["--valgrind"]);
|
||||
}
|
||||
|
||||
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], &all_flags[..]].concat());
|
||||
if !compile_out.stderr.is_empty() {
|
||||
panic!("{}", compile_out.stderr);
|
||||
}
|
||||
@ -111,20 +118,18 @@ mod cli_run {
|
||||
}
|
||||
|
||||
valgrind_out
|
||||
} else if let Some(input_file) = input_file {
|
||||
run_cmd(
|
||||
file.with_file_name(executable_filename).to_str().unwrap(),
|
||||
stdin,
|
||||
&[input_file.to_str().unwrap()],
|
||||
)
|
||||
} else {
|
||||
if let Some(input_file) = input_file {
|
||||
run_cmd(
|
||||
file.with_file_name(executable_filename).to_str().unwrap(),
|
||||
stdin,
|
||||
&[input_file.to_str().unwrap()],
|
||||
)
|
||||
} else {
|
||||
run_cmd(
|
||||
file.with_file_name(executable_filename).to_str().unwrap(),
|
||||
stdin,
|
||||
&[],
|
||||
)
|
||||
}
|
||||
run_cmd(
|
||||
file.with_file_name(executable_filename).to_str().unwrap(),
|
||||
stdin,
|
||||
&[],
|
||||
)
|
||||
};
|
||||
if !&out.stdout.ends_with(expected_ending) {
|
||||
panic!(
|
||||
@ -796,7 +801,7 @@ fn read_wasi_stdout(wasi_env: wasmer_wasi::WasiEnv) -> String {
|
||||
let mut buf = String::new();
|
||||
stdout.read_to_string(&mut buf).unwrap();
|
||||
|
||||
return buf;
|
||||
buf
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
|
61
cli_utils/Cargo.lock
generated
61
cli_utils/Cargo.lock
generated
@ -4,12 +4,12 @@ version = 3
|
||||
|
||||
[[package]]
|
||||
name = "ab_glyph"
|
||||
version = "0.2.11"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af0ac006645f86f20f6c6fa4dcaef920bf803df819123626f9440e35835e7d80"
|
||||
checksum = "20b228f2c198f98d4337ceb560333fb12cbb2f4948a953bf8c57d09deb219603"
|
||||
dependencies = [
|
||||
"ab_glyph_rasterizer",
|
||||
"owned_ttf_parser 0.12.1",
|
||||
"owned_ttf_parser 0.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -298,9 +298,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.71"
|
||||
version = "1.0.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd"
|
||||
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
@ -1121,9 +1121,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "glyph_brush"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e3f00b8574a76fb6c50890c48da03946ca50e4372a2778737922666a2238221"
|
||||
checksum = "21932fbf719272848eec4583740d978203c6e7da4c4e203358f5b95946c97409"
|
||||
dependencies = [
|
||||
"glyph_brush_draw_cache",
|
||||
"glyph_brush_layout",
|
||||
@ -1135,9 +1135,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "glyph_brush_draw_cache"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac2c82074cafb68b9e459c50c655f7eedcb92d6ee7166813802934bc6fc29fa3"
|
||||
checksum = "6010675390f6889e09a21e2c8b575b3ee25667ea8237a8d59423f73cb8c28610"
|
||||
dependencies = [
|
||||
"ab_glyph",
|
||||
"crossbeam-channel",
|
||||
@ -1409,9 +1409,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.106"
|
||||
version = "0.2.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a60553f9a9e039a333b4e9b20573b9e9b9c0bb3a11e201ccc48ef4283456d673"
|
||||
checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
@ -1912,11 +1912,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owned_ttf_parser"
|
||||
version = "0.12.1"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60ac8dda2e5cc09bf6480e3b3feff9783db251710c922ae9369a429c51efdeb0"
|
||||
checksum = "65ee3f72636e6f164cc41c9f9057f4e58c4e13507699ea7f5e5242b64b8198ee"
|
||||
dependencies = [
|
||||
"ttf-parser 0.12.3",
|
||||
"ttf-parser 0.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2398,8 +2398,6 @@ name = "roc_build"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"im",
|
||||
"im-rc",
|
||||
"inkwell 0.1.0",
|
||||
"libloading 0.7.1",
|
||||
"roc_builtins",
|
||||
@ -2440,8 +2438,6 @@ name = "roc_can"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"im",
|
||||
"im-rc",
|
||||
"roc_builtins",
|
||||
"roc_collections",
|
||||
"roc_module",
|
||||
@ -2459,8 +2455,6 @@ dependencies = [
|
||||
"bumpalo",
|
||||
"clap 3.0.0-beta.5",
|
||||
"const_format",
|
||||
"im",
|
||||
"im-rc",
|
||||
"inkwell 0.1.0",
|
||||
"libloading 0.7.1",
|
||||
"mimalloc",
|
||||
@ -2562,8 +2556,6 @@ dependencies = [
|
||||
"fs_extra",
|
||||
"futures",
|
||||
"glyph_brush",
|
||||
"im",
|
||||
"im-rc",
|
||||
"libc",
|
||||
"log",
|
||||
"nonempty",
|
||||
@ -2599,8 +2591,6 @@ name = "roc_fmt"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"im",
|
||||
"im-rc",
|
||||
"roc_collections",
|
||||
"roc_module",
|
||||
"roc_parse",
|
||||
@ -2612,8 +2602,6 @@ name = "roc_gen_dev"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"im",
|
||||
"im-rc",
|
||||
"object 0.26.2",
|
||||
"roc_builtins",
|
||||
"roc_collections",
|
||||
@ -2632,20 +2620,13 @@ name = "roc_gen_llvm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"im",
|
||||
"im-rc",
|
||||
"inkwell 0.1.0",
|
||||
"morphic_lib",
|
||||
"roc_builtins",
|
||||
"roc_collections",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
"roc_problem",
|
||||
"roc_region",
|
||||
"roc_solve",
|
||||
"roc_std",
|
||||
"roc_types",
|
||||
"roc_unify",
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
@ -2654,6 +2635,7 @@ name = "roc_gen_wasm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"roc_builtins",
|
||||
"roc_collections",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
@ -2726,7 +2708,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"hashbrown 0.11.2",
|
||||
"linked-hash-map",
|
||||
"morphic_lib",
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
@ -2737,7 +2718,7 @@ dependencies = [
|
||||
"roc_std",
|
||||
"roc_types",
|
||||
"roc_unify",
|
||||
"ven_ena",
|
||||
"static_assertions",
|
||||
"ven_graph",
|
||||
"ven_pretty",
|
||||
]
|
||||
@ -2773,8 +2754,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"distance",
|
||||
"im",
|
||||
"im-rc",
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_module",
|
||||
@ -2983,9 +2962,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.69"
|
||||
version = "1.0.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e466864e431129c7e0d3476b92f20458e5879919a0596c6472738d9fa2d342f8"
|
||||
checksum = "e277c495ac6cd1a01a58d0a0c574568b4d1ddf14f59965c6a58b8d96400b54f3"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
@ -3294,9 +3273,9 @@ checksum = "3e5d7cd7ab3e47dda6e56542f4bbf3824c15234958c6e1bd6aaa347e93499fdc"
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.12.3"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ae2f58a822f08abdaf668897e96a5656fe72f5a9ce66422423e8849384872e6"
|
||||
checksum = "3e835d06ed78a500d3d0e431a20c18ff5544b3f6e11376e834370cfd35e8948e"
|
||||
|
||||
[[package]]
|
||||
name = "twox-hash"
|
||||
|
@ -22,7 +22,7 @@ roc_load = { path = "../load" }
|
||||
roc_gen_llvm = { path = "../gen_llvm", optional = true }
|
||||
roc_gen_wasm = { path = "../gen_wasm", optional = true }
|
||||
roc_gen_dev = { path = "../gen_dev", default-features = false }
|
||||
roc_reporting = { path = "../reporting" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
roc_std = { path = "../../roc_std" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
libloading = "0.7.1"
|
||||
|
@ -86,6 +86,7 @@ pub fn build_zig_host_native(
|
||||
target: &str,
|
||||
opt_level: OptLevel,
|
||||
shared_lib_path: Option<&Path>,
|
||||
_target_valgrind: bool,
|
||||
) -> Output {
|
||||
let mut command = Command::new("zig");
|
||||
command
|
||||
@ -118,6 +119,15 @@ pub fn build_zig_host_native(
|
||||
"-target",
|
||||
target,
|
||||
]);
|
||||
|
||||
// use single threaded testing for cli_run and enable this code if valgrind fails with unhandled instruction bytes, see #1963.
|
||||
/*if target_valgrind {
|
||||
command.args(&[
|
||||
"-mcpu",
|
||||
"x86_64"
|
||||
]);
|
||||
}*/
|
||||
|
||||
if matches!(opt_level, OptLevel::Optimize) {
|
||||
command.args(&["-O", "ReleaseSafe"]);
|
||||
}
|
||||
@ -135,6 +145,8 @@ pub fn build_zig_host_native(
|
||||
_target: &str,
|
||||
opt_level: OptLevel,
|
||||
shared_lib_path: Option<&Path>,
|
||||
// For compatibility with the non-macOS def above. Keep these in sync.
|
||||
_target_valgrind: bool,
|
||||
) -> Output {
|
||||
use serde_json::Value;
|
||||
|
||||
@ -339,6 +351,7 @@ pub fn rebuild_host(
|
||||
target: &Triple,
|
||||
host_input_path: &Path,
|
||||
shared_lib_path: Option<&Path>,
|
||||
target_valgrind: bool,
|
||||
) {
|
||||
let c_host_src = host_input_path.with_file_name("host.c");
|
||||
let c_host_dest = host_input_path.with_file_name("c_host.o");
|
||||
@ -394,6 +407,7 @@ pub fn rebuild_host(
|
||||
"native",
|
||||
opt_level,
|
||||
shared_lib_path,
|
||||
target_valgrind,
|
||||
)
|
||||
}
|
||||
Architecture::X86_32(_) => {
|
||||
@ -407,6 +421,7 @@ pub fn rebuild_host(
|
||||
"i386-linux-musl",
|
||||
opt_level,
|
||||
shared_lib_path,
|
||||
target_valgrind,
|
||||
)
|
||||
}
|
||||
|
||||
@ -421,6 +436,7 @@ pub fn rebuild_host(
|
||||
target_triple_str(target),
|
||||
opt_level,
|
||||
shared_lib_path,
|
||||
target_valgrind,
|
||||
)
|
||||
}
|
||||
_ => panic!("Unsupported architecture {:?}", target.architecture),
|
||||
@ -863,6 +879,7 @@ fn get_macos_version() -> String {
|
||||
.expect("Failed to convert output of command 'sw_vers -productVersion' into a utf8 string");
|
||||
|
||||
full_version_string
|
||||
.trim_end()
|
||||
.split('.')
|
||||
.take(2)
|
||||
.collect::<Vec<&str>>()
|
||||
|
@ -862,94 +862,44 @@ pub fn listSwap(
|
||||
return newList;
|
||||
}
|
||||
|
||||
pub fn listTakeFirst(
|
||||
pub fn listSublist(
|
||||
list: RocList,
|
||||
alignment: u32,
|
||||
element_width: usize,
|
||||
take_count: usize,
|
||||
start: usize,
|
||||
len: usize,
|
||||
dec: Dec,
|
||||
) callconv(.C) RocList {
|
||||
if (len == 0) {
|
||||
return RocList.empty();
|
||||
}
|
||||
if (list.bytes) |source_ptr| {
|
||||
if (take_count == 0) {
|
||||
const size = list.len();
|
||||
|
||||
if (start >= size) {
|
||||
return RocList.empty();
|
||||
}
|
||||
const in_len = list.len();
|
||||
const out_len = std.math.min(take_count, in_len);
|
||||
|
||||
const output = RocList.allocate(alignment, out_len, element_width);
|
||||
const target_ptr = output.bytes orelse unreachable;
|
||||
|
||||
@memcpy(target_ptr, source_ptr, out_len * element_width);
|
||||
|
||||
utils.decref(list.bytes, in_len * element_width, alignment);
|
||||
|
||||
return output;
|
||||
} else {
|
||||
return RocList.empty();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listTakeLast(
|
||||
list: RocList,
|
||||
alignment: u32,
|
||||
element_width: usize,
|
||||
take_count: usize,
|
||||
dec: Dec,
|
||||
) callconv(.C) RocList {
|
||||
if (take_count == 0) {
|
||||
return RocList.empty();
|
||||
}
|
||||
if (list.bytes) |source_ptr| {
|
||||
const size = list.len();
|
||||
if (size <= take_count) {
|
||||
return list;
|
||||
}
|
||||
const drop_count = size - take_count;
|
||||
return listDrop(
|
||||
list,
|
||||
alignment,
|
||||
element_width,
|
||||
drop_count,
|
||||
dec,
|
||||
);
|
||||
} else {
|
||||
return RocList.empty();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listDrop(
|
||||
list: RocList,
|
||||
alignment: u32,
|
||||
element_width: usize,
|
||||
drop_count: usize,
|
||||
dec: Dec,
|
||||
) callconv(.C) RocList {
|
||||
if (list.bytes) |source_ptr| {
|
||||
const size = list.len();
|
||||
const keep_count = size - drop_count;
|
||||
const keep_len = std.math.min(len, size - start);
|
||||
const drop_len = std.math.max(start, 0);
|
||||
|
||||
var i: usize = 0;
|
||||
const iterations = std.math.min(drop_count, size);
|
||||
|
||||
while (i < iterations) : (i += 1) {
|
||||
while (i < drop_len) : (i += 1) {
|
||||
const element = source_ptr + i * element_width;
|
||||
dec(element);
|
||||
}
|
||||
|
||||
if (drop_count >= size) {
|
||||
return RocList.empty();
|
||||
}
|
||||
|
||||
const output = RocList.allocate(alignment, keep_count, element_width);
|
||||
const output = RocList.allocate(alignment, keep_len, element_width);
|
||||
const target_ptr = output.bytes orelse unreachable;
|
||||
|
||||
@memcpy(target_ptr, source_ptr + drop_count * element_width, keep_count * element_width);
|
||||
@memcpy(target_ptr, source_ptr + start * element_width, keep_len * element_width);
|
||||
|
||||
utils.decref(list.bytes, size * element_width, alignment);
|
||||
|
||||
return output;
|
||||
} else {
|
||||
return RocList.empty();
|
||||
}
|
||||
|
||||
return RocList.empty();
|
||||
}
|
||||
|
||||
pub fn listDropAt(
|
||||
@ -1162,6 +1112,36 @@ pub fn listAny(
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn listAll(
|
||||
list: RocList,
|
||||
caller: Caller1,
|
||||
data: Opaque,
|
||||
inc_n_data: IncN,
|
||||
data_is_owned: bool,
|
||||
element_width: usize,
|
||||
) callconv(.C) bool {
|
||||
if (list.bytes) |source_ptr| {
|
||||
const size = list.len();
|
||||
|
||||
if (data_is_owned) {
|
||||
inc_n_data(data, size);
|
||||
}
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < size) : (i += 1) {
|
||||
var satisfied = false;
|
||||
const element = source_ptr + i * element_width;
|
||||
caller(data, element, @ptrCast(?[*]u8, &satisfied));
|
||||
|
||||
if (!satisfied) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// SWAP ELEMENTS
|
||||
|
||||
inline fn swapHelp(width: usize, temporary: [*]u8, ptr1: [*]u8, ptr2: [*]u8) void {
|
||||
|
@ -45,14 +45,13 @@ comptime {
|
||||
exportListFn(list.listReverse, "reverse");
|
||||
exportListFn(list.listSortWith, "sort_with");
|
||||
exportListFn(list.listConcat, "concat");
|
||||
exportListFn(list.listTakeFirst, "take_first");
|
||||
exportListFn(list.listTakeLast, "take_last");
|
||||
exportListFn(list.listDrop, "drop");
|
||||
exportListFn(list.listSublist, "sublist");
|
||||
exportListFn(list.listDropAt, "drop_at");
|
||||
exportListFn(list.listSet, "set");
|
||||
exportListFn(list.listSetInPlace, "set_in_place");
|
||||
exportListFn(list.listSwap, "swap");
|
||||
exportListFn(list.listAny, "any");
|
||||
exportListFn(list.listAll, "all");
|
||||
exportListFn(list.listFindUnsafe, "find_unsafe");
|
||||
}
|
||||
|
||||
@ -128,6 +127,7 @@ comptime {
|
||||
exportStrFn(str.repeat, "repeat");
|
||||
exportStrFn(str.strTrim, "trim");
|
||||
exportStrFn(str.strTrimLeft, "trim_left");
|
||||
exportStrFn(str.strTrimRight, "trim_right");
|
||||
|
||||
inline for (INTEGERS) |T| {
|
||||
str.exportFromInt(T, ROC_BUILTINS ++ "." ++ STR ++ ".from_int.");
|
||||
|
@ -1584,6 +1584,41 @@ pub fn strTrimLeft(string: RocStr) callconv(.C) RocStr {
|
||||
return RocStr.empty();
|
||||
}
|
||||
|
||||
pub fn strTrimRight(string: RocStr) callconv(.C) RocStr {
|
||||
if (string.str_bytes) |bytes_ptr| {
|
||||
const trailing_bytes = countTrailingWhitespaceBytes(string);
|
||||
const original_len = string.len();
|
||||
|
||||
if (original_len == trailing_bytes) {
|
||||
string.deinit();
|
||||
return RocStr.empty();
|
||||
}
|
||||
|
||||
const new_len = original_len - trailing_bytes;
|
||||
|
||||
const small_or_shared = new_len <= SMALL_STR_MAX_LENGTH or !string.isRefcountOne();
|
||||
if (small_or_shared) {
|
||||
return RocStr.init(string.asU8ptr(), new_len);
|
||||
}
|
||||
|
||||
// nonempty, large, and unique:
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < new_len) : (i += 1) {
|
||||
const dest = bytes_ptr + i;
|
||||
const source = dest;
|
||||
@memcpy(dest, source, 1);
|
||||
}
|
||||
|
||||
var new_string = string;
|
||||
new_string.str_len = new_len;
|
||||
|
||||
return new_string;
|
||||
}
|
||||
|
||||
return RocStr.empty();
|
||||
}
|
||||
|
||||
fn countLeadingWhitespaceBytes(string: RocStr) usize {
|
||||
var byte_count: usize = 0;
|
||||
|
||||
@ -1820,6 +1855,77 @@ test "strTrimLeft: small to small" {
|
||||
try expect(trimmed.isSmallStr());
|
||||
}
|
||||
|
||||
test "strTrimRight: empty" {
|
||||
const trimmedEmpty = strTrimRight(RocStr.empty());
|
||||
try expect(trimmedEmpty.eq(RocStr.empty()));
|
||||
}
|
||||
|
||||
test "strTrimRight: blank" {
|
||||
const original_bytes = " ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.deinit();
|
||||
|
||||
const trimmed = strTrimRight(original);
|
||||
|
||||
try expect(trimmed.eq(RocStr.empty()));
|
||||
}
|
||||
|
||||
test "strTrimRight: large to large" {
|
||||
const original_bytes = " hello giant world ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.deinit();
|
||||
|
||||
try expect(!original.isSmallStr());
|
||||
|
||||
const expected_bytes = " hello giant world";
|
||||
const expected = RocStr.init(expected_bytes, expected_bytes.len);
|
||||
defer expected.deinit();
|
||||
|
||||
try expect(!expected.isSmallStr());
|
||||
|
||||
const trimmed = strTrimRight(original);
|
||||
|
||||
try expect(trimmed.eq(expected));
|
||||
}
|
||||
|
||||
test "strTrimRight: large to small" {
|
||||
const original_bytes = " hello world ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.deinit();
|
||||
|
||||
try expect(!original.isSmallStr());
|
||||
|
||||
const expected_bytes = " hello world";
|
||||
const expected = RocStr.init(expected_bytes, expected_bytes.len);
|
||||
defer expected.deinit();
|
||||
|
||||
try expect(expected.isSmallStr());
|
||||
|
||||
const trimmed = strTrimRight(original);
|
||||
|
||||
try expect(trimmed.eq(expected));
|
||||
try expect(trimmed.isSmallStr());
|
||||
}
|
||||
|
||||
test "strTrimRight: small to small" {
|
||||
const original_bytes = " hello world ";
|
||||
const original = RocStr.init(original_bytes, original_bytes.len);
|
||||
defer original.deinit();
|
||||
|
||||
try expect(original.isSmallStr());
|
||||
|
||||
const expected_bytes = " hello world";
|
||||
const expected = RocStr.init(expected_bytes, expected_bytes.len);
|
||||
defer expected.deinit();
|
||||
|
||||
try expect(expected.isSmallStr());
|
||||
|
||||
const trimmed = strTrimRight(original);
|
||||
|
||||
try expect(trimmed.eq(expected));
|
||||
try expect(trimmed.isSmallStr());
|
||||
}
|
||||
|
||||
test "ReverseUtf8View: hello world" {
|
||||
const original_bytes = "hello world";
|
||||
const expected_bytes = "dlrow olleh";
|
||||
|
@ -691,6 +691,10 @@ all : List elem, (elem -> Bool) -> Bool
|
||||
## any of the elements satisfy it.
|
||||
any : List elem, (elem -> Bool) -> Bool
|
||||
|
||||
## Run the given predicate on each element of the list, returning `True` if
|
||||
## all of the elements satisfy it.
|
||||
all : List elem, (elem -> Bool) -> Bool
|
||||
|
||||
## Returns the first element of the list satisfying a predicate function.
|
||||
## If no satisfying element is found, an `Err NotFound` is returned.
|
||||
find : List elem, (elem -> Bool) -> Result elem [ NotFound ]*
|
||||
|
@ -149,6 +149,7 @@ pub const STR_FROM_UTF8_RANGE: &str = "roc_builtins.str.from_utf8_range";
|
||||
pub const STR_REPEAT: &str = "roc_builtins.str.repeat";
|
||||
pub const STR_TRIM: &str = "roc_builtins.str.trim";
|
||||
pub const STR_TRIM_LEFT: &str = "roc_builtins.str.trim_left";
|
||||
pub const STR_TRIM_RIGHT: &str = "roc_builtins.str.trim_right";
|
||||
|
||||
pub const DICT_HASH: &str = "roc_builtins.dict.hash";
|
||||
pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str";
|
||||
@ -183,9 +184,7 @@ pub const LIST_CONTAINS: &str = "roc_builtins.list.contains";
|
||||
pub const LIST_REPEAT: &str = "roc_builtins.list.repeat";
|
||||
pub const LIST_APPEND: &str = "roc_builtins.list.append";
|
||||
pub const LIST_PREPEND: &str = "roc_builtins.list.prepend";
|
||||
pub const LIST_TAKE_FIRST: &str = "roc_builtins.list.take_first";
|
||||
pub const LIST_TAKE_LAST: &str = "roc_builtins.list.take_last";
|
||||
pub const LIST_DROP: &str = "roc_builtins.list.drop";
|
||||
pub const LIST_SUBLIST: &str = "roc_builtins.list.sublist";
|
||||
pub const LIST_DROP_AT: &str = "roc_builtins.list.drop_at";
|
||||
pub const LIST_SWAP: &str = "roc_builtins.list.swap";
|
||||
pub const LIST_SINGLE: &str = "roc_builtins.list.single";
|
||||
@ -197,6 +196,7 @@ pub const LIST_CONCAT: &str = "roc_builtins.list.concat";
|
||||
pub const LIST_SET: &str = "roc_builtins.list.set";
|
||||
pub const LIST_SET_IN_PLACE: &str = "roc_builtins.list.set_in_place";
|
||||
pub const LIST_ANY: &str = "roc_builtins.list.any";
|
||||
pub const LIST_ALL: &str = "roc_builtins.list.all";
|
||||
pub const LIST_FIND_UNSAFE: &str = "roc_builtins.list.find_unsafe";
|
||||
|
||||
pub const DEC_FROM_F64: &str = "roc_builtins.dec.from_f64";
|
||||
|
@ -639,6 +639,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||
Box::new(str_type())
|
||||
);
|
||||
|
||||
// trimRight : Str -> Str
|
||||
add_top_level_function_type!(
|
||||
Symbol::STR_TRIM_RIGHT,
|
||||
vec![str_type()],
|
||||
Box::new(str_type())
|
||||
);
|
||||
|
||||
// trim : Str -> Str
|
||||
add_top_level_function_type!(Symbol::STR_TRIM, vec![str_type()], Box::new(str_type()));
|
||||
|
||||
@ -1008,6 +1015,25 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||
Box::new(list_type(flex(TVAR1))),
|
||||
);
|
||||
|
||||
// split : List elem, Nat -> { before: List elem, others: List elem }
|
||||
add_top_level_function_type!(
|
||||
Symbol::LIST_SPLIT,
|
||||
vec![list_type(flex(TVAR1)), nat_type(),],
|
||||
Box::new(SolvedType::Record {
|
||||
fields: vec![
|
||||
(
|
||||
"before".into(),
|
||||
RecordField::Required(list_type(flex(TVAR1)))
|
||||
),
|
||||
(
|
||||
"others".into(),
|
||||
RecordField::Required(list_type(flex(TVAR1)))
|
||||
),
|
||||
],
|
||||
ext: Box::new(SolvedType::EmptyRecord),
|
||||
},),
|
||||
);
|
||||
|
||||
// drop : List elem, Nat -> List elem
|
||||
add_top_level_function_type!(
|
||||
Symbol::LIST_DROP,
|
||||
@ -1102,6 +1128,16 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||
Box::new(bool_type()),
|
||||
);
|
||||
|
||||
// all: List elem, (elem -> Bool) -> Bool
|
||||
add_top_level_function_type!(
|
||||
Symbol::LIST_ALL,
|
||||
vec![
|
||||
list_type(flex(TVAR1)),
|
||||
closure(vec![flex(TVAR1)], TVAR2, Box::new(bool_type())),
|
||||
],
|
||||
Box::new(bool_type()),
|
||||
);
|
||||
|
||||
// sortWith : List a, (a, a -> Ordering) -> List a
|
||||
add_top_level_function_type!(
|
||||
Symbol::LIST_SORT_WITH,
|
||||
@ -1133,6 +1169,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||
)
|
||||
}
|
||||
|
||||
// intersperse : List elem, elem -> List elem
|
||||
add_top_level_function_type!(
|
||||
Symbol::LIST_INTERSPERSE,
|
||||
vec![list_type(flex(TVAR1)), flex(TVAR1)],
|
||||
Box::new(list_type(flex(TVAR1))),
|
||||
);
|
||||
|
||||
// Dict module
|
||||
|
||||
// len : Dict * * -> Nat
|
||||
@ -1381,6 +1424,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||
Box::new(bool_type()),
|
||||
);
|
||||
|
||||
// isErr : Result * * -> bool
|
||||
add_top_level_function_type!(
|
||||
Symbol::RESULT_IS_ERR,
|
||||
vec![result_type(flex(TVAR1), flex(TVAR3))],
|
||||
Box::new(bool_type()),
|
||||
);
|
||||
|
||||
types
|
||||
}
|
||||
|
||||
|
@ -417,7 +417,7 @@ fn can_annotation_help(
|
||||
TagUnion { tags, ext, .. } => {
|
||||
let tag_types = can_tags(
|
||||
env,
|
||||
tags,
|
||||
tags.items,
|
||||
region,
|
||||
scope,
|
||||
var_store,
|
||||
@ -459,6 +459,9 @@ fn can_annotation_help(
|
||||
|
||||
Type::Variable(var)
|
||||
}
|
||||
Inferred => {
|
||||
unimplemented!();
|
||||
}
|
||||
Malformed(string) => {
|
||||
malformed(env, region, string);
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
use crate::def::Def;
|
||||
use crate::expr::{ClosureData, Expr::*};
|
||||
use crate::expr::{Expr, Recursive, WhenBranch};
|
||||
use crate::expr::{Expr, Field, Recursive, WhenBranch};
|
||||
use crate::pattern::Pattern;
|
||||
use roc_collections::all::SendMap;
|
||||
use roc_module::ident::TagName;
|
||||
use roc_module::called_via::CalledVia;
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::operator::CalledVia;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::{Located, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
@ -69,6 +69,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
||||
STR_REPEAT => str_repeat,
|
||||
STR_TRIM => str_trim,
|
||||
STR_TRIM_LEFT => str_trim_left,
|
||||
STR_TRIM_RIGHT => str_trim_right,
|
||||
LIST_LEN => list_len,
|
||||
LIST_GET => list_get,
|
||||
LIST_SET => list_set,
|
||||
@ -95,6 +96,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
||||
LIST_TAKE_FIRST => list_take_first,
|
||||
LIST_TAKE_LAST => list_take_last,
|
||||
LIST_SUBLIST => list_sublist,
|
||||
LIST_SPLIT => list_split,
|
||||
LIST_INTERSPERSE => list_intersperse,
|
||||
LIST_DROP => list_drop,
|
||||
LIST_DROP_AT => list_drop_at,
|
||||
LIST_DROP_FIRST => list_drop_first,
|
||||
@ -110,6 +113,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
||||
LIST_WALK_UNTIL => list_walk_until,
|
||||
LIST_SORT_WITH => list_sort_with,
|
||||
LIST_ANY => list_any,
|
||||
LIST_ALL => list_all,
|
||||
LIST_FIND => list_find,
|
||||
DICT_LEN => dict_len,
|
||||
DICT_EMPTY => dict_empty,
|
||||
@ -193,6 +197,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
||||
RESULT_AFTER => result_after,
|
||||
RESULT_WITH_DEFAULT => result_with_default,
|
||||
RESULT_IS_OK => result_is_ok,
|
||||
RESULT_IS_ERR => result_is_err,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1294,6 +1299,11 @@ fn str_trim_left(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_1(symbol, LowLevel::StrTrimLeft, var_store)
|
||||
}
|
||||
|
||||
/// Str.trimRight : Str -> Str
|
||||
fn str_trim_right(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_1(symbol, LowLevel::StrTrimRight, var_store)
|
||||
}
|
||||
|
||||
/// Str.repeat : Str, Nat -> Str
|
||||
fn str_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let str_var = var_store.fresh();
|
||||
@ -2020,11 +2030,13 @@ 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(len_var, Variable::NATURAL, 0);
|
||||
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::ListTakeFirst,
|
||||
op: LowLevel::ListSublist,
|
||||
args: vec![
|
||||
(list_var, Var(Symbol::ARG_1)),
|
||||
(len_var, zero),
|
||||
(len_var, Var(Symbol::ARG_2)),
|
||||
],
|
||||
ret_var: list_var,
|
||||
@ -2044,10 +2056,40 @@ 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(len_var, Variable::NATURAL, 0);
|
||||
let bool_var = var_store.fresh();
|
||||
|
||||
let get_list_len = RunLowLevel {
|
||||
op: LowLevel::ListLen,
|
||||
args: vec![(list_var, Var(Symbol::ARG_1))],
|
||||
ret_var: len_var,
|
||||
};
|
||||
|
||||
let get_sub = RunLowLevel {
|
||||
op: LowLevel::NumSubWrap,
|
||||
args: vec![(len_var, get_list_len), (len_var, Var(Symbol::ARG_2))],
|
||||
ret_var: len_var,
|
||||
};
|
||||
|
||||
let get_start = If {
|
||||
cond_var: bool_var,
|
||||
branch_var: len_var,
|
||||
branches: vec![(
|
||||
no_region(RunLowLevel {
|
||||
op: LowLevel::NumGt,
|
||||
args: vec![(len_var, get_sub.clone()), (len_var, zero.clone())],
|
||||
ret_var: bool_var,
|
||||
}),
|
||||
no_region(get_sub),
|
||||
)],
|
||||
final_else: Box::new(no_region(zero)),
|
||||
};
|
||||
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::ListTakeLast,
|
||||
op: LowLevel::ListSublist,
|
||||
args: vec![
|
||||
(list_var, Var(Symbol::ARG_1)),
|
||||
(len_var, get_start),
|
||||
(len_var, Var(Symbol::ARG_2)),
|
||||
],
|
||||
ret_var: list_var,
|
||||
@ -2089,15 +2131,13 @@ fn list_sublist(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
field: "len".into(),
|
||||
};
|
||||
|
||||
let body_drop = RunLowLevel {
|
||||
op: LowLevel::ListDrop,
|
||||
args: vec![(list_var, Var(sym_list)), (start_var, get_start)],
|
||||
ret_var: list_var,
|
||||
};
|
||||
|
||||
let body_take = RunLowLevel {
|
||||
op: LowLevel::ListTakeFirst,
|
||||
args: vec![(list_var, body_drop), (len_var, get_len)],
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::ListSublist,
|
||||
args: vec![
|
||||
(list_var, Var(sym_list)),
|
||||
(start_var, get_start),
|
||||
(len_var, get_len),
|
||||
],
|
||||
ret_var: list_var,
|
||||
};
|
||||
|
||||
@ -2105,28 +2145,229 @@ fn list_sublist(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
symbol,
|
||||
vec![(list_var, sym_list), (rec_var, sym_rec)],
|
||||
var_store,
|
||||
body_take,
|
||||
body,
|
||||
list_var,
|
||||
)
|
||||
}
|
||||
|
||||
/// List.intersperse : List elem, elem -> List elem
|
||||
fn list_intersperse(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let list_var = var_store.fresh();
|
||||
let sep_var = var_store.fresh();
|
||||
|
||||
let list_sym = Symbol::ARG_1;
|
||||
let sep_sym = Symbol::ARG_2;
|
||||
|
||||
let clos_var = var_store.fresh();
|
||||
let clos_acc_var = var_store.fresh();
|
||||
|
||||
let clos_sym = Symbol::LIST_INTERSPERSE_CLOS;
|
||||
let clos_acc_sym = Symbol::ARG_3;
|
||||
let clos_elem_sym = Symbol::ARG_4;
|
||||
|
||||
let int_var = var_store.fresh();
|
||||
let zero = int(int_var, Variable::NATURAL, 0);
|
||||
|
||||
// \acc, elem -> acc |> List.append sep |> List.append elem
|
||||
let clos = Closure(ClosureData {
|
||||
function_type: clos_var,
|
||||
closure_type: var_store.fresh(),
|
||||
closure_ext_var: var_store.fresh(),
|
||||
return_type: clos_acc_var,
|
||||
name: clos_sym,
|
||||
recursive: Recursive::NotRecursive,
|
||||
captured_symbols: vec![(sep_sym, sep_var)],
|
||||
arguments: vec![
|
||||
(clos_acc_var, no_region(Pattern::Identifier(clos_acc_sym))),
|
||||
(sep_var, no_region(Pattern::Identifier(clos_elem_sym))),
|
||||
],
|
||||
loc_body: {
|
||||
let append_sep = RunLowLevel {
|
||||
op: LowLevel::ListAppend,
|
||||
args: vec![(clos_acc_var, Var(clos_acc_sym)), (sep_var, Var(sep_sym))],
|
||||
ret_var: clos_acc_var,
|
||||
};
|
||||
|
||||
Box::new(no_region(RunLowLevel {
|
||||
op: LowLevel::ListAppend,
|
||||
args: vec![(clos_acc_var, append_sep), (sep_var, Var(clos_elem_sym))],
|
||||
ret_var: clos_acc_var,
|
||||
}))
|
||||
},
|
||||
});
|
||||
|
||||
// List.walk [] l (\acc, elem -> acc |> List.append sep |> List.append elem)
|
||||
let acc = RunLowLevel {
|
||||
op: LowLevel::ListWalk,
|
||||
args: vec![
|
||||
(list_var, Var(list_sym)),
|
||||
(
|
||||
clos_acc_var,
|
||||
List {
|
||||
elem_var: sep_var,
|
||||
loc_elems: vec![],
|
||||
},
|
||||
),
|
||||
(clos_var, clos),
|
||||
],
|
||||
ret_var: clos_acc_var,
|
||||
};
|
||||
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::ListDropAt,
|
||||
args: vec![(clos_acc_var, acc), (int_var, zero)],
|
||||
ret_var: clos_acc_var,
|
||||
};
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![(list_var, list_sym), (sep_var, sep_sym)],
|
||||
var_store,
|
||||
body,
|
||||
clos_acc_var,
|
||||
)
|
||||
}
|
||||
|
||||
/// List.split : List elem, Nat -> { before: List elem, others: List elem }
|
||||
fn list_split(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let list_var = var_store.fresh();
|
||||
let index_var = var_store.fresh();
|
||||
|
||||
let list_sym = Symbol::ARG_1;
|
||||
let index_sym = Symbol::ARG_2;
|
||||
|
||||
let clos_sym = Symbol::LIST_SPLIT_CLOS;
|
||||
let clos_start_sym = Symbol::ARG_3;
|
||||
let clos_len_sym = Symbol::ARG_4;
|
||||
|
||||
let clos_fun_var = var_store.fresh();
|
||||
let clos_start_var = var_store.fresh();
|
||||
let clos_len_var = var_store.fresh();
|
||||
let clos_ret_var = var_store.fresh();
|
||||
|
||||
let ret_var = var_store.fresh();
|
||||
let zero = int(index_var, Variable::NATURAL, 0);
|
||||
|
||||
let clos = Closure(ClosureData {
|
||||
function_type: clos_fun_var,
|
||||
closure_type: var_store.fresh(),
|
||||
closure_ext_var: var_store.fresh(),
|
||||
return_type: clos_ret_var,
|
||||
name: clos_sym,
|
||||
recursive: Recursive::NotRecursive,
|
||||
captured_symbols: vec![(list_sym, clos_ret_var)],
|
||||
arguments: vec![
|
||||
(
|
||||
clos_start_var,
|
||||
no_region(Pattern::Identifier(clos_start_sym)),
|
||||
),
|
||||
(clos_len_var, no_region(Pattern::Identifier(clos_len_sym))),
|
||||
],
|
||||
loc_body: {
|
||||
Box::new(no_region(RunLowLevel {
|
||||
op: LowLevel::ListSublist,
|
||||
args: vec![
|
||||
(clos_ret_var, Var(list_sym)),
|
||||
(clos_start_var, Var(clos_start_sym)),
|
||||
(clos_len_var, Var(clos_len_sym)),
|
||||
],
|
||||
ret_var: clos_ret_var,
|
||||
}))
|
||||
},
|
||||
});
|
||||
|
||||
let fun = Box::new((
|
||||
clos_fun_var,
|
||||
no_region(clos),
|
||||
var_store.fresh(),
|
||||
clos_ret_var,
|
||||
));
|
||||
|
||||
let get_before = Call(
|
||||
fun.clone(),
|
||||
vec![
|
||||
(index_var, no_region(zero)),
|
||||
(index_var, no_region(Var(index_sym))),
|
||||
],
|
||||
CalledVia::Space,
|
||||
);
|
||||
|
||||
let get_list_len = RunLowLevel {
|
||||
op: LowLevel::ListLen,
|
||||
args: vec![(list_var, Var(list_sym))],
|
||||
ret_var: index_var,
|
||||
};
|
||||
|
||||
let get_others_len = RunLowLevel {
|
||||
op: LowLevel::NumSubWrap,
|
||||
args: vec![(index_var, get_list_len), (index_var, Var(index_sym))],
|
||||
ret_var: index_var,
|
||||
};
|
||||
|
||||
let get_others = Call(
|
||||
fun,
|
||||
vec![
|
||||
(index_var, no_region(Var(index_sym))),
|
||||
(index_var, no_region(get_others_len)),
|
||||
],
|
||||
CalledVia::Space,
|
||||
);
|
||||
|
||||
let before = Field {
|
||||
var: clos_ret_var,
|
||||
region: Region::zero(),
|
||||
loc_expr: Box::new(no_region(get_before)),
|
||||
};
|
||||
let others = Field {
|
||||
var: clos_ret_var,
|
||||
region: Region::zero(),
|
||||
loc_expr: Box::new(no_region(get_others)),
|
||||
};
|
||||
|
||||
let body = record(
|
||||
vec![("before".into(), before), ("others".into(), others)],
|
||||
var_store,
|
||||
);
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![(list_var, list_sym), (index_var, index_sym)],
|
||||
var_store,
|
||||
body,
|
||||
ret_var,
|
||||
)
|
||||
}
|
||||
|
||||
/// List.drop : List elem, Nat -> List elem
|
||||
fn list_drop(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let list_var = var_store.fresh();
|
||||
let index_var = var_store.fresh();
|
||||
let len_var = var_store.fresh();
|
||||
|
||||
let get_list_len = RunLowLevel {
|
||||
op: LowLevel::ListLen,
|
||||
args: vec![(list_var, Var(Symbol::ARG_1))],
|
||||
ret_var: len_var,
|
||||
};
|
||||
|
||||
let get_len = RunLowLevel {
|
||||
op: LowLevel::NumSubWrap,
|
||||
args: vec![(len_var, get_list_len), (len_var, Var(Symbol::ARG_2))],
|
||||
ret_var: len_var,
|
||||
};
|
||||
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::ListDrop,
|
||||
op: LowLevel::ListSublist,
|
||||
args: vec![
|
||||
(list_var, Var(Symbol::ARG_1)),
|
||||
(index_var, Var(Symbol::ARG_2)),
|
||||
(len_var, Var(Symbol::ARG_2)),
|
||||
(len_var, get_len),
|
||||
],
|
||||
ret_var: list_var,
|
||||
};
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![(list_var, Symbol::ARG_1), (index_var, Symbol::ARG_2)],
|
||||
vec![(list_var, Symbol::ARG_1), (len_var, Symbol::ARG_2)],
|
||||
var_store,
|
||||
body,
|
||||
list_var,
|
||||
@ -2805,6 +3046,11 @@ fn list_any(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_2(symbol, LowLevel::ListAny, var_store)
|
||||
}
|
||||
|
||||
/// List.all: List elem, (elem -> Bool) -> Bool
|
||||
fn list_all(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
lowlevel_2(symbol, LowLevel::ListAll, var_store)
|
||||
}
|
||||
|
||||
/// List.find : List elem, (elem -> Bool) -> Result elem [ NotFound ]*
|
||||
fn list_find(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let list = Symbol::ARG_1;
|
||||
@ -3992,6 +4238,83 @@ fn result_with_default(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
)
|
||||
}
|
||||
|
||||
fn result_is_err(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let ret_var = var_store.fresh();
|
||||
let result_var = var_store.fresh();
|
||||
|
||||
let mut branches = vec![];
|
||||
|
||||
{
|
||||
// ok branch
|
||||
let tag_name = TagName::Global("Ok".into());
|
||||
|
||||
let pattern = Pattern::AppliedTag {
|
||||
whole_var: result_var,
|
||||
ext_var: var_store.fresh(),
|
||||
tag_name,
|
||||
arguments: vec![(var_store.fresh(), no_region(Pattern::Underscore))],
|
||||
};
|
||||
|
||||
let false_expr = Tag {
|
||||
variant_var: var_store.fresh(),
|
||||
ext_var: var_store.fresh(),
|
||||
name: TagName::Global("False".into()),
|
||||
arguments: vec![],
|
||||
};
|
||||
|
||||
let branch = WhenBranch {
|
||||
patterns: vec![no_region(pattern)],
|
||||
value: no_region(false_expr),
|
||||
guard: None,
|
||||
};
|
||||
|
||||
branches.push(branch);
|
||||
}
|
||||
|
||||
{
|
||||
// err branch
|
||||
let tag_name = TagName::Global("Err".into());
|
||||
|
||||
let pattern = Pattern::AppliedTag {
|
||||
whole_var: result_var,
|
||||
ext_var: var_store.fresh(),
|
||||
tag_name,
|
||||
arguments: vec![(var_store.fresh(), no_region(Pattern::Underscore))],
|
||||
};
|
||||
|
||||
let true_expr = Tag {
|
||||
variant_var: var_store.fresh(),
|
||||
ext_var: var_store.fresh(),
|
||||
name: TagName::Global("True".into()),
|
||||
arguments: vec![],
|
||||
};
|
||||
|
||||
let branch = WhenBranch {
|
||||
patterns: vec![no_region(pattern)],
|
||||
value: no_region(true_expr),
|
||||
guard: None,
|
||||
};
|
||||
|
||||
branches.push(branch);
|
||||
}
|
||||
|
||||
let body = When {
|
||||
cond_var: result_var,
|
||||
expr_var: ret_var,
|
||||
region: Region::zero(),
|
||||
loc_cond: Box::new(no_region(Var(Symbol::ARG_1))),
|
||||
branches,
|
||||
};
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![(result_var, Symbol::ARG_1)],
|
||||
var_store,
|
||||
body,
|
||||
ret_var,
|
||||
)
|
||||
}
|
||||
|
||||
fn result_is_ok(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let ret_var = var_store.fresh();
|
||||
let result_var = var_store.fresh();
|
||||
@ -4182,17 +4505,17 @@ fn tag(name: &'static str, args: Vec<Expr>, var_store: &mut VarStore) -> Expr {
|
||||
}
|
||||
}
|
||||
|
||||
// #[inline(always)]
|
||||
// fn record(fields: Vec<(Lowercase, Field)>, var_store: &mut VarStore) -> Expr {
|
||||
// let mut send_map = SendMap::default();
|
||||
// for (k, v) in fields {
|
||||
// send_map.insert(k, v);
|
||||
// }
|
||||
// Expr::Record {
|
||||
// record_var: var_store.fresh(),
|
||||
// fields: send_map,
|
||||
// }
|
||||
// }
|
||||
#[inline(always)]
|
||||
fn record(fields: Vec<(Lowercase, Field)>, var_store: &mut VarStore) -> Expr {
|
||||
let mut send_map = SendMap::default();
|
||||
for (k, v) in fields {
|
||||
send_map.insert(k, v);
|
||||
}
|
||||
Expr::Record {
|
||||
record_var: var_store.fresh(),
|
||||
fields: send_map,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn defn(
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::procedure::References;
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_module::ident::{Ident, ModuleName};
|
||||
use roc_module::ident::{Ident, Lowercase, ModuleName};
|
||||
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
|
||||
use roc_problem::can::{Problem, RuntimeError};
|
||||
use roc_region::all::{Located, Region};
|
||||
@ -99,23 +99,37 @@ impl<'a> Env<'a> {
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
match self
|
||||
.dep_idents
|
||||
.get(&module_id)
|
||||
.and_then(|exposed_ids| exposed_ids.get_id(&ident))
|
||||
{
|
||||
Some(ident_id) => {
|
||||
let symbol = Symbol::new(module_id, *ident_id);
|
||||
match self.dep_idents.get(&module_id) {
|
||||
Some(exposed_ids) => match exposed_ids.get_id(&ident) {
|
||||
Some(ident_id) => {
|
||||
let symbol = Symbol::new(module_id, *ident_id);
|
||||
|
||||
self.qualified_lookups.insert(symbol);
|
||||
self.qualified_lookups.insert(symbol);
|
||||
|
||||
Ok(symbol)
|
||||
Ok(symbol)
|
||||
}
|
||||
None => {
|
||||
let exposed_values = exposed_ids
|
||||
.idents()
|
||||
.filter(|(_, ident)| {
|
||||
ident.as_ref().starts_with(|c: char| c.is_lowercase())
|
||||
})
|
||||
.map(|(_, ident)| Lowercase::from(ident.as_ref()))
|
||||
.collect();
|
||||
Err(RuntimeError::ValueNotExposed {
|
||||
module_name,
|
||||
ident,
|
||||
region,
|
||||
exposed_values,
|
||||
})
|
||||
}
|
||||
},
|
||||
None => {
|
||||
panic!(
|
||||
"Module {} exists, but is not recorded in dep_idents",
|
||||
module_name
|
||||
)
|
||||
}
|
||||
None => Err(RuntimeError::ValueNotExposed {
|
||||
module_name,
|
||||
ident,
|
||||
region,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,9 +10,9 @@ use crate::pattern::{canonicalize_pattern, Pattern};
|
||||
use crate::procedure::References;
|
||||
use crate::scope::Scope;
|
||||
use roc_collections::all::{ImSet, MutMap, MutSet, SendMap};
|
||||
use roc_module::called_via::CalledVia;
|
||||
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::operator::CalledVia;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_parse::ast::{self, EscapedChar, StrLiteral};
|
||||
use roc_parse::pattern::PatternType::*;
|
||||
@ -228,14 +228,11 @@ pub fn canonicalize_expr<'a>(
|
||||
|
||||
(answer, Output::default())
|
||||
}
|
||||
ast::Expr::Record {
|
||||
fields,
|
||||
final_comments: _,
|
||||
} => {
|
||||
ast::Expr::Record(fields) => {
|
||||
if fields.is_empty() {
|
||||
(EmptyRecord, Output::default())
|
||||
} else {
|
||||
match canonicalize_fields(env, var_store, scope, region, fields) {
|
||||
match canonicalize_fields(env, var_store, scope, region, fields.items) {
|
||||
Ok((can_fields, output)) => (
|
||||
Record {
|
||||
record_var: var_store.fresh(),
|
||||
@ -261,12 +258,11 @@ pub fn canonicalize_expr<'a>(
|
||||
ast::Expr::RecordUpdate {
|
||||
fields,
|
||||
update: loc_update,
|
||||
final_comments: _,
|
||||
} => {
|
||||
let (can_update, update_out) =
|
||||
canonicalize_expr(env, var_store, scope, loc_update.region, &loc_update.value);
|
||||
if let Var(symbol) = &can_update.value {
|
||||
match canonicalize_fields(env, var_store, scope, region, fields) {
|
||||
match canonicalize_fields(env, var_store, scope, region, fields.items) {
|
||||
Ok((can_fields, mut output)) => {
|
||||
output.references = output.references.union(update_out.references);
|
||||
|
||||
@ -307,9 +303,7 @@ pub fn canonicalize_expr<'a>(
|
||||
}
|
||||
}
|
||||
ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal),
|
||||
ast::Expr::List {
|
||||
items: loc_elems, ..
|
||||
} => {
|
||||
ast::Expr::List(loc_elems) => {
|
||||
if loc_elems.is_empty() {
|
||||
(
|
||||
List {
|
||||
@ -1717,7 +1711,7 @@ fn desugar_str_segments(var_store: &mut VarStore, segments: Vec<StrSegment>) ->
|
||||
(var_store.fresh(), loc_new_expr),
|
||||
(var_store.fresh(), loc_expr),
|
||||
],
|
||||
CalledVia::Space,
|
||||
CalledVia::StringInterpolation,
|
||||
);
|
||||
|
||||
loc_expr = Located::new(0, 0, 0, 0, expr);
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use roc_module::called_via::BinOp::Pizza;
|
||||
use roc_module::called_via::{BinOp, CalledVia};
|
||||
use roc_module::ident::ModuleName;
|
||||
use roc_module::operator::BinOp::Pizza;
|
||||
use roc_module::operator::{BinOp, CalledVia};
|
||||
use roc_parse::ast::Expr::{self, *};
|
||||
use roc_parse::ast::{AssignedField, Def, WhenBranch};
|
||||
use roc_region::all::{Located, Region};
|
||||
@ -144,77 +144,46 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
|
||||
|
||||
arena.alloc(Located { region, value })
|
||||
}
|
||||
List {
|
||||
items,
|
||||
final_comments,
|
||||
} => {
|
||||
List(items) => {
|
||||
let mut new_items = Vec::with_capacity_in(items.len(), arena);
|
||||
|
||||
for item in items.iter() {
|
||||
new_items.push(desugar_expr(arena, item));
|
||||
}
|
||||
let new_items = new_items.into_bump_slice();
|
||||
let value: Expr<'a> = List {
|
||||
items: new_items,
|
||||
final_comments,
|
||||
};
|
||||
let value: Expr<'a> = List(items.replace_items(new_items));
|
||||
|
||||
arena.alloc(Located {
|
||||
region: loc_expr.region,
|
||||
value,
|
||||
})
|
||||
}
|
||||
Record {
|
||||
fields,
|
||||
final_comments,
|
||||
} => {
|
||||
let mut new_fields = Vec::with_capacity_in(fields.len(), arena);
|
||||
|
||||
for field in fields.iter() {
|
||||
Record(fields) => arena.alloc(Located {
|
||||
region: loc_expr.region,
|
||||
value: Record(fields.map_items(arena, |field| {
|
||||
let value = desugar_field(arena, &field.value);
|
||||
|
||||
new_fields.push(Located {
|
||||
Located {
|
||||
value,
|
||||
region: field.region,
|
||||
});
|
||||
}
|
||||
}
|
||||
})),
|
||||
}),
|
||||
|
||||
let new_fields = new_fields.into_bump_slice();
|
||||
|
||||
arena.alloc(Located {
|
||||
region: loc_expr.region,
|
||||
value: Record {
|
||||
fields: new_fields,
|
||||
final_comments,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
RecordUpdate {
|
||||
fields,
|
||||
update,
|
||||
final_comments,
|
||||
} => {
|
||||
RecordUpdate { fields, update } => {
|
||||
// NOTE the `update` field is always a `Var { .. }` and does not need to be desugared
|
||||
let mut new_fields = Vec::with_capacity_in(fields.len(), arena);
|
||||
|
||||
for field in fields.iter() {
|
||||
let new_fields = fields.map_items(arena, |field| {
|
||||
let value = desugar_field(arena, &field.value);
|
||||
|
||||
new_fields.push(Located {
|
||||
Located {
|
||||
value,
|
||||
region: field.region,
|
||||
});
|
||||
}
|
||||
|
||||
let new_fields = new_fields.into_bump_slice();
|
||||
}
|
||||
});
|
||||
|
||||
arena.alloc(Located {
|
||||
region: loc_expr.region,
|
||||
value: RecordUpdate {
|
||||
update: *update,
|
||||
fields: new_fields,
|
||||
final_comments,
|
||||
},
|
||||
})
|
||||
}
|
||||
@ -308,7 +277,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
|
||||
})
|
||||
}
|
||||
UnaryOp(loc_arg, loc_op) => {
|
||||
use roc_module::operator::UnaryOp::*;
|
||||
use roc_module::called_via::UnaryOp::*;
|
||||
|
||||
let region = loc_op.region;
|
||||
let op = loc_op.value;
|
||||
@ -506,7 +475,7 @@ fn binop_step<'a>(
|
||||
op_stack: &mut Vec<Located<BinOp>>,
|
||||
next_op: Located<BinOp>,
|
||||
) -> Step<'a> {
|
||||
use roc_module::operator::Associativity::*;
|
||||
use roc_module::called_via::Associativity::*;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
match op_stack.pop() {
|
||||
|
@ -246,7 +246,7 @@ pub fn canonicalize_pattern<'a>(
|
||||
let mut destructs = Vec::with_capacity(patterns.len());
|
||||
let mut opt_erroneous = None;
|
||||
|
||||
for loc_pattern in *patterns {
|
||||
for loc_pattern in patterns.iter() {
|
||||
match loc_pattern.value {
|
||||
Identifier(label) => {
|
||||
match scope.introduce(
|
||||
|
@ -254,7 +254,7 @@ pub fn constrain_expr(
|
||||
exists(vec![*elem_var], And(constraints))
|
||||
}
|
||||
}
|
||||
Call(boxed, loc_args, _application_style) => {
|
||||
Call(boxed, loc_args, called_via) => {
|
||||
let (fn_var, loc_fn, closure_var, ret_var) = &**boxed;
|
||||
// The expression that evaluates to the function being called, e.g. `foo` in
|
||||
// (foo) bar baz
|
||||
@ -317,7 +317,7 @@ pub fn constrain_expr(
|
||||
region,
|
||||
);
|
||||
|
||||
let category = Category::CallResult(opt_symbol);
|
||||
let category = Category::CallResult(opt_symbol, *called_via);
|
||||
|
||||
exists(
|
||||
vars,
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::spaces::{fmt_comments_only, fmt_spaces, newline, NewlineAt, INDENT};
|
||||
use bumpalo::collections::String;
|
||||
use roc_parse::ast::{AssignedField, Collection, Expr, Tag, TypeAnnotation};
|
||||
use roc_parse::ast::{AssignedField, Expr, Tag, TypeAnnotation};
|
||||
use roc_region::all::Located;
|
||||
|
||||
/// Does an AST node need parens around it?
|
||||
@ -81,9 +81,9 @@ where
|
||||
}
|
||||
|
||||
macro_rules! format_sequence {
|
||||
($buf: expr, $indent:expr, $start:expr, $end:expr, $items:expr, $final_comments:expr, $newline:expr, $t:ident) => {
|
||||
let is_multiline =
|
||||
$items.iter().any(|item| item.value.is_multiline()) || !$final_comments.is_empty();
|
||||
($buf: expr, $indent:expr, $start:expr, $end:expr, $items:expr, $newline:expr, $t:ident) => {
|
||||
let is_multiline = $items.iter().any(|item| item.value.is_multiline())
|
||||
|| !$items.final_comments().is_empty();
|
||||
|
||||
if is_multiline {
|
||||
let braces_indent = $indent + INDENT;
|
||||
@ -138,7 +138,12 @@ macro_rules! format_sequence {
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt_comments_only($buf, $final_comments.iter(), NewlineAt::Top, item_indent);
|
||||
fmt_comments_only(
|
||||
$buf,
|
||||
$items.final_comments().iter(),
|
||||
NewlineAt::Top,
|
||||
item_indent,
|
||||
);
|
||||
newline($buf, braces_indent);
|
||||
$buf.push($end);
|
||||
} else {
|
||||
@ -175,7 +180,7 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
|
||||
true
|
||||
}
|
||||
|
||||
Wildcard | BoundVariable(_) | Malformed(_) => false,
|
||||
Wildcard | Inferred | BoundVariable(_) | Malformed(_) => false,
|
||||
Function(args, result) => {
|
||||
(&result.value).is_multiline()
|
||||
|| args.iter().any(|loc_arg| (&loc_arg.value).is_multiline())
|
||||
@ -192,11 +197,7 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
|
||||
fields.items.iter().any(|field| field.value.is_multiline())
|
||||
}
|
||||
|
||||
TagUnion {
|
||||
tags,
|
||||
ext,
|
||||
final_comments: _,
|
||||
} => {
|
||||
TagUnion { tags, ext } => {
|
||||
match ext {
|
||||
Some(ann) if ann.value.is_multiline() => return true,
|
||||
_ => {}
|
||||
@ -278,37 +279,18 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
|
||||
}
|
||||
BoundVariable(v) => buf.push_str(v),
|
||||
Wildcard => buf.push('*'),
|
||||
Inferred => buf.push('_'),
|
||||
|
||||
TagUnion {
|
||||
tags,
|
||||
ext,
|
||||
final_comments,
|
||||
} => {
|
||||
format_sequence!(buf, indent, '[', ']', tags, final_comments, newlines, Tag);
|
||||
TagUnion { tags, ext } => {
|
||||
format_sequence!(buf, indent, '[', ']', tags, newlines, Tag);
|
||||
|
||||
if let Some(loc_ext_ann) = *ext {
|
||||
loc_ext_ann.value.format(buf, indent);
|
||||
}
|
||||
}
|
||||
|
||||
Record {
|
||||
fields:
|
||||
Collection {
|
||||
items,
|
||||
final_comments,
|
||||
},
|
||||
ext,
|
||||
} => {
|
||||
format_sequence!(
|
||||
buf,
|
||||
indent,
|
||||
'{',
|
||||
'}',
|
||||
items,
|
||||
final_comments,
|
||||
newlines,
|
||||
AssignedField
|
||||
);
|
||||
Record { fields, ext } => {
|
||||
format_sequence!(buf, indent, '{', '}', fields, newlines, AssignedField);
|
||||
|
||||
if let Some(loc_ext_ann) = *ext {
|
||||
loc_ext_ann.value.format(buf, indent);
|
||||
|
@ -3,9 +3,11 @@ use crate::def::fmt_def;
|
||||
use crate::pattern::fmt_pattern;
|
||||
use crate::spaces::{add_spaces, fmt_comments_only, fmt_spaces, newline, NewlineAt, INDENT};
|
||||
use bumpalo::collections::String;
|
||||
use roc_module::operator::{self, BinOp};
|
||||
use roc_module::called_via::{self, BinOp};
|
||||
use roc_parse::ast::StrSegment;
|
||||
use roc_parse::ast::{AssignedField, Base, CommentOrNewline, Expr, Pattern, WhenBranch};
|
||||
use roc_parse::ast::{
|
||||
AssignedField, Base, Collection, CommentOrNewline, Expr, Pattern, WhenBranch,
|
||||
};
|
||||
use roc_region::all::Located;
|
||||
|
||||
impl<'a> Formattable<'a> for Expr<'a> {
|
||||
@ -39,7 +41,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
|
||||
// These expressions always have newlines
|
||||
Defs(_, _) | When(_, _) => true,
|
||||
|
||||
List { items, .. } => items.iter().any(|loc_expr| loc_expr.is_multiline()),
|
||||
List(items) => items.iter().any(|loc_expr| loc_expr.is_multiline()),
|
||||
|
||||
Str(literal) => {
|
||||
use roc_parse::ast::StrLiteral::*;
|
||||
@ -98,7 +100,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
|
||||
.any(|loc_pattern| loc_pattern.is_multiline())
|
||||
}
|
||||
|
||||
Record { fields, .. } => fields.iter().any(|loc_field| loc_field.is_multiline()),
|
||||
Record(fields) => fields.iter().any(|loc_field| loc_field.is_multiline()),
|
||||
RecordUpdate { fields, .. } => fields.iter().any(|loc_field| loc_field.is_multiline()),
|
||||
}
|
||||
}
|
||||
@ -250,18 +252,11 @@ impl<'a> Formattable<'a> for Expr<'a> {
|
||||
|
||||
buf.push_str(string);
|
||||
}
|
||||
Record {
|
||||
fields,
|
||||
final_comments,
|
||||
} => {
|
||||
fmt_record(buf, None, fields, final_comments, indent);
|
||||
Record(fields) => {
|
||||
fmt_record(buf, None, *fields, indent);
|
||||
}
|
||||
RecordUpdate {
|
||||
fields,
|
||||
update,
|
||||
final_comments,
|
||||
} => {
|
||||
fmt_record(buf, Some(*update), fields, final_comments, indent);
|
||||
RecordUpdate { update, fields } => {
|
||||
fmt_record(buf, Some(*update), *fields, indent);
|
||||
}
|
||||
Closure(loc_patterns, loc_ret) => {
|
||||
fmt_closure(buf, loc_patterns, loc_ret, indent);
|
||||
@ -295,19 +290,16 @@ impl<'a> Formattable<'a> for Expr<'a> {
|
||||
fmt_if(buf, branches, final_else, self.is_multiline(), indent);
|
||||
}
|
||||
When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, indent),
|
||||
List {
|
||||
items,
|
||||
final_comments,
|
||||
} => {
|
||||
fmt_list(buf, items, final_comments, indent);
|
||||
List(items) => {
|
||||
fmt_list(buf, *items, indent);
|
||||
}
|
||||
BinOps(lefts, right) => fmt_bin_ops(buf, lefts, right, false, parens, indent),
|
||||
UnaryOp(sub_expr, unary_op) => {
|
||||
match &unary_op.value {
|
||||
operator::UnaryOp::Negate => {
|
||||
called_via::UnaryOp::Negate => {
|
||||
buf.push('-');
|
||||
}
|
||||
operator::UnaryOp::Not => {
|
||||
called_via::UnaryOp::Not => {
|
||||
buf.push('!');
|
||||
}
|
||||
}
|
||||
@ -362,26 +354,26 @@ fn format_str_segment<'a>(seg: &StrSegment<'a>, buf: &mut String<'a>, indent: u1
|
||||
|
||||
fn push_op(buf: &mut String, op: BinOp) {
|
||||
match op {
|
||||
operator::BinOp::Caret => buf.push('^'),
|
||||
operator::BinOp::Star => buf.push('*'),
|
||||
operator::BinOp::Slash => buf.push('/'),
|
||||
operator::BinOp::DoubleSlash => buf.push_str("//"),
|
||||
operator::BinOp::Percent => buf.push('%'),
|
||||
operator::BinOp::DoublePercent => buf.push_str("%%"),
|
||||
operator::BinOp::Plus => buf.push('+'),
|
||||
operator::BinOp::Minus => buf.push('-'),
|
||||
operator::BinOp::Equals => buf.push_str("=="),
|
||||
operator::BinOp::NotEquals => buf.push_str("!="),
|
||||
operator::BinOp::LessThan => buf.push('<'),
|
||||
operator::BinOp::GreaterThan => buf.push('>'),
|
||||
operator::BinOp::LessThanOrEq => buf.push_str("<="),
|
||||
operator::BinOp::GreaterThanOrEq => buf.push_str(">="),
|
||||
operator::BinOp::And => buf.push_str("&&"),
|
||||
operator::BinOp::Or => buf.push_str("||"),
|
||||
operator::BinOp::Pizza => buf.push_str("|>"),
|
||||
operator::BinOp::Assignment => unreachable!(),
|
||||
operator::BinOp::HasType => unreachable!(),
|
||||
operator::BinOp::Backpassing => unreachable!(),
|
||||
called_via::BinOp::Caret => buf.push('^'),
|
||||
called_via::BinOp::Star => buf.push('*'),
|
||||
called_via::BinOp::Slash => buf.push('/'),
|
||||
called_via::BinOp::DoubleSlash => buf.push_str("//"),
|
||||
called_via::BinOp::Percent => buf.push('%'),
|
||||
called_via::BinOp::DoublePercent => buf.push_str("%%"),
|
||||
called_via::BinOp::Plus => buf.push('+'),
|
||||
called_via::BinOp::Minus => buf.push('-'),
|
||||
called_via::BinOp::Equals => buf.push_str("=="),
|
||||
called_via::BinOp::NotEquals => buf.push_str("!="),
|
||||
called_via::BinOp::LessThan => buf.push('<'),
|
||||
called_via::BinOp::GreaterThan => buf.push('>'),
|
||||
called_via::BinOp::LessThanOrEq => buf.push_str("<="),
|
||||
called_via::BinOp::GreaterThanOrEq => buf.push_str(">="),
|
||||
called_via::BinOp::And => buf.push_str("&&"),
|
||||
called_via::BinOp::Or => buf.push_str("||"),
|
||||
called_via::BinOp::Pizza => buf.push_str("|>"),
|
||||
called_via::BinOp::Assignment => unreachable!(),
|
||||
called_via::BinOp::HasType => unreachable!(),
|
||||
called_via::BinOp::Backpassing => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -416,12 +408,9 @@ fn fmt_bin_ops<'a>(
|
||||
loc_right_side.format_with_options(buf, apply_needs_parens, Newlines::Yes, indent);
|
||||
}
|
||||
|
||||
fn fmt_list<'a>(
|
||||
buf: &mut String<'a>,
|
||||
loc_items: &[&Located<Expr<'a>>],
|
||||
final_comments: &'a [CommentOrNewline<'a>],
|
||||
indent: u16,
|
||||
) {
|
||||
fn fmt_list<'a>(buf: &mut String<'a>, items: Collection<'a, &'a Located<Expr<'a>>>, indent: u16) {
|
||||
let loc_items = items.items;
|
||||
let final_comments = items.final_comments();
|
||||
if loc_items.is_empty() && final_comments.iter().all(|c| c.is_newline()) {
|
||||
buf.push_str("[]");
|
||||
} else {
|
||||
@ -917,10 +906,11 @@ fn fmt_backpassing<'a>(
|
||||
fn fmt_record<'a>(
|
||||
buf: &mut String<'a>,
|
||||
update: Option<&'a Located<Expr<'a>>>,
|
||||
loc_fields: &[Located<AssignedField<'a, Expr<'a>>>],
|
||||
final_comments: &'a [CommentOrNewline<'a>],
|
||||
fields: Collection<'a, Located<AssignedField<'a, Expr<'a>>>>,
|
||||
indent: u16,
|
||||
) {
|
||||
let loc_fields = fields.items;
|
||||
let final_comments = fields.final_comments();
|
||||
if loc_fields.is_empty() && final_comments.iter().all(|c| c.is_newline()) {
|
||||
buf.push_str("{}");
|
||||
} else {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::spaces::{fmt_spaces, INDENT};
|
||||
use bumpalo::collections::{String, Vec};
|
||||
use roc_parse::ast::Module;
|
||||
use bumpalo::collections::String;
|
||||
use roc_parse::ast::{Collection, Module};
|
||||
use roc_parse::header::{AppHeader, ExposesEntry, ImportsEntry, InterfaceHeader, PlatformHeader};
|
||||
use roc_region::all::Located;
|
||||
|
||||
@ -64,7 +64,7 @@ pub fn fmt_interface_header<'a>(buf: &mut String<'a>, header: &'a InterfaceHeade
|
||||
fmt_spaces(buf, header.after_imports.iter(), indent);
|
||||
}
|
||||
|
||||
fmt_imports(buf, &header.imports, indent);
|
||||
fmt_imports(buf, header.imports, indent);
|
||||
}
|
||||
|
||||
pub fn fmt_app_header<'a>(buf: &mut String<'a>, header: &'a AppHeader<'a>) {
|
||||
@ -76,7 +76,7 @@ pub fn fmt_app_header<'a>(buf: &mut String<'a>, header: &'a AppHeader<'a>) {
|
||||
buf.push_str("imports");
|
||||
|
||||
fmt_spaces(buf, header.before_imports.iter(), indent);
|
||||
fmt_imports(buf, &header.imports, indent);
|
||||
fmt_imports(buf, header.imports, indent);
|
||||
fmt_spaces(buf, header.after_imports.iter(), indent);
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ pub fn fmt_platform_header<'a>(_buf: &mut String<'a>, _header: &'a PlatformHeade
|
||||
|
||||
fn fmt_imports<'a>(
|
||||
buf: &mut String<'a>,
|
||||
loc_entries: &'a Vec<'a, Located<ImportsEntry<'a>>>,
|
||||
loc_entries: Collection<'a, Located<ImportsEntry<'a>>>,
|
||||
indent: u16,
|
||||
) {
|
||||
buf.push('[');
|
||||
@ -112,7 +112,7 @@ fn fmt_imports<'a>(
|
||||
|
||||
fn fmt_exposes<'a>(
|
||||
buf: &mut String<'a>,
|
||||
loc_entries: &'a Vec<'a, Located<ExposesEntry<'a, &'a str>>>,
|
||||
loc_entries: &'a Collection<'a, Located<ExposesEntry<'a, &'a str>>>,
|
||||
indent: u16,
|
||||
) {
|
||||
buf.push('[');
|
||||
|
@ -70,7 +70,7 @@ This is the general procedure I follow with some helpful links:
|
||||
A good place to look for missing features is in the test files for generation in [test_gen](https://github.com/rtfeldman/roc/tree/trunk/compiler/test_gen). Any test that is not enabled for the `gen-dev` feature still needs to be added to the dev backend. Eventually all features should be enabled for the dev backend.
|
||||
1. Pick/write the simplest test case you can find for the new feature.
|
||||
Just add `feature = "gen-dev"` to the `cfg` line for the test case.
|
||||
1. Uncomment the code to print out procedures [from here](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/tests/helpers/eval.rs) and run the test.
|
||||
1. Uncomment the code to print out procedures [from here](https://github.com/rtfeldman/roc/blob/b03ed18553569314a420d5bf1fb0ead4b6b5ecda/compiler/test_gen/src/helpers/dev.rs#L76) and run the test.
|
||||
It should fail and print out the mono ir for this test case.
|
||||
Seeing the actual mono ir tends to be very helpful for complex additions.
|
||||
1. Generally it will fail in one of the match statements in the [Backend](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/lib.rs) trait.
|
||||
|
@ -446,6 +446,10 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
|
||||
unimplemented!("stack offsets over 32k are not yet implement for AArch64");
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn neg_reg64_reg64(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _src: AArch64GeneralReg) {
|
||||
unimplemented!("neg is not yet implement for AArch64");
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn sub_reg64_reg64_imm32(
|
||||
@ -486,6 +490,26 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
|
||||
unimplemented!("registers equality not implemented yet for AArch64");
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn neq_reg64_reg64_reg64(
|
||||
_buf: &mut Vec<'_, u8>,
|
||||
_dst: AArch64GeneralReg,
|
||||
_src1: AArch64GeneralReg,
|
||||
_src2: AArch64GeneralReg,
|
||||
) {
|
||||
unimplemented!("registers non-equality not implemented yet for AArch64");
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn lt_reg64_reg64_reg64(
|
||||
_buf: &mut Vec<'_, u8>,
|
||||
_dst: AArch64GeneralReg,
|
||||
_src1: AArch64GeneralReg,
|
||||
_src2: AArch64GeneralReg,
|
||||
) {
|
||||
unimplemented!("registers less than not implemented yet for AArch64");
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn ret(buf: &mut Vec<'_, u8>) {
|
||||
ret_reg64(buf, AArch64GeneralReg::LR)
|
||||
|
@ -149,6 +149,7 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait> {
|
||||
fn mov_stack32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
|
||||
fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
|
||||
|
||||
fn neg_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg);
|
||||
fn imul_reg64_reg64_reg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: GeneralReg,
|
||||
@ -171,6 +172,20 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait> {
|
||||
src2: GeneralReg,
|
||||
);
|
||||
|
||||
fn neq_reg64_reg64_reg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: GeneralReg,
|
||||
src1: GeneralReg,
|
||||
src2: GeneralReg,
|
||||
);
|
||||
|
||||
fn lt_reg64_reg64_reg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: GeneralReg,
|
||||
src1: GeneralReg,
|
||||
src2: GeneralReg,
|
||||
);
|
||||
|
||||
fn ret(buf: &mut Vec<'_, u8>);
|
||||
}
|
||||
|
||||
@ -786,6 +801,23 @@ impl<
|
||||
}
|
||||
}
|
||||
|
||||
fn build_num_neg(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
src: &Symbol,
|
||||
layout: &Layout<'a>,
|
||||
) -> Result<(), String> {
|
||||
match layout {
|
||||
Layout::Builtin(Builtin::Int64) => {
|
||||
let dst_reg = self.claim_general_reg(dst)?;
|
||||
let src_reg = self.load_to_general_reg(src)?;
|
||||
ASM::neg_reg64_reg64(&mut self.buf, dst_reg, src_reg);
|
||||
Ok(())
|
||||
}
|
||||
x => Err(format!("NumNeg: layout, {:?}, not implemented yet", x)),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_num_sub(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
@ -824,6 +856,44 @@ impl<
|
||||
}
|
||||
}
|
||||
|
||||
fn build_neq(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
src1: &Symbol,
|
||||
src2: &Symbol,
|
||||
arg_layout: &Layout<'a>,
|
||||
) -> Result<(), String> {
|
||||
match arg_layout {
|
||||
Layout::Builtin(Builtin::Int64) => {
|
||||
let dst_reg = self.claim_general_reg(dst)?;
|
||||
let src1_reg = self.load_to_general_reg(src1)?;
|
||||
let src2_reg = self.load_to_general_reg(src2)?;
|
||||
ASM::neq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
|
||||
Ok(())
|
||||
}
|
||||
x => Err(format!("NumNeq: layout, {:?}, not implemented yet", x)),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_num_lt(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
src1: &Symbol,
|
||||
src2: &Symbol,
|
||||
arg_layout: &Layout<'a>,
|
||||
) -> Result<(), String> {
|
||||
match arg_layout {
|
||||
Layout::Builtin(Builtin::Int64) => {
|
||||
let dst_reg = self.claim_general_reg(dst)?;
|
||||
let src1_reg = self.load_to_general_reg(src1)?;
|
||||
let src2_reg = self.load_to_general_reg(src2)?;
|
||||
ASM::lt_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
|
||||
Ok(())
|
||||
}
|
||||
x => Err(format!("NumLt: layout, {:?}, not implemented yet", x)),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_struct(
|
||||
&mut self,
|
||||
sym: &Symbol,
|
||||
|
@ -1053,6 +1053,11 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
|
||||
mov_base64_offset32_reg64(buf, X86_64GeneralReg::RSP, offset, src)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn neg_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) {
|
||||
mov_reg64_reg64(buf, dst, src);
|
||||
neg_reg64(buf, dst);
|
||||
}
|
||||
#[inline(always)]
|
||||
fn sub_reg64_reg64_imm32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
@ -1091,6 +1096,28 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
|
||||
sete_reg64(buf, dst);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn neq_reg64_reg64_reg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: X86_64GeneralReg,
|
||||
src1: X86_64GeneralReg,
|
||||
src2: X86_64GeneralReg,
|
||||
) {
|
||||
cmp_reg64_reg64(buf, src1, src2);
|
||||
setne_reg64(buf, dst);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn lt_reg64_reg64_reg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: X86_64GeneralReg,
|
||||
src1: X86_64GeneralReg,
|
||||
src2: X86_64GeneralReg,
|
||||
) {
|
||||
cmp_reg64_reg64(buf, src1, src2);
|
||||
setl_reg64(buf, dst);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn ret(buf: &mut Vec<'_, u8>) {
|
||||
ret(buf);
|
||||
@ -1448,9 +1475,9 @@ fn neg_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
|
||||
buf.extend(&[rex, 0xF7, 0xD8 + reg_mod]);
|
||||
}
|
||||
|
||||
/// `SETE r/m64` -> Set Byte on Condition - zero/equal (ZF=1)
|
||||
// helper function for `set*` instructions
|
||||
#[inline(always)]
|
||||
fn sete_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
|
||||
fn set_reg64_help(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg, value: u8) {
|
||||
// XOR needs 3 bytes, actual SETE instruction need 3 or 4 bytes
|
||||
buf.reserve(7);
|
||||
|
||||
@ -1458,10 +1485,10 @@ fn sete_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
|
||||
let reg_mod = reg as u8 % 8;
|
||||
use X86_64GeneralReg::*;
|
||||
match reg {
|
||||
RAX | RCX | RDX | RBX => buf.extend(&[0x0F, 0x94, 0xC0 + reg_mod]),
|
||||
RSP | RBP | RSI | RDI => buf.extend(&[REX, 0x0F, 0x94, 0xC0 + reg_mod]),
|
||||
RAX | RCX | RDX | RBX => buf.extend(&[0x0F, value, 0xC0 + reg_mod]),
|
||||
RSP | RBP | RSI | RDI => buf.extend(&[REX, 0x0F, value, 0xC0 + reg_mod]),
|
||||
R8 | R9 | R10 | R11 | R12 | R13 | R14 | R15 => {
|
||||
buf.extend(&[REX + 1, 0x0F, 0x94, 0xC0 + reg_mod])
|
||||
buf.extend(&[REX + 1, 0x0F, value, 0xC0 + reg_mod])
|
||||
}
|
||||
}
|
||||
|
||||
@ -1470,6 +1497,24 @@ fn sete_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
|
||||
and_reg64_imm8(buf, reg, 1);
|
||||
}
|
||||
|
||||
/// `SETE r/m64` -> Set Byte on Condition - zero/equal (ZF=1)
|
||||
#[inline(always)]
|
||||
fn sete_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
|
||||
set_reg64_help(buf, reg, 0x94);
|
||||
}
|
||||
|
||||
/// `SETNE r/m64` -> Set byte if not equal (ZF=0).
|
||||
#[inline(always)]
|
||||
fn setne_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
|
||||
set_reg64_help(buf, reg, 0x95);
|
||||
}
|
||||
|
||||
/// `SETL r/m64` -> Set byte if less (SF≠ OF).
|
||||
#[inline(always)]
|
||||
fn setl_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
|
||||
set_reg64_help(buf, reg, 0x9c);
|
||||
}
|
||||
|
||||
/// `RET` -> Near return to calling procedure.
|
||||
#[inline(always)]
|
||||
fn ret(buf: &mut Vec<'_, u8>) {
|
||||
@ -1576,6 +1621,34 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sub_reg64_reg64() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
for ((dst, src), expected) in &[
|
||||
(
|
||||
(X86_64GeneralReg::RAX, X86_64GeneralReg::RAX),
|
||||
[0x48, 0x29, 0xC0],
|
||||
),
|
||||
(
|
||||
(X86_64GeneralReg::RAX, X86_64GeneralReg::R15),
|
||||
[0x4C, 0x29, 0xF8],
|
||||
),
|
||||
(
|
||||
(X86_64GeneralReg::R15, X86_64GeneralReg::RAX),
|
||||
[0x49, 0x29, 0xC7],
|
||||
),
|
||||
(
|
||||
(X86_64GeneralReg::R15, X86_64GeneralReg::R15),
|
||||
[0x4D, 0x29, 0xFF],
|
||||
),
|
||||
] {
|
||||
buf.clear();
|
||||
sub_reg64_reg64(&mut buf, *dst, *src);
|
||||
assert_eq!(expected, &buf[..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_addsd_freg64_freg64() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
@ -2026,7 +2099,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sete_reg64() {
|
||||
fn test_set_reg64_help() {
|
||||
let arena = bumpalo::Bump::new();
|
||||
let mut buf = bumpalo::vec![in &arena];
|
||||
|
||||
@ -2039,7 +2112,7 @@ mod tests {
|
||||
],
|
||||
);
|
||||
buf.clear();
|
||||
sete_reg64(&mut buf, reg);
|
||||
set_reg64_help(&mut buf, reg, 0x94); // sete_reg64
|
||||
assert_eq!(expected, &buf[..]);
|
||||
|
||||
// tests for 8 bytes in the output buffer
|
||||
@ -2064,7 +2137,7 @@ mod tests {
|
||||
),
|
||||
] {
|
||||
buf.clear();
|
||||
sete_reg64(&mut buf, *reg);
|
||||
set_reg64_help(&mut buf, *reg, 0x94); // sete_reg64
|
||||
assert_eq!(expected, &buf[..]);
|
||||
}
|
||||
}
|
||||
|
@ -227,97 +227,30 @@ where
|
||||
ret_layout,
|
||||
..
|
||||
} => {
|
||||
// For most builtins instead of calling a function, we can just inline the low level.
|
||||
match *func_sym {
|
||||
Symbol::NUM_ABS => self.build_run_low_level(
|
||||
// If this function is just a lowlevel wrapper, then inline it
|
||||
if let Some(lowlevel) = LowLevel::from_inlined_wrapper(*func_sym) {
|
||||
self.build_run_low_level(
|
||||
sym,
|
||||
&LowLevel::NumAbs,
|
||||
&lowlevel,
|
||||
arguments,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
Symbol::NUM_ADD => self.build_run_low_level(
|
||||
sym,
|
||||
&LowLevel::NumAdd,
|
||||
arguments,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
Symbol::NUM_ACOS => self.build_run_low_level(
|
||||
sym,
|
||||
&LowLevel::NumAcos,
|
||||
arguments,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
Symbol::NUM_ASIN => self.build_run_low_level(
|
||||
sym,
|
||||
&LowLevel::NumAsin,
|
||||
arguments,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
Symbol::NUM_ATAN => self.build_run_low_level(
|
||||
sym,
|
||||
&LowLevel::NumAtan,
|
||||
arguments,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
Symbol::NUM_MUL => self.build_run_low_level(
|
||||
sym,
|
||||
&LowLevel::NumMul,
|
||||
arguments,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
Symbol::NUM_POW_INT => self.build_run_low_level(
|
||||
sym,
|
||||
&LowLevel::NumPowInt,
|
||||
arguments,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
Symbol::NUM_SUB => self.build_run_low_level(
|
||||
sym,
|
||||
&LowLevel::NumSub,
|
||||
arguments,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
Symbol::NUM_ROUND => self.build_run_low_level(
|
||||
sym,
|
||||
&LowLevel::NumRound,
|
||||
arguments,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
Symbol::BOOL_EQ => self.build_run_low_level(
|
||||
sym,
|
||||
&LowLevel::Eq,
|
||||
arguments,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
Symbol::STR_CONCAT => self.build_run_low_level(
|
||||
sym,
|
||||
&LowLevel::StrConcat,
|
||||
arguments,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
),
|
||||
x if x
|
||||
.module_string(&self.env().interns)
|
||||
.starts_with(ModuleName::APP) =>
|
||||
{
|
||||
let fn_name = LayoutIds::default()
|
||||
.get(*func_sym, layout)
|
||||
.to_symbol_string(*func_sym, &self.env().interns);
|
||||
// Now that the arguments are needed, load them if they are literals.
|
||||
self.load_literal_symbols(arguments)?;
|
||||
self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout)
|
||||
}
|
||||
x => Err(format!("the function, {:?}, is not yet implemented", x)),
|
||||
)
|
||||
} else if func_sym
|
||||
.module_string(&self.env().interns)
|
||||
.starts_with(ModuleName::APP)
|
||||
{
|
||||
let fn_name = LayoutIds::default()
|
||||
.get(*func_sym, layout)
|
||||
.to_symbol_string(*func_sym, &self.env().interns);
|
||||
// Now that the arguments are needed, load them if they are literals.
|
||||
self.load_literal_symbols(arguments)?;
|
||||
self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout)
|
||||
} else {
|
||||
Err(format!(
|
||||
"the function, {:?}, is not yet implemented",
|
||||
func_sym
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@ -435,6 +368,18 @@ where
|
||||
);
|
||||
self.build_num_mul(sym, &args[0], &args[1], ret_layout)
|
||||
}
|
||||
LowLevel::NumNeg => {
|
||||
debug_assert_eq!(
|
||||
1,
|
||||
args.len(),
|
||||
"NumNeg: expected to have exactly one argument"
|
||||
);
|
||||
debug_assert_eq!(
|
||||
arg_layouts[0], *ret_layout,
|
||||
"NumNeg: expected to have the same argument and return layout"
|
||||
);
|
||||
self.build_num_neg(sym, &args[0], ret_layout)
|
||||
}
|
||||
LowLevel::NumPowInt => self.build_fn_call(
|
||||
sym,
|
||||
bitcode::NUM_POW_INT[IntWidth::I64].to_string(),
|
||||
@ -471,6 +416,40 @@ where
|
||||
);
|
||||
self.build_eq(sym, &args[0], &args[1], &arg_layouts[0])
|
||||
}
|
||||
LowLevel::NotEq => {
|
||||
debug_assert_eq!(
|
||||
2,
|
||||
args.len(),
|
||||
"NotEq: expected to have exactly two argument"
|
||||
);
|
||||
debug_assert_eq!(
|
||||
arg_layouts[0], arg_layouts[1],
|
||||
"NotEq: expected all arguments of to have the same layout"
|
||||
);
|
||||
debug_assert_eq!(
|
||||
Layout::Builtin(Builtin::Int1),
|
||||
*ret_layout,
|
||||
"NotEq: expected to have return layout of type I1"
|
||||
);
|
||||
self.build_neq(sym, &args[0], &args[1], &arg_layouts[0])
|
||||
}
|
||||
LowLevel::NumLt => {
|
||||
debug_assert_eq!(
|
||||
2,
|
||||
args.len(),
|
||||
"NumLt: expected to have exactly two argument"
|
||||
);
|
||||
debug_assert_eq!(
|
||||
arg_layouts[0], arg_layouts[1],
|
||||
"NumLt: expected all arguments of to have the same layout"
|
||||
);
|
||||
debug_assert_eq!(
|
||||
Layout::Builtin(Builtin::Int1),
|
||||
*ret_layout,
|
||||
"NumLt: expected to have return layout of type I1"
|
||||
);
|
||||
self.build_num_lt(sym, &args[0], &args[1], &arg_layouts[0])
|
||||
}
|
||||
LowLevel::NumRound => self.build_fn_call(
|
||||
sym,
|
||||
bitcode::NUM_ROUND[FloatWidth::F64].to_string(),
|
||||
@ -526,6 +505,14 @@ where
|
||||
layout: &Layout<'a>,
|
||||
) -> Result<(), String>;
|
||||
|
||||
/// build_num_neg stores the negated value of src into dst.
|
||||
fn build_num_neg(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
src: &Symbol,
|
||||
layout: &Layout<'a>,
|
||||
) -> Result<(), String>;
|
||||
|
||||
/// build_num_sub stores the `src1 - src2` difference into dst.
|
||||
fn build_num_sub(
|
||||
&mut self,
|
||||
@ -544,6 +531,24 @@ where
|
||||
arg_layout: &Layout<'a>,
|
||||
) -> Result<(), String>;
|
||||
|
||||
/// build_neq stores the result of `src1 != src2` into dst.
|
||||
fn build_neq(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
src1: &Symbol,
|
||||
src2: &Symbol,
|
||||
arg_layout: &Layout<'a>,
|
||||
) -> Result<(), String>;
|
||||
|
||||
/// build_num_lt stores the result of `src1 < src2` into dst.
|
||||
fn build_num_lt(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
src1: &Symbol,
|
||||
src2: &Symbol,
|
||||
arg_layout: &Layout<'a>,
|
||||
) -> Result<(), String>;
|
||||
|
||||
/// literal_map gets the map from symbol to literal, used for lazy loading and literal folding.
|
||||
fn literal_map(&mut self) -> &mut MutMap<Symbol, Literal<'a>>;
|
||||
|
||||
@ -785,7 +790,7 @@ where
|
||||
match call_type {
|
||||
CallType::ByName { .. } => {}
|
||||
CallType::LowLevel { .. } => {}
|
||||
CallType::HigherOrderLowLevel { .. } => {}
|
||||
CallType::HigherOrder { .. } => {}
|
||||
CallType::Foreign { .. } => {}
|
||||
}
|
||||
}
|
||||
|
@ -128,20 +128,19 @@ fn build_has_tag_id_help<'a, 'ctx, 'env>(
|
||||
[tag_id, tag_value_ptr] => {
|
||||
let tag_type = basic_type_from_layout(env, &Layout::Union(union_layout));
|
||||
|
||||
let argument_cast = env
|
||||
.builder
|
||||
.build_bitcast(
|
||||
*tag_value_ptr,
|
||||
tag_type.ptr_type(AddressSpace::Generic),
|
||||
"load_opaque",
|
||||
)
|
||||
.into_pointer_value();
|
||||
|
||||
let tag_value = env.builder.build_load(argument_cast, "get_value");
|
||||
let tag_value = env.builder.build_pointer_cast(
|
||||
tag_value_ptr.into_pointer_value(),
|
||||
tag_type.ptr_type(AddressSpace::Generic),
|
||||
"load_opaque_get_tag_id",
|
||||
);
|
||||
|
||||
let actual_tag_id = {
|
||||
let tag_id_i64 =
|
||||
crate::llvm::build::get_tag_id(env, function_value, &union_layout, tag_value);
|
||||
let tag_id_i64 = crate::llvm::build::get_tag_id(
|
||||
env,
|
||||
function_value,
|
||||
&union_layout,
|
||||
tag_value.into(),
|
||||
);
|
||||
|
||||
env.builder
|
||||
.build_int_cast(tag_id_i64, env.context.i16_type(), "to_i16")
|
||||
@ -155,18 +154,11 @@ fn build_has_tag_id_help<'a, 'ctx, 'env>(
|
||||
);
|
||||
|
||||
let tag_data_ptr = {
|
||||
let data_index = env
|
||||
.context
|
||||
.i64_type()
|
||||
.const_int(TAG_DATA_INDEX as u64, false);
|
||||
let ptr = env
|
||||
.builder
|
||||
.build_struct_gep(tag_value, TAG_DATA_INDEX, "get_data_ptr")
|
||||
.unwrap();
|
||||
|
||||
let ptr = unsafe {
|
||||
env.builder.build_gep(
|
||||
tag_value_ptr.into_pointer_value(),
|
||||
&[data_index],
|
||||
"get_data_ptr",
|
||||
)
|
||||
};
|
||||
env.builder.build_bitcast(ptr, i8_ptr_type, "to_opaque")
|
||||
};
|
||||
|
||||
@ -191,6 +183,7 @@ pub fn build_transform_caller<'a, 'ctx, 'env>(
|
||||
function: FunctionValue<'ctx>,
|
||||
closure_data_layout: LambdaSet<'a>,
|
||||
argument_layouts: &[Layout<'a>],
|
||||
result_layout: Layout<'a>,
|
||||
) -> FunctionValue<'ctx> {
|
||||
let fn_name: &str = &format!(
|
||||
"{}_zig_function_caller",
|
||||
@ -204,6 +197,7 @@ pub fn build_transform_caller<'a, 'ctx, 'env>(
|
||||
function,
|
||||
closure_data_layout,
|
||||
argument_layouts,
|
||||
result_layout,
|
||||
fn_name,
|
||||
),
|
||||
}
|
||||
@ -214,6 +208,7 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
|
||||
roc_function: FunctionValue<'ctx>,
|
||||
closure_data_layout: LambdaSet<'a>,
|
||||
argument_layouts: &[Layout<'a>],
|
||||
result_layout: Layout<'a>,
|
||||
fn_name: &str,
|
||||
) -> FunctionValue<'ctx> {
|
||||
debug_assert!(argument_layouts.len() <= 7);
|
||||
@ -260,12 +255,22 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
|
||||
for (argument_ptr, layout) in arguments.iter().zip(argument_layouts) {
|
||||
let basic_type = basic_type_from_layout(env, layout).ptr_type(AddressSpace::Generic);
|
||||
|
||||
let argument_cast = env
|
||||
.builder
|
||||
.build_bitcast(*argument_ptr, basic_type, "load_opaque")
|
||||
.into_pointer_value();
|
||||
let argument = if layout.is_passed_by_reference() {
|
||||
env.builder
|
||||
.build_pointer_cast(
|
||||
argument_ptr.into_pointer_value(),
|
||||
basic_type,
|
||||
"cast_ptr_to_tag_build_transform_caller_help",
|
||||
)
|
||||
.into()
|
||||
} else {
|
||||
let argument_cast = env
|
||||
.builder
|
||||
.build_bitcast(*argument_ptr, basic_type, "load_opaque_1")
|
||||
.into_pointer_value();
|
||||
|
||||
let argument = env.builder.build_load(argument_cast, "load_opaque");
|
||||
env.builder.build_load(argument_cast, "load_opaque_2")
|
||||
};
|
||||
|
||||
arguments_cast.push(argument);
|
||||
}
|
||||
@ -288,31 +293,19 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
|
||||
}
|
||||
}
|
||||
|
||||
let call = {
|
||||
env.builder
|
||||
.build_call(roc_function, arguments_cast.as_slice(), "tmp")
|
||||
};
|
||||
|
||||
call.set_call_convention(FAST_CALL_CONV);
|
||||
|
||||
let result = call
|
||||
.try_as_basic_value()
|
||||
.left()
|
||||
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."));
|
||||
let result = crate::llvm::build::call_roc_function(
|
||||
env,
|
||||
roc_function,
|
||||
&result_layout,
|
||||
arguments_cast.as_slice(),
|
||||
);
|
||||
|
||||
let result_u8_ptr = function_value
|
||||
.get_nth_param(argument_layouts.len() as u32 + 1)
|
||||
.unwrap();
|
||||
let result_ptr = env
|
||||
.builder
|
||||
.build_bitcast(
|
||||
result_u8_ptr,
|
||||
result.get_type().ptr_type(AddressSpace::Generic),
|
||||
"write_result",
|
||||
)
|
||||
.unwrap()
|
||||
.into_pointer_value();
|
||||
|
||||
env.builder.build_store(result_ptr, result);
|
||||
crate::llvm::build::store_roc_value_opaque(env, result_layout, result_u8_ptr, result);
|
||||
env.builder.build_return(None);
|
||||
|
||||
env.builder.position_at_end(block);
|
||||
@ -414,12 +407,18 @@ fn build_rc_wrapper<'a, 'ctx, 'env>(
|
||||
|
||||
let value_type = basic_type_from_layout(env, layout).ptr_type(AddressSpace::Generic);
|
||||
|
||||
let value_cast = env
|
||||
.builder
|
||||
.build_bitcast(value_ptr, value_type, "load_opaque")
|
||||
.into_pointer_value();
|
||||
let value = if layout.is_passed_by_reference() {
|
||||
env.builder
|
||||
.build_pointer_cast(value_ptr, value_type, "cast_ptr_to_tag_build_rc_wrapper")
|
||||
.into()
|
||||
} else {
|
||||
let value_cast = env
|
||||
.builder
|
||||
.build_bitcast(value_ptr, value_type, "load_opaque")
|
||||
.into_pointer_value();
|
||||
|
||||
let value = env.builder.build_load(value_cast, "load_opaque");
|
||||
env.builder.build_load(value_cast, "load_opaque")
|
||||
};
|
||||
|
||||
match rc_operation {
|
||||
Mode::Inc => {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2,9 +2,9 @@ use crate::debug_info_init;
|
||||
use crate::llvm::bitcode::call_bitcode_fn;
|
||||
use crate::llvm::build::tag_pointer_clear_tag_id;
|
||||
use crate::llvm::build::Env;
|
||||
use crate::llvm::build::{cast_block_of_memory_to_tag, get_tag_id, FAST_CALL_CONV, TAG_DATA_INDEX};
|
||||
use crate::llvm::build::{get_tag_id, FAST_CALL_CONV, TAG_DATA_INDEX};
|
||||
use crate::llvm::build_str;
|
||||
use crate::llvm::convert::basic_type_from_layout;
|
||||
use crate::llvm::convert::{basic_type_from_layout, basic_type_from_layout_1};
|
||||
use bumpalo::collections::Vec;
|
||||
use inkwell::values::{
|
||||
BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue,
|
||||
@ -339,7 +339,8 @@ fn build_hash_tag<'a, 'ctx, 'env>(
|
||||
None => {
|
||||
let seed_type = env.context.i64_type();
|
||||
|
||||
let arg_type = basic_type_from_layout(env, layout);
|
||||
let arg_type = basic_type_from_layout_1(env, layout);
|
||||
dbg!(layout, arg_type);
|
||||
|
||||
let function_value = crate::llvm::refcounting::build_header_help(
|
||||
env,
|
||||
@ -423,14 +424,6 @@ fn hash_tag<'a, 'ctx, 'env>(
|
||||
let block = env.context.append_basic_block(parent, "tag_id_modify");
|
||||
env.builder.position_at_end(block);
|
||||
|
||||
let struct_layout = Layout::Struct(field_layouts);
|
||||
|
||||
let wrapper_type = basic_type_from_layout(env, &struct_layout);
|
||||
debug_assert!(wrapper_type.is_struct_type());
|
||||
|
||||
let as_struct =
|
||||
cast_block_of_memory_to_tag(env.builder, tag.into_struct_value(), wrapper_type);
|
||||
|
||||
// hash the tag id
|
||||
let hash_bytes = store_and_use_as_u8_ptr(
|
||||
env,
|
||||
@ -440,7 +433,6 @@ fn hash_tag<'a, 'ctx, 'env>(
|
||||
.into(),
|
||||
&tag_id_layout,
|
||||
);
|
||||
|
||||
let seed = hash_bitcode_fn(
|
||||
env,
|
||||
seed,
|
||||
@ -449,14 +441,9 @@ fn hash_tag<'a, 'ctx, 'env>(
|
||||
);
|
||||
|
||||
// hash the tag data
|
||||
let answer = build_hash_struct(
|
||||
env,
|
||||
layout_ids,
|
||||
field_layouts,
|
||||
WhenRecursive::Unreachable,
|
||||
seed,
|
||||
as_struct,
|
||||
);
|
||||
let tag = tag.into_pointer_value();
|
||||
let answer =
|
||||
hash_ptr_to_struct(env, layout_ids, union_layout, field_layouts, seed, tag);
|
||||
|
||||
merge_phi.add_incoming(&[(&answer, block)]);
|
||||
env.builder.build_unconditional_branch(merge_block);
|
||||
@ -793,7 +780,15 @@ fn hash_list<'a, 'ctx, 'env>(
|
||||
env.builder.build_store(result, answer);
|
||||
};
|
||||
|
||||
incrementing_elem_loop(env, parent, ptr, length, "current_index", loop_fn);
|
||||
incrementing_elem_loop(
|
||||
env,
|
||||
parent,
|
||||
*element_layout,
|
||||
ptr,
|
||||
length,
|
||||
"current_index",
|
||||
loop_fn,
|
||||
);
|
||||
|
||||
env.builder.build_unconditional_branch(done_block);
|
||||
|
||||
@ -822,12 +817,12 @@ fn hash_ptr_to_struct<'a, 'ctx, 'env>(
|
||||
) -> IntValue<'ctx> {
|
||||
use inkwell::types::BasicType;
|
||||
|
||||
let wrapper_type = basic_type_from_layout(env, &Layout::Union(*union_layout));
|
||||
let wrapper_type = basic_type_from_layout_1(env, &Layout::Union(*union_layout));
|
||||
|
||||
// cast the opaque pointer to a pointer of the correct shape
|
||||
let wrapper_ptr = env
|
||||
.builder
|
||||
.build_bitcast(tag, wrapper_type, "opaque_to_correct")
|
||||
.build_bitcast(tag, wrapper_type, "hash_ptr_to_struct_opaque_to_correct")
|
||||
.into_pointer_value();
|
||||
|
||||
let struct_ptr = env
|
||||
|
@ -17,6 +17,8 @@ use morphic_lib::UpdateMode;
|
||||
use roc_builtins::bitcode;
|
||||
use roc_mono::layout::{Builtin, Layout, LayoutIds};
|
||||
|
||||
use super::build::{load_roc_value, store_roc_value};
|
||||
|
||||
pub fn pass_update_mode<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
update_mode: UpdateMode,
|
||||
@ -53,9 +55,13 @@ pub fn call_bitcode_fn_returns_list<'a, 'ctx, 'env>(
|
||||
fn pass_element_as_opaque<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
element: BasicValueEnum<'ctx>,
|
||||
layout: Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let element_ptr = env.builder.build_alloca(element.get_type(), "element");
|
||||
env.builder.build_store(element_ptr, element);
|
||||
let element_type = basic_type_from_layout(env, &layout);
|
||||
let element_ptr = env
|
||||
.builder
|
||||
.build_alloca(element_type, "element_to_pass_as_opaque");
|
||||
store_roc_value(env, layout, element_ptr, element);
|
||||
|
||||
env.builder.build_bitcast(
|
||||
element_ptr,
|
||||
@ -106,7 +112,7 @@ pub fn list_single<'a, 'ctx, 'env>(
|
||||
env,
|
||||
&[
|
||||
env.alignment_intvalue(element_layout),
|
||||
pass_element_as_opaque(env, element),
|
||||
pass_element_as_opaque(env, element, *element_layout),
|
||||
layout_width(env, element_layout),
|
||||
],
|
||||
bitcode::LIST_SINGLE,
|
||||
@ -128,7 +134,7 @@ pub fn list_repeat<'a, 'ctx, 'env>(
|
||||
&[
|
||||
list_len.into(),
|
||||
env.alignment_intvalue(element_layout),
|
||||
pass_element_as_opaque(env, element),
|
||||
pass_element_as_opaque(env, element, *element_layout),
|
||||
layout_width(env, element_layout),
|
||||
inc_element_fn.as_global_value().as_pointer_value().into(),
|
||||
],
|
||||
@ -216,10 +222,11 @@ pub fn list_get_unsafe<'a, 'ctx, 'env>(
|
||||
|
||||
// Assume the bounds have already been checked earlier
|
||||
// (e.g. by List.get or List.first, which wrap List.#getUnsafe)
|
||||
let elem_ptr =
|
||||
unsafe { builder.build_in_bounds_gep(array_data_ptr, &[elem_index], "elem") };
|
||||
let elem_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(array_data_ptr, &[elem_index], "list_get_element")
|
||||
};
|
||||
|
||||
let result = builder.build_load(elem_ptr, "List.get");
|
||||
let result = load_roc_value(env, **elem_layout, elem_ptr, "list_get_load_element");
|
||||
|
||||
increment_refcount_layout(env, parent, layout_ids, 1, result, elem_layout);
|
||||
|
||||
@ -247,7 +254,7 @@ pub fn list_append<'a, 'ctx, 'env>(
|
||||
&[
|
||||
pass_list_cc(env, original_wrapper.into()),
|
||||
env.alignment_intvalue(element_layout),
|
||||
pass_element_as_opaque(env, element),
|
||||
pass_element_as_opaque(env, element, *element_layout),
|
||||
layout_width(env, element_layout),
|
||||
pass_update_mode(env, update_mode),
|
||||
],
|
||||
@ -267,7 +274,7 @@ pub fn list_prepend<'a, 'ctx, 'env>(
|
||||
&[
|
||||
pass_list_cc(env, original_wrapper.into()),
|
||||
env.alignment_intvalue(element_layout),
|
||||
pass_element_as_opaque(env, element),
|
||||
pass_element_as_opaque(env, element, *element_layout),
|
||||
layout_width(env, element_layout),
|
||||
],
|
||||
bitcode::LIST_PREPEND,
|
||||
@ -297,31 +304,13 @@ pub fn list_swap<'a, 'ctx, 'env>(
|
||||
)
|
||||
}
|
||||
|
||||
/// List.takeFirst : List elem, Nat -> List elem
|
||||
pub fn list_take_first<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
original_wrapper: StructValue<'ctx>,
|
||||
count: IntValue<'ctx>,
|
||||
element_layout: &Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
call_bitcode_fn_returns_list(
|
||||
env,
|
||||
&[
|
||||
pass_list_cc(env, original_wrapper.into()),
|
||||
env.alignment_intvalue(element_layout),
|
||||
layout_width(env, element_layout),
|
||||
count.into(),
|
||||
],
|
||||
bitcode::LIST_TAKE_FIRST,
|
||||
)
|
||||
}
|
||||
|
||||
/// List.takeLast : List elem, Nat -> List elem
|
||||
pub fn list_take_last<'a, 'ctx, 'env>(
|
||||
/// List.sublist : List elem, { start : Nat, len : Nat } -> List elem
|
||||
pub fn list_sublist<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
original_wrapper: StructValue<'ctx>,
|
||||
count: IntValue<'ctx>,
|
||||
start: IntValue<'ctx>,
|
||||
len: IntValue<'ctx>,
|
||||
element_layout: &Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
|
||||
@ -331,32 +320,11 @@ pub fn list_take_last<'a, 'ctx, 'env>(
|
||||
pass_list_cc(env, original_wrapper.into()),
|
||||
env.alignment_intvalue(element_layout),
|
||||
layout_width(env, element_layout),
|
||||
count.into(),
|
||||
start.into(),
|
||||
len.into(),
|
||||
dec_element_fn.as_global_value().as_pointer_value().into(),
|
||||
],
|
||||
bitcode::LIST_TAKE_LAST,
|
||||
)
|
||||
}
|
||||
|
||||
/// List.drop : List elem, Nat -> List elem
|
||||
pub fn list_drop<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
original_wrapper: StructValue<'ctx>,
|
||||
count: IntValue<'ctx>,
|
||||
element_layout: &Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
|
||||
call_bitcode_fn_returns_list(
|
||||
env,
|
||||
&[
|
||||
pass_list_cc(env, original_wrapper.into()),
|
||||
env.alignment_intvalue(element_layout),
|
||||
layout_width(env, element_layout),
|
||||
count.into(),
|
||||
dec_element_fn.as_global_value().as_pointer_value().into(),
|
||||
],
|
||||
bitcode::LIST_DROP,
|
||||
bitcode::LIST_SUBLIST,
|
||||
)
|
||||
}
|
||||
|
||||
@ -406,7 +374,7 @@ pub fn list_set<'a, 'ctx, 'env>(
|
||||
&[
|
||||
bytes.into(),
|
||||
index.into(),
|
||||
pass_element_as_opaque(env, element),
|
||||
pass_element_as_opaque(env, element, *element_layout),
|
||||
layout_width(env, element_layout),
|
||||
dec_element_fn.as_global_value().as_pointer_value().into(),
|
||||
],
|
||||
@ -419,7 +387,7 @@ pub fn list_set<'a, 'ctx, 'env>(
|
||||
length.into(),
|
||||
env.alignment_intvalue(element_layout),
|
||||
index.into(),
|
||||
pass_element_as_opaque(env, element),
|
||||
pass_element_as_opaque(env, element, *element_layout),
|
||||
layout_width(env, element_layout),
|
||||
dec_element_fn.as_global_value().as_pointer_value().into(),
|
||||
],
|
||||
@ -595,7 +563,7 @@ pub fn list_contains<'a, 'ctx, 'env>(
|
||||
env,
|
||||
&[
|
||||
pass_list_cc(env, list),
|
||||
pass_element_as_opaque(env, element),
|
||||
pass_element_as_opaque(env, element, *element_layout),
|
||||
layout_width(env, element_layout),
|
||||
eq_fn,
|
||||
],
|
||||
@ -958,6 +926,27 @@ pub fn list_any<'a, 'ctx, 'env>(
|
||||
)
|
||||
}
|
||||
|
||||
/// List.all : List elem, \(elem -> Bool) -> Bool
|
||||
pub fn list_all<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
roc_function_call: RocFunctionCall<'ctx>,
|
||||
list: BasicValueEnum<'ctx>,
|
||||
element_layout: &Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
call_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
pass_list_cc(env, list),
|
||||
roc_function_call.caller.into(),
|
||||
pass_as_opaque(env, roc_function_call.data),
|
||||
roc_function_call.inc_n_data.into(),
|
||||
roc_function_call.data_is_owned.into(),
|
||||
layout_width(env, element_layout),
|
||||
],
|
||||
bitcode::LIST_ALL,
|
||||
)
|
||||
}
|
||||
|
||||
/// List.findUnsafe : List elem, (elem -> Bool) -> { value: elem, found: bool }
|
||||
pub fn list_find_unsafe<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
@ -1154,6 +1143,7 @@ where
|
||||
pub fn incrementing_elem_loop<'a, 'ctx, 'env, LoopFn>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
element_layout: Layout<'a>,
|
||||
ptr: PointerValue<'ctx>,
|
||||
len: IntValue<'ctx>,
|
||||
index_name: &str,
|
||||
@ -1166,9 +1156,14 @@ where
|
||||
|
||||
incrementing_index_loop(env, parent, len, index_name, |index| {
|
||||
// The pointer to the element in the list
|
||||
let elem_ptr = unsafe { builder.build_in_bounds_gep(ptr, &[index], "load_index") };
|
||||
let element_ptr = unsafe { builder.build_in_bounds_gep(ptr, &[index], "load_index") };
|
||||
|
||||
let elem = builder.build_load(elem_ptr, "get_elem");
|
||||
let elem = load_roc_value(
|
||||
env,
|
||||
element_layout,
|
||||
element_ptr,
|
||||
"incrementing_element_loop_load",
|
||||
);
|
||||
|
||||
loop_fn(index, elem);
|
||||
})
|
||||
|
@ -269,6 +269,16 @@ pub fn str_trim_left<'a, 'ctx, 'env>(
|
||||
call_bitcode_fn(env, &[str_i128.into()], bitcode::STR_TRIM_LEFT)
|
||||
}
|
||||
|
||||
/// Str.trimRight : Str -> Str
|
||||
pub fn str_trim_right<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
str_symbol: Symbol,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let str_i128 = str_symbol_to_c_abi(env, scope, str_symbol);
|
||||
call_bitcode_fn(env, &[str_i128.into()], bitcode::STR_TRIM_RIGHT)
|
||||
}
|
||||
|
||||
/// Str.fromInt : Int -> Str
|
||||
pub fn str_from_int<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
|
@ -1,10 +1,8 @@
|
||||
use crate::llvm::bitcode::call_bitcode_fn;
|
||||
use crate::llvm::build::{
|
||||
cast_block_of_memory_to_tag, get_tag_id, tag_pointer_clear_tag_id, Env, FAST_CALL_CONV,
|
||||
};
|
||||
use crate::llvm::build::{get_tag_id, tag_pointer_clear_tag_id, Env, FAST_CALL_CONV};
|
||||
use crate::llvm::build_list::{list_len, load_list_ptr};
|
||||
use crate::llvm::build_str::str_equal;
|
||||
use crate::llvm::convert::basic_type_from_layout;
|
||||
use crate::llvm::convert::{basic_type_from_layout, basic_type_from_layout_1};
|
||||
use bumpalo::collections::Vec;
|
||||
use inkwell::types::BasicType;
|
||||
use inkwell::values::{
|
||||
@ -751,7 +749,7 @@ fn build_tag_eq<'a, 'ctx, 'env>(
|
||||
let function = match env.module.get_function(fn_name.as_str()) {
|
||||
Some(function_value) => function_value,
|
||||
None => {
|
||||
let arg_type = basic_type_from_layout(env, tag_layout);
|
||||
let arg_type = basic_type_from_layout_1(env, tag_layout);
|
||||
|
||||
let function_value = crate::llvm::refcounting::build_header_help(
|
||||
env,
|
||||
@ -844,9 +842,29 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
|
||||
|
||||
match union_layout {
|
||||
NonRecursive(tags) => {
|
||||
let ptr_equal = env.builder.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
env.builder
|
||||
.build_ptr_to_int(tag1.into_pointer_value(), env.ptr_int(), "pti"),
|
||||
env.builder
|
||||
.build_ptr_to_int(tag2.into_pointer_value(), env.ptr_int(), "pti"),
|
||||
"compare_pointers",
|
||||
);
|
||||
|
||||
let compare_tag_ids = ctx.append_basic_block(parent, "compare_tag_ids");
|
||||
|
||||
env.builder
|
||||
.build_conditional_branch(ptr_equal, return_true, compare_tag_ids);
|
||||
|
||||
env.builder.position_at_end(compare_tag_ids);
|
||||
|
||||
let id1 = get_tag_id(env, parent, union_layout, tag1);
|
||||
let id2 = get_tag_id(env, parent, union_layout, tag2);
|
||||
|
||||
// clear the tag_id so we get a pointer to the actual data
|
||||
let tag1 = tag1.into_pointer_value();
|
||||
let tag2 = tag2.into_pointer_value();
|
||||
|
||||
let compare_tag_fields = ctx.append_basic_block(parent, "compare_tag_fields");
|
||||
|
||||
let same_tag =
|
||||
@ -866,30 +884,14 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
|
||||
let block = env.context.append_basic_block(parent, "tag_id_modify");
|
||||
env.builder.position_at_end(block);
|
||||
|
||||
// TODO drop tag id?
|
||||
let struct_layout = Layout::Struct(field_layouts);
|
||||
|
||||
let wrapper_type = basic_type_from_layout(env, &struct_layout);
|
||||
debug_assert!(wrapper_type.is_struct_type());
|
||||
|
||||
let struct1 = cast_block_of_memory_to_tag(
|
||||
env.builder,
|
||||
tag1.into_struct_value(),
|
||||
wrapper_type,
|
||||
);
|
||||
let struct2 = cast_block_of_memory_to_tag(
|
||||
env.builder,
|
||||
tag2.into_struct_value(),
|
||||
wrapper_type,
|
||||
);
|
||||
|
||||
let answer = build_struct_eq(
|
||||
let answer = eq_ptr_to_struct(
|
||||
env,
|
||||
layout_ids,
|
||||
union_layout,
|
||||
Some(when_recursive.clone()),
|
||||
field_layouts,
|
||||
when_recursive.clone(),
|
||||
struct1,
|
||||
struct2,
|
||||
tag1,
|
||||
tag2,
|
||||
);
|
||||
|
||||
env.builder.build_return(Some(&answer));
|
||||
@ -946,8 +948,15 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
|
||||
let block = env.context.append_basic_block(parent, "tag_id_modify");
|
||||
env.builder.position_at_end(block);
|
||||
|
||||
let answer =
|
||||
eq_ptr_to_struct(env, layout_ids, union_layout, field_layouts, tag1, tag2);
|
||||
let answer = eq_ptr_to_struct(
|
||||
env,
|
||||
layout_ids,
|
||||
union_layout,
|
||||
None,
|
||||
field_layouts,
|
||||
tag1,
|
||||
tag2,
|
||||
);
|
||||
|
||||
env.builder.build_return(Some(&answer));
|
||||
|
||||
@ -1003,6 +1012,7 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
|
||||
env,
|
||||
layout_ids,
|
||||
union_layout,
|
||||
None,
|
||||
other_fields,
|
||||
tag1.into_pointer_value(),
|
||||
tag2.into_pointer_value(),
|
||||
@ -1093,8 +1103,15 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
|
||||
let block = env.context.append_basic_block(parent, "tag_id_modify");
|
||||
env.builder.position_at_end(block);
|
||||
|
||||
let answer =
|
||||
eq_ptr_to_struct(env, layout_ids, union_layout, field_layouts, tag1, tag2);
|
||||
let answer = eq_ptr_to_struct(
|
||||
env,
|
||||
layout_ids,
|
||||
union_layout,
|
||||
None,
|
||||
field_layouts,
|
||||
tag1,
|
||||
tag2,
|
||||
);
|
||||
|
||||
env.builder.build_return(Some(&answer));
|
||||
|
||||
@ -1128,6 +1145,7 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
|
||||
env,
|
||||
layout_ids,
|
||||
union_layout,
|
||||
None,
|
||||
field_layouts,
|
||||
tag1.into_pointer_value(),
|
||||
tag2.into_pointer_value(),
|
||||
@ -1142,6 +1160,7 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
union_layout: &UnionLayout<'a>,
|
||||
opt_when_recursive: Option<WhenRecursive<'a>>,
|
||||
field_layouts: &'a [Layout<'a>],
|
||||
tag1: PointerValue<'ctx>,
|
||||
tag2: PointerValue<'ctx>,
|
||||
@ -1184,7 +1203,7 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>(
|
||||
env,
|
||||
layout_ids,
|
||||
field_layouts,
|
||||
WhenRecursive::Loop(*union_layout),
|
||||
opt_when_recursive.unwrap_or(WhenRecursive::Loop(*union_layout)),
|
||||
struct1,
|
||||
struct2,
|
||||
)
|
||||
|
@ -76,6 +76,66 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn basic_type_from_layout_1<'a, 'ctx, 'env>(
|
||||
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
|
||||
layout: &Layout<'_>,
|
||||
) -> BasicTypeEnum<'ctx> {
|
||||
use Layout::*;
|
||||
|
||||
match layout {
|
||||
Struct(sorted_fields) => basic_type_from_record(env, sorted_fields),
|
||||
LambdaSet(lambda_set) => {
|
||||
basic_type_from_layout_1(env, &lambda_set.runtime_representation())
|
||||
}
|
||||
Union(union_layout) => {
|
||||
use UnionLayout::*;
|
||||
|
||||
let tag_id_type = basic_type_from_layout(env, &union_layout.tag_id_layout());
|
||||
|
||||
match union_layout {
|
||||
NonRecursive(tags) => {
|
||||
let data = block_of_memory_slices(env.context, tags, env.ptr_bytes);
|
||||
let struct_type = env.context.struct_type(&[data, tag_id_type], false);
|
||||
|
||||
struct_type.ptr_type(AddressSpace::Generic).into()
|
||||
}
|
||||
Recursive(tags)
|
||||
| NullableWrapped {
|
||||
other_tags: tags, ..
|
||||
} => {
|
||||
let data = block_of_memory_slices(env.context, tags, env.ptr_bytes);
|
||||
|
||||
if union_layout.stores_tag_id_as_data(env.ptr_bytes) {
|
||||
env.context
|
||||
.struct_type(&[data, tag_id_type], false)
|
||||
.ptr_type(AddressSpace::Generic)
|
||||
.into()
|
||||
} else {
|
||||
data.ptr_type(AddressSpace::Generic).into()
|
||||
}
|
||||
}
|
||||
NullableUnwrapped { other_fields, .. } => {
|
||||
let block = block_of_memory_slices(env.context, &[other_fields], env.ptr_bytes);
|
||||
block.ptr_type(AddressSpace::Generic).into()
|
||||
}
|
||||
NonNullableUnwrapped(fields) => {
|
||||
let block = block_of_memory_slices(env.context, &[fields], env.ptr_bytes);
|
||||
block.ptr_type(AddressSpace::Generic).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
RecursivePointer => {
|
||||
// TODO make this dynamic
|
||||
env.context
|
||||
.i64_type()
|
||||
.ptr_type(AddressSpace::Generic)
|
||||
.as_basic_type_enum()
|
||||
}
|
||||
|
||||
Builtin(builtin) => basic_type_from_builtin(env, builtin),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn basic_type_from_builtin<'a, 'ctx, 'env>(
|
||||
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
|
||||
builtin: &Builtin<'_>,
|
||||
|
@ -1,11 +1,11 @@
|
||||
use crate::debug_info_init;
|
||||
use crate::llvm::bitcode::call_void_bitcode_fn;
|
||||
use crate::llvm::build::{
|
||||
add_func, cast_basic_basic, cast_block_of_memory_to_tag, get_tag_id, get_tag_id_non_recursive,
|
||||
tag_pointer_clear_tag_id, Env, FAST_CALL_CONV, TAG_DATA_INDEX,
|
||||
add_func, cast_basic_basic, get_tag_id, tag_pointer_clear_tag_id, use_roc_value, Env,
|
||||
FAST_CALL_CONV, TAG_DATA_INDEX, TAG_ID_INDEX,
|
||||
};
|
||||
use crate::llvm::build_list::{incrementing_elem_loop, list_len, load_list};
|
||||
use crate::llvm::convert::{basic_type_from_layout, ptr_int};
|
||||
use crate::llvm::convert::{basic_type_from_layout, basic_type_from_layout_1, ptr_int};
|
||||
use bumpalo::collections::Vec;
|
||||
use inkwell::basic_block::BasicBlock;
|
||||
use inkwell::context::Context;
|
||||
@ -139,8 +139,10 @@ impl<'ctx> PointerToRefcount<'ctx> {
|
||||
let block = env.builder.get_insert_block().expect("to be in a function");
|
||||
let parent = block.get_parent().unwrap();
|
||||
|
||||
let modify_block = env.context.append_basic_block(parent, "inc_str_modify");
|
||||
let cont_block = env.context.append_basic_block(parent, "inc_str_cont");
|
||||
let modify_block = env
|
||||
.context
|
||||
.append_basic_block(parent, "inc_refcount_modify");
|
||||
let cont_block = env.context.append_basic_block(parent, "inc_refcount_cont");
|
||||
|
||||
env.builder
|
||||
.build_conditional_branch(is_static_allocation, cont_block, modify_block);
|
||||
@ -181,7 +183,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
|
||||
env.module,
|
||||
fn_name,
|
||||
fn_type,
|
||||
Linkage::Private,
|
||||
Linkage::Internal,
|
||||
FAST_CALL_CONV, // Because it's an internal-only function, it should use the fast calling convention.
|
||||
);
|
||||
|
||||
@ -349,18 +351,25 @@ fn modify_refcount_struct_help<'a, 'ctx, 'env>(
|
||||
|
||||
for (i, field_layout) in layouts.iter().enumerate() {
|
||||
if field_layout.contains_refcounted() {
|
||||
let field_ptr = env
|
||||
let raw_value = env
|
||||
.builder
|
||||
.build_extract_value(wrapper_struct, i as u32, "decrement_struct_field")
|
||||
.unwrap();
|
||||
|
||||
let field_value = use_roc_value(
|
||||
env,
|
||||
*field_layout,
|
||||
raw_value,
|
||||
"load_struct_tag_field_for_decrement",
|
||||
);
|
||||
|
||||
modify_refcount_layout_help(
|
||||
env,
|
||||
parent,
|
||||
layout_ids,
|
||||
mode.to_call_mode(fn_val),
|
||||
when_recursive,
|
||||
field_ptr,
|
||||
field_value,
|
||||
field_layout,
|
||||
);
|
||||
}
|
||||
@ -753,7 +762,15 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
|
||||
);
|
||||
};
|
||||
|
||||
incrementing_elem_loop(env, parent, ptr, len, "modify_rc_index", loop_fn);
|
||||
incrementing_elem_loop(
|
||||
env,
|
||||
parent,
|
||||
*element_layout,
|
||||
ptr,
|
||||
len,
|
||||
"modify_rc_index",
|
||||
loop_fn,
|
||||
);
|
||||
}
|
||||
|
||||
let refcount_ptr = PointerToRefcount::from_list_wrapper(env, original_wrapper);
|
||||
@ -1289,7 +1306,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
|
||||
.build_bitcast(
|
||||
value_ptr,
|
||||
wrapper_type.ptr_type(AddressSpace::Generic),
|
||||
"opaque_to_correct",
|
||||
"opaque_to_correct_recursive_decrement",
|
||||
)
|
||||
.into_pointer_value();
|
||||
|
||||
@ -1602,7 +1619,7 @@ fn modify_refcount_union<'a, 'ctx, 'env>(
|
||||
let function = match env.module.get_function(fn_name.as_str()) {
|
||||
Some(function_value) => function_value,
|
||||
None => {
|
||||
let basic_type = basic_type_from_layout(env, &layout);
|
||||
let basic_type = basic_type_from_layout_1(env, &layout);
|
||||
let function_value = build_header(env, basic_type, mode, &fn_name);
|
||||
|
||||
modify_refcount_union_help(
|
||||
@ -1647,18 +1664,24 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
|
||||
|
||||
// Add args to scope
|
||||
let arg_symbol = Symbol::ARG_1;
|
||||
let arg_val = fn_val.get_param_iter().next().unwrap();
|
||||
let arg_ptr = fn_val.get_param_iter().next().unwrap().into_pointer_value();
|
||||
|
||||
arg_val.set_name(arg_symbol.as_str(&env.interns));
|
||||
arg_ptr.set_name(arg_symbol.as_str(&env.interns));
|
||||
|
||||
let parent = fn_val;
|
||||
|
||||
let before_block = env.builder.get_insert_block().expect("to be in a function");
|
||||
|
||||
let wrapper_struct = arg_val.into_struct_value();
|
||||
|
||||
// read the tag_id
|
||||
let tag_id = get_tag_id_non_recursive(env, wrapper_struct);
|
||||
let tag_id_ptr = env
|
||||
.builder
|
||||
.build_struct_gep(arg_ptr, TAG_ID_INDEX, "tag_id_ptr")
|
||||
.unwrap();
|
||||
|
||||
let tag_id = env
|
||||
.builder
|
||||
.build_load(tag_id_ptr, "load_tag_id")
|
||||
.into_int_value();
|
||||
|
||||
let tag_id_u8 = env
|
||||
.builder
|
||||
@ -1686,12 +1709,16 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
|
||||
let wrapper_type = basic_type_from_layout(env, &Layout::Struct(field_layouts));
|
||||
|
||||
debug_assert!(wrapper_type.is_struct_type());
|
||||
let data_bytes = env
|
||||
let opaque_tag_data_ptr = env
|
||||
.builder
|
||||
.build_extract_value(wrapper_struct, TAG_DATA_INDEX, "read_tag_id")
|
||||
.unwrap()
|
||||
.into_struct_value();
|
||||
let wrapper_struct = cast_block_of_memory_to_tag(env.builder, data_bytes, wrapper_type);
|
||||
.build_struct_gep(arg_ptr, TAG_DATA_INDEX, "field_ptr")
|
||||
.unwrap();
|
||||
|
||||
let cast_tag_data_pointer = env.builder.build_pointer_cast(
|
||||
opaque_tag_data_ptr,
|
||||
wrapper_type.ptr_type(AddressSpace::Generic),
|
||||
"cast_to_concrete_tag",
|
||||
);
|
||||
|
||||
for (i, field_layout) in field_layouts.iter().enumerate() {
|
||||
if let Layout::RecursivePointer = field_layout {
|
||||
@ -1699,16 +1726,22 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
|
||||
} else if field_layout.contains_refcounted() {
|
||||
let field_ptr = env
|
||||
.builder
|
||||
.build_extract_value(wrapper_struct, i as u32, "modify_tag_field")
|
||||
.build_struct_gep(cast_tag_data_pointer, i as u32, "modify_tag_field")
|
||||
.unwrap();
|
||||
|
||||
let field_value = if field_layout.is_passed_by_reference() {
|
||||
field_ptr.into()
|
||||
} else {
|
||||
env.builder.build_load(field_ptr, "field_value")
|
||||
};
|
||||
|
||||
modify_refcount_layout_help(
|
||||
env,
|
||||
parent,
|
||||
layout_ids,
|
||||
mode.to_call_mode(fn_val),
|
||||
when_recursive,
|
||||
field_ptr,
|
||||
field_value,
|
||||
field_layout,
|
||||
);
|
||||
}
|
||||
|
BIN
compiler/gen_wasm/lib/libc.a
Normal file
BIN
compiler/gen_wasm/lib/libc.a
Normal file
Binary file not shown.
@ -2,12 +2,13 @@ use bumpalo::{self, collections::Vec};
|
||||
|
||||
use code_builder::Align;
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt};
|
||||
use roc_mono::layout::{Layout, LayoutIds};
|
||||
use roc_mono::layout::{Builtin, Layout, LayoutIds};
|
||||
|
||||
use crate::layout::WasmLayout;
|
||||
use crate::low_level::{build_call_low_level, LowlevelBuildResult};
|
||||
use crate::low_level::{decode_low_level, LowlevelBuildResult};
|
||||
use crate::storage::{Storage, StoredValue, StoredValueKind};
|
||||
use crate::wasm_module::linking::{
|
||||
DataSymbol, LinkingSection, RelocationSection, WasmObjectSymbol, WASM_SYM_BINDING_WEAK,
|
||||
@ -22,8 +23,8 @@ use crate::wasm_module::{
|
||||
LocalId, Signature, SymInfo, ValueType,
|
||||
};
|
||||
use crate::{
|
||||
copy_memory, CopyMemoryConfig, Env, BUILTINS_IMPORT_MODULE_NAME, MEMORY_NAME, PTR_TYPE,
|
||||
STACK_POINTER_GLOBAL_ID, STACK_POINTER_NAME,
|
||||
copy_memory, CopyMemoryConfig, Env, BUILTINS_IMPORT_MODULE_NAME, MEMORY_NAME, PTR_SIZE,
|
||||
PTR_TYPE, STACK_POINTER_GLOBAL_ID, STACK_POINTER_NAME,
|
||||
};
|
||||
|
||||
/// The memory address where the constants data will be loaded during module instantiation.
|
||||
@ -294,16 +295,17 @@ impl<'a> WasmBackend<'a> {
|
||||
|
||||
_ => {
|
||||
self.storage.load_symbols(&mut self.code_builder, &[*sym]);
|
||||
self.code_builder.br(self.block_depth); // jump to end of function (for stack frame pop)
|
||||
}
|
||||
}
|
||||
// jump to the "stack frame pop" code at the end of the function
|
||||
self.code_builder.br(self.block_depth - 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Stmt::Switch {
|
||||
cond_symbol,
|
||||
cond_layout: _,
|
||||
cond_layout,
|
||||
branches,
|
||||
default_branch,
|
||||
ret_layout: _,
|
||||
@ -321,21 +323,45 @@ impl<'a> WasmBackend<'a> {
|
||||
cond_storage,
|
||||
);
|
||||
|
||||
// create (number_of_branches - 1) new blocks.
|
||||
// create a block for each branch except the default
|
||||
for _ in 0..branches.len() {
|
||||
self.start_block(BlockType::NoResult)
|
||||
}
|
||||
|
||||
let is_bool = matches!(cond_layout, Layout::Builtin(Builtin::Int1));
|
||||
let cond_type = WasmLayout::new(cond_layout).value_type();
|
||||
|
||||
// then, we jump whenever the value under scrutiny is equal to the value of a branch
|
||||
for (i, (value, _, _)) in branches.iter().enumerate() {
|
||||
// put the cond_symbol on the top of the stack
|
||||
self.storage
|
||||
.load_symbols(&mut self.code_builder, &[*cond_symbol]);
|
||||
|
||||
self.code_builder.i32_const(*value as i32);
|
||||
|
||||
// compare the 2 topmost values
|
||||
self.code_builder.i32_eq();
|
||||
if is_bool {
|
||||
// We already have a bool, don't need to compare against a const to get one
|
||||
if *value == 0 {
|
||||
self.code_builder.i32_eqz();
|
||||
}
|
||||
} else {
|
||||
match cond_type {
|
||||
ValueType::I32 => {
|
||||
self.code_builder.i32_const(*value as i32);
|
||||
self.code_builder.i32_eq();
|
||||
}
|
||||
ValueType::I64 => {
|
||||
self.code_builder.i64_const(*value as i64);
|
||||
self.code_builder.i64_eq();
|
||||
}
|
||||
ValueType::F32 => {
|
||||
self.code_builder.f32_const(f32::from_bits(*value as u32));
|
||||
self.code_builder.f32_eq();
|
||||
}
|
||||
ValueType::F64 => {
|
||||
self.code_builder.f64_const(f64::from_bits(*value as u64));
|
||||
self.code_builder.f64_eq();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// "break" out of `i` surrounding blocks
|
||||
self.code_builder.br_if(i as u32);
|
||||
@ -418,6 +444,13 @@ impl<'a> WasmBackend<'a> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Stmt::Refcounting(_modify, following) => {
|
||||
// TODO: actually deal with refcounting. For hello world, we just skipped it.
|
||||
self.build_stmt(following, ret_layout)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
x => Err(format!("statement not yet implemented: {:?}", x)),
|
||||
}
|
||||
}
|
||||
@ -444,6 +477,11 @@ impl<'a> WasmBackend<'a> {
|
||||
arguments,
|
||||
}) => match call_type {
|
||||
CallType::ByName { name: func_sym, .. } => {
|
||||
// If this function is just a lowlevel wrapper, then inline it
|
||||
if let Some(lowlevel) = LowLevel::from_inlined_wrapper(*func_sym) {
|
||||
return self.build_low_level(lowlevel, arguments, *sym, wasm_layout);
|
||||
}
|
||||
|
||||
let mut wasm_args_tmp: Vec<Symbol>;
|
||||
let (wasm_args, has_return_val) = match wasm_layout {
|
||||
WasmLayout::StackMemory { .. } => {
|
||||
@ -486,39 +524,80 @@ impl<'a> WasmBackend<'a> {
|
||||
}
|
||||
|
||||
CallType::LowLevel { op: lowlevel, .. } => {
|
||||
let return_layout = WasmLayout::new(layout);
|
||||
self.storage.load_symbols(&mut self.code_builder, arguments);
|
||||
|
||||
let build_result = build_call_low_level(
|
||||
&mut self.code_builder,
|
||||
&mut self.storage,
|
||||
lowlevel,
|
||||
arguments,
|
||||
&return_layout,
|
||||
);
|
||||
use LowlevelBuildResult::*;
|
||||
|
||||
match build_result {
|
||||
Done => Ok(()),
|
||||
BuiltinCall(name) => {
|
||||
self.call_imported_builtin(name, arguments, &return_layout);
|
||||
Ok(())
|
||||
}
|
||||
NotImplemented => Err(format!(
|
||||
"Low level operation {:?} is not yet implemented",
|
||||
lowlevel
|
||||
)),
|
||||
}
|
||||
self.build_low_level(*lowlevel, arguments, *sym, wasm_layout)
|
||||
}
|
||||
|
||||
x => Err(format!("the call type, {:?}, is not yet implemented", x)),
|
||||
},
|
||||
|
||||
Expr::Struct(fields) => self.create_struct(sym, layout, fields),
|
||||
|
||||
Expr::StructAtIndex {
|
||||
index,
|
||||
field_layouts,
|
||||
structure,
|
||||
} => {
|
||||
if let StoredValue::StackMemory { location, .. } = self.storage.get(structure) {
|
||||
let (local_id, mut offset) =
|
||||
location.local_and_offset(self.storage.stack_frame_pointer);
|
||||
for field in field_layouts.iter().take(*index as usize) {
|
||||
offset += field.stack_size(PTR_SIZE);
|
||||
}
|
||||
self.storage.copy_value_from_memory(
|
||||
&mut self.code_builder,
|
||||
*sym,
|
||||
local_id,
|
||||
offset,
|
||||
);
|
||||
} else {
|
||||
unreachable!("Unexpected storage for {:?}", structure)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
x => Err(format!("Expression is not yet implemented {:?}", x)),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_low_level(
|
||||
&mut self,
|
||||
lowlevel: LowLevel,
|
||||
arguments: &'a [Symbol],
|
||||
return_sym: Symbol,
|
||||
return_layout: WasmLayout,
|
||||
) -> Result<(), String> {
|
||||
// Load symbols using the "fast calling convention" that Zig uses instead of the C ABI we normally use.
|
||||
// It's only different from the C ABI for small structs, and we are using Zig for all of those cases.
|
||||
// This is a workaround for a bug in Zig. If later versions fix it, we can change to the C ABI.
|
||||
self.storage.load_symbols_fastcc(
|
||||
&mut self.code_builder,
|
||||
arguments,
|
||||
return_sym,
|
||||
&return_layout,
|
||||
);
|
||||
|
||||
let build_result = decode_low_level(
|
||||
&mut self.code_builder,
|
||||
&mut self.storage,
|
||||
lowlevel,
|
||||
arguments,
|
||||
&return_layout,
|
||||
);
|
||||
use LowlevelBuildResult::*;
|
||||
|
||||
match build_result {
|
||||
Done => Ok(()),
|
||||
BuiltinCall(name) => {
|
||||
self.call_zig_builtin(name, arguments, &return_layout);
|
||||
Ok(())
|
||||
}
|
||||
NotImplemented => Err(format!(
|
||||
"Low level operation {:?} is not yet implemented",
|
||||
lowlevel
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn load_literal(
|
||||
&mut self,
|
||||
lit: &Literal<'a>,
|
||||
@ -578,8 +657,8 @@ impl<'a> WasmBackend<'a> {
|
||||
self.lookup_string_constant(string, sym, layout);
|
||||
|
||||
self.code_builder.get_local(local_id);
|
||||
self.code_builder.insert_memory_relocation(linker_sym_index);
|
||||
self.code_builder.i32_const(elements_addr as i32);
|
||||
self.code_builder
|
||||
.i32_const_mem_addr(elements_addr, linker_sym_index);
|
||||
self.code_builder.i32_store(Align::Bytes4, offset);
|
||||
|
||||
self.code_builder.get_local(local_id);
|
||||
@ -701,12 +780,10 @@ impl<'a> WasmBackend<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn call_imported_builtin(
|
||||
&mut self,
|
||||
name: &'a str,
|
||||
arguments: &[Symbol],
|
||||
ret_layout: &WasmLayout,
|
||||
) {
|
||||
/// Generate a call instruction to a Zig builtin function.
|
||||
/// And if we haven't seen it before, add an Import and linker data for it.
|
||||
/// Zig calls use LLVM's "fast" calling convention rather than our usual C ABI.
|
||||
fn call_zig_builtin(&mut self, name: &'a str, arguments: &[Symbol], ret_layout: &WasmLayout) {
|
||||
let (fn_index, linker_symbol_index) = match self.builtin_sym_index_map.get(name) {
|
||||
Some(sym_idx) => match &self.linker_symbols[*sym_idx] {
|
||||
SymInfo::Function(WasmObjectSymbol::Imported { index, .. }) => {
|
||||
@ -716,11 +793,29 @@ impl<'a> WasmBackend<'a> {
|
||||
},
|
||||
|
||||
None => {
|
||||
let mut param_types = Vec::with_capacity_in(arguments.len(), self.env.arena);
|
||||
param_types.extend(arguments.iter().map(|a| self.storage.get(a).value_type()));
|
||||
let mut param_types = Vec::with_capacity_in(1 + arguments.len(), self.env.arena);
|
||||
|
||||
let ret_type = if ret_layout.is_stack_memory() {
|
||||
param_types.push(ValueType::I32);
|
||||
None
|
||||
} else {
|
||||
Some(ret_layout.value_type())
|
||||
};
|
||||
|
||||
// Zig's "fast calling convention" packs structs into CPU registers (stack machine slots) if possible.
|
||||
// If they're small enough they can go into an I32 or I64. If they're big, they're pointers (I32).
|
||||
for arg in arguments {
|
||||
param_types.push(match self.storage.get(arg) {
|
||||
StoredValue::StackMemory { size, .. } if *size > 4 && *size <= 8 => {
|
||||
ValueType::I64
|
||||
}
|
||||
stored => stored.value_type(),
|
||||
});
|
||||
}
|
||||
|
||||
let signature_index = self.module.types.insert(Signature {
|
||||
param_types,
|
||||
ret_type: Some(ret_layout.value_type()), // TODO: handle builtins with no return value
|
||||
ret_type,
|
||||
});
|
||||
|
||||
let import_index = self.module.import.entries.len() as u32;
|
||||
@ -731,14 +826,15 @@ impl<'a> WasmBackend<'a> {
|
||||
};
|
||||
self.module.import.entries.push(import);
|
||||
|
||||
let sym_idx = self.linker_symbols.len() as u32;
|
||||
let sym_idx = self.linker_symbols.len();
|
||||
let sym_info = SymInfo::Function(WasmObjectSymbol::Imported {
|
||||
flags: WASM_SYM_UNDEFINED,
|
||||
index: import_index,
|
||||
});
|
||||
self.linker_symbols.push(sym_info);
|
||||
self.builtin_sym_index_map.insert(name, sym_idx);
|
||||
|
||||
(import_index, sym_idx)
|
||||
(import_index, sym_idx as u32)
|
||||
}
|
||||
};
|
||||
self.code_builder.call(
|
||||
|
@ -7,6 +7,7 @@ pub mod wasm_module;
|
||||
use bumpalo::{self, collections::Vec, Bump};
|
||||
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::{Interns, Symbol};
|
||||
use roc_mono::ir::{Proc, ProcLayout};
|
||||
use roc_mono::layout::LayoutIds;
|
||||
@ -36,7 +37,7 @@ pub fn build_module<'a>(
|
||||
env: &'a Env,
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
) -> Result<std::vec::Vec<u8>, String> {
|
||||
let mut wasm_module = build_module_help(env, procedures)?;
|
||||
let (mut wasm_module, _) = build_module_help(env, procedures)?;
|
||||
let mut buffer = std::vec::Vec::with_capacity(4096);
|
||||
wasm_module.serialize_mut(&mut buffer);
|
||||
Ok(buffer)
|
||||
@ -45,36 +46,54 @@ pub fn build_module<'a>(
|
||||
pub fn build_module_help<'a>(
|
||||
env: &'a Env,
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
) -> Result<WasmModule<'a>, String> {
|
||||
) -> Result<(WasmModule<'a>, u32), String> {
|
||||
let mut layout_ids = LayoutIds::default();
|
||||
let mut proc_symbols = Vec::with_capacity_in(procedures.len(), env.arena);
|
||||
let mut generated_procs = Vec::with_capacity_in(procedures.len(), env.arena);
|
||||
let mut generated_symbols = Vec::with_capacity_in(procedures.len(), env.arena);
|
||||
let mut linker_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena);
|
||||
let mut exports = Vec::with_capacity_in(procedures.len(), env.arena);
|
||||
let mut exports = Vec::with_capacity_in(4, env.arena);
|
||||
let mut main_fn_index = None;
|
||||
|
||||
// Collect the symbols & names for the procedures
|
||||
for (i, (sym, layout)) in procedures.keys().enumerate() {
|
||||
proc_symbols.push(*sym);
|
||||
// Collect the symbols & names for the procedures,
|
||||
// and filter out procs we're going to inline
|
||||
let mut fn_index: u32 = 0;
|
||||
for ((sym, layout), proc) in procedures.into_iter() {
|
||||
if LowLevel::from_inlined_wrapper(sym).is_some() {
|
||||
continue;
|
||||
}
|
||||
generated_procs.push(proc);
|
||||
generated_symbols.push(sym);
|
||||
|
||||
let fn_name = layout_ids
|
||||
.get_toplevel(*sym, layout)
|
||||
.to_symbol_string(*sym, &env.interns);
|
||||
.get_toplevel(sym, &layout)
|
||||
.to_symbol_string(sym, &env.interns);
|
||||
|
||||
if env.exposed_to_host.contains(sym) {
|
||||
if env.exposed_to_host.contains(&sym) {
|
||||
main_fn_index = Some(fn_index);
|
||||
exports.push(Export {
|
||||
name: fn_name.clone(),
|
||||
ty: ExportType::Func,
|
||||
index: i as u32,
|
||||
index: fn_index,
|
||||
});
|
||||
}
|
||||
|
||||
let linker_sym = SymInfo::for_function(i as u32, fn_name);
|
||||
let linker_sym = SymInfo::for_function(fn_index, fn_name);
|
||||
linker_symbols.push(linker_sym);
|
||||
|
||||
fn_index += 1;
|
||||
}
|
||||
|
||||
// Main loop: Build the Wasm module
|
||||
// Build the Wasm module
|
||||
let (mut module, linker_symbols) = {
|
||||
let mut backend = WasmBackend::new(env, layout_ids, proc_symbols, linker_symbols, exports);
|
||||
for ((sym, _), proc) in procedures.into_iter() {
|
||||
let mut backend = WasmBackend::new(
|
||||
env,
|
||||
layout_ids,
|
||||
generated_symbols.clone(),
|
||||
linker_symbols,
|
||||
exports,
|
||||
);
|
||||
|
||||
for (proc, sym) in generated_procs.into_iter().zip(generated_symbols) {
|
||||
backend.build_proc(proc, sym)?;
|
||||
}
|
||||
(backend.module, backend.linker_symbols)
|
||||
@ -83,7 +102,7 @@ pub fn build_module_help<'a>(
|
||||
let symbol_table = LinkingSubSection::SymbolTable(linker_symbols);
|
||||
module.linking.subsections.push(symbol_table);
|
||||
|
||||
Ok(module)
|
||||
Ok((module, main_fn_index.unwrap()))
|
||||
}
|
||||
|
||||
pub struct CopyMemoryConfig {
|
||||
|
@ -15,10 +15,10 @@ pub enum LowlevelBuildResult {
|
||||
NotImplemented,
|
||||
}
|
||||
|
||||
pub fn build_call_low_level<'a>(
|
||||
pub fn decode_low_level<'a>(
|
||||
code_builder: &mut CodeBuilder<'a>,
|
||||
storage: &mut Storage<'a>,
|
||||
lowlevel: &LowLevel,
|
||||
lowlevel: LowLevel,
|
||||
args: &'a [Symbol],
|
||||
ret_layout: &WasmLayout,
|
||||
) -> LowlevelBuildResult {
|
||||
@ -27,16 +27,39 @@ pub fn build_call_low_level<'a>(
|
||||
let panic_ret_type = || panic!("Invalid return layout for {:?}: {:?}", lowlevel, ret_layout);
|
||||
|
||||
match lowlevel {
|
||||
StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt
|
||||
| StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8 | StrTrimLeft
|
||||
| StrFromUtf8Range | StrToUtf8 | StrRepeat | StrFromFloat | StrTrim | ListLen
|
||||
| ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat
|
||||
StrConcat => return BuiltinCall(bitcode::STR_CONCAT),
|
||||
StrJoinWith => return NotImplemented, // needs Array
|
||||
StrIsEmpty => {
|
||||
code_builder.i64_const(i64::MIN);
|
||||
code_builder.i64_eq();
|
||||
}
|
||||
StrStartsWith => return BuiltinCall(bitcode::STR_STARTS_WITH),
|
||||
StrStartsWithCodePt => return BuiltinCall(bitcode::STR_STARTS_WITH_CODE_PT),
|
||||
StrEndsWith => return BuiltinCall(bitcode::STR_ENDS_WITH),
|
||||
StrSplit => return NotImplemented, // needs Array
|
||||
StrCountGraphemes => return NotImplemented, // test needs Array
|
||||
StrFromInt => return NotImplemented, // choose builtin based on storage size
|
||||
StrFromUtf8 => return NotImplemented, // needs Array
|
||||
StrTrimLeft => return BuiltinCall(bitcode::STR_TRIM_LEFT),
|
||||
StrTrimRight => return BuiltinCall(bitcode::STR_TRIM_RIGHT),
|
||||
StrFromUtf8Range => return NotImplemented, // needs Array
|
||||
StrToUtf8 => return NotImplemented, // needs Array
|
||||
StrRepeat => return BuiltinCall(bitcode::STR_REPEAT),
|
||||
StrFromFloat => {
|
||||
// linker errors for __ashlti3, __fixunsdfti, __multi3, __udivti3, __umodti3
|
||||
// https://gcc.gnu.org/onlinedocs/gccint/Integer-library-routines.html
|
||||
// https://gcc.gnu.org/onlinedocs/gccint/Soft-float-library-routines.html
|
||||
return NotImplemented;
|
||||
}
|
||||
StrTrim => return BuiltinCall(bitcode::STR_TRIM),
|
||||
|
||||
ListLen | ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat
|
||||
| ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap | ListMap2
|
||||
| ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil
|
||||
| ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListTakeFirst
|
||||
| ListTakeLast | ListDrop | ListDropAt | ListSwap | ListAny | ListFindUnsafe | DictSize
|
||||
| DictEmpty | DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys
|
||||
| DictValues | DictUnion | DictIntersection | DictDifference | DictWalk | SetFromList => {
|
||||
| ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListSublist
|
||||
| ListDropAt | ListSwap | ListAny | ListAll | ListFindUnsafe | DictSize | DictEmpty
|
||||
| DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues
|
||||
| DictUnion | DictIntersection | DictDifference | DictWalk | SetFromList => {
|
||||
return NotImplemented;
|
||||
}
|
||||
|
||||
@ -129,6 +152,9 @@ pub fn build_call_low_level<'a>(
|
||||
NumIsMultipleOf => return NotImplemented,
|
||||
NumAbs => match ret_layout.value_type() {
|
||||
I32 => {
|
||||
let arg_storage = storage.get(&args[0]).to_owned();
|
||||
storage.ensure_value_has_local(code_builder, args[0], arg_storage);
|
||||
storage.load_symbols(code_builder, args);
|
||||
code_builder.i32_const(0);
|
||||
storage.load_symbols(code_builder, args);
|
||||
code_builder.i32_sub();
|
||||
@ -138,6 +164,9 @@ pub fn build_call_low_level<'a>(
|
||||
code_builder.select();
|
||||
}
|
||||
I64 => {
|
||||
let arg_storage = storage.get(&args[0]).to_owned();
|
||||
storage.ensure_value_has_local(code_builder, args[0], arg_storage);
|
||||
storage.load_symbols(code_builder, args);
|
||||
code_builder.i64_const(0);
|
||||
storage.load_symbols(code_builder, args);
|
||||
code_builder.i64_sub();
|
||||
|
@ -5,7 +5,7 @@ use roc_collections::all::MutMap;
|
||||
use roc_module::symbol::Symbol;
|
||||
|
||||
use crate::layout::WasmLayout;
|
||||
use crate::wasm_module::{CodeBuilder, LocalId, ValueType, VirtualMachineSymbolState};
|
||||
use crate::wasm_module::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState};
|
||||
use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, PTR_SIZE, PTR_TYPE};
|
||||
|
||||
pub enum StoredValueKind {
|
||||
@ -33,7 +33,7 @@ impl StackMemoryLocation {
|
||||
pub enum StoredValue {
|
||||
/// A value stored implicitly in the VM stack (primitives only)
|
||||
VirtualMachineStack {
|
||||
vm_state: VirtualMachineSymbolState,
|
||||
vm_state: VmSymbolState,
|
||||
value_type: ValueType,
|
||||
size: u32,
|
||||
},
|
||||
@ -126,7 +126,7 @@ impl<'a> Storage<'a> {
|
||||
}
|
||||
}
|
||||
_ => StoredValue::VirtualMachineStack {
|
||||
vm_state: VirtualMachineSymbolState::NotYetPushed,
|
||||
vm_state: VmSymbolState::NotYetPushed,
|
||||
value_type: *value_type,
|
||||
size: *size,
|
||||
},
|
||||
@ -194,6 +194,63 @@ impl<'a> Storage<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Load a single symbol using the C Calling Convention
|
||||
/// *Private* because external code should always load symbols in bulk (see load_symbols)
|
||||
fn load_symbol_ccc(&mut self, code_builder: &mut CodeBuilder, sym: Symbol) {
|
||||
let storage = self.get(&sym).to_owned();
|
||||
match storage {
|
||||
StoredValue::VirtualMachineStack {
|
||||
vm_state,
|
||||
value_type,
|
||||
size,
|
||||
} => {
|
||||
let next_local_id = self.get_next_local_id();
|
||||
let maybe_next_vm_state = code_builder.load_symbol(sym, vm_state, next_local_id);
|
||||
match maybe_next_vm_state {
|
||||
// The act of loading the value changed the VM state, so update it
|
||||
Some(next_vm_state) => {
|
||||
self.symbol_storage_map.insert(
|
||||
sym,
|
||||
StoredValue::VirtualMachineStack {
|
||||
vm_state: next_vm_state,
|
||||
value_type,
|
||||
size,
|
||||
},
|
||||
);
|
||||
}
|
||||
None => {
|
||||
// Loading the value required creating a new local, because
|
||||
// it was not in a convenient position in the VM stack.
|
||||
self.local_types.push(value_type);
|
||||
self.symbol_storage_map.insert(
|
||||
sym,
|
||||
StoredValue::Local {
|
||||
local_id: next_local_id,
|
||||
value_type,
|
||||
size,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StoredValue::Local { local_id, .. } => {
|
||||
code_builder.get_local(local_id);
|
||||
code_builder.set_top_symbol(sym);
|
||||
}
|
||||
|
||||
StoredValue::StackMemory { location, .. } => {
|
||||
let (local_id, offset) = location.local_and_offset(self.stack_frame_pointer);
|
||||
code_builder.get_local(local_id);
|
||||
if offset != 0 {
|
||||
code_builder.i32_const(offset as i32);
|
||||
code_builder.i32_add();
|
||||
}
|
||||
code_builder.set_top_symbol(sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Load symbols to the top of the VM stack
|
||||
/// Avoid calling this method in a loop with one symbol at a time! It will work,
|
||||
/// but it generates very inefficient Wasm code.
|
||||
@ -204,61 +261,60 @@ impl<'a> Storage<'a> {
|
||||
return;
|
||||
}
|
||||
for sym in symbols.iter() {
|
||||
let storage = self.get(sym).to_owned();
|
||||
match storage {
|
||||
StoredValue::VirtualMachineStack {
|
||||
vm_state,
|
||||
value_type,
|
||||
size,
|
||||
} => {
|
||||
let next_local_id = self.get_next_local_id();
|
||||
let maybe_next_vm_state =
|
||||
code_builder.load_symbol(*sym, vm_state, next_local_id);
|
||||
match maybe_next_vm_state {
|
||||
// The act of loading the value changed the VM state, so update it
|
||||
Some(next_vm_state) => {
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
StoredValue::VirtualMachineStack {
|
||||
vm_state: next_vm_state,
|
||||
value_type,
|
||||
size,
|
||||
},
|
||||
);
|
||||
}
|
||||
None => {
|
||||
// Loading the value required creating a new local, because
|
||||
// it was not in a convenient position in the VM stack.
|
||||
self.local_types.push(value_type);
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
StoredValue::Local {
|
||||
local_id: next_local_id,
|
||||
value_type,
|
||||
size,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
StoredValue::Local { local_id, .. }
|
||||
| StoredValue::StackMemory {
|
||||
location: StackMemoryLocation::PointerArg(local_id),
|
||||
..
|
||||
} => {
|
||||
code_builder.get_local(local_id);
|
||||
code_builder.set_top_symbol(*sym);
|
||||
self.load_symbol_ccc(code_builder, *sym);
|
||||
}
|
||||
}
|
||||
|
||||
/// Load symbols in a way compatible with LLVM's "fast calling convention"
|
||||
/// A bug in Zig means it always uses this for Wasm even when we specify C calling convention.
|
||||
/// It squashes small structs into primitive values where possible, avoiding stack memory
|
||||
/// in favour of CPU registers (or VM stack values, which eventually become CPU registers).
|
||||
/// We need to convert some of our structs from our internal C-like representation to work with Zig.
|
||||
/// We are sticking to C ABI for better compatibility on the platform side.
|
||||
pub fn load_symbols_fastcc(
|
||||
&mut self,
|
||||
code_builder: &mut CodeBuilder,
|
||||
symbols: &[Symbol],
|
||||
return_symbol: Symbol,
|
||||
return_layout: &WasmLayout,
|
||||
) {
|
||||
// Note: we are not doing verify_stack_match in this case so we may generate more code.
|
||||
// We would need more bookkeeping in CodeBuilder to track which representation is on the stack!
|
||||
|
||||
if return_layout.is_stack_memory() {
|
||||
// Load the address where the return value should be written
|
||||
// Apparently for return values we still use a pointer to stack memory
|
||||
self.load_symbol_ccc(code_builder, return_symbol);
|
||||
};
|
||||
|
||||
for sym in symbols {
|
||||
if let StoredValue::StackMemory {
|
||||
location,
|
||||
size,
|
||||
alignment_bytes,
|
||||
} = self.get(sym)
|
||||
{
|
||||
if *size == 0 {
|
||||
unimplemented!("Passing zero-sized values is not implemented yet");
|
||||
} else if *size > 8 {
|
||||
return self.load_symbol_ccc(code_builder, *sym);
|
||||
}
|
||||
|
||||
StoredValue::StackMemory {
|
||||
location: StackMemoryLocation::FrameOffset(offset),
|
||||
..
|
||||
} => {
|
||||
code_builder.get_local(self.stack_frame_pointer.unwrap());
|
||||
code_builder.i32_const(offset as i32);
|
||||
code_builder.i32_add();
|
||||
code_builder.set_top_symbol(*sym);
|
||||
let (local_id, offset) = location.local_and_offset(self.stack_frame_pointer);
|
||||
code_builder.get_local(local_id);
|
||||
let align = Align::from(*alignment_bytes);
|
||||
|
||||
if *size == 1 {
|
||||
code_builder.i32_load8_u(align, offset);
|
||||
} else if *size == 2 {
|
||||
code_builder.i32_load16_u(align, offset);
|
||||
} else if *size <= 4 {
|
||||
code_builder.i32_load(align, offset);
|
||||
} else {
|
||||
code_builder.i64_load(align, offset);
|
||||
}
|
||||
} else {
|
||||
self.load_symbol_ccc(code_builder, *sym);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -319,6 +375,67 @@ impl<'a> Storage<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate code to copy a StoredValue from an arbitrary memory location
|
||||
/// (defined by a pointer and offset).
|
||||
pub fn copy_value_from_memory(
|
||||
&mut self,
|
||||
code_builder: &mut CodeBuilder,
|
||||
to_symbol: Symbol,
|
||||
from_ptr: LocalId,
|
||||
from_offset: u32,
|
||||
) -> u32 {
|
||||
let to_storage = self.get(&to_symbol).to_owned();
|
||||
match to_storage {
|
||||
StoredValue::StackMemory {
|
||||
location,
|
||||
size,
|
||||
alignment_bytes,
|
||||
} => {
|
||||
let (to_ptr, to_offset) = location.local_and_offset(self.stack_frame_pointer);
|
||||
copy_memory(
|
||||
code_builder,
|
||||
CopyMemoryConfig {
|
||||
from_ptr,
|
||||
from_offset,
|
||||
to_ptr,
|
||||
to_offset,
|
||||
size,
|
||||
alignment_bytes,
|
||||
},
|
||||
);
|
||||
size
|
||||
}
|
||||
|
||||
StoredValue::VirtualMachineStack {
|
||||
value_type, size, ..
|
||||
}
|
||||
| StoredValue::Local {
|
||||
value_type, size, ..
|
||||
} => {
|
||||
use crate::wasm_module::Align::*;
|
||||
|
||||
code_builder.get_local(from_ptr);
|
||||
match (value_type, size) {
|
||||
(ValueType::I64, 8) => code_builder.i64_load(Bytes8, from_offset),
|
||||
(ValueType::I32, 4) => code_builder.i32_load(Bytes4, from_offset),
|
||||
(ValueType::I32, 2) => code_builder.i32_load16_s(Bytes2, from_offset),
|
||||
(ValueType::I32, 1) => code_builder.i32_load8_s(Bytes1, from_offset),
|
||||
(ValueType::F32, 4) => code_builder.f32_load(Bytes4, from_offset),
|
||||
(ValueType::F64, 8) => code_builder.f64_load(Bytes8, from_offset),
|
||||
_ => {
|
||||
panic!("Cannot store {:?} with alignment of {:?}", value_type, size);
|
||||
}
|
||||
};
|
||||
|
||||
if let StoredValue::Local { local_id, .. } = to_storage {
|
||||
code_builder.set_local(local_id);
|
||||
}
|
||||
|
||||
size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate code to copy from one StoredValue to another
|
||||
/// Copies the _entire_ value. For struct fields etc., see `copy_value_to_memory`
|
||||
pub fn clone_value(
|
||||
@ -422,7 +539,7 @@ impl<'a> Storage<'a> {
|
||||
} = storage
|
||||
{
|
||||
let local_id = self.get_next_local_id();
|
||||
if vm_state != VirtualMachineSymbolState::NotYetPushed {
|
||||
if vm_state != VmSymbolState::NotYetPushed {
|
||||
code_builder.load_symbol(symbol, vm_state, local_id);
|
||||
code_builder.set_local(local_id);
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
use bumpalo::collections::vec::Vec;
|
||||
use bumpalo::Bump;
|
||||
use core::panic;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use roc_module::symbol::Symbol;
|
||||
|
||||
@ -10,6 +9,13 @@ use super::opcodes::{OpCode, OpCode::*};
|
||||
use super::serialize::{SerialBuffer, Serialize};
|
||||
use crate::{round_up_to_alignment, FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID};
|
||||
|
||||
const ENABLE_DEBUG_LOG: bool = false;
|
||||
macro_rules! log_instruction {
|
||||
($($x: expr),+) => {
|
||||
if ENABLE_DEBUG_LOG { println!($($x,)*); }
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct LocalId(pub u32);
|
||||
|
||||
@ -29,6 +35,7 @@ impl Serialize for ValueType {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub enum BlockType {
|
||||
NoResult,
|
||||
Value(ValueType),
|
||||
@ -43,6 +50,31 @@ impl BlockType {
|
||||
}
|
||||
}
|
||||
|
||||
/// A control block in our model of the VM
|
||||
/// Child blocks cannot "see" values from their parent block
|
||||
struct VmBlock<'a> {
|
||||
/// opcode indicating what kind of block this is
|
||||
opcode: OpCode,
|
||||
/// the stack of values for this block
|
||||
value_stack: Vec<'a, Symbol>,
|
||||
/// whether this block pushes a result value to its parent
|
||||
has_result: bool,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for VmBlock<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_fmt(format_args!(
|
||||
"{:?} {}",
|
||||
self.opcode,
|
||||
if self.has_result {
|
||||
"Result"
|
||||
} else {
|
||||
"NoResult"
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Wasm memory alignment. (Rust representation matches Wasm encoding)
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@ -73,7 +105,7 @@ impl From<u32> for Align {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
pub enum VirtualMachineSymbolState {
|
||||
pub enum VmSymbolState {
|
||||
/// Value doesn't exist yet
|
||||
NotYetPushed,
|
||||
|
||||
@ -113,6 +145,8 @@ macro_rules! instruction_memargs {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CodeBuilder<'a> {
|
||||
arena: &'a Bump,
|
||||
|
||||
/// The main container for the instructions
|
||||
code: Vec<'a, u8>,
|
||||
|
||||
@ -135,8 +169,8 @@ pub struct CodeBuilder<'a> {
|
||||
inner_length: Vec<'a, u8>,
|
||||
|
||||
/// Our simulation model of the Wasm stack machine
|
||||
/// Keeps track of where Symbol values are in the VM stack
|
||||
vm_stack: Vec<'a, Symbol>,
|
||||
/// Nested blocks of instructions. A child block can't "see" the stack of its parent block
|
||||
vm_block_stack: Vec<'a, VmBlock<'a>>,
|
||||
|
||||
/// Linker info to help combine the Roc module with builtin & platform modules,
|
||||
/// e.g. to modify call instructions when function indices change
|
||||
@ -146,13 +180,22 @@ pub struct CodeBuilder<'a> {
|
||||
#[allow(clippy::new_without_default)]
|
||||
impl<'a> CodeBuilder<'a> {
|
||||
pub fn new(arena: &'a Bump) -> Self {
|
||||
let mut vm_block_stack = Vec::with_capacity_in(8, arena);
|
||||
let function_block = VmBlock {
|
||||
opcode: BLOCK,
|
||||
has_result: true,
|
||||
value_stack: Vec::with_capacity_in(8, arena),
|
||||
};
|
||||
vm_block_stack.push(function_block);
|
||||
|
||||
CodeBuilder {
|
||||
arena,
|
||||
code: Vec::with_capacity_in(1024, arena),
|
||||
insertions: Vec::with_capacity_in(32, arena),
|
||||
insert_bytes: Vec::with_capacity_in(64, arena),
|
||||
preamble: Vec::with_capacity_in(32, arena),
|
||||
inner_length: Vec::with_capacity_in(5, arena),
|
||||
vm_stack: Vec::with_capacity_in(32, arena),
|
||||
vm_block_stack,
|
||||
relocations: Vec::with_capacity_in(32, arena),
|
||||
}
|
||||
}
|
||||
@ -167,45 +210,49 @@ impl<'a> CodeBuilder<'a> {
|
||||
|
||||
***********************************************************/
|
||||
|
||||
fn current_stack(&self) -> &Vec<'a, Symbol> {
|
||||
let block = self.vm_block_stack.last().unwrap();
|
||||
&block.value_stack
|
||||
}
|
||||
|
||||
fn current_stack_mut(&mut self) -> &mut Vec<'a, Symbol> {
|
||||
let block = self.vm_block_stack.last_mut().unwrap();
|
||||
&mut block.value_stack
|
||||
}
|
||||
|
||||
/// Set the Symbol that is at the top of the VM stack right now
|
||||
/// We will use this later when we need to load the Symbol
|
||||
pub fn set_top_symbol(&mut self, sym: Symbol) -> VirtualMachineSymbolState {
|
||||
let len = self.vm_stack.len();
|
||||
pub fn set_top_symbol(&mut self, sym: Symbol) -> VmSymbolState {
|
||||
let current_stack = &mut self.vm_block_stack.last_mut().unwrap().value_stack;
|
||||
let pushed_at = self.code.len();
|
||||
let top_symbol: &mut Symbol = current_stack.last_mut().unwrap();
|
||||
*top_symbol = sym;
|
||||
|
||||
if len == 0 {
|
||||
panic!(
|
||||
"trying to set symbol with nothing on stack, code = {:?}",
|
||||
self.code
|
||||
);
|
||||
}
|
||||
|
||||
self.vm_stack[len - 1] = sym;
|
||||
|
||||
VirtualMachineSymbolState::Pushed { pushed_at }
|
||||
VmSymbolState::Pushed { pushed_at }
|
||||
}
|
||||
|
||||
/// Verify if a sequence of symbols is at the top of the stack
|
||||
pub fn verify_stack_match(&self, symbols: &[Symbol]) -> bool {
|
||||
let current_stack = self.current_stack();
|
||||
let n_symbols = symbols.len();
|
||||
let stack_depth = self.vm_stack.len();
|
||||
let stack_depth = current_stack.len();
|
||||
if n_symbols > stack_depth {
|
||||
return false;
|
||||
}
|
||||
let offset = stack_depth - n_symbols;
|
||||
|
||||
for (i, sym) in symbols.iter().enumerate() {
|
||||
if self.vm_stack[offset + i] != *sym {
|
||||
if current_stack[offset + i] != *sym {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn add_insertion(&mut self, insert_at: usize, opcode: u8, immediate: u32) {
|
||||
fn add_insertion(&mut self, insert_at: usize, opcode: OpCode, immediate: u32) {
|
||||
let start = self.insert_bytes.len();
|
||||
|
||||
self.insert_bytes.push(opcode);
|
||||
self.insert_bytes.push(opcode as u8);
|
||||
self.insert_bytes.encode_u32(immediate);
|
||||
|
||||
self.insertions.push(Insertion {
|
||||
@ -213,6 +260,13 @@ impl<'a> CodeBuilder<'a> {
|
||||
start,
|
||||
end: self.insert_bytes.len(),
|
||||
});
|
||||
|
||||
log_instruction!(
|
||||
"**insert {:?} {} at byte offset {}**",
|
||||
opcode,
|
||||
immediate,
|
||||
insert_at
|
||||
);
|
||||
}
|
||||
|
||||
/// Load a Symbol that is stored in the VM stack
|
||||
@ -225,41 +279,56 @@ impl<'a> CodeBuilder<'a> {
|
||||
pub fn load_symbol(
|
||||
&mut self,
|
||||
symbol: Symbol,
|
||||
vm_state: VirtualMachineSymbolState,
|
||||
vm_state: VmSymbolState,
|
||||
next_local_id: LocalId,
|
||||
) -> Option<VirtualMachineSymbolState> {
|
||||
use VirtualMachineSymbolState::*;
|
||||
) -> Option<VmSymbolState> {
|
||||
use VmSymbolState::*;
|
||||
|
||||
match vm_state {
|
||||
NotYetPushed => panic!("Symbol {:?} has no value yet. Nothing to load.", symbol),
|
||||
NotYetPushed => unreachable!("Symbol {:?} has no value yet. Nothing to load.", symbol),
|
||||
|
||||
Pushed { pushed_at } => {
|
||||
let &top = self.vm_stack.last().unwrap();
|
||||
if top == symbol {
|
||||
// We're lucky, the symbol is already on top of the VM stack
|
||||
// No code to generate! (This reduces code size by up to 25% in tests.)
|
||||
// Just let the caller know what happened
|
||||
Some(Popped { pushed_at })
|
||||
} else {
|
||||
// Symbol is not on top of the stack. Find it.
|
||||
if let Some(found_index) = self.vm_stack.iter().rposition(|&s| s == symbol) {
|
||||
// Insert a local.set where the value was created
|
||||
self.add_insertion(pushed_at, SETLOCAL as u8, next_local_id.0);
|
||||
match self.current_stack().last() {
|
||||
Some(top_symbol) if *top_symbol == symbol => {
|
||||
// We're lucky, the symbol is already on top of the current block's stack.
|
||||
// No code to generate! (This reduces code size by up to 25% in tests.)
|
||||
// Just let the caller know what happened
|
||||
Some(Popped { pushed_at })
|
||||
}
|
||||
_ => {
|
||||
// Symbol is not on top of the stack.
|
||||
// We should have saved it to a local, so go back and do that now.
|
||||
|
||||
// Take the value out of the stack where local.set was inserted
|
||||
self.vm_stack.remove(found_index);
|
||||
// It should still be on the stack in the block where it was assigned. Remove it.
|
||||
let mut found = false;
|
||||
for block in self.vm_block_stack.iter_mut() {
|
||||
if let Some(found_index) =
|
||||
block.value_stack.iter().position(|&s| s == symbol)
|
||||
{
|
||||
block.value_stack.remove(found_index);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert a local.get at the current position
|
||||
// Go back to the code position where it was pushed, and save it to a local
|
||||
if found {
|
||||
self.add_insertion(pushed_at, SETLOCAL, next_local_id.0);
|
||||
} else {
|
||||
if ENABLE_DEBUG_LOG {
|
||||
println!(
|
||||
"{:?} has been popped implicitly. Leaving it on the stack.",
|
||||
symbol
|
||||
);
|
||||
}
|
||||
self.add_insertion(pushed_at, TEELOCAL, next_local_id.0);
|
||||
}
|
||||
|
||||
// Recover the value again at the current position
|
||||
self.get_local(next_local_id);
|
||||
self.vm_stack.push(symbol);
|
||||
self.set_top_symbol(symbol);
|
||||
|
||||
// This Symbol is no longer stored in the VM stack, but in a local
|
||||
None
|
||||
} else {
|
||||
panic!(
|
||||
"{:?} has state {:?} but not found in VM stack",
|
||||
symbol, vm_state
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -267,11 +336,11 @@ impl<'a> CodeBuilder<'a> {
|
||||
Popped { pushed_at } => {
|
||||
// This Symbol is being used for a second time
|
||||
// Insert a local.tee where it was pushed, so we don't interfere with the first usage
|
||||
self.add_insertion(pushed_at, TEELOCAL as u8, next_local_id.0);
|
||||
self.add_insertion(pushed_at, TEELOCAL, next_local_id.0);
|
||||
|
||||
// Insert a local.get at the current position
|
||||
self.get_local(next_local_id);
|
||||
self.vm_stack.push(symbol);
|
||||
self.set_top_symbol(symbol);
|
||||
|
||||
// This symbol has been promoted to a Local
|
||||
// Tell the caller it no longer has a VirtualMachineSymbolState
|
||||
@ -282,7 +351,7 @@ impl<'a> CodeBuilder<'a> {
|
||||
|
||||
/**********************************************************
|
||||
|
||||
FINALIZE AND SERIALIZE
|
||||
FUNCTION HEADER
|
||||
|
||||
***********************************************************/
|
||||
|
||||
@ -375,6 +444,12 @@ impl<'a> CodeBuilder<'a> {
|
||||
self.insertions.sort_by_key(|ins| ins.at);
|
||||
}
|
||||
|
||||
/**********************************************************
|
||||
|
||||
SERIALIZE
|
||||
|
||||
***********************************************************/
|
||||
|
||||
/// Serialize all byte vectors in the right order
|
||||
/// Also update relocation offsets relative to the base offset (code section body start)
|
||||
pub fn serialize_with_relocs<T: SerialBuffer>(
|
||||
@ -433,38 +508,78 @@ impl<'a> CodeBuilder<'a> {
|
||||
|
||||
/// Base method for generating instructions
|
||||
/// Emits the opcode and simulates VM stack push/pop
|
||||
fn inst(&mut self, opcode: OpCode, pops: usize, push: bool) {
|
||||
let new_len = self.vm_stack.len() - pops as usize;
|
||||
self.vm_stack.truncate(new_len);
|
||||
fn inst_base(&mut self, opcode: OpCode, pops: usize, push: bool) {
|
||||
let current_stack = self.current_stack_mut();
|
||||
let new_len = current_stack.len() - pops as usize;
|
||||
current_stack.truncate(new_len);
|
||||
if push {
|
||||
self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE);
|
||||
current_stack.push(Symbol::WASM_TMP);
|
||||
}
|
||||
|
||||
self.code.push(opcode as u8);
|
||||
}
|
||||
|
||||
fn inst_imm8(&mut self, opcode: OpCode, pops: usize, push: bool, immediate: u8) {
|
||||
self.inst(opcode, pops, push);
|
||||
self.code.push(immediate);
|
||||
/// Plain instruction without any immediates
|
||||
fn inst(&mut self, opcode: OpCode, pops: usize, push: bool) {
|
||||
self.inst_base(opcode, pops, push);
|
||||
log_instruction!(
|
||||
"{:10}\t\t{:?}",
|
||||
format!("{:?}", opcode),
|
||||
self.current_stack()
|
||||
);
|
||||
}
|
||||
|
||||
// public for use in test code
|
||||
pub fn inst_imm32(&mut self, opcode: OpCode, pops: usize, push: bool, immediate: u32) {
|
||||
self.inst(opcode, pops, push);
|
||||
/// Block instruction
|
||||
fn inst_block(&mut self, opcode: OpCode, pops: usize, block_type: BlockType) {
|
||||
self.inst_base(opcode, pops, false);
|
||||
self.code.push(block_type.as_byte());
|
||||
|
||||
// Start a new block with a fresh value stack
|
||||
self.vm_block_stack.push(VmBlock {
|
||||
opcode,
|
||||
value_stack: Vec::with_capacity_in(8, self.arena),
|
||||
has_result: block_type != BlockType::NoResult,
|
||||
});
|
||||
|
||||
log_instruction!(
|
||||
"{:10} {:?}\t{:?}",
|
||||
format!("{:?}", opcode),
|
||||
block_type,
|
||||
&self.vm_block_stack
|
||||
);
|
||||
}
|
||||
|
||||
fn inst_imm32(&mut self, opcode: OpCode, pops: usize, push: bool, immediate: u32) {
|
||||
self.inst_base(opcode, pops, push);
|
||||
self.code.encode_u32(immediate);
|
||||
log_instruction!(
|
||||
"{:10}\t{}\t{:?}",
|
||||
format!("{:?}", opcode),
|
||||
immediate,
|
||||
self.current_stack()
|
||||
);
|
||||
}
|
||||
|
||||
fn inst_mem(&mut self, opcode: OpCode, pops: usize, push: bool, align: Align, offset: u32) {
|
||||
self.inst(opcode, pops, push);
|
||||
self.inst_base(opcode, pops, push);
|
||||
self.code.push(align as u8);
|
||||
self.code.encode_u32(offset);
|
||||
log_instruction!(
|
||||
"{:10} {:?} {}\t{:?}",
|
||||
format!("{:?}", opcode),
|
||||
align,
|
||||
offset,
|
||||
self.current_stack()
|
||||
);
|
||||
}
|
||||
|
||||
/// Insert a linker relocation for a memory address
|
||||
pub fn insert_memory_relocation(&mut self, symbol_index: u32) {
|
||||
/// Insert a const reference to a memory address
|
||||
pub fn i32_const_mem_addr(&mut self, addr: u32, symbol_index: u32) {
|
||||
self.inst_base(I32CONST, 0, true);
|
||||
let offset = self.code.len() as u32;
|
||||
self.code.encode_padded_u32(addr);
|
||||
self.relocations.push(RelocationEntry::Offset {
|
||||
type_id: OffsetRelocType::MemoryAddrLeb,
|
||||
offset: self.code.len() as u32,
|
||||
offset,
|
||||
symbol_index,
|
||||
addend: 0,
|
||||
});
|
||||
@ -484,22 +599,38 @@ impl<'a> CodeBuilder<'a> {
|
||||
instruction_no_args!(nop, NOP, 0, false);
|
||||
|
||||
pub fn block(&mut self, ty: BlockType) {
|
||||
self.inst_imm8(BLOCK, 0, false, ty.as_byte());
|
||||
self.inst_block(BLOCK, 0, ty);
|
||||
}
|
||||
pub fn loop_(&mut self, ty: BlockType) {
|
||||
self.inst_imm8(LOOP, 0, false, ty.as_byte());
|
||||
self.inst_block(LOOP, 0, ty);
|
||||
}
|
||||
pub fn if_(&mut self, ty: BlockType) {
|
||||
self.inst_imm8(IF, 1, false, ty.as_byte());
|
||||
self.inst_block(IF, 1, ty);
|
||||
}
|
||||
pub fn else_(&mut self) {
|
||||
// Reuse the 'then' block but clear its value stack
|
||||
self.current_stack_mut().clear();
|
||||
self.inst(ELSE, 0, false);
|
||||
}
|
||||
|
||||
instruction_no_args!(else_, ELSE, 0, false);
|
||||
instruction_no_args!(end, END, 0, false);
|
||||
pub fn end(&mut self) {
|
||||
self.inst_base(END, 0, false);
|
||||
|
||||
let ended_block = self.vm_block_stack.pop().unwrap();
|
||||
if ended_block.has_result {
|
||||
let result = ended_block.value_stack.last().unwrap();
|
||||
self.current_stack_mut().push(*result)
|
||||
}
|
||||
|
||||
log_instruction!("END \t\t{:?}", &self.vm_block_stack);
|
||||
}
|
||||
pub fn br(&mut self, levels: u32) {
|
||||
self.inst_imm32(BR, 0, false, levels);
|
||||
}
|
||||
pub fn br_if(&mut self, levels: u32) {
|
||||
// In dynamic execution, br_if can pop 2 values if condition is true and the target block has a result.
|
||||
// But our stack model is for *static* analysis and we need it to be correct at the next instruction,
|
||||
// where the branch was not taken. So we only pop 1 value, the condition.
|
||||
self.inst_imm32(BRIF, 1, false, levels);
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
@ -516,30 +647,26 @@ impl<'a> CodeBuilder<'a> {
|
||||
n_args: usize,
|
||||
has_return_val: bool,
|
||||
) {
|
||||
let stack_depth = self.vm_stack.len();
|
||||
if n_args > stack_depth {
|
||||
panic!(
|
||||
"Trying to call to call function {:?} with {:?} values but only {:?} on the VM stack\n{:?}",
|
||||
function_index, n_args, stack_depth, self
|
||||
);
|
||||
}
|
||||
self.vm_stack.truncate(stack_depth - n_args);
|
||||
if has_return_val {
|
||||
self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE);
|
||||
}
|
||||
self.code.push(CALL as u8);
|
||||
self.inst_base(CALL, n_args, has_return_val);
|
||||
|
||||
// Write the index of the function to be called.
|
||||
// Also make a RelocationEntry so the linker can see that this byte offset relates to a function by name.
|
||||
// Here we initialise the offset to an index of self.code. After completing the function, we'll add
|
||||
// other factors to make it relative to the code section. (All insertions will be known then.)
|
||||
let offset = self.code.len() as u32;
|
||||
self.code.encode_padded_u32(function_index);
|
||||
|
||||
// Make a RelocationEntry so the linker can see that this byte offset relates to a function by name.
|
||||
// Here we initialise the offset to an index of self.code. After completing the function, we'll add
|
||||
// other factors to make it relative to the code section. (All insertions will be known then.)
|
||||
self.relocations.push(RelocationEntry::Index {
|
||||
type_id: IndexRelocType::FunctionIndexLeb,
|
||||
offset,
|
||||
symbol_index,
|
||||
});
|
||||
|
||||
log_instruction!(
|
||||
"{:10}\t{}\t{:?}",
|
||||
format!("{:?}", CALL),
|
||||
function_index,
|
||||
self.current_stack()
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@ -591,26 +718,44 @@ impl<'a> CodeBuilder<'a> {
|
||||
instruction_memargs!(i64_store32, I64STORE32, 2, false);
|
||||
|
||||
pub fn memory_size(&mut self) {
|
||||
self.inst_imm8(CURRENTMEMORY, 0, true, 0);
|
||||
self.inst(CURRENTMEMORY, 0, true);
|
||||
self.code.push(0);
|
||||
}
|
||||
pub fn memory_grow(&mut self) {
|
||||
self.inst_imm8(GROWMEMORY, 1, true, 0);
|
||||
self.inst(GROWMEMORY, 1, true);
|
||||
self.code.push(0);
|
||||
}
|
||||
|
||||
fn log_const<T>(&self, opcode: OpCode, x: T)
|
||||
where
|
||||
T: std::fmt::Debug + std::fmt::Display,
|
||||
{
|
||||
log_instruction!(
|
||||
"{:10}\t{}\t{:?}",
|
||||
format!("{:?}", opcode),
|
||||
x,
|
||||
self.current_stack()
|
||||
);
|
||||
}
|
||||
pub fn i32_const(&mut self, x: i32) {
|
||||
self.inst(I32CONST, 0, true);
|
||||
self.inst_base(I32CONST, 0, true);
|
||||
self.code.encode_i32(x);
|
||||
self.log_const(I32CONST, x);
|
||||
}
|
||||
pub fn i64_const(&mut self, x: i64) {
|
||||
self.inst(I64CONST, 0, true);
|
||||
self.inst_base(I64CONST, 0, true);
|
||||
self.code.encode_i64(x);
|
||||
self.log_const(I64CONST, x);
|
||||
}
|
||||
pub fn f32_const(&mut self, x: f32) {
|
||||
self.inst(F32CONST, 0, true);
|
||||
self.inst_base(F32CONST, 0, true);
|
||||
self.code.encode_f32(x);
|
||||
self.log_const(F32CONST, x);
|
||||
}
|
||||
pub fn f64_const(&mut self, x: f64) {
|
||||
self.inst(F64CONST, 0, true);
|
||||
self.inst_base(F64CONST, 0, true);
|
||||
self.code.encode_f64(x);
|
||||
self.log_const(F64CONST, x);
|
||||
}
|
||||
|
||||
// TODO: Consider creating unified methods for numerical ops like 'eq' and 'add',
|
||||
|
@ -4,8 +4,6 @@ pub mod opcodes;
|
||||
pub mod sections;
|
||||
pub mod serialize;
|
||||
|
||||
pub use code_builder::{
|
||||
Align, BlockType, CodeBuilder, LocalId, ValueType, VirtualMachineSymbolState,
|
||||
};
|
||||
pub use code_builder::{Align, BlockType, CodeBuilder, LocalId, ValueType, VmSymbolState};
|
||||
pub use linking::{LinkingSubSection, SymInfo};
|
||||
pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature, WasmModule};
|
||||
|
@ -1,5 +1,5 @@
|
||||
#[repr(u8)]
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum OpCode {
|
||||
UNREACHABLE = 0x00,
|
||||
NOP = 0x01,
|
||||
|
@ -29,6 +29,8 @@ pub enum SectionId {
|
||||
Element = 9,
|
||||
Code = 10,
|
||||
Data = 11,
|
||||
/// DataCount section is unused. Only needed for single-pass validation of
|
||||
/// memory.init and data.drop, which we don't use
|
||||
DataCount = 12,
|
||||
}
|
||||
|
||||
@ -239,8 +241,7 @@ impl<'a> Serialize for ImportSection<'a> {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FunctionSection<'a> {
|
||||
/// Private. See WasmModule::add_function_signature
|
||||
signature_indices: Vec<'a, u32>,
|
||||
pub signature_indices: Vec<'a, u32>,
|
||||
}
|
||||
|
||||
impl<'a> FunctionSection<'a> {
|
||||
@ -525,42 +526,6 @@ impl Serialize for DataSection<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
*
|
||||
* Data Count section
|
||||
*
|
||||
* Pre-declares the number of segments in the Data section.
|
||||
* This helps the runtime to validate the module in a single pass.
|
||||
* The order of sections is DataCount -> Code -> Data
|
||||
*
|
||||
*******************************************************************/
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DataCountSection {
|
||||
count: u32,
|
||||
}
|
||||
|
||||
impl DataCountSection {
|
||||
fn new(data_section: &DataSection<'_>) -> Self {
|
||||
let count = data_section
|
||||
.segments
|
||||
.iter()
|
||||
.filter(|seg| !seg.init.is_empty())
|
||||
.count() as u32;
|
||||
DataCountSection { count }
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for DataCountSection {
|
||||
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
|
||||
if self.count > 0 {
|
||||
let header_indices = write_section_header(buffer, SectionId::DataCount);
|
||||
buffer.encode_u32(self.count);
|
||||
update_section_size(buffer, header_indices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************
|
||||
*
|
||||
* Module
|
||||
@ -658,11 +623,6 @@ impl<'a> WasmModule<'a> {
|
||||
counter.serialize_and_count(buffer, &self.start);
|
||||
counter.serialize_and_count(buffer, &self.element);
|
||||
|
||||
// Data Count section forward-declares the size of the Data section
|
||||
// so that Code section can be validated in one pass
|
||||
let data_count_section = DataCountSection::new(&self.data);
|
||||
counter.serialize_and_count(buffer, &data_count_section);
|
||||
|
||||
// Code section is the only one with relocations so we can stop counting
|
||||
let code_section_index = counter.section_index;
|
||||
let code_section_body_index = self
|
||||
|
@ -18,7 +18,7 @@ roc_unify = { path = "../unify" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_solve = { path = "../solve" }
|
||||
roc_mono = { path = "../mono" }
|
||||
roc_reporting = { path = "../reporting" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
morphic_lib = { path = "../../vendor/morphic_lib" }
|
||||
ven_pretty = { path = "../../vendor/pretty" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
|
@ -235,16 +235,12 @@ fn generate_entry_doc<'a>(
|
||||
|
||||
fn type_to_docs(in_func_type_ann: bool, type_annotation: ast::TypeAnnotation) -> TypeAnnotation {
|
||||
match type_annotation {
|
||||
ast::TypeAnnotation::TagUnion {
|
||||
tags,
|
||||
ext,
|
||||
final_comments: _,
|
||||
} => {
|
||||
ast::TypeAnnotation::TagUnion { tags, ext } => {
|
||||
let mut tags_to_render: Vec<Tag> = Vec::new();
|
||||
|
||||
let mut any_tags_are_private = false;
|
||||
|
||||
for tag in tags {
|
||||
for tag in tags.iter() {
|
||||
match tag_to_doc(in_func_type_ann, tag.value) {
|
||||
None => {
|
||||
any_tags_are_private = true;
|
||||
|
@ -5,8 +5,8 @@ use roc_can::expr::{ClosureData, Expr, Recursive};
|
||||
use roc_can::pattern::Pattern;
|
||||
use roc_can::scope::Scope;
|
||||
use roc_collections::all::{MutSet, SendMap};
|
||||
use roc_module::called_via::CalledVia;
|
||||
use roc_module::ident::TagName;
|
||||
use roc_module::operator::CalledVia;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::{Located, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
|
@ -19,8 +19,7 @@ use roc_module::symbol::{
|
||||
Symbol,
|
||||
};
|
||||
use roc_mono::ir::{
|
||||
CapturedSymbols, EntryPoint, ExternalSpecializations, PartialProc, PendingSpecialization, Proc,
|
||||
ProcLayout, Procs,
|
||||
CapturedSymbols, EntryPoint, ExternalSpecializations, PartialProc, Proc, ProcLayout, Procs,
|
||||
};
|
||||
use roc_mono::layout::{Layout, LayoutCache, LayoutProblem};
|
||||
use roc_parse::ast::{self, StrLiteral, TypeAnnotation};
|
||||
@ -356,7 +355,7 @@ struct ModuleCache<'a> {
|
||||
constrained: MutMap<ModuleId, ConstrainedModule>,
|
||||
typechecked: MutMap<ModuleId, TypeCheckedModule<'a>>,
|
||||
found_specializations: MutMap<ModuleId, FoundSpecializationsModule<'a>>,
|
||||
external_specializations_requested: MutMap<ModuleId, ExternalSpecializations<'a>>,
|
||||
external_specializations_requested: MutMap<ModuleId, Vec<ExternalSpecializations>>,
|
||||
|
||||
/// Various information
|
||||
imports: MutMap<ModuleId, MutSet<ModuleId>>,
|
||||
@ -587,7 +586,7 @@ fn start_phase<'a>(
|
||||
.module_cache
|
||||
.external_specializations_requested
|
||||
.remove(&module_id)
|
||||
.unwrap_or_else(|| ExternalSpecializations::new_in(arena));
|
||||
.unwrap_or_default();
|
||||
|
||||
let FoundSpecializationsModule {
|
||||
module_id,
|
||||
@ -831,7 +830,7 @@ enum Msg<'a> {
|
||||
module_id: ModuleId,
|
||||
ident_ids: IdentIds,
|
||||
layout_cache: LayoutCache<'a>,
|
||||
external_specializations_requested: BumpMap<ModuleId, ExternalSpecializations<'a>>,
|
||||
external_specializations_requested: BumpMap<ModuleId, ExternalSpecializations>,
|
||||
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||
problems: Vec<roc_mono::ir::MonoProblem>,
|
||||
module_timing: ModuleTiming,
|
||||
@ -911,9 +910,6 @@ struct State<'a> {
|
||||
/// pending specializations in the same thread.
|
||||
pub needs_specialization: MutSet<ModuleId>,
|
||||
|
||||
pub all_pending_specializations:
|
||||
MutMap<Symbol, MutMap<ProcLayout<'a>, PendingSpecialization<'a>>>,
|
||||
|
||||
pub specializations_in_flight: u32,
|
||||
|
||||
pub timings: MutMap<ModuleId, ModuleTiming>,
|
||||
@ -1054,7 +1050,7 @@ enum BuildTask<'a> {
|
||||
subs: Subs,
|
||||
procs_base: ProcsBase<'a>,
|
||||
layout_cache: LayoutCache<'a>,
|
||||
specializations_we_must_make: ExternalSpecializations<'a>,
|
||||
specializations_we_must_make: Vec<ExternalSpecializations>,
|
||||
module_timing: ModuleTiming,
|
||||
},
|
||||
}
|
||||
@ -1538,7 +1534,6 @@ where
|
||||
unsolved_modules: MutMap::default(),
|
||||
timings: MutMap::default(),
|
||||
needs_specialization: MutSet::default(),
|
||||
all_pending_specializations: MutMap::default(),
|
||||
specializations_in_flight: 0,
|
||||
layout_caches: std::vec::Vec::with_capacity(num_cpus::get()),
|
||||
procs: Procs::new_in(arena),
|
||||
@ -2067,17 +2062,6 @@ fn update<'a>(
|
||||
log!("found specializations for {:?}", module_id);
|
||||
let subs = solved_subs.into_inner();
|
||||
|
||||
for (symbol, specs) in &procs_base.specializations_for_host {
|
||||
let existing = match state.all_pending_specializations.entry(*symbol) {
|
||||
Vacant(entry) => entry.insert(MutMap::default()),
|
||||
Occupied(entry) => entry.into_mut(),
|
||||
};
|
||||
|
||||
for (layout, pend) in specs {
|
||||
existing.insert(*layout, pend.clone());
|
||||
}
|
||||
}
|
||||
|
||||
state
|
||||
.module_cache
|
||||
.top_level_thunks
|
||||
@ -2171,11 +2155,11 @@ fn update<'a>(
|
||||
.external_specializations_requested
|
||||
.entry(module_id)
|
||||
{
|
||||
Vacant(entry) => entry.insert(ExternalSpecializations::new_in(arena)),
|
||||
Vacant(entry) => entry.insert(vec![]),
|
||||
Occupied(entry) => entry.into_mut(),
|
||||
};
|
||||
|
||||
existing.extend(requested);
|
||||
existing.push(requested);
|
||||
}
|
||||
|
||||
msg_tx
|
||||
@ -2198,11 +2182,11 @@ fn update<'a>(
|
||||
.external_specializations_requested
|
||||
.entry(module_id)
|
||||
{
|
||||
Vacant(entry) => entry.insert(ExternalSpecializations::new_in(arena)),
|
||||
Vacant(entry) => entry.insert(vec![]),
|
||||
Occupied(entry) => entry.into_mut(),
|
||||
};
|
||||
|
||||
existing.extend(requested);
|
||||
existing.push(requested);
|
||||
}
|
||||
|
||||
start_tasks(arena, &mut state, work, injector, worker_listeners)?;
|
||||
@ -2608,8 +2592,8 @@ fn parse_header<'a>(
|
||||
opt_shorthand,
|
||||
header_src,
|
||||
packages: &[],
|
||||
exposes: header.exposes.into_bump_slice(),
|
||||
imports: header.imports.into_bump_slice(),
|
||||
exposes: header.exposes.items,
|
||||
imports: header.imports.items,
|
||||
to_platform: None,
|
||||
};
|
||||
|
||||
@ -2642,8 +2626,8 @@ fn parse_header<'a>(
|
||||
opt_shorthand,
|
||||
header_src,
|
||||
packages,
|
||||
exposes: header.provides.into_bump_slice(),
|
||||
imports: header.imports.into_bump_slice(),
|
||||
exposes: header.provides.items,
|
||||
imports: header.imports.items,
|
||||
to_platform: Some(header.to.value.clone()),
|
||||
};
|
||||
|
||||
@ -3236,7 +3220,7 @@ fn send_header_two<'a>(
|
||||
|
||||
let extra = HeaderFor::PkgConfig {
|
||||
config_shorthand: shorthand,
|
||||
platform_main_type: requires[0].value.clone(),
|
||||
platform_main_type: requires[0].value,
|
||||
main_for_host,
|
||||
};
|
||||
|
||||
@ -3409,8 +3393,7 @@ fn fabricate_pkg_config_module<'a>(
|
||||
header_src: &'a str,
|
||||
module_timing: ModuleTiming,
|
||||
) -> (ModuleId, Msg<'a>) {
|
||||
let provides: &'a [Located<ExposesEntry<'a, &'a str>>] =
|
||||
header.provides.clone().into_bump_slice();
|
||||
let provides: &'a [Located<ExposesEntry<'a, &'a str>>] = header.provides.items;
|
||||
|
||||
let info = PlatformHeaderInfo {
|
||||
filename,
|
||||
@ -3420,8 +3403,8 @@ fn fabricate_pkg_config_module<'a>(
|
||||
app_module_id,
|
||||
packages: &[],
|
||||
provides,
|
||||
requires: arena.alloc([header.requires.signature.clone()]),
|
||||
imports: header.imports.clone().into_bump_slice(),
|
||||
requires: arena.alloc([header.requires.signature]),
|
||||
imports: header.imports.items,
|
||||
};
|
||||
|
||||
send_header_two(
|
||||
@ -3467,7 +3450,7 @@ fn fabricate_effects_module<'a>(
|
||||
{
|
||||
let mut module_ids = (*module_ids).lock();
|
||||
|
||||
for exposed in header.exposes {
|
||||
for exposed in header.exposes.iter() {
|
||||
if let ExposesEntry::Exposed(module_name) = exposed.value {
|
||||
module_ids.get_or_insert(&PQModuleName::Qualified(
|
||||
shorthand,
|
||||
@ -3886,7 +3869,7 @@ fn exposed_from_import<'a>(entry: &ImportsEntry<'a>) -> (QualifiedModuleName<'a>
|
||||
Module(module_name, exposes) => {
|
||||
let mut exposed = Vec::with_capacity(exposes.len());
|
||||
|
||||
for loc_entry in exposes {
|
||||
for loc_entry in exposes.iter() {
|
||||
exposed.push(ident_from_exposed(&loc_entry.value));
|
||||
}
|
||||
|
||||
@ -3901,7 +3884,7 @@ fn exposed_from_import<'a>(entry: &ImportsEntry<'a>) -> (QualifiedModuleName<'a>
|
||||
Package(package_name, module_name, exposes) => {
|
||||
let mut exposed = Vec::with_capacity(exposes.len());
|
||||
|
||||
for loc_entry in exposes {
|
||||
for loc_entry in exposes.iter() {
|
||||
exposed.push(ident_from_exposed(&loc_entry.value));
|
||||
}
|
||||
|
||||
@ -3937,7 +3920,7 @@ fn make_specializations<'a>(
|
||||
mut subs: Subs,
|
||||
procs_base: ProcsBase<'a>,
|
||||
mut layout_cache: LayoutCache<'a>,
|
||||
specializations_we_must_make: ExternalSpecializations<'a>,
|
||||
specializations_we_must_make: Vec<ExternalSpecializations>,
|
||||
mut module_timing: ModuleTiming,
|
||||
ptr_bytes: u32,
|
||||
) -> Msg<'a> {
|
||||
@ -3973,7 +3956,7 @@ fn make_specializations<'a>(
|
||||
&mut mono_env,
|
||||
procs,
|
||||
specializations_we_must_make,
|
||||
procs_base.specializations_for_host,
|
||||
procs_base.host_specializations,
|
||||
&mut layout_cache,
|
||||
);
|
||||
|
||||
@ -4005,27 +3988,11 @@ struct ProcsBase<'a> {
|
||||
partial_procs: BumpMap<Symbol, PartialProc<'a>>,
|
||||
module_thunks: &'a [Symbol],
|
||||
/// A host-exposed function must be specialized; it's a seed for subsequent specializations
|
||||
specializations_for_host: BumpMap<Symbol, MutMap<ProcLayout<'a>, PendingSpecialization<'a>>>,
|
||||
host_specializations: roc_mono::ir::HostSpecializations,
|
||||
runtime_errors: BumpMap<Symbol, &'a str>,
|
||||
imported_module_thunks: &'a [Symbol],
|
||||
}
|
||||
|
||||
impl<'a> ProcsBase<'a> {
|
||||
fn add_specialization_for_host(
|
||||
&mut self,
|
||||
symbol: Symbol,
|
||||
layout: ProcLayout<'a>,
|
||||
pending: PendingSpecialization<'a>,
|
||||
) {
|
||||
let all_pending = self
|
||||
.specializations_for_host
|
||||
.entry(symbol)
|
||||
.or_insert_with(|| HashMap::with_capacity_and_hasher(1, default_hasher()));
|
||||
|
||||
all_pending.insert(layout, pending);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn build_pending_specializations<'a>(
|
||||
arena: &'a Bump,
|
||||
@ -4047,7 +4014,7 @@ fn build_pending_specializations<'a>(
|
||||
let mut procs_base = ProcsBase {
|
||||
partial_procs: BumpMap::default(),
|
||||
module_thunks: &[],
|
||||
specializations_for_host: BumpMap::default(),
|
||||
host_specializations: roc_mono::ir::HostSpecializations::new(),
|
||||
runtime_errors: BumpMap::default(),
|
||||
imported_module_thunks,
|
||||
};
|
||||
@ -4135,7 +4102,7 @@ fn add_def_to_module<'a>(
|
||||
|
||||
match def.loc_pattern.value {
|
||||
Identifier(symbol) => {
|
||||
let is_exposed = exposed_to_host.contains_key(&symbol);
|
||||
let is_host_exposed = exposed_to_host.contains_key(&symbol);
|
||||
|
||||
match def.loc_expr.value {
|
||||
Closure(ClosureData {
|
||||
@ -4153,42 +4120,37 @@ fn add_def_to_module<'a>(
|
||||
// register it as such. Otherwise, since it
|
||||
// never gets called by Roc code, it will never
|
||||
// get specialized!
|
||||
if is_exposed {
|
||||
let layout = match layout_cache.raw_from_var(
|
||||
mono_env.arena,
|
||||
annotation,
|
||||
mono_env.subs,
|
||||
) {
|
||||
Ok(l) => l,
|
||||
Err(LayoutProblem::Erroneous) => {
|
||||
let message = "top level function has erroneous type";
|
||||
procs.runtime_errors.insert(symbol, message);
|
||||
return;
|
||||
}
|
||||
Err(LayoutProblem::UnresolvedTypeVar(v)) => {
|
||||
let message = format!(
|
||||
"top level function has unresolved type variable {:?}",
|
||||
v
|
||||
);
|
||||
procs
|
||||
.runtime_errors
|
||||
.insert(symbol, mono_env.arena.alloc(message));
|
||||
return;
|
||||
}
|
||||
};
|
||||
if is_host_exposed {
|
||||
let layout_result =
|
||||
layout_cache.raw_from_var(mono_env.arena, annotation, mono_env.subs);
|
||||
|
||||
let pending = PendingSpecialization::from_exposed_function(
|
||||
mono_env.arena,
|
||||
// cannot specialize when e.g. main's type contains type variables
|
||||
if let Err(e) = layout_result {
|
||||
match e {
|
||||
LayoutProblem::Erroneous => {
|
||||
let message = "top level function has erroneous type";
|
||||
procs.runtime_errors.insert(symbol, message);
|
||||
return;
|
||||
}
|
||||
LayoutProblem::UnresolvedTypeVar(v) => {
|
||||
let message = format!(
|
||||
"top level function has unresolved type variable {:?}",
|
||||
v
|
||||
);
|
||||
procs
|
||||
.runtime_errors
|
||||
.insert(symbol, mono_env.arena.alloc(message));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
procs.host_specializations.insert_host_exposed(
|
||||
mono_env.subs,
|
||||
symbol,
|
||||
def.annotation,
|
||||
annotation,
|
||||
);
|
||||
|
||||
procs.add_specialization_for_host(
|
||||
symbol,
|
||||
ProcLayout::from_raw(mono_env.arena, layout),
|
||||
pending,
|
||||
);
|
||||
}
|
||||
|
||||
let partial_proc = PartialProc::from_named_function(
|
||||
@ -4208,51 +4170,47 @@ fn add_def_to_module<'a>(
|
||||
// mark this symbols as a top-level thunk before any other work on the procs
|
||||
module_thunks.push(symbol);
|
||||
|
||||
let annotation = def.expr_var;
|
||||
|
||||
// If this is an exposed symbol, we need to
|
||||
// register it as such. Otherwise, since it
|
||||
// never gets called by Roc code, it will never
|
||||
// get specialized!
|
||||
if is_exposed {
|
||||
let annotation = def.expr_var;
|
||||
if is_host_exposed {
|
||||
let layout_result =
|
||||
layout_cache.raw_from_var(mono_env.arena, annotation, mono_env.subs);
|
||||
|
||||
let top_level = match layout_cache.from_var(
|
||||
mono_env.arena,
|
||||
annotation,
|
||||
mono_env.subs,
|
||||
) {
|
||||
Ok(l) => {
|
||||
// remember, this is a 0-argument thunk
|
||||
ProcLayout::new(mono_env.arena, &[], l)
|
||||
// cannot specialize when e.g. main's type contains type variables
|
||||
if let Err(e) = layout_result {
|
||||
match e {
|
||||
LayoutProblem::Erroneous => {
|
||||
let message = "top level function has erroneous type";
|
||||
procs.runtime_errors.insert(symbol, message);
|
||||
return;
|
||||
}
|
||||
LayoutProblem::UnresolvedTypeVar(v) => {
|
||||
let message = format!(
|
||||
"top level function has unresolved type variable {:?}",
|
||||
v
|
||||
);
|
||||
procs
|
||||
.runtime_errors
|
||||
.insert(symbol, mono_env.arena.alloc(message));
|
||||
return;
|
||||
}
|
||||
}
|
||||
Err(LayoutProblem::Erroneous) => {
|
||||
let message = "top level function has erroneous type";
|
||||
procs.runtime_errors.insert(symbol, message);
|
||||
return;
|
||||
}
|
||||
Err(LayoutProblem::UnresolvedTypeVar(v)) => {
|
||||
let message = format!(
|
||||
"top level function has unresolved type variable {:?}",
|
||||
v
|
||||
);
|
||||
procs
|
||||
.runtime_errors
|
||||
.insert(symbol, mono_env.arena.alloc(message));
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let pending = PendingSpecialization::from_exposed_function(
|
||||
mono_env.arena,
|
||||
procs.host_specializations.insert_host_exposed(
|
||||
mono_env.subs,
|
||||
symbol,
|
||||
def.annotation,
|
||||
annotation,
|
||||
);
|
||||
|
||||
procs.add_specialization_for_host(symbol, top_level, pending);
|
||||
}
|
||||
|
||||
let proc = PartialProc {
|
||||
annotation: def.expr_var,
|
||||
annotation,
|
||||
// This is a 0-arity thunk, so it has no arguments.
|
||||
pattern_symbols: &[],
|
||||
// This is a top-level definition, so it cannot capture anything
|
||||
|
@ -12,6 +12,10 @@ pub enum CalledVia {
|
||||
|
||||
/// Calling with a unary operator, e.g. (!foo bar baz) or (-foo bar baz)
|
||||
UnaryOp(UnaryOp),
|
||||
|
||||
/// This call is the result of desugaring string interpolation,
|
||||
/// e.g. "\(first) \(last)" is transformed into Str.concat (Str.concat first " ") last.
|
||||
StringInterpolation,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
@ -2,10 +2,10 @@
|
||||
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
|
||||
#![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
|
||||
|
||||
pub mod called_via;
|
||||
pub mod ident;
|
||||
pub mod low_level;
|
||||
pub mod module_err;
|
||||
pub mod operator;
|
||||
pub mod symbol;
|
||||
|
||||
#[macro_use]
|
||||
|
@ -1,3 +1,5 @@
|
||||
use crate::symbol::Symbol;
|
||||
|
||||
/// Low-level operations that get translated directly into e.g. LLVM instructions.
|
||||
/// These are always wrapped when exposed to end users, and can only make it
|
||||
/// into an Expr when added directly by can::builtins
|
||||
@ -19,6 +21,7 @@ pub enum LowLevel {
|
||||
StrFromFloat,
|
||||
StrTrim,
|
||||
StrTrimLeft,
|
||||
StrTrimRight,
|
||||
ListLen,
|
||||
ListGetUnsafe,
|
||||
ListSet,
|
||||
@ -43,12 +46,11 @@ pub enum LowLevel {
|
||||
ListKeepOks,
|
||||
ListKeepErrs,
|
||||
ListSortWith,
|
||||
ListTakeFirst,
|
||||
ListTakeLast,
|
||||
ListDrop,
|
||||
ListSublist,
|
||||
ListDropAt,
|
||||
ListSwap,
|
||||
ListAny,
|
||||
ListAll,
|
||||
ListFindUnsafe,
|
||||
DictSize,
|
||||
DictEmpty,
|
||||
@ -115,106 +117,6 @@ pub enum LowLevel {
|
||||
ExpectTrue,
|
||||
}
|
||||
|
||||
macro_rules! first_order {
|
||||
() => {
|
||||
StrConcat
|
||||
| StrJoinWith
|
||||
| StrIsEmpty
|
||||
| StrStartsWith
|
||||
| StrStartsWithCodePt
|
||||
| StrEndsWith
|
||||
| StrSplit
|
||||
| StrCountGraphemes
|
||||
| StrFromInt
|
||||
| StrFromUtf8
|
||||
| StrFromUtf8Range
|
||||
| StrToUtf8
|
||||
| StrRepeat
|
||||
| StrTrim
|
||||
| StrTrimLeft
|
||||
| StrFromFloat
|
||||
| ListLen
|
||||
| ListGetUnsafe
|
||||
| ListSet
|
||||
| ListTakeFirst
|
||||
| ListTakeLast
|
||||
| ListDrop
|
||||
| ListDropAt
|
||||
| ListSingle
|
||||
| ListRepeat
|
||||
| ListReverse
|
||||
| ListConcat
|
||||
| ListContains
|
||||
| ListAppend
|
||||
| ListPrepend
|
||||
| ListJoin
|
||||
| ListRange
|
||||
| ListSwap
|
||||
| DictSize
|
||||
| DictEmpty
|
||||
| DictInsert
|
||||
| DictRemove
|
||||
| DictContains
|
||||
| DictGetUnsafe
|
||||
| DictKeys
|
||||
| DictValues
|
||||
| DictUnion
|
||||
| DictIntersection
|
||||
| DictDifference
|
||||
| SetFromList
|
||||
| NumAdd
|
||||
| NumAddWrap
|
||||
| NumAddChecked
|
||||
| NumSub
|
||||
| NumSubWrap
|
||||
| NumSubChecked
|
||||
| NumMul
|
||||
| NumMulWrap
|
||||
| NumMulChecked
|
||||
| NumGt
|
||||
| NumGte
|
||||
| NumLt
|
||||
| NumLte
|
||||
| NumCompare
|
||||
| NumDivUnchecked
|
||||
| NumDivCeilUnchecked
|
||||
| NumRemUnchecked
|
||||
| NumIsMultipleOf
|
||||
| NumAbs
|
||||
| NumNeg
|
||||
| NumSin
|
||||
| NumCos
|
||||
| NumSqrtUnchecked
|
||||
| NumLogUnchecked
|
||||
| NumRound
|
||||
| NumToFloat
|
||||
| NumPow
|
||||
| NumCeiling
|
||||
| NumPowInt
|
||||
| NumFloor
|
||||
| NumIsFinite
|
||||
| NumAtan
|
||||
| NumAcos
|
||||
| NumAsin
|
||||
| NumBitwiseAnd
|
||||
| NumBitwiseXor
|
||||
| NumBitwiseOr
|
||||
| NumShiftLeftBy
|
||||
| NumShiftRightBy
|
||||
| NumBytesToU16
|
||||
| NumBytesToU32
|
||||
| NumShiftRightZfBy
|
||||
| NumIntCast
|
||||
| Eq
|
||||
| NotEq
|
||||
| And
|
||||
| Or
|
||||
| Not
|
||||
| Hash
|
||||
| ExpectTrue
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! higher_order {
|
||||
() => {
|
||||
ListMap
|
||||
@ -230,6 +132,7 @@ macro_rules! higher_order {
|
||||
| ListKeepErrs
|
||||
| ListSortWith
|
||||
| ListAny
|
||||
| ListAll
|
||||
| ListFindUnsafe
|
||||
| DictWalk
|
||||
};
|
||||
@ -241,17 +144,13 @@ impl LowLevel {
|
||||
pub fn is_higher_order(&self) -> bool {
|
||||
use LowLevel::*;
|
||||
|
||||
match self {
|
||||
first_order!() => false,
|
||||
higher_order!() => true,
|
||||
}
|
||||
matches!(self, higher_order!())
|
||||
}
|
||||
|
||||
pub fn function_argument_position(&self) -> usize {
|
||||
use LowLevel::*;
|
||||
|
||||
match self {
|
||||
first_order!() => unreachable!(),
|
||||
ListMap => 1,
|
||||
ListMap2 => 2,
|
||||
ListMap3 => 3,
|
||||
@ -265,8 +164,131 @@ impl LowLevel {
|
||||
ListKeepErrs => 1,
|
||||
ListSortWith => 1,
|
||||
ListAny => 1,
|
||||
ListAll => 1,
|
||||
ListFindUnsafe => 1,
|
||||
DictWalk => 2,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Used in dev backends to inline some lowlevel wrapper functions
|
||||
/// For wrappers that contain logic, we return None to prevent inlining
|
||||
/// (Mention each explicitly rather than using `_`, to show they have not been forgotten)
|
||||
pub fn from_inlined_wrapper(symbol: Symbol) -> Option<LowLevel> {
|
||||
use LowLevel::*;
|
||||
|
||||
match symbol {
|
||||
Symbol::STR_CONCAT => Some(StrConcat),
|
||||
Symbol::STR_JOIN_WITH => Some(StrJoinWith),
|
||||
Symbol::STR_IS_EMPTY => Some(StrIsEmpty),
|
||||
Symbol::STR_STARTS_WITH => Some(StrStartsWith),
|
||||
Symbol::STR_STARTS_WITH_CODE_PT => Some(StrStartsWithCodePt),
|
||||
Symbol::STR_ENDS_WITH => Some(StrEndsWith),
|
||||
Symbol::STR_SPLIT => Some(StrSplit),
|
||||
Symbol::STR_COUNT_GRAPHEMES => Some(StrCountGraphemes),
|
||||
Symbol::STR_FROM_INT => Some(StrFromInt),
|
||||
Symbol::STR_FROM_UTF8 => None,
|
||||
Symbol::STR_FROM_UTF8_RANGE => None,
|
||||
Symbol::STR_TO_UTF8 => Some(StrToUtf8),
|
||||
Symbol::STR_REPEAT => Some(StrRepeat),
|
||||
Symbol::STR_FROM_FLOAT => Some(StrFromFloat),
|
||||
Symbol::STR_TRIM => Some(StrTrim),
|
||||
Symbol::STR_TRIM_LEFT => Some(StrTrimLeft),
|
||||
Symbol::STR_TRIM_RIGHT => Some(StrTrimRight),
|
||||
Symbol::LIST_LEN => Some(ListLen),
|
||||
Symbol::LIST_GET => None,
|
||||
Symbol::LIST_SET => None,
|
||||
Symbol::LIST_SINGLE => Some(ListSingle),
|
||||
Symbol::LIST_REPEAT => Some(ListRepeat),
|
||||
Symbol::LIST_REVERSE => Some(ListReverse),
|
||||
Symbol::LIST_CONCAT => Some(ListConcat),
|
||||
Symbol::LIST_CONTAINS => Some(ListContains),
|
||||
Symbol::LIST_APPEND => Some(ListAppend),
|
||||
Symbol::LIST_PREPEND => Some(ListPrepend),
|
||||
Symbol::LIST_JOIN => Some(ListJoin),
|
||||
Symbol::LIST_RANGE => Some(ListRange),
|
||||
Symbol::LIST_MAP => Some(ListMap),
|
||||
Symbol::LIST_MAP2 => Some(ListMap2),
|
||||
Symbol::LIST_MAP3 => Some(ListMap3),
|
||||
Symbol::LIST_MAP4 => Some(ListMap4),
|
||||
Symbol::LIST_MAP_WITH_INDEX => Some(ListMapWithIndex),
|
||||
Symbol::LIST_KEEP_IF => Some(ListKeepIf),
|
||||
Symbol::LIST_WALK => Some(ListWalk),
|
||||
Symbol::LIST_WALK_UNTIL => Some(ListWalkUntil),
|
||||
Symbol::LIST_WALK_BACKWARDS => Some(ListWalkBackwards),
|
||||
Symbol::LIST_KEEP_OKS => Some(ListKeepOks),
|
||||
Symbol::LIST_KEEP_ERRS => Some(ListKeepErrs),
|
||||
Symbol::LIST_SORT_WITH => Some(ListSortWith),
|
||||
Symbol::LIST_SUBLIST => Some(ListSublist),
|
||||
Symbol::LIST_DROP_AT => Some(ListDropAt),
|
||||
Symbol::LIST_SWAP => Some(ListSwap),
|
||||
Symbol::LIST_ANY => Some(ListAny),
|
||||
Symbol::LIST_ALL => Some(ListAll),
|
||||
Symbol::LIST_FIND => None,
|
||||
Symbol::DICT_LEN => Some(DictSize),
|
||||
Symbol::DICT_EMPTY => Some(DictEmpty),
|
||||
Symbol::DICT_INSERT => Some(DictInsert),
|
||||
Symbol::DICT_REMOVE => Some(DictRemove),
|
||||
Symbol::DICT_CONTAINS => Some(DictContains),
|
||||
Symbol::DICT_GET => None,
|
||||
Symbol::DICT_KEYS => Some(DictKeys),
|
||||
Symbol::DICT_VALUES => Some(DictValues),
|
||||
Symbol::DICT_UNION => Some(DictUnion),
|
||||
Symbol::DICT_INTERSECTION => Some(DictIntersection),
|
||||
Symbol::DICT_DIFFERENCE => Some(DictDifference),
|
||||
Symbol::DICT_WALK => Some(DictWalk),
|
||||
Symbol::SET_FROM_LIST => Some(SetFromList),
|
||||
Symbol::NUM_ADD => Some(NumAdd),
|
||||
Symbol::NUM_ADD_WRAP => Some(NumAddWrap),
|
||||
Symbol::NUM_ADD_CHECKED => None,
|
||||
Symbol::NUM_SUB => Some(NumSub),
|
||||
Symbol::NUM_SUB_WRAP => Some(NumSubWrap),
|
||||
Symbol::NUM_SUB_CHECKED => None,
|
||||
Symbol::NUM_MUL => Some(NumMul),
|
||||
Symbol::NUM_MUL_WRAP => Some(NumMulWrap),
|
||||
Symbol::NUM_MUL_CHECKED => None,
|
||||
Symbol::NUM_GT => Some(NumGt),
|
||||
Symbol::NUM_GTE => Some(NumGte),
|
||||
Symbol::NUM_LT => Some(NumLt),
|
||||
Symbol::NUM_LTE => Some(NumLte),
|
||||
Symbol::NUM_COMPARE => Some(NumCompare),
|
||||
Symbol::NUM_DIV_FLOAT => None,
|
||||
Symbol::NUM_DIV_CEIL => None,
|
||||
Symbol::NUM_REM => None,
|
||||
Symbol::NUM_IS_MULTIPLE_OF => Some(NumIsMultipleOf),
|
||||
Symbol::NUM_ABS => Some(NumAbs),
|
||||
Symbol::NUM_NEG => Some(NumNeg),
|
||||
Symbol::NUM_SIN => Some(NumSin),
|
||||
Symbol::NUM_COS => Some(NumCos),
|
||||
Symbol::NUM_SQRT => None,
|
||||
Symbol::NUM_LOG => None,
|
||||
Symbol::NUM_ROUND => Some(NumRound),
|
||||
Symbol::NUM_TO_FLOAT => Some(NumToFloat),
|
||||
Symbol::NUM_POW => Some(NumPow),
|
||||
Symbol::NUM_CEILING => Some(NumCeiling),
|
||||
Symbol::NUM_POW_INT => Some(NumPowInt),
|
||||
Symbol::NUM_FLOOR => Some(NumFloor),
|
||||
// => Some(NumIsFinite),
|
||||
Symbol::NUM_ATAN => Some(NumAtan),
|
||||
Symbol::NUM_ACOS => Some(NumAcos),
|
||||
Symbol::NUM_ASIN => Some(NumAsin),
|
||||
Symbol::NUM_BYTES_TO_U16 => None,
|
||||
Symbol::NUM_BYTES_TO_U32 => None,
|
||||
Symbol::NUM_BITWISE_AND => Some(NumBitwiseAnd),
|
||||
Symbol::NUM_BITWISE_XOR => Some(NumBitwiseXor),
|
||||
Symbol::NUM_BITWISE_OR => Some(NumBitwiseOr),
|
||||
Symbol::NUM_SHIFT_LEFT => Some(NumShiftLeftBy),
|
||||
Symbol::NUM_SHIFT_RIGHT => Some(NumShiftRightBy),
|
||||
Symbol::NUM_SHIFT_RIGHT_ZERO_FILL => Some(NumShiftRightZfBy),
|
||||
Symbol::NUM_INT_CAST => Some(NumIntCast),
|
||||
Symbol::BOOL_EQ => Some(Eq),
|
||||
Symbol::BOOL_NEQ => Some(NotEq),
|
||||
Symbol::BOOL_AND => Some(And),
|
||||
Symbol::BOOL_OR => Some(Or),
|
||||
Symbol::BOOL_NOT => Some(Not),
|
||||
// => Some(Hash),
|
||||
// => Some(ExpectTrue),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -875,8 +875,11 @@ define_builtins! {
|
||||
// used by the dev backend to store the pointer to where to store large return types
|
||||
23 RET_POINTER: "#ret_pointer"
|
||||
|
||||
// used in wasm dev backend to mark values in the VM stack that have no other Symbol
|
||||
24 WASM_ANONYMOUS_STACK_VALUE: "#wasm_anonymous_stack_value"
|
||||
// used in wasm dev backend to mark temporary values in the VM stack
|
||||
24 WASM_TMP: "#wasm_tmp"
|
||||
|
||||
// the _ used in mono when a specialized symbol is deleted
|
||||
25 REMOVED_SPECIALIZATION: "#removed_specialization"
|
||||
}
|
||||
1 NUM: "Num" => {
|
||||
0 NUM_NUM: "Num" imported // the Num.Num type alias
|
||||
@ -989,12 +992,16 @@ define_builtins! {
|
||||
}
|
||||
2 BOOL: "Bool" => {
|
||||
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias
|
||||
1 BOOL_AND: "and"
|
||||
2 BOOL_OR: "or"
|
||||
3 BOOL_NOT: "not"
|
||||
4 BOOL_XOR: "xor"
|
||||
5 BOOL_EQ: "isEq"
|
||||
6 BOOL_NEQ: "isNotEq"
|
||||
1 BOOL_FALSE: "False" imported // Bool.Bool = [ False, True ]
|
||||
// NB: not strictly needed; used for finding global tag names in error suggestions
|
||||
2 BOOL_TRUE: "True" imported // Bool.Bool = [ False, True ]
|
||||
// NB: not strictly needed; used for finding global tag names in error suggestions
|
||||
3 BOOL_AND: "and"
|
||||
4 BOOL_OR: "or"
|
||||
5 BOOL_NOT: "not"
|
||||
6 BOOL_XOR: "xor"
|
||||
7 BOOL_EQ: "isEq"
|
||||
8 BOOL_NEQ: "isNotEq"
|
||||
}
|
||||
3 STR: "Str" => {
|
||||
0 STR_STR: "Str" imported // the Str.Str type alias
|
||||
@ -1019,6 +1026,7 @@ define_builtins! {
|
||||
19 STR_REPEAT: "repeat"
|
||||
20 STR_TRIM: "trim"
|
||||
21 STR_TRIM_LEFT: "trimLeft"
|
||||
22 STR_TRIM_RIGHT: "trimRight"
|
||||
}
|
||||
4 LIST: "List" => {
|
||||
0 LIST_LIST: "List" imported // the List.List type alias
|
||||
@ -1071,14 +1079,24 @@ define_builtins! {
|
||||
47 LIST_FIND: "find"
|
||||
48 LIST_FIND_RESULT: "#find_result" // symbol used in the definition of List.find
|
||||
49 LIST_SUBLIST: "sublist"
|
||||
50 LIST_INTERSPERSE: "intersperse"
|
||||
51 LIST_INTERSPERSE_CLOS: "#intersperseClos"
|
||||
52 LIST_SPLIT: "split"
|
||||
53 LIST_SPLIT_CLOS: "#splitClos"
|
||||
54 LIST_ALL: "all"
|
||||
}
|
||||
5 RESULT: "Result" => {
|
||||
0 RESULT_RESULT: "Result" imported // the Result.Result type alias
|
||||
1 RESULT_MAP: "map"
|
||||
2 RESULT_MAP_ERR: "mapErr"
|
||||
3 RESULT_WITH_DEFAULT: "withDefault"
|
||||
4 RESULT_AFTER: "after"
|
||||
5 RESULT_IS_OK: "isOk"
|
||||
1 RESULT_OK: "Ok" imported // Result.Result a e = [ Ok a, Err e ]
|
||||
// NB: not strictly needed; used for finding global tag names in error suggestions
|
||||
2 RESULT_ERR: "Err" imported // Result.Result a e = [ Ok a, Err e ]
|
||||
// NB: not strictly needed; used for finding global tag names in error suggestions
|
||||
3 RESULT_MAP: "map"
|
||||
4 RESULT_MAP_ERR: "mapErr"
|
||||
5 RESULT_WITH_DEFAULT: "withDefault"
|
||||
6 RESULT_AFTER: "after"
|
||||
7 RESULT_IS_OK: "isOk"
|
||||
8 RESULT_IS_ERR: "isErr"
|
||||
}
|
||||
6 DICT: "Dict" => {
|
||||
0 DICT_DICT: "Dict" imported // the Dict.Dict type alias
|
||||
|
@ -20,3 +20,4 @@ morphic_lib = { path = "../../vendor/morphic_lib" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
hashbrown = { version = "0.11.2", features = [ "bumpalo" ] }
|
||||
ven_graph = { path = "../../vendor/pathfinding" }
|
||||
static_assertions = "1.1.0"
|
||||
|
@ -10,8 +10,8 @@ use roc_module::symbol::Symbol;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use crate::ir::{
|
||||
Call, CallType, Expr, HostExposedLayouts, ListLiteralElement, Literal, ModifyRc, OptLevel,
|
||||
Proc, Stmt,
|
||||
Call, CallType, Expr, HigherOrderLowLevel, HostExposedLayouts, ListLiteralElement, Literal,
|
||||
ModifyRc, OptLevel, Proc, Stmt,
|
||||
};
|
||||
use crate::layout::{Builtin, Layout, ListLayout, RawFunctionLayout, UnionLayout};
|
||||
|
||||
@ -24,7 +24,7 @@ pub const STATIC_LIST_NAME: ConstName = ConstName(b"THIS IS A STATIC LIST");
|
||||
const ENTRY_POINT_NAME: &[u8] = b"mainForHost";
|
||||
|
||||
pub fn func_name_bytes(proc: &Proc) -> [u8; SIZE] {
|
||||
func_name_bytes_help(proc.name, proc.args.iter().map(|x| x.0), proc.ret_layout)
|
||||
func_name_bytes_help(proc.name, proc.args.iter().map(|x| x.0), &proc.ret_layout)
|
||||
}
|
||||
|
||||
const DEBUG: bool = false;
|
||||
@ -53,7 +53,7 @@ impl TagUnionId {
|
||||
pub fn func_name_bytes_help<'a, I>(
|
||||
symbol: Symbol,
|
||||
argument_layouts: I,
|
||||
return_layout: Layout<'a>,
|
||||
return_layout: &Layout<'a>,
|
||||
) -> [u8; SIZE]
|
||||
where
|
||||
I: Iterator<Item = Layout<'a>>,
|
||||
@ -162,13 +162,13 @@ where
|
||||
match layout {
|
||||
RawFunctionLayout::Function(_, _, _) => {
|
||||
let it = top_level.arguments.iter().copied();
|
||||
let bytes = func_name_bytes_help(*symbol, it, top_level.result);
|
||||
let bytes = func_name_bytes_help(*symbol, it, &top_level.result);
|
||||
|
||||
host_exposed_functions.push((bytes, top_level.arguments));
|
||||
}
|
||||
RawFunctionLayout::ZeroArgumentThunk(_) => {
|
||||
let it = std::iter::once(Layout::Struct(&[]));
|
||||
let bytes = func_name_bytes_help(*symbol, it, top_level.result);
|
||||
let bytes = func_name_bytes_help(*symbol, it, &top_level.result);
|
||||
|
||||
host_exposed_functions.push((bytes, top_level.arguments));
|
||||
}
|
||||
@ -196,7 +196,7 @@ where
|
||||
let roc_main_bytes = func_name_bytes_help(
|
||||
entry_point.symbol,
|
||||
entry_point.layout.arguments.iter().copied(),
|
||||
entry_point.layout.result,
|
||||
&entry_point.layout.result,
|
||||
);
|
||||
let roc_main = FuncName(&roc_main_bytes);
|
||||
|
||||
@ -660,7 +660,7 @@ fn call_spec(
|
||||
|
||||
let arg_value_id = build_tuple_value(builder, env, block, call.arguments)?;
|
||||
let it = arg_layouts.iter().copied();
|
||||
let bytes = func_name_bytes_help(*symbol, it, *ret_layout);
|
||||
let bytes = func_name_bytes_help(*symbol, it, ret_layout);
|
||||
let name = FuncName(&bytes);
|
||||
let module = MOD_APP;
|
||||
builder.add_call(block, spec_var, module, name, arg_value_id)
|
||||
@ -688,7 +688,7 @@ fn call_spec(
|
||||
*update_mode,
|
||||
call.arguments,
|
||||
),
|
||||
HigherOrderLowLevel {
|
||||
HigherOrder(HigherOrderLowLevel {
|
||||
specialization_id,
|
||||
closure_env_layout,
|
||||
update_mode,
|
||||
@ -698,7 +698,7 @@ fn call_spec(
|
||||
function_name,
|
||||
function_env,
|
||||
..
|
||||
} => {
|
||||
}) => {
|
||||
use crate::low_level::HigherOrder::*;
|
||||
|
||||
let array = specialization_id.to_bytes();
|
||||
@ -708,7 +708,7 @@ fn call_spec(
|
||||
let update_mode_var = UpdateModeVar(&mode);
|
||||
|
||||
let it = arg_layouts.iter().copied();
|
||||
let bytes = func_name_bytes_help(*function_name, it, *ret_layout);
|
||||
let bytes = func_name_bytes_help(*function_name, it, ret_layout);
|
||||
let name = FuncName(&bytes);
|
||||
let module = MOD_APP;
|
||||
|
||||
@ -1093,6 +1093,25 @@ fn call_spec(
|
||||
|
||||
add_loop(builder, block, state_type, init_state, loop_body)
|
||||
}
|
||||
ListAll { xs } => {
|
||||
let list = env.symbols[xs];
|
||||
|
||||
let loop_body = |builder: &mut FuncDefBuilder, block, _state| {
|
||||
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
|
||||
let element = builder.add_bag_get(block, bag)?;
|
||||
|
||||
let new_state = call_function!(builder, block, [element]);
|
||||
|
||||
Ok(new_state)
|
||||
};
|
||||
|
||||
let state_layout = Layout::Builtin(Builtin::Int1);
|
||||
let state_type = layout_spec(builder, &state_layout)?;
|
||||
|
||||
let init_state = new_num(builder, block)?;
|
||||
|
||||
add_loop(builder, block, state_type, init_state, loop_body)
|
||||
}
|
||||
ListFindUnsafe { xs } => {
|
||||
let list = env.symbols[xs];
|
||||
|
||||
@ -1477,14 +1496,14 @@ fn expr_spec<'a>(
|
||||
}
|
||||
UnionLayout::Recursive(_) => builder.add_make_tuple(block, &[cell_id, data_id])?,
|
||||
UnionLayout::NullableWrapped { nullable_id, .. } => {
|
||||
if *tag_id == *nullable_id as u8 {
|
||||
if *tag_id == *nullable_id as _ {
|
||||
data_id
|
||||
} else {
|
||||
builder.add_make_tuple(block, &[cell_id, data_id])?
|
||||
}
|
||||
}
|
||||
UnionLayout::NullableUnwrapped { nullable_id, .. } => {
|
||||
if *tag_id == *nullable_id as u8 {
|
||||
if *tag_id == *nullable_id as _ {
|
||||
data_id
|
||||
} else {
|
||||
builder.add_make_tuple(block, &[cell_id, data_id])?
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::ir::{Expr, JoinPointId, Param, Proc, ProcLayout, Stmt};
|
||||
use crate::ir::{Expr, HigherOrderLowLevel, JoinPointId, Param, Proc, ProcLayout, Stmt};
|
||||
use crate::layout::Layout;
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
@ -560,7 +560,7 @@ impl<'a> BorrowInfState<'a> {
|
||||
arg_layouts,
|
||||
..
|
||||
} => {
|
||||
let top_level = ProcLayout::new(self.arena, arg_layouts, *ret_layout);
|
||||
let top_level = ProcLayout::new(self.arena, arg_layouts, **ret_layout);
|
||||
|
||||
// get the borrow signature of the applied function
|
||||
let ps = param_map
|
||||
@ -593,14 +593,14 @@ impl<'a> BorrowInfState<'a> {
|
||||
self.own_args_using_bools(arguments, ps);
|
||||
}
|
||||
|
||||
HigherOrderLowLevel {
|
||||
HigherOrder(HigherOrderLowLevel {
|
||||
op,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
function_name,
|
||||
function_env,
|
||||
..
|
||||
} => {
|
||||
}) => {
|
||||
use crate::low_level::HigherOrder::*;
|
||||
|
||||
let closure_layout = ProcLayout {
|
||||
@ -619,6 +619,7 @@ impl<'a> BorrowInfState<'a> {
|
||||
| ListKeepOks { xs }
|
||||
| ListKeepErrs { xs }
|
||||
| ListAny { xs }
|
||||
| ListAll { xs }
|
||||
| ListFindUnsafe { xs } => {
|
||||
// own the list if the function wants to own the element
|
||||
if !function_ps[0].borrow {
|
||||
@ -795,7 +796,7 @@ impl<'a> BorrowInfState<'a> {
|
||||
Stmt::Ret(z),
|
||||
) = (v, b)
|
||||
{
|
||||
let top_level = ProcLayout::new(self.arena, arg_layouts, *ret_layout);
|
||||
let top_level = ProcLayout::new(self.arena, arg_layouts, **ret_layout);
|
||||
|
||||
if self.current_proc == *g && x == *z {
|
||||
// anonymous functions (for which the ps may not be known)
|
||||
@ -941,6 +942,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
||||
StrConcat => arena.alloc_slice_copy(&[owned, borrowed]),
|
||||
StrTrim => arena.alloc_slice_copy(&[owned]),
|
||||
StrTrimLeft => arena.alloc_slice_copy(&[owned]),
|
||||
StrTrimRight => arena.alloc_slice_copy(&[owned]),
|
||||
StrSplit => arena.alloc_slice_copy(&[borrowed, borrowed]),
|
||||
ListSingle => arena.alloc_slice_copy(&[irrelevant]),
|
||||
ListRepeat => arena.alloc_slice_copy(&[irrelevant, borrowed]),
|
||||
@ -952,7 +954,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
||||
ListMap2 => arena.alloc_slice_copy(&[owned, owned, function, closure_data]),
|
||||
ListMap3 => arena.alloc_slice_copy(&[owned, owned, owned, function, closure_data]),
|
||||
ListMap4 => arena.alloc_slice_copy(&[owned, owned, owned, owned, function, closure_data]),
|
||||
ListKeepIf | ListKeepOks | ListKeepErrs | ListAny => {
|
||||
ListKeepIf | ListKeepOks | ListKeepErrs | ListAny | ListAll => {
|
||||
arena.alloc_slice_copy(&[owned, function, closure_data])
|
||||
}
|
||||
ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||
@ -966,9 +968,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
||||
// TODO when we have lists with capacity (if ever)
|
||||
// List.append should own its first argument
|
||||
ListAppend => arena.alloc_slice_copy(&[owned, owned]),
|
||||
ListTakeFirst => arena.alloc_slice_copy(&[owned, irrelevant]),
|
||||
ListTakeLast => arena.alloc_slice_copy(&[owned, irrelevant]),
|
||||
ListDrop => arena.alloc_slice_copy(&[owned, irrelevant]),
|
||||
ListSublist => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
|
||||
ListDropAt => arena.alloc_slice_copy(&[owned, irrelevant]),
|
||||
ListSwap => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
|
||||
|
||||
@ -1046,7 +1046,7 @@ fn call_info_call<'a>(call: &crate::ir::Call<'a>, info: &mut CallInfo<'a>) {
|
||||
}
|
||||
Foreign { .. } => {}
|
||||
LowLevel { .. } => {}
|
||||
HigherOrderLowLevel { .. } => {}
|
||||
HigherOrder(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ use crate::ir::{
|
||||
BranchInfo, DestructType, Env, Expr, FloatPrecision, IntPrecision, JoinPointId, Literal, Param,
|
||||
Pattern, Procs, Stmt,
|
||||
};
|
||||
use crate::layout::{Builtin, Layout, LayoutCache, UnionLayout};
|
||||
use crate::layout::{Builtin, Layout, LayoutCache, TagIdIntType, UnionLayout};
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_module::ident::TagName;
|
||||
use roc_module::low_level::LowLevel;
|
||||
@ -81,7 +81,7 @@ enum GuardedTest<'a> {
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
enum Test<'a> {
|
||||
IsCtor {
|
||||
tag_id: u8,
|
||||
tag_id: TagIdIntType,
|
||||
tag_name: TagName,
|
||||
union: crate::exhaustive::Union,
|
||||
arguments: Vec<(Pattern<'a>, Layout<'a>)>,
|
||||
@ -92,7 +92,7 @@ enum Test<'a> {
|
||||
IsStr(Box<str>),
|
||||
IsBit(bool),
|
||||
IsByte {
|
||||
tag_id: u8,
|
||||
tag_id: TagIdIntType,
|
||||
num_alts: usize,
|
||||
},
|
||||
}
|
||||
@ -419,10 +419,9 @@ fn gather_edges<'a>(
|
||||
|
||||
let check = guarded_tests_are_complete(&relevant_tests);
|
||||
|
||||
// TODO remove clone
|
||||
let all_edges = relevant_tests
|
||||
.into_iter()
|
||||
.map(|t| edges_for(path, branches.clone(), t))
|
||||
.map(|t| edges_for(path, &branches, t))
|
||||
.collect();
|
||||
|
||||
let fallbacks = if check {
|
||||
@ -562,7 +561,7 @@ fn test_at_path<'a>(
|
||||
},
|
||||
BitLiteral { value, .. } => IsBit(*value),
|
||||
EnumLiteral { tag_id, union, .. } => IsByte {
|
||||
tag_id: *tag_id,
|
||||
tag_id: *tag_id as _,
|
||||
num_alts: union.alternatives.len(),
|
||||
},
|
||||
IntLiteral(v, precision) => IsInt(*v, *precision),
|
||||
@ -583,7 +582,7 @@ fn test_at_path<'a>(
|
||||
// understanding: if the test is successful, where could we go?
|
||||
fn edges_for<'a>(
|
||||
path: &[PathInstruction],
|
||||
branches: Vec<Branch<'a>>,
|
||||
branches: &[Branch<'a>],
|
||||
test: GuardedTest<'a>,
|
||||
) -> (GuardedTest<'a>, Vec<Branch<'a>>) {
|
||||
let mut new_branches = Vec::new();
|
||||
@ -864,7 +863,7 @@ fn to_relevant_branch_help<'a>(
|
||||
EnumLiteral { tag_id, .. } => match test {
|
||||
IsByte {
|
||||
tag_id: test_id, ..
|
||||
} if tag_id == *test_id => {
|
||||
} if tag_id == *test_id as _ => {
|
||||
start.extend(end);
|
||||
Some(Branch {
|
||||
goal: branch.goal,
|
||||
@ -1055,9 +1054,19 @@ fn small_defaults(branches: &[Branch], path: &[PathInstruction]) -> usize {
|
||||
}
|
||||
|
||||
fn small_branching_factor(branches: &[Branch], path: &[PathInstruction]) -> usize {
|
||||
let (edges, fallback) = gather_edges(branches.to_vec(), path);
|
||||
// a specialized version of gather_edges that just counts the number of options
|
||||
|
||||
edges.len() + (if fallback.is_empty() { 0 } else { 1 })
|
||||
let relevant_tests = tests_at_path(path, branches);
|
||||
|
||||
let check = guarded_tests_are_complete(&relevant_tests);
|
||||
|
||||
let fallbacks = if check {
|
||||
false
|
||||
} else {
|
||||
branches.iter().any(|b| is_irrelevant_to(path, b))
|
||||
};
|
||||
|
||||
relevant_tests.len() + (if !fallbacks { 0 } else { 1 })
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@ -1171,7 +1180,7 @@ pub fn optimize_when<'a>(
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
struct PathInstruction {
|
||||
index: u64,
|
||||
tag_id: u8,
|
||||
tag_id: TagIdIntType,
|
||||
}
|
||||
|
||||
fn path_to_expr_help<'a>(
|
||||
@ -1198,7 +1207,7 @@ fn path_to_expr_help<'a>(
|
||||
union_layout: *union_layout,
|
||||
};
|
||||
|
||||
let inner_layout = union_layout.layout_at(*tag_id as u8, index as usize);
|
||||
let inner_layout = union_layout.layout_at(*tag_id as TagIdIntType, index as usize);
|
||||
|
||||
symbol = env.unique_symbol();
|
||||
stores.push((symbol, inner_layout, inner_expr));
|
||||
@ -1317,7 +1326,9 @@ fn test_to_equality<'a>(
|
||||
Test::IsByte {
|
||||
tag_id: test_byte, ..
|
||||
} => {
|
||||
let lhs = Expr::Literal(Literal::Byte(test_byte));
|
||||
debug_assert!(test_byte <= (u8::MAX as u16));
|
||||
|
||||
let lhs = Expr::Literal(Literal::Byte(test_byte as u8));
|
||||
let lhs_symbol = env.unique_symbol();
|
||||
stores.push((lhs_symbol, Layout::Builtin(Builtin::Int8), lhs));
|
||||
|
||||
@ -1504,13 +1515,13 @@ enum ConstructorKnown<'a> {
|
||||
Both {
|
||||
scrutinee: Symbol,
|
||||
layout: Layout<'a>,
|
||||
pass: u8,
|
||||
fail: u8,
|
||||
pass: TagIdIntType,
|
||||
fail: TagIdIntType,
|
||||
},
|
||||
OnlyPass {
|
||||
scrutinee: Symbol,
|
||||
layout: Layout<'a>,
|
||||
tag_id: u8,
|
||||
tag_id: TagIdIntType,
|
||||
},
|
||||
Neither,
|
||||
}
|
||||
@ -1530,7 +1541,7 @@ impl<'a> ConstructorKnown<'a> {
|
||||
layout: *cond_layout,
|
||||
scrutinee: cond_symbol,
|
||||
pass: *tag_id,
|
||||
fail: (*tag_id == 0) as u8,
|
||||
fail: (*tag_id == 0) as _,
|
||||
}
|
||||
} else {
|
||||
ConstructorKnown::OnlyPass {
|
||||
@ -1774,7 +1785,7 @@ fn decide_to_branching<'a>(
|
||||
BranchInfo::Constructor {
|
||||
scrutinee: inner_cond_symbol,
|
||||
layout: inner_cond_layout,
|
||||
tag_id: tag_id_sum as u8,
|
||||
tag_id: tag_id_sum as u8 as _,
|
||||
}
|
||||
} else {
|
||||
BranchInfo::None
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::ir::DestructType;
|
||||
use crate::{ir::DestructType, layout::TagIdIntType};
|
||||
use roc_collections::all::{Index, MutMap};
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use roc_region::all::{Located, Region};
|
||||
@ -35,7 +35,7 @@ pub enum RenderAs {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
|
||||
pub struct TagId(pub u8);
|
||||
pub struct TagId(pub TagIdIntType);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Ctor {
|
||||
@ -71,8 +71,12 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern {
|
||||
StrLiteral(v) => Literal(Literal::Str(v.clone())),
|
||||
|
||||
// To make sure these are exhaustive, we have to "fake" a union here
|
||||
BitLiteral { value, union, .. } => Ctor(union.clone(), TagId(*value as u8), vec![]),
|
||||
EnumLiteral { tag_id, union, .. } => Ctor(union.clone(), TagId(*tag_id), vec![]),
|
||||
BitLiteral { value, union, .. } => {
|
||||
Ctor(union.clone(), TagId(*value as TagIdIntType), vec![])
|
||||
}
|
||||
EnumLiteral { tag_id, union, .. } => {
|
||||
Ctor(union.clone(), TagId(*tag_id as TagIdIntType), vec![])
|
||||
}
|
||||
|
||||
Underscore => Anything,
|
||||
Identifier(_) => Anything,
|
||||
|
@ -1,5 +1,7 @@
|
||||
use crate::borrow::{ParamMap, BORROWED, OWNED};
|
||||
use crate::ir::{Expr, JoinPointId, ModifyRc, Param, Proc, ProcLayout, Stmt};
|
||||
use crate::ir::{
|
||||
CallType, Expr, HigherOrderLowLevel, JoinPointId, ModifyRc, Param, Proc, ProcLayout, Stmt,
|
||||
};
|
||||
use crate::layout::Layout;
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
@ -463,7 +465,7 @@ impl<'a> Context<'a> {
|
||||
&*self.arena.alloc(Stmt::Let(z, v, l, b))
|
||||
}
|
||||
|
||||
HigherOrderLowLevel {
|
||||
HigherOrder(HigherOrderLowLevel {
|
||||
op,
|
||||
closure_env_layout,
|
||||
specialization_id,
|
||||
@ -473,7 +475,7 @@ impl<'a> Context<'a> {
|
||||
function_name,
|
||||
function_env,
|
||||
..
|
||||
} => {
|
||||
}) => {
|
||||
// setup
|
||||
use crate::low_level::HigherOrder::*;
|
||||
|
||||
@ -481,7 +483,7 @@ impl<'a> Context<'a> {
|
||||
($borrows:expr) => {
|
||||
Expr::Call(crate::ir::Call {
|
||||
call_type: if let Some(OWNED) = $borrows.map(|p| p.borrow) {
|
||||
HigherOrderLowLevel {
|
||||
let higher_order = HigherOrderLowLevel {
|
||||
op: *op,
|
||||
closure_env_layout: *closure_env_layout,
|
||||
function_owns_closure_data: true,
|
||||
@ -491,7 +493,9 @@ impl<'a> Context<'a> {
|
||||
function_env: *function_env,
|
||||
arg_layouts,
|
||||
ret_layout: *ret_layout,
|
||||
}
|
||||
};
|
||||
|
||||
CallType::HigherOrder(self.arena.alloc(higher_order))
|
||||
} else {
|
||||
call_type
|
||||
},
|
||||
@ -532,6 +536,7 @@ impl<'a> Context<'a> {
|
||||
| ListKeepOks { xs }
|
||||
| ListKeepErrs { xs }
|
||||
| ListAny { xs }
|
||||
| ListAll { xs }
|
||||
| ListFindUnsafe { xs } => {
|
||||
let borrows = [function_ps[0].borrow, FUNCTION, CLOSURE_DATA];
|
||||
|
||||
@ -663,7 +668,7 @@ impl<'a> Context<'a> {
|
||||
arg_layouts,
|
||||
..
|
||||
} => {
|
||||
let top_level = ProcLayout::new(self.arena, arg_layouts, *ret_layout);
|
||||
let top_level = ProcLayout::new(self.arena, arg_layouts, **ret_layout);
|
||||
|
||||
// get the borrow signature
|
||||
let ps = self
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,16 @@ use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
use ven_pretty::{DocAllocator, DocBuilder};
|
||||
|
||||
pub const MAX_ENUM_SIZE: usize = (std::mem::size_of::<u8>() * 8) as usize;
|
||||
// if your changes cause this number to go down, great!
|
||||
// please change it to the lower number.
|
||||
// if it went up, maybe check that the change is really required
|
||||
static_assertions::assert_eq_size!([u8; 3 * 8], Builtin);
|
||||
static_assertions::assert_eq_size!([u8; 4 * 8], Layout);
|
||||
static_assertions::assert_eq_size!([u8; 3 * 8], UnionLayout);
|
||||
static_assertions::assert_eq_size!([u8; 3 * 8], LambdaSet);
|
||||
|
||||
pub type TagIdIntType = u16;
|
||||
pub const MAX_ENUM_SIZE: usize = (std::mem::size_of::<TagIdIntType>() * 8) as usize;
|
||||
const GENERATE_NULLABLE: bool = true;
|
||||
|
||||
/// If a (Num *) gets translated to a Layout, this is the numeric type it defaults to.
|
||||
@ -209,7 +218,7 @@ pub enum UnionLayout<'a> {
|
||||
/// e.g. `FingerTree a : [ Empty, Single a, More (Some a) (FingerTree (Tuple a)) (Some a) ]`
|
||||
/// see also: https://youtu.be/ip92VMpf_-A?t=164
|
||||
NullableWrapped {
|
||||
nullable_id: i64,
|
||||
nullable_id: u16,
|
||||
other_tags: &'a [&'a [Layout<'a>]],
|
||||
},
|
||||
/// A recursive tag union where the non-nullable variant does NOT store the tag id
|
||||
@ -247,7 +256,7 @@ impl<'a> UnionLayout<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layout_at(self, tag_id: u8, index: usize) -> Layout<'a> {
|
||||
pub fn layout_at(self, tag_id: TagIdIntType, index: usize) -> Layout<'a> {
|
||||
let result = match self {
|
||||
UnionLayout::NonRecursive(tag_layouts) => {
|
||||
let field_layouts = tag_layouts[tag_id as usize];
|
||||
@ -265,9 +274,9 @@ impl<'a> UnionLayout<'a> {
|
||||
nullable_id,
|
||||
other_tags,
|
||||
} => {
|
||||
debug_assert_ne!(nullable_id, tag_id as i64);
|
||||
debug_assert_ne!(nullable_id, tag_id);
|
||||
|
||||
let tag_index = if (tag_id as i64) < nullable_id {
|
||||
let tag_index = if tag_id < nullable_id {
|
||||
tag_id
|
||||
} else {
|
||||
tag_id - 1
|
||||
@ -370,12 +379,12 @@ impl<'a> UnionLayout<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tag_is_null(&self, tag_id: u8) -> bool {
|
||||
pub fn tag_is_null(&self, tag_id: TagIdIntType) -> bool {
|
||||
match self {
|
||||
UnionLayout::NonRecursive(_)
|
||||
| UnionLayout::NonNullableUnwrapped(_)
|
||||
| UnionLayout::Recursive(_) => false,
|
||||
UnionLayout::NullableWrapped { nullable_id, .. } => *nullable_id == tag_id as i64,
|
||||
UnionLayout::NullableWrapped { nullable_id, .. } => *nullable_id == tag_id,
|
||||
UnionLayout::NullableUnwrapped { nullable_id, .. } => *nullable_id == (tag_id != 0),
|
||||
}
|
||||
}
|
||||
@ -431,7 +440,7 @@ pub enum ClosureRepresentation<'a> {
|
||||
Union {
|
||||
alphabetic_order_fields: &'a [Layout<'a>],
|
||||
tag_name: TagName,
|
||||
tag_id: u8,
|
||||
tag_id: TagIdIntType,
|
||||
union_layout: UnionLayout<'a>,
|
||||
},
|
||||
/// The closure is represented as a struct. The layouts are sorted
|
||||
@ -489,7 +498,7 @@ impl<'a> LambdaSet<'a> {
|
||||
.unwrap();
|
||||
|
||||
ClosureRepresentation::Union {
|
||||
tag_id: index as u8,
|
||||
tag_id: index as TagIdIntType,
|
||||
alphabetic_order_fields: fields,
|
||||
tag_name: TagName::Closure(function_symbol),
|
||||
union_layout: *union,
|
||||
@ -863,6 +872,16 @@ impl<'a> Layout<'a> {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_passed_by_reference(&self) -> bool {
|
||||
match self {
|
||||
Layout::Union(UnionLayout::NonRecursive(_)) => true,
|
||||
Layout::LambdaSet(lambda_set) => {
|
||||
lambda_set.runtime_representation().is_passed_by_reference()
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stack_size(&self, pointer_size: u32) -> u32 {
|
||||
let width = self.stack_size_without_alignment(pointer_size);
|
||||
let alignment = self.alignment_bytes(pointer_size);
|
||||
@ -941,16 +960,16 @@ impl<'a> Layout<'a> {
|
||||
})
|
||||
.max();
|
||||
|
||||
let tag_id_builtin = variant.tag_id_builtin();
|
||||
match max_alignment {
|
||||
Some(align) => {
|
||||
let tag_id_builtin = variant.tag_id_builtin();
|
||||
|
||||
round_up_to_alignment(
|
||||
align,
|
||||
tag_id_builtin.alignment_bytes(pointer_size),
|
||||
)
|
||||
Some(align) => round_up_to_alignment(
|
||||
align.max(tag_id_builtin.alignment_bytes(pointer_size)),
|
||||
tag_id_builtin.alignment_bytes(pointer_size),
|
||||
),
|
||||
None => {
|
||||
// none of the tags had any payload, but the tag id still contains information
|
||||
tag_id_builtin.alignment_bytes(pointer_size)
|
||||
}
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
Recursive(_)
|
||||
@ -1475,7 +1494,7 @@ fn layout_from_flat_type<'a>(
|
||||
if GENERATE_NULLABLE {
|
||||
for (index, (_name, variables)) in tags_vec.iter().enumerate() {
|
||||
if variables.is_empty() {
|
||||
nullable = Some(index as i64);
|
||||
nullable = Some(index as TagIdIntType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1483,7 +1502,7 @@ fn layout_from_flat_type<'a>(
|
||||
|
||||
env.insert_seen(rec_var);
|
||||
for (index, (_name, variables)) in tags_vec.into_iter().enumerate() {
|
||||
if matches!(nullable, Some(i) if i == index as i64) {
|
||||
if matches!(nullable, Some(i) if i == index as TagIdIntType) {
|
||||
// don't add the nullable case
|
||||
continue;
|
||||
}
|
||||
@ -1633,7 +1652,7 @@ pub enum WrappedVariant<'a> {
|
||||
sorted_tag_layouts: Vec<'a, (TagName, &'a [Layout<'a>])>,
|
||||
},
|
||||
NullableWrapped {
|
||||
nullable_id: i64,
|
||||
nullable_id: TagIdIntType,
|
||||
nullable_name: TagName,
|
||||
sorted_tag_layouts: Vec<'a, (TagName, &'a [Layout<'a>])>,
|
||||
},
|
||||
@ -1650,7 +1669,7 @@ pub enum WrappedVariant<'a> {
|
||||
}
|
||||
|
||||
impl<'a> WrappedVariant<'a> {
|
||||
pub fn tag_name_to_id(&self, tag_name: &TagName) -> (u8, &'a [Layout<'a>]) {
|
||||
pub fn tag_name_to_id(&self, tag_name: &TagName) -> (TagIdIntType, &'a [Layout<'a>]) {
|
||||
use WrappedVariant::*;
|
||||
|
||||
match self {
|
||||
@ -1662,7 +1681,7 @@ impl<'a> WrappedVariant<'a> {
|
||||
.expect("tag name is not in its own type");
|
||||
|
||||
debug_assert!(tag_id < 256);
|
||||
(tag_id as u8, *argument_layouts)
|
||||
(tag_id as TagIdIntType, *argument_layouts)
|
||||
}
|
||||
NullableWrapped {
|
||||
nullable_id,
|
||||
@ -1672,7 +1691,7 @@ impl<'a> WrappedVariant<'a> {
|
||||
// assumption: the nullable_name is not included in sorted_tag_layouts
|
||||
|
||||
if tag_name == nullable_name {
|
||||
(*nullable_id as u8, &[] as &[_])
|
||||
(*nullable_id as TagIdIntType, &[] as &[_])
|
||||
} else {
|
||||
let (mut tag_id, (_, argument_layouts)) = sorted_tag_layouts
|
||||
.iter()
|
||||
@ -1685,7 +1704,7 @@ impl<'a> WrappedVariant<'a> {
|
||||
}
|
||||
|
||||
debug_assert!(tag_id < 256);
|
||||
(tag_id as u8, *argument_layouts)
|
||||
(tag_id as TagIdIntType, *argument_layouts)
|
||||
}
|
||||
}
|
||||
NullableUnwrapped {
|
||||
@ -1695,11 +1714,11 @@ impl<'a> WrappedVariant<'a> {
|
||||
other_fields,
|
||||
} => {
|
||||
if tag_name == nullable_name {
|
||||
(*nullable_id as u8, &[] as &[_])
|
||||
(*nullable_id as TagIdIntType, &[] as &[_])
|
||||
} else {
|
||||
debug_assert_eq!(other_name, tag_name);
|
||||
|
||||
(!*nullable_id as u8, *other_fields)
|
||||
(!*nullable_id as TagIdIntType, *other_fields)
|
||||
}
|
||||
}
|
||||
NonNullableUnwrapped { fields, .. } => (0, fields),
|
||||
@ -1855,14 +1874,14 @@ fn union_sorted_tags_help_new<'a>(
|
||||
let mut answer = Vec::with_capacity_in(tags_vec.len(), arena);
|
||||
let mut has_any_arguments = false;
|
||||
|
||||
let mut nullable: Option<(i64, TagName)> = None;
|
||||
let mut nullable: Option<(TagIdIntType, TagName)> = None;
|
||||
|
||||
// only recursive tag unions can be nullable
|
||||
let is_recursive = opt_rec_var.is_some();
|
||||
if is_recursive && GENERATE_NULLABLE {
|
||||
for (index, (name, variables)) in tags_vec.iter().enumerate() {
|
||||
if variables.is_empty() {
|
||||
nullable = Some((index as i64, (*name).clone()));
|
||||
nullable = Some((index as TagIdIntType, (*name).clone()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -2073,7 +2092,7 @@ pub fn union_sorted_tags_help<'a>(
|
||||
if is_recursive && GENERATE_NULLABLE {
|
||||
for (index, (name, variables)) in tags_vec.iter().enumerate() {
|
||||
if variables.is_empty() {
|
||||
nullable = Some((index as i64, name.clone()));
|
||||
nullable = Some((index as TagIdIntType, name.clone()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -2674,3 +2693,27 @@ impl<'a> std::convert::TryFrom<&Layout<'a>> for ListLayout<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn width_and_alignment_union_empty_struct() {
|
||||
let lambda_set = LambdaSet {
|
||||
set: &[(Symbol::LIST_MAP, &[])],
|
||||
representation: &Layout::Struct(&[]),
|
||||
};
|
||||
|
||||
let a = &[Layout::Struct(&[])] as &[_];
|
||||
let b = &[Layout::LambdaSet(lambda_set)] as &[_];
|
||||
let tt = [a, b];
|
||||
|
||||
let layout = Layout::Union(UnionLayout::NonRecursive(&tt));
|
||||
|
||||
// at the moment, the tag id uses an I64, so
|
||||
let ptr_width = 8;
|
||||
assert_eq!(layout.stack_size(ptr_width), 8);
|
||||
assert_eq!(layout.alignment_bytes(ptr_width), 8);
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,9 @@ pub enum HigherOrder {
|
||||
ListAny {
|
||||
xs: Symbol,
|
||||
},
|
||||
ListAll {
|
||||
xs: Symbol,
|
||||
},
|
||||
ListFindUnsafe {
|
||||
xs: Symbol,
|
||||
},
|
||||
@ -77,6 +80,7 @@ impl HigherOrder {
|
||||
HigherOrder::ListFindUnsafe { .. } => 1,
|
||||
HigherOrder::DictWalk { .. } => 2,
|
||||
HigherOrder::ListAny { .. } => 1,
|
||||
HigherOrder::ListAll { .. } => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -100,9 +104,7 @@ enum FirstOrder {
|
||||
ListLen,
|
||||
ListGetUnsafe,
|
||||
ListSet,
|
||||
ListTakeFirst,
|
||||
ListTakeLast,
|
||||
ListDrop,
|
||||
ListSublist,
|
||||
ListDropAt,
|
||||
ListSingle,
|
||||
ListRepeat,
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::inc_dec::{collect_stmt, occurring_variables_expr, JPLiveVarMap, LiveVarSet};
|
||||
use crate::ir::{BranchInfo, Call, Expr, ListLiteralElement, Proc, Stmt};
|
||||
use crate::layout::{Layout, UnionLayout};
|
||||
use crate::layout::{Layout, TagIdIntType, UnionLayout};
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use roc_collections::all::MutSet;
|
||||
@ -27,17 +27,17 @@ pub fn insert_reset_reuse<'a, 'i>(
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CtorInfo<'a> {
|
||||
id: u8,
|
||||
id: TagIdIntType,
|
||||
layout: UnionLayout<'a>,
|
||||
}
|
||||
|
||||
fn may_reuse(tag_layout: UnionLayout, tag_id: u8, other: &CtorInfo) -> bool {
|
||||
fn may_reuse(tag_layout: UnionLayout, tag_id: TagIdIntType, other: &CtorInfo) -> bool {
|
||||
if tag_layout != other.layout {
|
||||
return false;
|
||||
}
|
||||
|
||||
// we should not get here if the tag we matched on is represented as NULL
|
||||
debug_assert!(!tag_layout.tag_is_null(other.id));
|
||||
debug_assert!(!tag_layout.tag_is_null(other.id as _));
|
||||
|
||||
// furthermore, we can only use the memory if the tag we're creating is non-NULL
|
||||
!tag_layout.tag_is_null(tag_id)
|
||||
|
@ -17,3 +17,5 @@ pretty_assertions = "1.0.0"
|
||||
indoc = "1.0.3"
|
||||
quickcheck = "1.0.3"
|
||||
quickcheck_macros = "1.0.0"
|
||||
diff = "0.1.12"
|
||||
ansi_term = "0.12.1"
|
||||
|
@ -1,32 +1,12 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::header::{AppHeader, ImportsEntry, InterfaceHeader, PlatformHeader, TypedIdent};
|
||||
use crate::ident::Ident;
|
||||
use bumpalo::collections::String;
|
||||
use bumpalo::collections::{String, Vec};
|
||||
use bumpalo::Bump;
|
||||
use roc_module::operator::{BinOp, CalledVia, UnaryOp};
|
||||
use roc_module::called_via::{BinOp, CalledVia, UnaryOp};
|
||||
use roc_region::all::{Loc, Position, Region};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct Collection<'a, T> {
|
||||
pub items: &'a [T],
|
||||
pub final_comments: &'a [CommentOrNewline<'a>],
|
||||
}
|
||||
|
||||
impl<'a, T> Collection<'a, T> {
|
||||
pub fn empty() -> Collection<'a, T> {
|
||||
Collection {
|
||||
items: &[],
|
||||
final_comments: &[],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_items(items: &'a [T]) -> Collection<'a, T> {
|
||||
Collection {
|
||||
items,
|
||||
final_comments: &[],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Module<'a> {
|
||||
Interface { header: InterfaceHeader<'a> },
|
||||
@ -115,21 +95,14 @@ pub enum Expr<'a> {
|
||||
AccessorFunction(&'a str),
|
||||
|
||||
// Collection Literals
|
||||
List {
|
||||
items: &'a [&'a Loc<Expr<'a>>],
|
||||
final_comments: &'a [CommentOrNewline<'a>],
|
||||
},
|
||||
List(Collection<'a, &'a Loc<Expr<'a>>>),
|
||||
|
||||
RecordUpdate {
|
||||
update: &'a Loc<Expr<'a>>,
|
||||
fields: &'a [Loc<AssignedField<'a, Expr<'a>>>],
|
||||
final_comments: &'a &'a [CommentOrNewline<'a>],
|
||||
fields: Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>,
|
||||
},
|
||||
|
||||
Record {
|
||||
fields: &'a [Loc<AssignedField<'a, Expr<'a>>>],
|
||||
final_comments: &'a [CommentOrNewline<'a>],
|
||||
},
|
||||
Record(Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>),
|
||||
|
||||
// Lookups
|
||||
Var {
|
||||
@ -257,18 +230,19 @@ pub enum TypeAnnotation<'a> {
|
||||
/// The row type variable in an open record, e.g. the `r` in `{ name: Str }r`.
|
||||
/// This is None if it's a closed record annotation like `{ name: Str }`.
|
||||
ext: Option<&'a Loc<TypeAnnotation<'a>>>,
|
||||
// final_comments: &'a [CommentOrNewline<'a>],
|
||||
},
|
||||
|
||||
/// A tag union, e.g. `[
|
||||
TagUnion {
|
||||
tags: &'a [Loc<Tag<'a>>],
|
||||
/// The row type variable in an open tag union, e.g. the `a` in `[ Foo, Bar ]a`.
|
||||
/// This is None if it's a closed tag union like `[ Foo, Bar]`.
|
||||
ext: Option<&'a Loc<TypeAnnotation<'a>>>,
|
||||
final_comments: &'a [CommentOrNewline<'a>],
|
||||
tags: Collection<'a, Loc<Tag<'a>>>,
|
||||
},
|
||||
|
||||
/// '_', indicating the compiler should infer the type
|
||||
Inferred,
|
||||
|
||||
/// The `*` type variable, e.g. in (List *)
|
||||
Wildcard,
|
||||
|
||||
@ -357,10 +331,11 @@ pub enum Pattern<'a> {
|
||||
GlobalTag(&'a str),
|
||||
PrivateTag(&'a str),
|
||||
Apply(&'a Loc<Pattern<'a>>, &'a [Loc<Pattern<'a>>]),
|
||||
|
||||
/// This is Loc<Pattern> rather than Loc<str> so we can record comments
|
||||
/// around the destructured names, e.g. { x ### x does stuff ###, y }
|
||||
/// In practice, these patterns will always be Identifier
|
||||
RecordDestructure(&'a [Loc<Pattern<'a>>]),
|
||||
RecordDestructure(Collection<'a, Loc<Pattern<'a>>>),
|
||||
|
||||
/// A required field pattern, e.g. { x: Just 0 } -> ...
|
||||
/// Can only occur inside of a RecordDestructure
|
||||
@ -519,6 +494,126 @@ impl<'a> Pattern<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Collection<'a, T> {
|
||||
pub items: &'a [T],
|
||||
// Use a pointer to a slice (rather than just a slice), in order to avoid bloating
|
||||
// Ast variants. The final_comments field is rarely accessed in the hot path, so
|
||||
// this shouldn't matter much for perf.
|
||||
// Use an Option, so it's possible to initialize without allocating.
|
||||
final_comments: Option<&'a &'a [CommentOrNewline<'a>]>,
|
||||
}
|
||||
|
||||
impl<'a, T> Collection<'a, T> {
|
||||
pub fn empty() -> Collection<'a, T> {
|
||||
Collection {
|
||||
items: &[],
|
||||
final_comments: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_items(items: &'a [T]) -> Collection<'a, T> {
|
||||
Collection {
|
||||
items,
|
||||
final_comments: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_items_and_comments(
|
||||
arena: &'a Bump,
|
||||
items: &'a [T],
|
||||
comments: &'a [CommentOrNewline<'a>],
|
||||
) -> Collection<'a, T> {
|
||||
Collection {
|
||||
items,
|
||||
final_comments: if comments.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(arena.alloc(comments))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace_items<V>(&self, new_items: &'a [V]) -> Collection<'a, V> {
|
||||
Collection {
|
||||
items: new_items,
|
||||
final_comments: self.final_comments,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ptrify_items(&self, arena: &'a Bump) -> Collection<'a, &'a T> {
|
||||
let mut allocated = Vec::with_capacity_in(self.len(), arena);
|
||||
|
||||
for parsed_elem in self.items {
|
||||
allocated.push(parsed_elem);
|
||||
}
|
||||
|
||||
self.replace_items(allocated.into_bump_slice())
|
||||
}
|
||||
|
||||
pub fn map_items<V: 'a>(&self, arena: &'a Bump, f: impl Fn(&'a T) -> V) -> Collection<'a, V> {
|
||||
let mut allocated = Vec::with_capacity_in(self.len(), arena);
|
||||
|
||||
for parsed_elem in self.items {
|
||||
allocated.push(f(parsed_elem));
|
||||
}
|
||||
|
||||
self.replace_items(allocated.into_bump_slice())
|
||||
}
|
||||
|
||||
pub fn map_items_result<V: 'a, E>(
|
||||
&self,
|
||||
arena: &'a Bump,
|
||||
f: impl Fn(&T) -> Result<V, E>,
|
||||
) -> Result<Collection<'a, V>, E> {
|
||||
let mut allocated = Vec::with_capacity_in(self.len(), arena);
|
||||
|
||||
for parsed_elem in self.items {
|
||||
allocated.push(f(parsed_elem)?);
|
||||
}
|
||||
|
||||
Ok(self.replace_items(allocated.into_bump_slice()))
|
||||
}
|
||||
|
||||
pub fn final_comments(&self) -> &'a [CommentOrNewline<'a>] {
|
||||
if let Some(final_comments) = self.final_comments {
|
||||
*final_comments
|
||||
} else {
|
||||
&[]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &'a T> {
|
||||
self.items.iter()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.items.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.items.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: PartialEq> PartialEq for Collection<'a, T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.items == other.items && self.final_comments() == other.final_comments()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Debug> Debug for Collection<'a, T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.final_comments().is_empty() {
|
||||
f.debug_list().entries(self.items.iter()).finish()
|
||||
} else {
|
||||
f.debug_struct("Collection")
|
||||
.field("items", &self.items)
|
||||
.field("final_comments", &self.final_comments())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Spaceable<'a> {
|
||||
fn before(&'a self, _: &'a [CommentOrNewline<'a>]) -> Self;
|
||||
|
@ -1,17 +1,19 @@
|
||||
use crate::ast::{AssignedField, CommentOrNewline, Def, Expr, Pattern, Spaceable, TypeAnnotation};
|
||||
use crate::ast::{
|
||||
AssignedField, Collection, CommentOrNewline, Def, Expr, Pattern, Spaceable, TypeAnnotation,
|
||||
};
|
||||
use crate::blankspace::{space0_after_e, space0_around_ee, space0_before_e, space0_e};
|
||||
use crate::ident::{lowercase_ident, parse_ident, Ident};
|
||||
use crate::keyword;
|
||||
use crate::parser::{
|
||||
self, backtrackable, optional, sep_by1, sep_by1_e, specialize, specialize_ref, then,
|
||||
trailing_sep_by0, word1, word2, EExpr, EInParens, ELambda, EPattern, ERecord, EString, Either,
|
||||
Expect, If, List, Number, ParseResult, Parser, State, Type, When,
|
||||
trailing_sep_by0, word1, word2, EExpect, EExpr, EIf, EInParens, ELambda, EList, ENumber,
|
||||
EPattern, ERecord, EString, EType, EWhen, Either, ParseResult, Parser, State,
|
||||
};
|
||||
use crate::pattern::loc_closure_param;
|
||||
use crate::type_annotation;
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use roc_module::operator::{BinOp, CalledVia, UnaryOp};
|
||||
use roc_module::called_via::{BinOp, CalledVia, UnaryOp};
|
||||
use roc_region::all::{Located, Position, Region};
|
||||
|
||||
use crate::parser::Progress::{self, *};
|
||||
@ -801,7 +803,7 @@ fn parse_defs_end<'a>(
|
||||
Err((NoProgress, _, _)) => {
|
||||
let start = state.get_position();
|
||||
|
||||
match crate::parser::keyword_e(crate::keyword::EXPECT, Expect::Expect)
|
||||
match crate::parser::keyword_e(crate::keyword::EXPECT, EExpect::Expect)
|
||||
.parse(arena, state)
|
||||
{
|
||||
Err((_, _, _)) => {
|
||||
@ -861,8 +863,8 @@ fn parse_defs_end<'a>(
|
||||
space0_before_e(
|
||||
type_annotation::located_help(min_indent + 1),
|
||||
min_indent + 1,
|
||||
Type::TSpace,
|
||||
Type::TIndentStart,
|
||||
EType::TSpace,
|
||||
EType::TIndentStart,
|
||||
),
|
||||
)
|
||||
.parse(arena, state)?;
|
||||
@ -1099,8 +1101,8 @@ fn parse_expr_operator<'a>(
|
||||
space0_before_e(
|
||||
type_annotation::located_help(indented_more),
|
||||
min_indent,
|
||||
Type::TSpace,
|
||||
Type::TIndentStart,
|
||||
EType::TSpace,
|
||||
EType::TIndentStart,
|
||||
),
|
||||
)
|
||||
.parse(arena, state)?;
|
||||
@ -1126,8 +1128,8 @@ fn parse_expr_operator<'a>(
|
||||
space0_before_e(
|
||||
type_annotation::located_help(indented_more),
|
||||
min_indent,
|
||||
Type::TSpace,
|
||||
Type::TIndentStart,
|
||||
EType::TSpace,
|
||||
EType::TIndentStart,
|
||||
),
|
||||
);
|
||||
|
||||
@ -1446,20 +1448,14 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
|
||||
|
||||
Expr::ParensAround(sub_expr) => expr_to_pattern_help(arena, sub_expr),
|
||||
|
||||
Expr::Record {
|
||||
fields,
|
||||
final_comments: _,
|
||||
} => {
|
||||
let mut loc_patterns = Vec::with_capacity_in(fields.len(), arena);
|
||||
|
||||
for loc_assigned_field in fields.iter() {
|
||||
Expr::Record(fields) => {
|
||||
let patterns = fields.map_items_result(arena, |loc_assigned_field| {
|
||||
let region = loc_assigned_field.region;
|
||||
let value = assigned_expr_field_to_pattern_help(arena, &loc_assigned_field.value)?;
|
||||
Ok(Located { region, value })
|
||||
})?;
|
||||
|
||||
loc_patterns.push(Located { region, value });
|
||||
}
|
||||
|
||||
Ok(Pattern::RecordDestructure(loc_patterns.into_bump_slice()))
|
||||
Ok(Pattern::RecordDestructure(patterns))
|
||||
}
|
||||
|
||||
Expr::Float(string) => Ok(Pattern::FloatLiteral(string)),
|
||||
@ -1651,21 +1647,21 @@ mod when {
|
||||
pub fn expr_help<'a>(
|
||||
min_indent: u16,
|
||||
options: ExprParseOptions,
|
||||
) -> impl Parser<'a, Expr<'a>, When<'a>> {
|
||||
) -> impl Parser<'a, Expr<'a>, EWhen<'a>> {
|
||||
then(
|
||||
and!(
|
||||
when_with_indent(),
|
||||
skip_second!(
|
||||
space0_around_ee(
|
||||
specialize_ref(When::Condition, move |arena, state| {
|
||||
specialize_ref(EWhen::Condition, move |arena, state| {
|
||||
parse_loc_expr_with_options(min_indent, options, arena, state)
|
||||
}),
|
||||
min_indent,
|
||||
When::Space,
|
||||
When::IndentCondition,
|
||||
When::IndentIs,
|
||||
EWhen::Space,
|
||||
EWhen::IndentCondition,
|
||||
EWhen::IndentIs,
|
||||
),
|
||||
parser::keyword_e(keyword::IS, When::Is)
|
||||
parser::keyword_e(keyword::IS, EWhen::Is)
|
||||
)
|
||||
),
|
||||
move |arena, state, progress, (case_indent, loc_condition)| {
|
||||
@ -1673,7 +1669,7 @@ mod when {
|
||||
return Err((
|
||||
progress,
|
||||
// TODO maybe pass case_indent here?
|
||||
When::PatternAlignment(5, state.line, state.column),
|
||||
EWhen::PatternAlignment(5, state.line, state.column),
|
||||
state,
|
||||
));
|
||||
}
|
||||
@ -1693,9 +1689,9 @@ mod when {
|
||||
}
|
||||
|
||||
/// Parsing when with indentation.
|
||||
fn when_with_indent<'a>() -> impl Parser<'a, u16, When<'a>> {
|
||||
fn when_with_indent<'a>() -> impl Parser<'a, u16, EWhen<'a>> {
|
||||
move |arena, state: State<'a>| {
|
||||
parser::keyword_e(keyword::WHEN, When::When)
|
||||
parser::keyword_e(keyword::WHEN, EWhen::When)
|
||||
.parse(arena, state)
|
||||
.map(|(progress, (), state)| (progress, state.indent_col, state))
|
||||
}
|
||||
@ -1704,7 +1700,7 @@ mod when {
|
||||
fn branches<'a>(
|
||||
min_indent: u16,
|
||||
options: ExprParseOptions,
|
||||
) -> impl Parser<'a, Vec<'a, &'a WhenBranch<'a>>, When<'a>> {
|
||||
) -> impl Parser<'a, Vec<'a, &'a WhenBranch<'a>>, EWhen<'a>> {
|
||||
move |arena, state: State<'a>| {
|
||||
let when_indent = state.indent_col;
|
||||
|
||||
@ -1741,7 +1737,7 @@ mod when {
|
||||
let indent = pattern_indent_level - indent_col;
|
||||
Err((
|
||||
MadeProgress,
|
||||
When::PatternAlignment(indent, state.line, state.column),
|
||||
EWhen::PatternAlignment(indent, state.line, state.column),
|
||||
state,
|
||||
))
|
||||
}
|
||||
@ -1799,7 +1795,7 @@ mod when {
|
||||
(Col, Vec<'a, Located<Pattern<'a>>>),
|
||||
Option<Located<Expr<'a>>>,
|
||||
),
|
||||
When<'a>,
|
||||
EWhen<'a>,
|
||||
> {
|
||||
let options = ExprParseOptions {
|
||||
check_for_arrow: false,
|
||||
@ -1810,16 +1806,16 @@ mod when {
|
||||
one_of![
|
||||
map!(
|
||||
skip_first!(
|
||||
parser::keyword_e(keyword::IF, When::IfToken),
|
||||
parser::keyword_e(keyword::IF, EWhen::IfToken),
|
||||
// TODO we should require space before the expression but not after
|
||||
space0_around_ee(
|
||||
specialize_ref(When::IfGuard, move |arena, state| {
|
||||
specialize_ref(EWhen::IfGuard, move |arena, state| {
|
||||
parse_loc_expr_with_options(min_indent + 1, options, arena, state)
|
||||
}),
|
||||
min_indent,
|
||||
When::Space,
|
||||
When::IndentIfGuard,
|
||||
When::IndentArrow,
|
||||
EWhen::Space,
|
||||
EWhen::IndentIfGuard,
|
||||
EWhen::IndentArrow,
|
||||
)
|
||||
),
|
||||
Some
|
||||
@ -1831,17 +1827,17 @@ mod when {
|
||||
|
||||
fn branch_single_alternative<'a>(
|
||||
min_indent: u16,
|
||||
) -> impl Parser<'a, Located<Pattern<'a>>, When<'a>> {
|
||||
) -> impl Parser<'a, Located<Pattern<'a>>, EWhen<'a>> {
|
||||
move |arena, state| {
|
||||
let (_, spaces, state) =
|
||||
backtrackable(space0_e(min_indent, When::Space, When::IndentPattern))
|
||||
backtrackable(space0_e(min_indent, EWhen::Space, EWhen::IndentPattern))
|
||||
.parse(arena, state)?;
|
||||
|
||||
let (_, loc_pattern, state) = space0_after_e(
|
||||
specialize(When::Pattern, crate::pattern::loc_pattern_help(min_indent)),
|
||||
specialize(EWhen::Pattern, crate::pattern::loc_pattern_help(min_indent)),
|
||||
min_indent,
|
||||
When::Space,
|
||||
When::IndentPattern,
|
||||
EWhen::Space,
|
||||
EWhen::IndentPattern,
|
||||
)
|
||||
.parse(arena, state)?;
|
||||
|
||||
@ -1862,12 +1858,12 @@ mod when {
|
||||
fn branch_alternatives_help<'a>(
|
||||
min_indent: u16,
|
||||
pattern_indent_level: Option<u16>,
|
||||
) -> impl Parser<'a, (Col, Vec<'a, Located<Pattern<'a>>>), When<'a>> {
|
||||
) -> impl Parser<'a, (Col, Vec<'a, Located<Pattern<'a>>>), EWhen<'a>> {
|
||||
move |arena, state: State<'a>| {
|
||||
let initial = state;
|
||||
|
||||
// put no restrictions on the indent after the spaces; we'll check it manually
|
||||
match space0_e(0, When::Space, When::IndentPattern).parse(arena, state) {
|
||||
match space0_e(0, EWhen::Space, EWhen::IndentPattern).parse(arena, state) {
|
||||
Err((MadeProgress, fail, _)) => Err((NoProgress, fail, initial)),
|
||||
Err((NoProgress, fail, _)) => Err((NoProgress, fail, initial)),
|
||||
Ok((_progress, spaces, state)) => {
|
||||
@ -1876,7 +1872,7 @@ mod when {
|
||||
// this branch is indented too much
|
||||
Err((
|
||||
NoProgress,
|
||||
When::IndentPattern(state.line, state.column),
|
||||
EWhen::IndentPattern(state.line, state.column),
|
||||
initial,
|
||||
))
|
||||
}
|
||||
@ -1884,7 +1880,7 @@ mod when {
|
||||
let indent = wanted - state.column;
|
||||
Err((
|
||||
NoProgress,
|
||||
When::PatternAlignment(indent, state.line, state.column),
|
||||
EWhen::PatternAlignment(indent, state.line, state.column),
|
||||
initial,
|
||||
))
|
||||
}
|
||||
@ -1896,7 +1892,7 @@ mod when {
|
||||
let pattern_indent_col = state.column;
|
||||
|
||||
let parser = sep_by1(
|
||||
word1(b'|', When::Bar),
|
||||
word1(b'|', EWhen::Bar),
|
||||
branch_single_alternative(pattern_indent + 1),
|
||||
);
|
||||
|
||||
@ -1930,16 +1926,16 @@ mod when {
|
||||
}
|
||||
|
||||
/// Parsing the righthandside of a branch in a when conditional.
|
||||
fn branch_result<'a>(indent: u16) -> impl Parser<'a, Located<Expr<'a>>, When<'a>> {
|
||||
fn branch_result<'a>(indent: u16) -> impl Parser<'a, Located<Expr<'a>>, EWhen<'a>> {
|
||||
skip_first!(
|
||||
word2(b'-', b'>', When::Arrow),
|
||||
word2(b'-', b'>', EWhen::Arrow),
|
||||
space0_before_e(
|
||||
specialize_ref(When::Branch, move |arena, state| parse_loc_expr(
|
||||
specialize_ref(EWhen::Branch, move |arena, state| parse_loc_expr(
|
||||
indent, arena, state
|
||||
)),
|
||||
indent,
|
||||
When::Space,
|
||||
When::IndentBranch,
|
||||
EWhen::Space,
|
||||
EWhen::IndentBranch,
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -1947,38 +1943,38 @@ mod when {
|
||||
|
||||
fn if_branch<'a>(
|
||||
min_indent: u16,
|
||||
) -> impl Parser<'a, (Located<Expr<'a>>, Located<Expr<'a>>), If<'a>> {
|
||||
) -> impl Parser<'a, (Located<Expr<'a>>, Located<Expr<'a>>), EIf<'a>> {
|
||||
move |arena, state| {
|
||||
// NOTE: only parse spaces before the expression
|
||||
let (_, cond, state) = space0_around_ee(
|
||||
specialize_ref(If::Condition, move |arena, state| {
|
||||
specialize_ref(EIf::Condition, move |arena, state| {
|
||||
parse_loc_expr(min_indent, arena, state)
|
||||
}),
|
||||
min_indent,
|
||||
If::Space,
|
||||
If::IndentCondition,
|
||||
If::IndentThenToken,
|
||||
EIf::Space,
|
||||
EIf::IndentCondition,
|
||||
EIf::IndentThenToken,
|
||||
)
|
||||
.parse(arena, state)
|
||||
.map_err(|(_, f, s)| (MadeProgress, f, s))?;
|
||||
|
||||
let (_, _, state) = parser::keyword_e(keyword::THEN, If::Then)
|
||||
let (_, _, state) = parser::keyword_e(keyword::THEN, EIf::Then)
|
||||
.parse(arena, state)
|
||||
.map_err(|(_, f, s)| (MadeProgress, f, s))?;
|
||||
|
||||
let (_, then_branch, state) = space0_around_ee(
|
||||
specialize_ref(If::ThenBranch, move |arena, state| {
|
||||
specialize_ref(EIf::ThenBranch, move |arena, state| {
|
||||
parse_loc_expr(min_indent, arena, state)
|
||||
}),
|
||||
min_indent,
|
||||
If::Space,
|
||||
If::IndentThenBranch,
|
||||
If::IndentElseToken,
|
||||
EIf::Space,
|
||||
EIf::IndentThenBranch,
|
||||
EIf::IndentElseToken,
|
||||
)
|
||||
.parse(arena, state)
|
||||
.map_err(|(_, f, s)| (MadeProgress, f, s))?;
|
||||
|
||||
let (_, _, state) = parser::keyword_e(keyword::ELSE, If::Else)
|
||||
let (_, _, state) = parser::keyword_e(keyword::ELSE, EIf::Else)
|
||||
.parse(arena, state)
|
||||
.map_err(|(_, f, s)| (MadeProgress, f, s))?;
|
||||
|
||||
@ -1989,26 +1985,26 @@ fn if_branch<'a>(
|
||||
fn expect_help<'a>(
|
||||
min_indent: u16,
|
||||
options: ExprParseOptions,
|
||||
) -> impl Parser<'a, Expr<'a>, Expect<'a>> {
|
||||
) -> impl Parser<'a, Expr<'a>, EExpect<'a>> {
|
||||
move |arena: &'a Bump, state: State<'a>| {
|
||||
let start = state.get_position();
|
||||
|
||||
let (_, _, state) =
|
||||
parser::keyword_e(keyword::EXPECT, Expect::Expect).parse(arena, state)?;
|
||||
parser::keyword_e(keyword::EXPECT, EExpect::Expect).parse(arena, state)?;
|
||||
|
||||
let (_, condition, state) = space0_before_e(
|
||||
specialize_ref(Expect::Condition, move |arena, state| {
|
||||
specialize_ref(EExpect::Condition, move |arena, state| {
|
||||
parse_loc_expr_with_options(start.col + 1, options, arena, state)
|
||||
}),
|
||||
start.col + 1,
|
||||
Expect::Space,
|
||||
Expect::IndentCondition,
|
||||
EExpect::Space,
|
||||
EExpect::IndentCondition,
|
||||
)
|
||||
.parse(arena, state)
|
||||
.map_err(|(_, f, s)| (MadeProgress, f, s))?;
|
||||
|
||||
let parse_cont = specialize_ref(
|
||||
Expect::Continuation,
|
||||
EExpect::Continuation,
|
||||
space0_before_e(
|
||||
move |a, s| parse_loc_expr(min_indent, a, s),
|
||||
min_indent,
|
||||
@ -2028,9 +2024,9 @@ fn expect_help<'a>(
|
||||
fn if_expr_help<'a>(
|
||||
min_indent: u16,
|
||||
options: ExprParseOptions,
|
||||
) -> impl Parser<'a, Expr<'a>, If<'a>> {
|
||||
) -> impl Parser<'a, Expr<'a>, EIf<'a>> {
|
||||
move |arena: &'a Bump, state| {
|
||||
let (_, _, state) = parser::keyword_e(keyword::IF, If::If).parse(arena, state)?;
|
||||
let (_, _, state) = parser::keyword_e(keyword::IF, EIf::If).parse(arena, state)?;
|
||||
|
||||
let mut branches = Vec::with_capacity_in(1, arena);
|
||||
|
||||
@ -2044,8 +2040,8 @@ fn if_expr_help<'a>(
|
||||
// try to parse another `if`
|
||||
// NOTE this drops spaces between the `else` and the `if`
|
||||
let optional_if = and!(
|
||||
backtrackable(space0_e(min_indent, If::Space, If::IndentIf)),
|
||||
parser::keyword_e(keyword::IF, If::If)
|
||||
backtrackable(space0_e(min_indent, EIf::Space, EIf::IndentIf)),
|
||||
parser::keyword_e(keyword::IF, EIf::If)
|
||||
);
|
||||
|
||||
match optional_if.parse(arena, state) {
|
||||
@ -2058,12 +2054,12 @@ fn if_expr_help<'a>(
|
||||
};
|
||||
|
||||
let (_, else_branch, state) = space0_before_e(
|
||||
specialize_ref(If::ElseBranch, move |arena, state| {
|
||||
specialize_ref(EIf::ElseBranch, move |arena, state| {
|
||||
parse_loc_expr_with_options(min_indent, options, arena, state)
|
||||
}),
|
||||
min_indent,
|
||||
If::Space,
|
||||
If::IndentElseBranch,
|
||||
EIf::Space,
|
||||
EIf::IndentElseBranch,
|
||||
)
|
||||
.parse(arena, state_final_else)
|
||||
.map_err(|(_, f, s)| (MadeProgress, f, s))?;
|
||||
@ -2147,33 +2143,26 @@ fn ident_to_expr<'a>(arena: &'a Bump, src: Ident<'a>) -> Expr<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn list_literal_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, List<'a>> {
|
||||
fn list_literal_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, EList<'a>> {
|
||||
move |arena, state| {
|
||||
let (_, elements, state) = collection_trailing_sep_e!(
|
||||
word1(b'[', List::Open),
|
||||
specialize_ref(List::Expr, move |a, s| parse_loc_expr_no_multi_backpassing(
|
||||
min_indent, a, s
|
||||
)),
|
||||
word1(b',', List::End),
|
||||
word1(b']', List::End),
|
||||
word1(b'[', EList::Open),
|
||||
specialize_ref(
|
||||
EList::Expr,
|
||||
move |a, s| parse_loc_expr_no_multi_backpassing(min_indent, a, s)
|
||||
),
|
||||
word1(b',', EList::End),
|
||||
word1(b']', EList::End),
|
||||
min_indent,
|
||||
List::Open,
|
||||
List::Space,
|
||||
List::IndentEnd,
|
||||
EList::Open,
|
||||
EList::Space,
|
||||
EList::IndentEnd,
|
||||
Expr::SpaceBefore
|
||||
)
|
||||
.parse(arena, state)?;
|
||||
|
||||
let mut allocated = Vec::with_capacity_in(elements.items.len(), arena);
|
||||
|
||||
for parsed_elem in elements.items {
|
||||
allocated.push(parsed_elem);
|
||||
}
|
||||
|
||||
let expr = Expr::List {
|
||||
items: allocated.into_bump_slice(),
|
||||
final_comments: elements.final_comments,
|
||||
};
|
||||
let elements = elements.ptrify_items(arena);
|
||||
let expr = Expr::List(elements);
|
||||
|
||||
Ok((MadeProgress, expr, state))
|
||||
}
|
||||
@ -2311,13 +2300,17 @@ fn record_literal_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, EExpr<'
|
||||
let mut value = match opt_update {
|
||||
Some(update) => Expr::RecordUpdate {
|
||||
update: &*arena.alloc(update),
|
||||
fields: loc_assigned_fields_with_comments.value.0.into_bump_slice(),
|
||||
final_comments: arena.alloc(loc_assigned_fields_with_comments.value.1),
|
||||
},
|
||||
None => Expr::Record {
|
||||
fields: loc_assigned_fields_with_comments.value.0.into_bump_slice(),
|
||||
final_comments: loc_assigned_fields_with_comments.value.1,
|
||||
fields: Collection::with_items_and_comments(
|
||||
arena,
|
||||
loc_assigned_fields_with_comments.value.0.into_bump_slice(),
|
||||
arena.alloc(loc_assigned_fields_with_comments.value.1),
|
||||
),
|
||||
},
|
||||
None => Expr::Record(Collection::with_items_and_comments(
|
||||
arena,
|
||||
loc_assigned_fields_with_comments.value.0.into_bump_slice(),
|
||||
loc_assigned_fields_with_comments.value.1,
|
||||
)),
|
||||
};
|
||||
|
||||
// there can be field access, e.g. `{ x : 4 }.x`
|
||||
@ -2341,7 +2334,7 @@ fn string_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EString<'a>> {
|
||||
map!(crate::string_literal::parse(), Expr::Str)
|
||||
}
|
||||
|
||||
fn positive_number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, Number> {
|
||||
fn positive_number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, ENumber> {
|
||||
map!(
|
||||
crate::number_literal::positive_number_literal(),
|
||||
|literal| {
|
||||
@ -2364,7 +2357,7 @@ fn positive_number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, Number> {
|
||||
)
|
||||
}
|
||||
|
||||
fn number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, Number> {
|
||||
fn number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, ENumber> {
|
||||
map!(crate::number_literal::number_literal(), |literal| {
|
||||
use crate::number_literal::NumLiteral::*;
|
||||
|
||||
|
@ -38,7 +38,7 @@ pub enum PackageOrPath<'a> {
|
||||
Path(StrLiteral<'a>),
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct ModuleName<'a>(&'a str);
|
||||
|
||||
impl<'a> From<ModuleName<'a>> for &'a str {
|
||||
@ -60,8 +60,8 @@ impl<'a> ModuleName<'a> {
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct InterfaceHeader<'a> {
|
||||
pub name: Loc<ModuleName<'a>>,
|
||||
pub exposes: Vec<'a, Loc<ExposesEntry<'a, &'a str>>>,
|
||||
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
|
||||
pub exposes: Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
|
||||
pub imports: Collection<'a, Loc<ImportsEntry<'a>>>,
|
||||
|
||||
// Potential comments and newlines - these will typically all be empty.
|
||||
pub before_header: &'a [CommentOrNewline<'a>],
|
||||
@ -82,8 +82,8 @@ pub enum To<'a> {
|
||||
pub struct AppHeader<'a> {
|
||||
pub name: Loc<StrLiteral<'a>>,
|
||||
pub packages: Collection<'a, Loc<PackageEntry<'a>>>,
|
||||
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
|
||||
pub provides: Vec<'a, Loc<ExposesEntry<'a, &'a str>>>,
|
||||
pub imports: Collection<'a, Loc<ImportsEntry<'a>>>,
|
||||
pub provides: Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
|
||||
pub to: Loc<To<'a>>,
|
||||
|
||||
// Potential comments and newlines - these will typically all be empty.
|
||||
@ -117,7 +117,7 @@ pub struct PackageHeader<'a> {
|
||||
pub after_imports: &'a [CommentOrNewline<'a>],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum PlatformRigid<'a> {
|
||||
Entry { rigid: &'a str, alias: &'a str },
|
||||
|
||||
@ -137,7 +137,7 @@ impl<'a> Spaceable<'a> for PlatformRigid<'a> {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct PlatformRequires<'a> {
|
||||
pub rigids: Vec<'a, Loc<PlatformRigid<'a>>>,
|
||||
pub rigids: Collection<'a, Loc<PlatformRigid<'a>>>,
|
||||
pub signature: Loc<TypedIdent<'a>>,
|
||||
}
|
||||
|
||||
@ -145,10 +145,10 @@ pub struct PlatformRequires<'a> {
|
||||
pub struct PlatformHeader<'a> {
|
||||
pub name: Loc<PackageName<'a>>,
|
||||
pub requires: PlatformRequires<'a>,
|
||||
pub exposes: Vec<'a, Loc<ExposesEntry<'a, ModuleName<'a>>>>,
|
||||
pub exposes: Collection<'a, Loc<ExposesEntry<'a, ModuleName<'a>>>>,
|
||||
pub packages: Collection<'a, Loc<PackageEntry<'a>>>,
|
||||
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
|
||||
pub provides: Vec<'a, Loc<ExposesEntry<'a, &'a str>>>,
|
||||
pub imports: Collection<'a, Loc<ImportsEntry<'a>>>,
|
||||
pub provides: Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
|
||||
pub effects: Effects<'a>,
|
||||
|
||||
// Potential comments and newlines - these will typically all be empty.
|
||||
@ -177,7 +177,7 @@ pub struct Effects<'a> {
|
||||
pub entries: &'a [Loc<TypedIdent<'a>>],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum ExposesEntry<'a, T> {
|
||||
/// e.g. `Task`
|
||||
Exposed(T),
|
||||
@ -196,16 +196,19 @@ impl<'a, T> Spaceable<'a> for ExposesEntry<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum ImportsEntry<'a> {
|
||||
/// e.g. `Task` or `Task.{ Task, after }`
|
||||
Module(ModuleName<'a>, Vec<'a, Loc<ExposesEntry<'a, &'a str>>>),
|
||||
Module(
|
||||
ModuleName<'a>,
|
||||
Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
|
||||
),
|
||||
|
||||
/// e.g. `base.Task` or `base.Task.{ after }` or `base.{ Task.{ Task, after } }`
|
||||
Package(
|
||||
&'a str,
|
||||
ModuleName<'a>,
|
||||
Vec<'a, Loc<ExposesEntry<'a, &'a str>>>,
|
||||
Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
|
||||
),
|
||||
|
||||
// Spaces
|
||||
@ -224,7 +227,7 @@ impl<'a> ExposesEntry<'a, &'a str> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum TypedIdent<'a> {
|
||||
/// e.g.
|
||||
///
|
||||
|
@ -220,11 +220,11 @@ fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> {
|
||||
#[allow(clippy::type_complexity)]
|
||||
let opt_imports: Option<(
|
||||
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
|
||||
Vec<'a, Located<ImportsEntry<'a>>>,
|
||||
Collection<'a, Located<ImportsEntry<'a>>>,
|
||||
)> = opt_imports;
|
||||
|
||||
let ((before_imports, after_imports), imports) =
|
||||
opt_imports.unwrap_or_else(|| ((&[] as _, &[] as _), Vec::new_in(arena)));
|
||||
opt_imports.unwrap_or_else(|| ((&[] as _, &[] as _), Collection::empty()));
|
||||
let provides: ProvidesTo<'a> = provides; // rustc must be told the type here
|
||||
|
||||
let header = AppHeader {
|
||||
@ -303,7 +303,7 @@ fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> {
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ProvidesTo<'a> {
|
||||
entries: Vec<'a, Located<ExposesEntry<'a, &'a str>>>,
|
||||
entries: Collection<'a, Located<ExposesEntry<'a, &'a str>>>,
|
||||
to: Located<To<'a>>,
|
||||
|
||||
before_provides_keyword: &'a [CommentOrNewline<'a>],
|
||||
@ -362,7 +362,7 @@ fn provides_without_to<'a>() -> impl Parser<
|
||||
'a,
|
||||
(
|
||||
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
|
||||
Vec<'a, Located<ExposesEntry<'a, &'a str>>>,
|
||||
Collection<'a, Located<ExposesEntry<'a, &'a str>>>,
|
||||
),
|
||||
EProvides<'a>,
|
||||
> {
|
||||
@ -376,14 +376,16 @@ fn provides_without_to<'a>() -> impl Parser<
|
||||
EProvides::IndentProvides,
|
||||
EProvides::IndentListStart
|
||||
),
|
||||
collection_e!(
|
||||
collection_trailing_sep_e!(
|
||||
word1(b'[', EProvides::ListStart),
|
||||
exposes_entry(EProvides::Identifier),
|
||||
word1(b',', EProvides::ListEnd),
|
||||
word1(b']', EProvides::ListEnd),
|
||||
min_indent,
|
||||
EProvides::Open,
|
||||
EProvides::Space,
|
||||
EProvides::IndentListEnd
|
||||
EProvides::IndentListEnd,
|
||||
ExposesEntry::SpaceBefore
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -442,15 +444,17 @@ fn platform_requires<'a>() -> impl Parser<'a, PlatformRequires<'a>, ERequires<'a
|
||||
#[inline(always)]
|
||||
fn requires_rigids<'a>(
|
||||
min_indent: u16,
|
||||
) -> impl Parser<'a, Vec<'a, Located<PlatformRigid<'a>>>, ERequires<'a>> {
|
||||
collection_e!(
|
||||
) -> impl Parser<'a, Collection<'a, Located<PlatformRigid<'a>>>, ERequires<'a>> {
|
||||
collection_trailing_sep_e!(
|
||||
word1(b'{', ERequires::ListStart),
|
||||
specialize(|_, r, c| ERequires::Rigid(r, c), loc!(requires_rigid())),
|
||||
word1(b',', ERequires::ListEnd),
|
||||
word1(b'}', ERequires::ListEnd),
|
||||
min_indent,
|
||||
ERequires::Open,
|
||||
ERequires::Space,
|
||||
ERequires::IndentListEnd
|
||||
ERequires::IndentListEnd,
|
||||
PlatformRigid::SpaceBefore
|
||||
)
|
||||
}
|
||||
|
||||
@ -487,7 +491,7 @@ fn exposes_values<'a>() -> impl Parser<
|
||||
'a,
|
||||
(
|
||||
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
|
||||
Vec<'a, Located<ExposesEntry<'a, &'a str>>>,
|
||||
Collection<'a, Located<ExposesEntry<'a, &'a str>>>,
|
||||
),
|
||||
EExposes,
|
||||
> {
|
||||
@ -502,14 +506,16 @@ fn exposes_values<'a>() -> impl Parser<
|
||||
EExposes::IndentExposes,
|
||||
EExposes::IndentListStart
|
||||
),
|
||||
collection_e!(
|
||||
collection_trailing_sep_e!(
|
||||
word1(b'[', EExposes::ListStart),
|
||||
exposes_entry(EExposes::Identifier),
|
||||
word1(b',', EExposes::ListEnd),
|
||||
word1(b']', EExposes::ListEnd),
|
||||
min_indent,
|
||||
EExposes::Open,
|
||||
EExposes::Space,
|
||||
EExposes::IndentListEnd
|
||||
EExposes::IndentListEnd,
|
||||
ExposesEntry::SpaceBefore
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -539,7 +545,7 @@ fn exposes_modules<'a>() -> impl Parser<
|
||||
'a,
|
||||
(
|
||||
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
|
||||
Vec<'a, Located<ExposesEntry<'a, ModuleName<'a>>>>,
|
||||
Collection<'a, Located<ExposesEntry<'a, ModuleName<'a>>>>,
|
||||
),
|
||||
EExposes,
|
||||
> {
|
||||
@ -554,14 +560,16 @@ fn exposes_modules<'a>() -> impl Parser<
|
||||
EExposes::IndentExposes,
|
||||
EExposes::IndentListStart
|
||||
),
|
||||
collection_e!(
|
||||
collection_trailing_sep_e!(
|
||||
word1(b'[', EExposes::ListStart),
|
||||
exposes_module(EExposes::Identifier),
|
||||
word1(b',', EExposes::ListEnd),
|
||||
word1(b']', EExposes::ListEnd),
|
||||
min_indent,
|
||||
EExposes::Open,
|
||||
EExposes::Space,
|
||||
EExposes::IndentListEnd
|
||||
EExposes::IndentListEnd,
|
||||
ExposesEntry::SpaceBefore
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -631,7 +639,7 @@ fn imports<'a>() -> impl Parser<
|
||||
'a,
|
||||
(
|
||||
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
|
||||
Vec<'a, Located<ImportsEntry<'a>>>,
|
||||
Collection<'a, Located<ImportsEntry<'a>>>,
|
||||
),
|
||||
EImports,
|
||||
> {
|
||||
@ -646,14 +654,16 @@ fn imports<'a>() -> impl Parser<
|
||||
EImports::IndentImports,
|
||||
EImports::IndentListStart
|
||||
),
|
||||
collection_e!(
|
||||
collection_trailing_sep_e!(
|
||||
word1(b'[', EImports::ListStart),
|
||||
loc!(imports_entry()),
|
||||
word1(b',', EImports::ListEnd),
|
||||
word1(b']', EImports::ListEnd),
|
||||
min_indent,
|
||||
EImports::Open,
|
||||
EImports::Space,
|
||||
EImports::IndentListEnd
|
||||
EImports::IndentListEnd,
|
||||
ImportsEntry::SpaceBefore
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -687,14 +697,16 @@ fn effects<'a>() -> impl Parser<'a, Effects<'a>, EEffects<'a>> {
|
||||
space0_e(min_indent, EEffects::Space, EEffects::IndentListStart)
|
||||
)
|
||||
.parse(arena, state)?;
|
||||
let (_, entries, state) = collection_e!(
|
||||
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
|
||||
EEffects::IndentListEnd,
|
||||
TypedIdent::SpaceBefore
|
||||
)
|
||||
.parse(arena, state)?;
|
||||
|
||||
@ -706,7 +718,7 @@ fn effects<'a>() -> impl Parser<'a, Effects<'a>, EEffects<'a>> {
|
||||
spaces_after_type_name,
|
||||
effect_shortname: type_shortname,
|
||||
effect_type_name: type_name,
|
||||
entries: entries.into_bump_slice(),
|
||||
entries: entries.items,
|
||||
},
|
||||
state,
|
||||
))
|
||||
@ -768,7 +780,7 @@ fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>, EImports> {
|
||||
|
||||
type Temp<'a> = (
|
||||
(Option<&'a str>, ModuleName<'a>),
|
||||
Option<Vec<'a, Located<ExposesEntry<'a, &'a str>>>>,
|
||||
Option<Collection<'a, Located<ExposesEntry<'a, &'a str>>>>,
|
||||
);
|
||||
|
||||
map_with_arena!(
|
||||
@ -785,19 +797,21 @@ fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>, EImports> {
|
||||
// e.g. `.{ Task, after}`
|
||||
maybe!(skip_first!(
|
||||
word1(b'.', EImports::ExposingDot),
|
||||
collection_e!(
|
||||
collection_trailing_sep_e!(
|
||||
word1(b'{', EImports::SetStart),
|
||||
exposes_entry(EImports::Identifier),
|
||||
word1(b',', EImports::SetEnd),
|
||||
word1(b'}', EImports::SetEnd),
|
||||
min_indent,
|
||||
EImports::Open,
|
||||
EImports::Space,
|
||||
EImports::IndentSetEnd
|
||||
EImports::IndentSetEnd,
|
||||
ExposesEntry::SpaceBefore
|
||||
)
|
||||
))
|
||||
),
|
||||
|arena, ((opt_shortname, module_name), opt_values): Temp<'a>| {
|
||||
let exposed_values = opt_values.unwrap_or_else(|| Vec::new_in(arena));
|
||||
|_arena, ((opt_shortname, module_name), opt_values): Temp<'a>| {
|
||||
let exposed_values = opt_values.unwrap_or_else(Collection::empty);
|
||||
|
||||
match opt_shortname {
|
||||
Some(shortname) => ImportsEntry::Package(shortname, module_name, exposed_values),
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::ast::Base;
|
||||
use crate::parser::{Number, ParseResult, Parser, Progress, State};
|
||||
use crate::parser::{ENumber, ParseResult, Parser, Progress, State};
|
||||
|
||||
pub enum NumLiteral<'a> {
|
||||
Float(&'a str),
|
||||
@ -11,7 +11,7 @@ pub enum NumLiteral<'a> {
|
||||
},
|
||||
}
|
||||
|
||||
pub fn positive_number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, Number> {
|
||||
pub fn positive_number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, ENumber> {
|
||||
move |_arena, state: State<'a>| {
|
||||
match state.bytes.get(0) {
|
||||
Some(first_byte) if (*first_byte as char).is_ascii_digit() => {
|
||||
@ -19,13 +19,13 @@ pub fn positive_number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, Number>
|
||||
}
|
||||
_ => {
|
||||
// this is not a number at all
|
||||
Err((Progress::NoProgress, Number::End, state))
|
||||
Err((Progress::NoProgress, ENumber::End, state))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, Number> {
|
||||
pub fn number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, ENumber> {
|
||||
move |_arena, state: State<'a>| {
|
||||
match state.bytes.get(0) {
|
||||
Some(first_byte) if *first_byte == b'-' => {
|
||||
@ -37,7 +37,7 @@ pub fn number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, Number> {
|
||||
}
|
||||
_ => {
|
||||
// this is not a number at all
|
||||
Err((Progress::NoProgress, Number::End, state))
|
||||
Err((Progress::NoProgress, ENumber::End, state))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -47,7 +47,7 @@ fn parse_number_base<'a>(
|
||||
is_negated: bool,
|
||||
bytes: &'a [u8],
|
||||
state: State<'a>,
|
||||
) -> ParseResult<'a, NumLiteral<'a>, Number> {
|
||||
) -> ParseResult<'a, NumLiteral<'a>, ENumber> {
|
||||
match bytes.get(0..2) {
|
||||
Some(b"0b") => chomp_number_base(Base::Binary, is_negated, &bytes[2..], state),
|
||||
Some(b"0o") => chomp_number_base(Base::Octal, is_negated, &bytes[2..], state),
|
||||
@ -61,13 +61,13 @@ fn chomp_number_base<'a>(
|
||||
is_negative: bool,
|
||||
bytes: &'a [u8],
|
||||
state: State<'a>,
|
||||
) -> ParseResult<'a, NumLiteral<'a>, Number> {
|
||||
) -> ParseResult<'a, NumLiteral<'a>, ENumber> {
|
||||
let (_is_float, chomped) = chomp_number(bytes);
|
||||
|
||||
let string = unsafe { std::str::from_utf8_unchecked(&bytes[..chomped]) };
|
||||
|
||||
let new = state.advance_without_indenting_ee(chomped + 2 + is_negative as usize, |_, _| {
|
||||
Number::LineTooLong
|
||||
ENumber::LineTooLong
|
||||
})?;
|
||||
|
||||
Ok((
|
||||
@ -85,24 +85,25 @@ fn chomp_number_dec<'a>(
|
||||
is_negative: bool,
|
||||
bytes: &'a [u8],
|
||||
state: State<'a>,
|
||||
) -> ParseResult<'a, NumLiteral<'a>, Number> {
|
||||
) -> ParseResult<'a, NumLiteral<'a>, ENumber> {
|
||||
let (is_float, chomped) = chomp_number(bytes);
|
||||
|
||||
if is_negative && chomped == 0 {
|
||||
// we're probably actually looking at unary negation here
|
||||
return Err((Progress::NoProgress, Number::End, state));
|
||||
return Err((Progress::NoProgress, ENumber::End, state));
|
||||
}
|
||||
|
||||
if !bytes.get(0).copied().unwrap_or_default().is_ascii_digit() {
|
||||
// we're probably actually looking at unary negation here
|
||||
return Err((Progress::NoProgress, Number::End, state));
|
||||
return Err((Progress::NoProgress, ENumber::End, state));
|
||||
}
|
||||
|
||||
let string =
|
||||
unsafe { std::str::from_utf8_unchecked(&state.bytes[0..chomped + is_negative as usize]) };
|
||||
|
||||
let new = state
|
||||
.advance_without_indenting_ee(chomped + is_negative as usize, |_, _| Number::LineTooLong)?;
|
||||
let new = state.advance_without_indenting_ee(chomped + is_negative as usize, |_, _| {
|
||||
ENumber::LineTooLong
|
||||
})?;
|
||||
|
||||
Ok((
|
||||
Progress::MadeProgress,
|
||||
|
@ -186,7 +186,7 @@ pub enum SyntaxError<'a> {
|
||||
ArgumentsBeforeEquals(Region),
|
||||
NotYetImplemented(String),
|
||||
Todo,
|
||||
Type(Type<'a>),
|
||||
Type(EType<'a>),
|
||||
Pattern(EPattern<'a>),
|
||||
Expr(EExpr<'a>),
|
||||
Header(EHeader<'a>),
|
||||
@ -214,6 +214,7 @@ pub enum EHeader<'a> {
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EProvides<'a> {
|
||||
Provides(Row, Col),
|
||||
Open(Row, Col),
|
||||
To(Row, Col),
|
||||
IndentProvides(Row, Col),
|
||||
IndentTo(Row, Col),
|
||||
@ -230,6 +231,7 @@ pub enum EProvides<'a> {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum EExposes {
|
||||
Exposes(Row, Col),
|
||||
Open(Row, Col),
|
||||
IndentExposes(Row, Col),
|
||||
IndentListStart(Row, Col),
|
||||
IndentListEnd(Row, Col),
|
||||
@ -242,6 +244,7 @@ pub enum EExposes {
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ERequires<'a> {
|
||||
Requires(Row, Col),
|
||||
Open(Row, Col),
|
||||
IndentRequires(Row, Col),
|
||||
IndentListStart(Row, Col),
|
||||
IndentListEnd(Row, Col),
|
||||
@ -258,7 +261,7 @@ pub enum ETypedIdent<'a> {
|
||||
HasType(Row, Col),
|
||||
IndentHasType(Row, Col),
|
||||
Name(Row, Col),
|
||||
Type(Type<'a>, Row, Col),
|
||||
Type(EType<'a>, Row, Col),
|
||||
IndentType(Row, Col),
|
||||
Identifier(Row, Col),
|
||||
}
|
||||
@ -302,6 +305,7 @@ pub enum EPackageEntry<'a> {
|
||||
pub enum EEffects<'a> {
|
||||
Space(BadInputError, Row, Col),
|
||||
Effects(Row, Col),
|
||||
Open(Row, Col),
|
||||
IndentEffects(Row, Col),
|
||||
ListStart(Row, Col),
|
||||
ListEnd(Row, Col),
|
||||
@ -315,6 +319,7 @@ pub enum EEffects<'a> {
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum EImports {
|
||||
Open(Row, Col),
|
||||
Imports(Row, Col),
|
||||
IndentImports(Row, Col),
|
||||
IndentListStart(Row, Col),
|
||||
@ -394,7 +399,7 @@ pub enum EExpr<'a> {
|
||||
|
||||
DefMissingFinalExpr(Row, Col),
|
||||
DefMissingFinalExpr2(&'a EExpr<'a>, Row, Col),
|
||||
Type(Type<'a>, Row, Col),
|
||||
Type(EType<'a>, Row, Col),
|
||||
Pattern(&'a EPattern<'a>, Row, Col),
|
||||
IndentDefBody(Row, Col),
|
||||
IndentEquals(Row, Col),
|
||||
@ -409,10 +414,10 @@ pub enum EExpr<'a> {
|
||||
BackpassComma(Row, Col),
|
||||
BackpassArrow(Row, Col),
|
||||
|
||||
When(When<'a>, Row, Col),
|
||||
If(If<'a>, Row, Col),
|
||||
When(EWhen<'a>, Row, Col),
|
||||
If(EIf<'a>, Row, Col),
|
||||
|
||||
Expect(Expect<'a>, Row, Col),
|
||||
Expect(EExpect<'a>, Row, Col),
|
||||
|
||||
Lambda(ELambda<'a>, Row, Col),
|
||||
Underscore(Row, Col),
|
||||
@ -420,15 +425,15 @@ pub enum EExpr<'a> {
|
||||
InParens(EInParens<'a>, Row, Col),
|
||||
Record(ERecord<'a>, Row, Col),
|
||||
Str(EString<'a>, Row, Col),
|
||||
Number(Number, Row, Col),
|
||||
List(List<'a>, Row, Col),
|
||||
Number(ENumber, Row, Col),
|
||||
List(EList<'a>, Row, Col),
|
||||
|
||||
IndentStart(Row, Col),
|
||||
IndentEnd(Row, Col),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Number {
|
||||
pub enum ENumber {
|
||||
End,
|
||||
LineTooLong,
|
||||
}
|
||||
@ -502,7 +507,7 @@ pub enum ELambda<'a> {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum List<'a> {
|
||||
pub enum EList<'a> {
|
||||
Open(Row, Col),
|
||||
End(Row, Col),
|
||||
Space(BadInputError, Row, Col),
|
||||
@ -514,7 +519,7 @@ pub enum List<'a> {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum When<'a> {
|
||||
pub enum EWhen<'a> {
|
||||
Space(BadInputError, Row, Col),
|
||||
When(Row, Col),
|
||||
Is(Row, Col),
|
||||
@ -538,7 +543,7 @@ pub enum When<'a> {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum If<'a> {
|
||||
pub enum EIf<'a> {
|
||||
Space(BadInputError, Row, Col),
|
||||
If(Row, Col),
|
||||
Then(Row, Col),
|
||||
@ -557,7 +562,7 @@ pub enum If<'a> {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Expect<'a> {
|
||||
pub enum EExpect<'a> {
|
||||
Space(BadInputError, Row, Col),
|
||||
Expect(Row, Col),
|
||||
Condition(&'a EExpr<'a>, Row, Col),
|
||||
@ -575,7 +580,7 @@ pub enum EPattern<'a> {
|
||||
Space(BadInputError, Row, Col),
|
||||
|
||||
PInParens(PInParens<'a>, Row, Col),
|
||||
NumLiteral(Number, Row, Col),
|
||||
NumLiteral(ENumber, Row, Col),
|
||||
|
||||
IndentStart(Row, Col),
|
||||
IndentEnd(Row, Col),
|
||||
@ -614,13 +619,14 @@ pub enum PInParens<'a> {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Type<'a> {
|
||||
TRecord(TRecord<'a>, Row, Col),
|
||||
TTagUnion(TTagUnion<'a>, Row, Col),
|
||||
TInParens(TInParens<'a>, Row, Col),
|
||||
TApply(TApply, Row, Col),
|
||||
pub enum EType<'a> {
|
||||
TRecord(ETypeRecord<'a>, Row, Col),
|
||||
TTagUnion(ETypeTagUnion<'a>, Row, Col),
|
||||
TInParens(ETypeInParens<'a>, Row, Col),
|
||||
TApply(ETypeApply, Row, Col),
|
||||
TBadTypeVariable(Row, Col),
|
||||
TWildcard(Row, Col),
|
||||
TInferred(Row, Col),
|
||||
///
|
||||
TStart(Row, Col),
|
||||
TEnd(Row, Col),
|
||||
@ -633,14 +639,14 @@ pub enum Type<'a> {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum TRecord<'a> {
|
||||
pub enum ETypeRecord<'a> {
|
||||
End(Row, Col),
|
||||
Open(Row, Col),
|
||||
|
||||
Field(Row, Col),
|
||||
Colon(Row, Col),
|
||||
Optional(Row, Col),
|
||||
Type(&'a Type<'a>, Row, Col),
|
||||
Type(&'a EType<'a>, Row, Col),
|
||||
|
||||
Space(BadInputError, Row, Col),
|
||||
|
||||
@ -651,11 +657,11 @@ pub enum TRecord<'a> {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum TTagUnion<'a> {
|
||||
pub enum ETypeTagUnion<'a> {
|
||||
End(Row, Col),
|
||||
Open(Row, Col),
|
||||
|
||||
Type(&'a Type<'a>, Row, Col),
|
||||
Type(&'a EType<'a>, Row, Col),
|
||||
|
||||
Space(BadInputError, Row, Col),
|
||||
|
||||
@ -664,11 +670,11 @@ pub enum TTagUnion<'a> {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum TInParens<'a> {
|
||||
pub enum ETypeInParens<'a> {
|
||||
End(Row, Col),
|
||||
Open(Row, Col),
|
||||
///
|
||||
Type(&'a Type<'a>, Row, Col),
|
||||
Type(&'a EType<'a>, Row, Col),
|
||||
|
||||
///
|
||||
Space(BadInputError, Row, Col),
|
||||
@ -678,7 +684,7 @@ pub enum TInParens<'a> {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum TApply {
|
||||
pub enum ETypeApply {
|
||||
///
|
||||
StartNotUppercase(Row, Col),
|
||||
End(Row, Col),
|
||||
@ -1183,83 +1189,6 @@ macro_rules! collection {
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! collection_e {
|
||||
($opening_brace:expr, $elem:expr, $delimiter:expr, $closing_brace:expr, $min_indent:expr, $space_problem:expr, $indent_problem:expr) => {
|
||||
skip_first!(
|
||||
$opening_brace,
|
||||
skip_first!(
|
||||
// We specifically allow space characters inside here, so that
|
||||
// `[ ]` can be successfully parsed as an empty list, and then
|
||||
// changed by the formatter back into `[]`.
|
||||
//
|
||||
// We don't allow newlines or comments in the middle of empty
|
||||
// roc_collections because those are normally stored in an Expr,
|
||||
// and there's no Expr in which to store them in an empty collection!
|
||||
//
|
||||
// We could change the AST to add extra storage specifically to
|
||||
// support empty literals containing newlines or comments, but this
|
||||
// does not seem worth even the tiniest regression in compiler performance.
|
||||
zero_or_more!($crate::parser::word1(b' ', |row, col| $space_problem(
|
||||
crate::parser::BadInputError::LineTooLong,
|
||||
row,
|
||||
col
|
||||
))),
|
||||
skip_second!(
|
||||
$crate::parser::sep_by0(
|
||||
$delimiter,
|
||||
$crate::blankspace::space0_around_ee(
|
||||
$elem,
|
||||
$min_indent,
|
||||
$space_problem,
|
||||
$indent_problem,
|
||||
$indent_problem
|
||||
)
|
||||
),
|
||||
$closing_brace
|
||||
)
|
||||
)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
/// Parse zero or more elements between two braces (e.g. square braces).
|
||||
/// Elements can be optionally surrounded by spaces, and are separated by a
|
||||
/// delimiter (e.g comma-separated) with optionally a trailing delimiter.
|
||||
/// Braces and delimiters get discarded.
|
||||
#[macro_export]
|
||||
macro_rules! collection_trailing_sep {
|
||||
($opening_brace:expr, $elem:expr, $delimiter:expr, $closing_brace:expr, $min_indent:expr) => {
|
||||
skip_first!(
|
||||
$opening_brace,
|
||||
skip_first!(
|
||||
// We specifically allow space characters inside here, so that
|
||||
// `[ ]` can be successfully parsed as an empty list, and then
|
||||
// changed by the formatter back into `[]`.
|
||||
//
|
||||
// We don't allow newlines or comments in the middle of empty
|
||||
// roc_collections because those are normally stored in an Expr,
|
||||
// and there's no Expr in which to store them in an empty collection!
|
||||
//
|
||||
// We could change the AST to add extra storage specifically to
|
||||
// support empty literals containing newlines or comments, but this
|
||||
// does not seem worth even the tiniest regression in compiler performance.
|
||||
zero_or_more!($crate::parser::ascii_char(b' ')),
|
||||
skip_second!(
|
||||
and!(
|
||||
$crate::parser::trailing_sep_by0(
|
||||
$delimiter,
|
||||
$crate::blankspace::space0_around($elem, $min_indent)
|
||||
),
|
||||
$crate::blankspace::space0($min_indent)
|
||||
),
|
||||
$closing_brace
|
||||
)
|
||||
)
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! collection_trailing_sep_e {
|
||||
($opening_brace:expr, $elem:expr, $delimiter:expr, $closing_brace:expr, $min_indent:expr, $open_problem:expr, $space_problem:expr, $indent_problem:expr, $space_before:expr) => {
|
||||
@ -1300,10 +1229,10 @@ macro_rules! collection_trailing_sep_e {
|
||||
}
|
||||
}
|
||||
|
||||
let collection = $crate::ast::Collection {
|
||||
items: parsed_elems.into_bump_slice(),
|
||||
final_comments,
|
||||
};
|
||||
let collection = $crate::ast::Collection::with_items_and_comments(
|
||||
arena,
|
||||
parsed_elems.into_bump_slice(),
|
||||
final_comments);
|
||||
|
||||
Ok((MadeProgress, collection, state))
|
||||
}
|
||||
|
@ -331,10 +331,7 @@ fn record_pattern_help<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>, PRec
|
||||
)
|
||||
.parse(arena, state)?;
|
||||
|
||||
// TODO
|
||||
let _unused = fields.final_comments;
|
||||
|
||||
let result = Pattern::RecordDestructure(fields.items);
|
||||
let result = Pattern::RecordDestructure(fields);
|
||||
|
||||
Ok((MadeProgress, result, state))
|
||||
}
|
||||
|
@ -1,63 +1,64 @@
|
||||
use crate::ast::{AssignedField, Collection, Tag, TypeAnnotation};
|
||||
use crate::ast::{AssignedField, Tag, TypeAnnotation};
|
||||
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
|
||||
use crate::keyword;
|
||||
use crate::parser::{
|
||||
allocated, backtrackable, optional, specialize, specialize_ref, word1, word2, ParseResult,
|
||||
Parser,
|
||||
allocated, backtrackable, optional, specialize, specialize_ref, word1, word2, EType,
|
||||
ETypeApply, ETypeInParens, ETypeRecord, ETypeTagUnion, ParseResult, Parser,
|
||||
Progress::{self, *},
|
||||
State, TApply, TInParens, TRecord, TTagUnion, Type,
|
||||
State,
|
||||
};
|
||||
use bumpalo::collections::vec::Vec;
|
||||
use bumpalo::Bump;
|
||||
use roc_region::all::{Located, Region};
|
||||
|
||||
pub fn located_help<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Type<'a>> {
|
||||
pub fn located_help<'a>(
|
||||
min_indent: u16,
|
||||
) -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
|
||||
expression(min_indent)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn tag_union_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, TTagUnion<'a>> {
|
||||
fn tag_union_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, ETypeTagUnion<'a>> {
|
||||
move |arena, state| {
|
||||
let (_, tags, state) = collection_trailing_sep_e!(
|
||||
word1(b'[', TTagUnion::Open),
|
||||
word1(b'[', ETypeTagUnion::Open),
|
||||
loc!(tag_type(min_indent)),
|
||||
word1(b',', TTagUnion::End),
|
||||
word1(b']', TTagUnion::End),
|
||||
word1(b',', ETypeTagUnion::End),
|
||||
word1(b']', ETypeTagUnion::End),
|
||||
min_indent,
|
||||
TTagUnion::Open,
|
||||
TTagUnion::Space,
|
||||
TTagUnion::IndentEnd,
|
||||
ETypeTagUnion::Open,
|
||||
ETypeTagUnion::Space,
|
||||
ETypeTagUnion::IndentEnd,
|
||||
Tag::SpaceBefore
|
||||
)
|
||||
.parse(arena, state)?;
|
||||
|
||||
// This could be an open tag union, e.g. `[ Foo, Bar ]a`
|
||||
let (_, ext, state) =
|
||||
optional(allocated(specialize_ref(TTagUnion::Type, term(min_indent))))
|
||||
.parse(arena, state)?;
|
||||
let (_, ext, state) = optional(allocated(specialize_ref(
|
||||
ETypeTagUnion::Type,
|
||||
term(min_indent),
|
||||
)))
|
||||
.parse(arena, state)?;
|
||||
|
||||
let result = TypeAnnotation::TagUnion {
|
||||
tags: tags.items,
|
||||
ext,
|
||||
final_comments: tags.final_comments,
|
||||
};
|
||||
let result = TypeAnnotation::TagUnion { tags, ext };
|
||||
|
||||
Ok((MadeProgress, result, state))
|
||||
}
|
||||
}
|
||||
|
||||
fn fail_type_start<'a, T: 'a>() -> impl Parser<'a, T, Type<'a>> {
|
||||
|_arena, state: State<'a>| Err((NoProgress, Type::TStart(state.line, state.column), state))
|
||||
fn fail_type_start<'a, T: 'a>() -> impl Parser<'a, T, EType<'a>> {
|
||||
|_arena, state: State<'a>| Err((NoProgress, EType::TStart(state.line, state.column), state))
|
||||
}
|
||||
|
||||
fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Type<'a>> {
|
||||
fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
|
||||
map_with_arena!(
|
||||
and!(
|
||||
one_of!(
|
||||
loc_wildcard(),
|
||||
specialize(Type::TInParens, loc_type_in_parens(min_indent)),
|
||||
loc!(specialize(Type::TRecord, record_type(min_indent))),
|
||||
loc!(specialize(Type::TTagUnion, tag_union_type(min_indent))),
|
||||
loc_inferred(),
|
||||
specialize(EType::TInParens, loc_type_in_parens(min_indent)),
|
||||
loc!(specialize(EType::TRecord, record_type(min_indent))),
|
||||
loc!(specialize(EType::TTagUnion, tag_union_type(min_indent))),
|
||||
loc!(applied_type(min_indent)),
|
||||
loc!(parse_type_variable),
|
||||
fail_type_start(),
|
||||
@ -67,14 +68,14 @@ fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Typ
|
||||
map!(
|
||||
and!(
|
||||
skip_second!(
|
||||
backtrackable(space0_e(min_indent, Type::TSpace, Type::TIndentEnd)),
|
||||
crate::parser::keyword_e(keyword::AS, Type::TEnd)
|
||||
backtrackable(space0_e(min_indent, EType::TSpace, EType::TIndentEnd)),
|
||||
crate::parser::keyword_e(keyword::AS, EType::TEnd)
|
||||
),
|
||||
space0_before_e(
|
||||
term(min_indent),
|
||||
min_indent,
|
||||
Type::TSpace,
|
||||
Type::TAsIndentStart
|
||||
EType::TSpace,
|
||||
EType::TAsIndentStart
|
||||
)
|
||||
),
|
||||
Some
|
||||
@ -103,24 +104,36 @@ fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Typ
|
||||
}
|
||||
|
||||
/// The `*` type variable, e.g. in (List *) Wildcard,
|
||||
fn loc_wildcard<'a>() -> impl Parser<'a, Located<TypeAnnotation<'a>>, Type<'a>> {
|
||||
map!(loc!(word1(b'*', Type::TWildcard)), |loc_val: Located<()>| {
|
||||
fn loc_wildcard<'a>() -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
|
||||
map!(loc!(word1(b'*', EType::TWildcard)), |loc_val: Located<
|
||||
(),
|
||||
>| {
|
||||
loc_val.map(|_| TypeAnnotation::Wildcard)
|
||||
})
|
||||
}
|
||||
|
||||
fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Type<'a>> {
|
||||
/// The `_` indicating an inferred type, e.g. in (List _)
|
||||
fn loc_inferred<'a>() -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
|
||||
map!(loc!(word1(b'_', EType::TInferred)), |loc_val: Located<
|
||||
(),
|
||||
>| {
|
||||
loc_val.map(|_| TypeAnnotation::Inferred)
|
||||
})
|
||||
}
|
||||
|
||||
fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
|
||||
use crate::ast::Spaceable;
|
||||
|
||||
map_with_arena!(
|
||||
and!(
|
||||
backtrackable(space0_e(min_indent, Type::TSpace, Type::TIndentStart)),
|
||||
backtrackable(space0_e(min_indent, EType::TSpace, EType::TIndentStart)),
|
||||
one_of!(
|
||||
loc_wildcard(),
|
||||
specialize(Type::TInParens, loc_type_in_parens(min_indent)),
|
||||
loc!(specialize(Type::TRecord, record_type(min_indent))),
|
||||
loc!(specialize(Type::TTagUnion, tag_union_type(min_indent))),
|
||||
loc!(specialize(Type::TApply, parse_concrete_type)),
|
||||
loc_inferred(),
|
||||
specialize(EType::TInParens, loc_type_in_parens(min_indent)),
|
||||
loc!(specialize(EType::TRecord, record_type(min_indent))),
|
||||
loc!(specialize(EType::TTagUnion, tag_union_type(min_indent))),
|
||||
loc!(specialize(EType::TApply, parse_concrete_type)),
|
||||
loc!(parse_type_variable)
|
||||
)
|
||||
),
|
||||
@ -137,28 +150,28 @@ fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotatio
|
||||
|
||||
fn loc_type_in_parens<'a>(
|
||||
min_indent: u16,
|
||||
) -> impl Parser<'a, Located<TypeAnnotation<'a>>, TInParens<'a>> {
|
||||
) -> impl Parser<'a, Located<TypeAnnotation<'a>>, ETypeInParens<'a>> {
|
||||
between!(
|
||||
word1(b'(', TInParens::Open),
|
||||
word1(b'(', ETypeInParens::Open),
|
||||
space0_around_ee(
|
||||
move |arena, state| specialize_ref(TInParens::Type, expression(min_indent))
|
||||
move |arena, state| specialize_ref(ETypeInParens::Type, expression(min_indent))
|
||||
.parse(arena, state),
|
||||
min_indent,
|
||||
TInParens::Space,
|
||||
TInParens::IndentOpen,
|
||||
TInParens::IndentEnd,
|
||||
ETypeInParens::Space,
|
||||
ETypeInParens::IndentOpen,
|
||||
ETypeInParens::IndentEnd,
|
||||
),
|
||||
word1(b')', TInParens::IndentEnd)
|
||||
word1(b')', ETypeInParens::IndentEnd)
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn tag_type<'a>(min_indent: u16) -> impl Parser<'a, Tag<'a>, TTagUnion<'a>> {
|
||||
fn tag_type<'a>(min_indent: u16) -> impl Parser<'a, Tag<'a>, ETypeTagUnion<'a>> {
|
||||
move |arena, state: State<'a>| {
|
||||
let (_, name, state) = loc!(parse_tag_name(TTagUnion::End)).parse(arena, state)?;
|
||||
let (_, name, state) = loc!(parse_tag_name(ETypeTagUnion::End)).parse(arena, state)?;
|
||||
|
||||
let (_, args, state) =
|
||||
specialize_ref(TTagUnion::Type, loc_applied_args_e(min_indent)).parse(arena, state)?;
|
||||
let (_, args, state) = specialize_ref(ETypeTagUnion::Type, loc_applied_args_e(min_indent))
|
||||
.parse(arena, state)?;
|
||||
|
||||
let result = if name.value.starts_with('@') {
|
||||
Tag::Private {
|
||||
@ -190,7 +203,7 @@ where
|
||||
|
||||
fn record_type_field<'a>(
|
||||
min_indent: u16,
|
||||
) -> impl Parser<'a, AssignedField<'a, TypeAnnotation<'a>>, TRecord<'a>> {
|
||||
) -> impl Parser<'a, AssignedField<'a, TypeAnnotation<'a>>, ETypeRecord<'a>> {
|
||||
use crate::ident::lowercase_ident;
|
||||
use crate::parser::Either::*;
|
||||
use AssignedField::*;
|
||||
@ -201,30 +214,34 @@ fn record_type_field<'a>(
|
||||
let row = state.line;
|
||||
let col = state.column;
|
||||
let (progress, loc_label, state) = loc!(specialize(
|
||||
move |_, _, _| TRecord::Field(row, col),
|
||||
move |_, _, _| ETypeRecord::Field(row, col),
|
||||
lowercase_ident()
|
||||
))
|
||||
.parse(arena, state)?;
|
||||
debug_assert_eq!(progress, MadeProgress);
|
||||
|
||||
let (_, spaces, state) =
|
||||
space0_e(min_indent, TRecord::Space, TRecord::IndentEnd).parse(arena, state)?;
|
||||
space0_e(min_indent, ETypeRecord::Space, ETypeRecord::IndentEnd).parse(arena, state)?;
|
||||
|
||||
// Having a value is optional; both `{ email }` and `{ email: blah }` work.
|
||||
// (This is true in both literals and types.)
|
||||
let (_, opt_loc_val, state) = optional(either!(
|
||||
word1(b':', TRecord::Colon),
|
||||
word1(b'?', TRecord::Optional)
|
||||
word1(b':', ETypeRecord::Colon),
|
||||
word1(b'?', ETypeRecord::Optional)
|
||||
))
|
||||
.parse(arena, state)?;
|
||||
|
||||
let val_parser = specialize_ref(TRecord::Type, term(min_indent));
|
||||
let val_parser = specialize_ref(ETypeRecord::Type, term(min_indent));
|
||||
|
||||
match opt_loc_val {
|
||||
Some(First(_)) => {
|
||||
let (_, loc_val, state) =
|
||||
space0_before_e(val_parser, min_indent, TRecord::Space, TRecord::IndentColon)
|
||||
.parse(arena, state)?;
|
||||
let (_, loc_val, state) = space0_before_e(
|
||||
val_parser,
|
||||
min_indent,
|
||||
ETypeRecord::Space,
|
||||
ETypeRecord::IndentColon,
|
||||
)
|
||||
.parse(arena, state)?;
|
||||
|
||||
Ok((
|
||||
MadeProgress,
|
||||
@ -236,8 +253,8 @@ fn record_type_field<'a>(
|
||||
let (_, loc_val, state) = space0_before_e(
|
||||
val_parser,
|
||||
min_indent,
|
||||
TRecord::Space,
|
||||
TRecord::IndentOptional,
|
||||
ETypeRecord::Space,
|
||||
ETypeRecord::IndentOptional,
|
||||
)
|
||||
.parse(arena, state)?;
|
||||
|
||||
@ -263,44 +280,38 @@ fn record_type_field<'a>(
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, TRecord<'a>> {
|
||||
fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, ETypeRecord<'a>> {
|
||||
use crate::type_annotation::TypeAnnotation::*;
|
||||
|
||||
move |arena, state| {
|
||||
let (_, fields, state) = collection_trailing_sep_e!(
|
||||
// word1_check_indent!(b'{', TRecord::Open, min_indent, TRecord::IndentOpen),
|
||||
word1(b'{', TRecord::Open),
|
||||
word1(b'{', ETypeRecord::Open),
|
||||
loc!(record_type_field(min_indent)),
|
||||
word1(b',', TRecord::End),
|
||||
word1(b',', ETypeRecord::End),
|
||||
// word1_check_indent!(b'}', TRecord::End, min_indent, TRecord::IndentEnd),
|
||||
word1(b'}', TRecord::End),
|
||||
word1(b'}', ETypeRecord::End),
|
||||
min_indent,
|
||||
TRecord::Open,
|
||||
TRecord::Space,
|
||||
TRecord::IndentEnd,
|
||||
ETypeRecord::Open,
|
||||
ETypeRecord::Space,
|
||||
ETypeRecord::IndentEnd,
|
||||
AssignedField::SpaceBefore
|
||||
)
|
||||
.parse(arena, state)?;
|
||||
|
||||
let field_term = specialize_ref(TRecord::Type, term(min_indent));
|
||||
let field_term = specialize_ref(ETypeRecord::Type, term(min_indent));
|
||||
let (_, ext, state) = optional(allocated(field_term)).parse(arena, state)?;
|
||||
|
||||
let result = Record {
|
||||
fields: Collection {
|
||||
items: fields.items,
|
||||
final_comments: fields.final_comments,
|
||||
},
|
||||
ext,
|
||||
};
|
||||
let result = Record { fields, ext };
|
||||
|
||||
Ok((MadeProgress, result, state))
|
||||
}
|
||||
}
|
||||
|
||||
fn applied_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, Type<'a>> {
|
||||
fn applied_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, EType<'a>> {
|
||||
map!(
|
||||
and!(
|
||||
specialize(Type::TApply, parse_concrete_type),
|
||||
specialize(EType::TApply, parse_concrete_type),
|
||||
// Optionally parse space-separated arguments for the constructor,
|
||||
// e.g. `Str Float` in `Map Str Float`
|
||||
loc_applied_args_e(min_indent)
|
||||
@ -324,33 +335,33 @@ fn applied_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, Type
|
||||
|
||||
fn loc_applied_args_e<'a>(
|
||||
min_indent: u16,
|
||||
) -> impl Parser<'a, Vec<'a, Located<TypeAnnotation<'a>>>, Type<'a>> {
|
||||
) -> impl Parser<'a, Vec<'a, Located<TypeAnnotation<'a>>>, EType<'a>> {
|
||||
zero_or_more!(loc_applied_arg(min_indent))
|
||||
}
|
||||
|
||||
fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, Type<'a>> {
|
||||
fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
|
||||
move |arena, state: State<'a>| {
|
||||
let (p1, first, state) = space0_before_e(
|
||||
term(min_indent),
|
||||
min_indent,
|
||||
Type::TSpace,
|
||||
Type::TIndentStart,
|
||||
EType::TSpace,
|
||||
EType::TIndentStart,
|
||||
)
|
||||
.parse(arena, state)?;
|
||||
|
||||
let (p2, rest, state) = zero_or_more!(skip_first!(
|
||||
word1(b',', Type::TFunctionArgument),
|
||||
word1(b',', EType::TFunctionArgument),
|
||||
one_of![
|
||||
space0_around_ee(
|
||||
term(min_indent),
|
||||
min_indent,
|
||||
Type::TSpace,
|
||||
Type::TIndentStart,
|
||||
Type::TIndentEnd
|
||||
EType::TSpace,
|
||||
EType::TIndentStart,
|
||||
EType::TIndentEnd
|
||||
),
|
||||
|_, state: State<'a>| Err((
|
||||
NoProgress,
|
||||
Type::TFunctionArgument(state.line, state.column),
|
||||
EType::TFunctionArgument(state.line, state.column),
|
||||
state
|
||||
))
|
||||
]
|
||||
@ -360,8 +371,8 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
|
||||
// TODO this space0 is dropped, so newlines just before the function arrow when there
|
||||
// is only one argument are not seen by the formatter. Can we do better?
|
||||
let (p3, is_function, state) = optional(skip_first!(
|
||||
space0_e(min_indent, Type::TSpace, Type::TIndentStart),
|
||||
word2(b'-', b'>', Type::TStart)
|
||||
space0_e(min_indent, EType::TSpace, EType::TIndentStart),
|
||||
word2(b'-', b'>', EType::TStart)
|
||||
))
|
||||
.parse(arena, state)?;
|
||||
|
||||
@ -369,8 +380,8 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
|
||||
let (p4, return_type, state) = space0_before_e(
|
||||
term(min_indent),
|
||||
min_indent,
|
||||
Type::TSpace,
|
||||
Type::TIndentStart,
|
||||
EType::TSpace,
|
||||
EType::TIndentStart,
|
||||
)
|
||||
.parse(arena, state)?;
|
||||
|
||||
@ -418,7 +429,7 @@ fn expression<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>
|
||||
fn parse_concrete_type<'a>(
|
||||
arena: &'a Bump,
|
||||
state: State<'a>,
|
||||
) -> ParseResult<'a, TypeAnnotation<'a>, TApply> {
|
||||
) -> ParseResult<'a, TypeAnnotation<'a>, ETypeApply> {
|
||||
let initial_bytes = state.bytes;
|
||||
|
||||
match crate::ident::concrete_type().parse(arena, state) {
|
||||
@ -428,7 +439,7 @@ fn parse_concrete_type<'a>(
|
||||
Ok((MadeProgress, answer, state))
|
||||
}
|
||||
Err((NoProgress, _, state)) => {
|
||||
Err((NoProgress, TApply::End(state.line, state.column), state))
|
||||
Err((NoProgress, ETypeApply::End(state.line, state.column), state))
|
||||
}
|
||||
Err((MadeProgress, _, mut state)) => {
|
||||
// we made some progress, but ultimately failed.
|
||||
@ -439,7 +450,7 @@ fn parse_concrete_type<'a>(
|
||||
unsafe { std::str::from_utf8_unchecked(&initial_bytes[..chomped + delta]) };
|
||||
|
||||
state = state.advance_without_indenting_ee(chomped, |r, c| {
|
||||
TApply::Space(crate::parser::BadInputError::LineTooLong, r, c)
|
||||
ETypeApply::Space(crate::parser::BadInputError::LineTooLong, r, c)
|
||||
})?;
|
||||
|
||||
Ok((MadeProgress, TypeAnnotation::Malformed(parsed_str), state))
|
||||
@ -450,7 +461,7 @@ fn parse_concrete_type<'a>(
|
||||
fn parse_type_variable<'a>(
|
||||
arena: &'a Bump,
|
||||
state: State<'a>,
|
||||
) -> ParseResult<'a, TypeAnnotation<'a>, Type<'a>> {
|
||||
) -> ParseResult<'a, TypeAnnotation<'a>, EType<'a>> {
|
||||
match crate::ident::lowercase_ident().parse(arena, state) {
|
||||
Ok((_, name, state)) => {
|
||||
let answer = TypeAnnotation::BoundVariable(name);
|
||||
@ -459,7 +470,7 @@ fn parse_type_variable<'a>(
|
||||
}
|
||||
Err((progress, _, state)) => Err((
|
||||
progress,
|
||||
Type::TBadTypeVariable(state.line, state.column),
|
||||
EType::TBadTypeVariable(state.line, state.column),
|
||||
state,
|
||||
)),
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
BinOps(
|
||||
[
|
||||
(
|
||||
|L 0-0, C 0-1| Var {
|
||||
module_name: "",
|
||||
ident: "x",
|
||||
},
|
||||
|L 0-0, C 2-3| Plus,
|
||||
),
|
||||
],
|
||||
|L 0-0, C 4-5| Num(
|
||||
"2",
|
||||
),
|
||||
)
|
@ -0,0 +1 @@
|
||||
x + 2
|
@ -0,0 +1,13 @@
|
||||
BinOps(
|
||||
[
|
||||
(
|
||||
|L 0-0, C 0-1| Num(
|
||||
"1",
|
||||
),
|
||||
|L 0-0, C 3-4| Plus,
|
||||
),
|
||||
],
|
||||
|L 0-0, C 7-8| Num(
|
||||
"2",
|
||||
),
|
||||
)
|
@ -0,0 +1 @@
|
||||
1 + 2
|
@ -0,0 +1,14 @@
|
||||
Apply(
|
||||
|L 0-0, C 0-4| GlobalTag(
|
||||
"Whee",
|
||||
),
|
||||
[
|
||||
|L 0-0, C 5-7| Num(
|
||||
"12",
|
||||
),
|
||||
|L 0-0, C 8-10| Num(
|
||||
"34",
|
||||
),
|
||||
],
|
||||
Space,
|
||||
)
|
@ -0,0 +1 @@
|
||||
Whee 12 34
|
@ -0,0 +1,18 @@
|
||||
Apply(
|
||||
|L 0-0, C 0-4| GlobalTag(
|
||||
"Whee",
|
||||
),
|
||||
[
|
||||
|L 0-0, C 6-8| ParensAround(
|
||||
Num(
|
||||
"12",
|
||||
),
|
||||
),
|
||||
|L 0-0, C 11-13| ParensAround(
|
||||
Num(
|
||||
"34",
|
||||
),
|
||||
),
|
||||
],
|
||||
Space,
|
||||
)
|
@ -0,0 +1 @@
|
||||
Whee (12) (34)
|
@ -0,0 +1,14 @@
|
||||
Apply(
|
||||
|L 0-0, C 0-5| PrivateTag(
|
||||
"@Whee",
|
||||
),
|
||||
[
|
||||
|L 0-0, C 6-8| Num(
|
||||
"12",
|
||||
),
|
||||
|L 0-0, C 9-11| Num(
|
||||
"34",
|
||||
),
|
||||
],
|
||||
Space,
|
||||
)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user