diff --git a/Cargo.lock b/Cargo.lock index 6f9b42c036..cc26d15406 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3218,6 +3218,28 @@ dependencies = [ "libc", ] +[[package]] +name = "roc_ast" +version = "0.1.0" +dependencies = [ + "arraystring", + "bumpalo", + "indoc 1.0.3", + "libc", + "page_size", + "pretty_assertions 0.6.1", + "roc_can", + "roc_collections", + "roc_module", + "roc_parse", + "roc_problem", + "roc_region", + "roc_types", + "roc_unify", + "snafu", + "ven_graph", +] + [[package]] name = "roc_build" version = "0.1.0" @@ -3342,6 +3364,19 @@ dependencies = [ "wasmer-wasi", ] +[[package]] +name = "roc_code_markup" +version = "0.1.0" +dependencies = [ + "bumpalo", + "palette", + "roc_ast", + "roc_module", + "roc_utils", + "serde", + "snafu", +] + [[package]] name = "roc_collections" version = "0.1.0" @@ -3419,8 +3454,10 @@ dependencies = [ "quickcheck 1.0.3", "quickcheck_macros 1.0.0", "rand 0.8.4", + "roc_ast", "roc_builtins", "roc_can", + "roc_code_markup", "roc_collections", "roc_fmt", "roc_load", @@ -3773,6 +3810,13 @@ dependencies = [ "roc_types", ] +[[package]] +name = "roc_utils" +version = "0.1.0" +dependencies = [ + "snafu", +] + [[package]] name = "ropey" version = "1.3.1" diff --git a/Cargo.toml b/Cargo.toml index 7363ba8600..a2fe43c612 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,9 +29,12 @@ members = [ "vendor/pathfinding", "vendor/pretty", "editor", + "ast", "cli", "cli/cli_utils", + "code_markup", "roc_std", + "utils", "docs", "linker", ] diff --git a/Earthfile b/Earthfile index 164f50af88..fe7629fb8a 100644 --- a/Earthfile +++ b/Earthfile @@ -46,7 +46,7 @@ install-zig-llvm-valgrind-clippy-rustfmt: copy-dirs: FROM +install-zig-llvm-valgrind-clippy-rustfmt - COPY --dir cli compiler docs editor roc_std vendor examples linker Cargo.toml Cargo.lock ./ + COPY --dir cli compiler docs editor ast code_markup utils roc_std vendor examples linker Cargo.toml Cargo.lock ./ test-zig: FROM +install-zig-llvm-valgrind-clippy-rustfmt @@ -66,7 +66,7 @@ check-rustfmt: check-typos: RUN cargo install typos-cli --version 1.0.11 # version set to prevent confusion if the version is updated automatically - COPY --dir .github ci cli compiler docs editor examples linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./ + COPY --dir .github ci cli compiler docs editor examples ast code_markup utils linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./ RUN typos test-rust: diff --git a/ast/Cargo.toml b/ast/Cargo.toml new file mode 100644 index 0000000000..39bf52a471 --- /dev/null +++ b/ast/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "roc_ast" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" +description = "AST as used by the editor and (soon) docs. In contrast to the compiler, these types do not keep track of a location in a file." + +[dependencies] +roc_can = { path = "../compiler/can" } +roc_collections = { path = "../compiler/collections" } +roc_region = { path = "../compiler/region" } +roc_module = { path = "../compiler/module" } +roc_parse = { path = "../compiler/parse" } +roc_problem = { path = "../compiler/problem" } +roc_types = { path = "../compiler/types" } +roc_unify = { path = "../compiler/unify"} +arraystring = "0.3.0" +bumpalo = { version = "3.6.1", features = ["collections"] } +libc = "0.2" +page_size = "0.4" +snafu = { version = "0.6", features = ["backtraces"] } +ven_graph = { path = "../vendor/pathfinding" } +indoc = "1.0" + +[dev-dependencies] +pretty_assertions = "0.6" diff --git a/ast/src/ast_error.rs b/ast/src/ast_error.rs new file mode 100644 index 0000000000..6fb8546212 --- /dev/null +++ b/ast/src/ast_error.rs @@ -0,0 +1,38 @@ +use snafu::{Backtrace, Snafu}; + +use crate::lang::core::ast::ASTNodeId; + +#[derive(Debug, Snafu)] +#[snafu(visibility(pub))] +pub enum ASTError { + #[snafu(display( + "ASTNodeIdWithoutExprId: The expr_id_opt in ASTNode({:?}) was `None` but I was expexting `Some(ExprId)` .", + ast_node_id + ))] + ASTNodeIdWithoutExprId { + ast_node_id: ASTNodeId, + backtrace: Backtrace, + }, + #[snafu(display( + "UnexpectedASTNode: required a {} at this position, node was a {}.", + required_node_type, + encountered_node_type + ))] + UnexpectedASTNode { + required_node_type: String, + encountered_node_type: String, + backtrace: Backtrace, + }, + #[snafu(display( + "UnexpectedPattern2Variant: required a {} at this position, Pattern2 was a {}.", + required_pattern2, + encountered_pattern2, + ))] + UnexpectedPattern2Variant { + required_pattern2: String, + encountered_pattern2: String, + backtrace: Backtrace, + }, +} + +pub type ASTResult = std::result::Result; diff --git a/ast/src/canonicalization/canonicalize.rs b/ast/src/canonicalization/canonicalize.rs new file mode 100644 index 0000000000..211e22cf97 --- /dev/null +++ b/ast/src/canonicalization/canonicalize.rs @@ -0,0 +1,305 @@ +use roc_collections::all::MutMap; +use roc_problem::can::Problem; +use roc_region::all::{Located, Region}; +use roc_types::subs::Variable; + +use crate::{ + lang::{ + core::{ + def::def::References, + expr::{ + expr2::{Expr2, ExprId, WhenBranch}, + expr_to_expr2::to_expr2, + output::Output, + record_field::RecordField, + }, + pattern::to_pattern2, + }, + env::Env, + scope::Scope, + }, + mem_pool::{pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}, +}; + +pub(crate) enum CanonicalizeRecordProblem { + #[allow(dead_code)] + InvalidOptionalValue { + field_name: PoolStr, + field_region: Region, + record_region: Region, + }, +} + +enum FieldVar { + VarAndExprId(Variable, ExprId), + OnlyVar(Variable), +} + +pub(crate) fn canonicalize_fields<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + fields: &'a [Located>>], +) -> Result<(PoolVec, Output), CanonicalizeRecordProblem> { + let mut can_fields: MutMap<&'a str, FieldVar> = MutMap::default(); + let mut output = Output::default(); + + for loc_field in fields.iter() { + match canonicalize_field(env, scope, &loc_field.value) { + Ok(can_field) => { + match can_field { + CanonicalField::LabelAndValue { + label, + value_expr, + value_output, + var, + } => { + let expr_id = env.pool.add(value_expr); + + let replaced = + can_fields.insert(label, FieldVar::VarAndExprId(var, expr_id)); + + if let Some(_old) = replaced { + // env.problems.push(Problem::DuplicateRecordFieldValue { + // field_name: label, + // field_region: loc_field.region, + // record_region: region, + // replaced_region: old.region, + // }); + todo!() + } + + output.references.union_mut(value_output.references); + } + CanonicalField::InvalidLabelOnly { label, var } => { + let replaced = can_fields.insert(label, FieldVar::OnlyVar(var)); + + if let Some(_old) = replaced { + todo!() + } + } + } + } + + Err(CanonicalizeFieldProblem::InvalidOptionalValue { + field_name: _, + field_region: _, + }) => { + // env.problem(Problem::InvalidOptionalValue { + // field_name: field_name.clone(), + // field_region, + // record_region: region, + // }); + // return Err(CanonicalizeRecordProblem::InvalidOptionalValue { + // field_name, + // field_region, + // record_region: region, + // }); + todo!() + } + } + } + + let pool_vec = PoolVec::with_capacity(can_fields.len() as u32, env.pool); + + for (node_id, (string, field_var)) in pool_vec.iter_node_ids().zip(can_fields.into_iter()) { + let name = PoolStr::new(string, env.pool); + + match field_var { + FieldVar::VarAndExprId(var, expr_id) => { + env.pool[node_id] = RecordField::LabeledValue(name, var, expr_id); + } + FieldVar::OnlyVar(var) => { + env.pool[node_id] = RecordField::InvalidLabelOnly(name, var); + } // TODO RecordField::LabelOnly + } + } + + Ok((pool_vec, output)) +} + +#[allow(dead_code)] +enum CanonicalizeFieldProblem { + InvalidOptionalValue { + field_name: PoolStr, + field_region: Region, + }, +} +enum CanonicalField<'a> { + LabelAndValue { + label: &'a str, + value_expr: Expr2, + value_output: Output, + var: Variable, + }, + InvalidLabelOnly { + label: &'a str, + var: Variable, + }, // TODO make ValidLabelOnly +} +fn canonicalize_field<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + field: &'a roc_parse::ast::AssignedField<'a, roc_parse::ast::Expr<'a>>, +) -> Result, CanonicalizeFieldProblem> { + use roc_parse::ast::AssignedField::*; + + match field { + // Both a label and a value, e.g. `{ name: "blah" }` + RequiredValue(label, _, loc_expr) => { + let field_var = env.var_store.fresh(); + let (loc_can_expr, output) = to_expr2(env, scope, &loc_expr.value, loc_expr.region); + + Ok(CanonicalField::LabelAndValue { + label: label.value, + value_expr: loc_can_expr, + value_output: output, + var: field_var, + }) + } + + OptionalValue(label, _, loc_expr) => Err(CanonicalizeFieldProblem::InvalidOptionalValue { + field_name: PoolStr::new(label.value, env.pool), + field_region: Region::span_across(&label.region, &loc_expr.region), + }), + + // A label with no value, e.g. `{ name }` (this is sugar for { name: name }) + LabelOnly(label) => { + let field_var = env.var_store.fresh(); + // TODO return ValidLabel if label points to in scope variable + Ok(CanonicalField::InvalidLabelOnly { + label: label.value, + var: field_var, + }) + } + + SpaceBefore(sub_field, _) | SpaceAfter(sub_field, _) => { + canonicalize_field(env, scope, sub_field) + } + + Malformed(_string) => { + panic!("TODO canonicalize malformed record field"); + } + } +} + +#[inline(always)] +pub(crate) fn canonicalize_when_branch<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + branch: &'a roc_parse::ast::WhenBranch<'a>, + output: &mut Output, +) -> (WhenBranch, References) { + let patterns = PoolVec::with_capacity(branch.patterns.len() as u32, env.pool); + + let original_scope = scope; + let mut scope = original_scope.shallow_clone(); + + // TODO report symbols not bound in all patterns + for (node_id, loc_pattern) in patterns.iter_node_ids().zip(branch.patterns.iter()) { + let (new_output, can_pattern) = to_pattern2( + env, + &mut scope, + roc_parse::pattern::PatternType::WhenBranch, + &loc_pattern.value, + loc_pattern.region, + ); + + output.union(new_output); + + env.set_region(node_id, loc_pattern.region); + env.pool[node_id] = can_pattern; + } + + let (value, mut branch_output) = + to_expr2(env, &mut scope, &branch.value.value, branch.value.region); + let value_id = env.pool.add(value); + env.set_region(value_id, branch.value.region); + + let guard = match &branch.guard { + None => None, + Some(loc_expr) => { + let (can_guard, guard_branch_output) = + to_expr2(env, &mut scope, &loc_expr.value, loc_expr.region); + + let expr_id = env.pool.add(can_guard); + env.set_region(expr_id, loc_expr.region); + + branch_output.union(guard_branch_output); + Some(expr_id) + } + }; + + // Now that we've collected all the references for this branch, check to see if + // any of the new idents it defined were unused. If any were, report it. + for (symbol, region) in scope.symbols() { + let symbol = symbol; + + if !output.references.has_lookup(symbol) + && !branch_output.references.has_lookup(symbol) + && !original_scope.contains_symbol(symbol) + { + env.problem(Problem::UnusedDef(symbol, region)); + } + } + + let references = branch_output.references.clone(); + output.union(branch_output); + + ( + WhenBranch { + patterns, + body: value_id, + guard, + }, + references, + ) +} + +pub(crate) fn canonicalize_lookup( + env: &mut Env<'_>, + scope: &mut Scope, + module_name: &str, + ident: &str, + region: Region, +) -> (Expr2, Output) { + use Expr2::*; + + let mut output = Output::default(); + let can_expr = if module_name.is_empty() { + // Since module_name was empty, this is an unqualified var. + // Look it up in scope! + match scope.lookup(&(*ident).into(), region) { + Ok(symbol) => { + output.references.lookups.insert(symbol); + + Var(symbol) + } + Err(problem) => { + env.problem(Problem::RuntimeError(problem)); + + RuntimeError() + } + } + } else { + // Since module_name was nonempty, this is a qualified var. + // Look it up in the env! + match env.qualified_lookup(module_name, ident, region) { + Ok(symbol) => { + output.references.lookups.insert(symbol); + + Var(symbol) + } + Err(problem) => { + // Either the module wasn't imported, or + // it was imported but it doesn't expose this ident. + env.problem(Problem::RuntimeError(problem)); + + RuntimeError() + } + } + }; + + // If it's valid, this ident should be in scope already. + + (can_expr, output) +} diff --git a/ast/src/canonicalization/mod.rs b/ast/src/canonicalization/mod.rs new file mode 100644 index 0000000000..fdbc754fc0 --- /dev/null +++ b/ast/src/canonicalization/mod.rs @@ -0,0 +1,2 @@ +pub mod canonicalize; +pub mod module; diff --git a/editor/src/lang/module.rs b/ast/src/canonicalization/module.rs similarity index 95% rename from editor/src/lang/module.rs rename to ast/src/canonicalization/module.rs index c0494d3193..c8286f8c2d 100644 --- a/editor/src/lang/module.rs +++ b/ast/src/canonicalization/module.rs @@ -2,14 +2,6 @@ #![allow(dead_code)] #![allow(unused_imports)] #![allow(unused_variables)] -use crate::lang::ast::{Expr2, FunctionDef, ValueDef}; -use crate::lang::def::{canonicalize_defs, sort_can_defs, Declaration, Def}; -use crate::lang::expr::Env; -use crate::lang::expr::Output; -use crate::lang::pattern::Pattern2; -use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone}; -use crate::lang::scope::Scope; -use crate::lang::types::Alias; use bumpalo::Bump; use roc_can::operator::desugar_def; use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap}; @@ -22,6 +14,21 @@ use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Located, Region}; use roc_types::subs::{VarStore, Variable}; +use crate::lang::core::def::def::canonicalize_defs; +use crate::lang::core::def::def::Def; +use crate::lang::core::def::def::{sort_can_defs, Declaration}; +use crate::lang::core::expr::expr2::Expr2; +use crate::lang::core::expr::output::Output; +use crate::lang::core::pattern::Pattern2; +use crate::lang::core::types::Alias; +use crate::lang::core::val_def::ValueDef; +use crate::lang::env::Env; +use crate::lang::scope::Scope; +use crate::mem_pool::pool::NodeId; +use crate::mem_pool::pool::Pool; +use crate::mem_pool::pool_vec::PoolVec; +use crate::mem_pool::shallow_clone::ShallowClone; + pub struct ModuleOutput { pub aliases: MutMap>, pub rigid_variables: MutMap, diff --git a/editor/src/lang/constrain.rs b/ast/src/constrain.rs similarity index 85% rename from editor/src/lang/constrain.rs rename to ast/src/constrain.rs index 77d4edb0cd..1454f1d936 100644 --- a/editor/src/lang/constrain.rs +++ b/ast/src/constrain.rs @@ -1,13 +1,5 @@ use bumpalo::{collections::Vec as BumpVec, Bump}; -use crate::lang::{ - ast::{ClosureExtra, Expr2, ExprId, RecordField, ValueDef, WhenBranch}, - expr::Env, - pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct}, - pool::{Pool, PoolStr, PoolVec, ShallowClone}, - types::{Type2, TypeId}, -}; - use roc_can::expected::{Expected, PExpected}; use roc_collections::all::{BumpMap, BumpMapDefault, Index, SendMap}; use roc_module::{ @@ -21,6 +13,22 @@ use roc_types::{ types::{Category, Reason}, }; +use crate::{ + lang::{ + core::{ + expr::{ + expr2::{ClosureExtra, Expr2, ExprId, WhenBranch}, + record_field::RecordField, + }, + pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct}, + types::{Type2, TypeId}, + val_def::ValueDef, + }, + env::Env, + }, + mem_pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}, +}; + #[derive(Debug)] pub enum Constraint<'a> { Eq(Type2, Expected, Category, Region), @@ -1744,3 +1752,397 @@ fn num_num(pool: &mut Pool, type_id: TypeId) -> Type2 { pool.add(alias_content), ) } + +#[cfg(test)] +pub mod test_constrain { + use bumpalo::Bump; + use roc_can::expected::Expected; + use roc_collections::all::MutMap; + use roc_module::{ + ident::Lowercase, + symbol::{IdentIds, Interns, ModuleIds, Symbol}, + }; + use roc_parse::parser::SyntaxError; + use roc_region::all::Region; + use roc_types::{ + pretty_print::content_to_string, + solved_types::Solved, + subs::{Subs, VarStore, Variable}, + }; + + use super::Constraint; + use crate::{ + constrain::constrain_expr, + lang::{ + core::{ + expr::{expr2::Expr2, expr_to_expr2::loc_expr_to_expr2, output::Output}, + types::Type2, + }, + env::Env, + scope::Scope, + }, + mem_pool::pool::Pool, + solve_type, + }; + use indoc::indoc; + + fn run_solve<'a>( + arena: &'a Bump, + mempool: &mut Pool, + aliases: MutMap, + rigid_variables: MutMap, + constraint: Constraint, + var_store: VarStore, + ) -> (Solved, solve_type::Env, Vec) { + let env = solve_type::Env { + vars_by_symbol: MutMap::default(), + aliases, + }; + + let mut subs = Subs::new(var_store); + + for (var, name) in rigid_variables { + subs.rigid_var(var, name); + } + + // Now that the module is parsed, canonicalized, and constrained, + // we need to type check it. + let mut problems = Vec::new(); + + // Run the solver to populate Subs. + let (solved_subs, solved_env) = + solve_type::run(arena, mempool, &env, &mut problems, subs, &constraint); + + (solved_subs, solved_env, problems) + } + + fn infer_eq(actual: &str, expected_str: &str) { + let mut env_pool = Pool::with_capacity(1024); + let env_arena = Bump::new(); + let code_arena = Bump::new(); + + let mut var_store = VarStore::default(); + let var = var_store.fresh(); + let dep_idents = IdentIds::exposed_builtins(8); + let exposed_ident_ids = IdentIds::default(); + let mut module_ids = ModuleIds::default(); + let mod_id = module_ids.get_or_insert(&"ModId123".into()); + + let mut env = Env::new( + mod_id, + &env_arena, + &mut env_pool, + &mut var_store, + dep_idents, + &module_ids, + exposed_ident_ids, + ); + + let mut scope = Scope::new(env.home, env.pool, env.var_store); + + let region = Region::zero(); + + let expr2_result = str_to_expr2(&code_arena, actual, &mut env, &mut scope, region); + + match expr2_result { + Ok((expr, _)) => { + let constraint = constrain_expr( + &code_arena, + &mut env, + &expr, + Expected::NoExpectation(Type2::Variable(var)), + Region::zero(), + ); + + let Env { + pool, + var_store: ref_var_store, + mut dep_idents, + .. + } = env; + + // extract the var_store out of the env again + let mut var_store = VarStore::default(); + std::mem::swap(ref_var_store, &mut var_store); + + let (mut solved, _, _) = run_solve( + &code_arena, + pool, + Default::default(), + Default::default(), + constraint, + var_store, + ); + + let subs = solved.inner_mut(); + + let content = subs.get_content_without_compacting(var); + + // Connect the ModuleId to it's IdentIds + dep_idents.insert(mod_id, env.ident_ids); + + let interns = Interns { + module_ids: env.module_ids.clone(), + all_ident_ids: dep_idents, + }; + + let actual_str = content_to_string(content, subs, mod_id, &interns); + + assert_eq!(actual_str, expected_str); + } + Err(e) => panic!("syntax error {:?}", e), + } + } + + pub fn str_to_expr2<'a>( + arena: &'a Bump, + input: &'a str, + env: &mut Env<'a>, + scope: &mut Scope, + region: Region, + ) -> Result<(Expr2, Output), SyntaxError<'a>> { + match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) { + Ok(loc_expr) => Ok(loc_expr_to_expr2(arena, loc_expr, env, scope, region)), + Err(fail) => Err(fail), + } + } + + #[test] + fn constrain_str() { + infer_eq( + indoc!( + r#" + "type inference!" + "# + ), + "Str", + ) + } + + // This will be more useful once we actually map + // strings less than 15 chars to SmallStr + #[test] + fn constrain_small_str() { + infer_eq( + indoc!( + r#" + "a" + "# + ), + "Str", + ) + } + + #[test] + fn constrain_empty_record() { + infer_eq( + indoc!( + r#" + {} + "# + ), + "{}", + ) + } + + #[test] + fn constrain_small_int() { + infer_eq( + indoc!( + r#" + 12 + "# + ), + "Num *", + ) + } + + #[test] + fn constrain_float() { + infer_eq( + indoc!( + r#" + 3.14 + "# + ), + "Float *", + ) + } + + #[test] + fn constrain_record() { + infer_eq( + indoc!( + r#" + { x : 1, y : "hi" } + "# + ), + "{ x : Num *, y : Str }", + ) + } + + #[test] + fn constrain_empty_list() { + infer_eq( + indoc!( + r#" + [] + "# + ), + "List *", + ) + } + + #[test] + fn constrain_list() { + infer_eq( + indoc!( + r#" + [ 1, 2 ] + "# + ), + "List (Num *)", + ) + } + + #[test] + fn constrain_list_of_records() { + infer_eq( + indoc!( + r#" + [ { x: 1 }, { x: 3 } ] + "# + ), + "List { x : Num * }", + ) + } + + #[test] + fn constrain_global_tag() { + infer_eq( + indoc!( + r#" + Foo + "# + ), + "[ Foo ]*", + ) + } + + #[test] + fn constrain_private_tag() { + infer_eq( + indoc!( + r#" + @Foo + "# + ), + "[ @Foo ]*", + ) + } + + #[test] + fn constrain_call_and_accessor() { + infer_eq( + indoc!( + r#" + .foo { foo: "bar" } + "# + ), + "Str", + ) + } + + #[test] + fn constrain_access() { + infer_eq( + indoc!( + r#" + { foo: "bar" }.foo + "# + ), + "Str", + ) + } + + #[test] + fn constrain_if() { + infer_eq( + indoc!( + r#" + if True then Green else Red + "# + ), + "[ Green, Red ]*", + ) + } + + #[test] + fn constrain_when() { + infer_eq( + indoc!( + r#" + when if True then Green else Red is + Green -> Blue + Red -> Purple + "# + ), + "[ Blue, Purple ]*", + ) + } + + #[test] + fn constrain_let_value() { + infer_eq( + indoc!( + r#" + person = { name: "roc" } + + person + "# + ), + "{ name : Str }", + ) + } + + #[test] + fn constrain_update() { + infer_eq( + indoc!( + r#" + person = { name: "roc" } + + { person & name: "bird" } + "# + ), + "{ name : Str }", + ) + } + + #[ignore = "TODO: implement builtins in the editor"] + #[test] + fn constrain_run_low_level() { + infer_eq( + indoc!( + r#" + List.map [ { name: "roc" }, { name: "bird" } ] .name + "# + ), + "List Str", + ) + } + + #[test] + fn constrain_closure() { + infer_eq( + indoc!( + r#" + x = 1 + + \{} -> x + "# + ), + "{}* -> Num *", + ) + } +} diff --git a/ast/src/lang/core/ast.rs b/ast/src/lang/core/ast.rs new file mode 100644 index 0000000000..586b29f19c --- /dev/null +++ b/ast/src/lang/core/ast.rs @@ -0,0 +1,45 @@ +use crate::{ + ast_error::{ASTNodeIdWithoutExprId, ASTResult}, + mem_pool::pool::Pool, +}; + +use super::{ + def::def2::{def2_to_string, DefId}, + expr::{expr2::ExprId, expr2_to_string::expr2_to_string}, + header::AppHeader, +}; + +#[derive(Debug)] +pub struct AST { + pub header: AppHeader, + pub def_ids: Vec, +} + +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum ASTNodeId { + ADefId(DefId), + AExprId(ExprId), +} + +impl ASTNodeId { + pub fn to_expr_id(&self) -> ASTResult { + match self { + ASTNodeId::AExprId(expr_id) => Ok(*expr_id), + _ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?, + } + } + + pub fn to_def_id(&self) -> ASTResult { + match self { + ASTNodeId::ADefId(def_id) => Ok(*def_id), + _ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?, + } + } +} + +pub fn ast_node_to_string(node_id: ASTNodeId, pool: &Pool) -> String { + match node_id { + ASTNodeId::ADefId(def_id) => def2_to_string(def_id, pool), + ASTNodeId::AExprId(expr_id) => expr2_to_string(expr_id, pool), + } +} diff --git a/ast/src/lang/core/declaration.rs b/ast/src/lang/core/declaration.rs new file mode 100644 index 0000000000..e45668b484 --- /dev/null +++ b/ast/src/lang/core/declaration.rs @@ -0,0 +1,70 @@ +use roc_types::subs::VarStore; + +use crate::{ + lang::core::{def::def::Def, expr::expr2::Expr2}, + mem_pool::{pool::Pool, pool_vec::PoolVec}, +}; + +use super::def::def::Declaration; + +pub(crate) fn decl_to_let( + pool: &mut Pool, + var_store: &mut VarStore, + decl: Declaration, + ret: Expr2, +) -> Expr2 { + match decl { + Declaration::Declare(def) => match def { + Def::AnnotationOnly { .. } => todo!(), + Def::Value(value_def) => { + let def_id = pool.add(value_def); + + let body_id = pool.add(ret); + + Expr2::LetValue { + def_id, + body_id, + body_var: var_store.fresh(), + } + } + Def::Function(function_def) => { + let def_id = pool.add(function_def); + let body_id = pool.add(ret); + + Expr2::LetFunction { + def_id, + body_id, + body_var: var_store.fresh(), + } + } + }, + Declaration::DeclareRec(defs) => { + let mut function_defs = vec![]; + + for def in defs { + match def { + Def::AnnotationOnly { .. } => todo!(), + Def::Function(function_def) => function_defs.push(function_def), + Def::Value(_) => unreachable!(), + } + } + + let body_id = pool.add(ret); + + Expr2::LetRec { + defs: PoolVec::new(function_defs.into_iter(), pool), + body_var: var_store.fresh(), + body_id, + } + } + Declaration::InvalidCycle(_entries, _) => { + // TODO: replace with something from Expr2 + // Expr::RuntimeError(RuntimeError::CircularDef(entries)) + todo!() + } + Declaration::Builtin(_) => { + // Builtins should only be added to top-level decls, not to let-exprs! + unreachable!() + } + } +} diff --git a/editor/src/lang/def.rs b/ast/src/lang/core/def/def.rs similarity index 98% rename from editor/src/lang/def.rs rename to ast/src/lang/core/def/def.rs index c179145a7c..c8654b3d73 100644 --- a/editor/src/lang/def.rs +++ b/ast/src/lang/core/def/def.rs @@ -12,15 +12,6 @@ // }; // use crate::pattern::{bindings_from_patterns, canonicalize_pattern, Pattern}; // use crate::procedure::References; -use crate::lang::ast::{Expr2, FunctionDef, Rigids, ValueDef}; -use crate::lang::expr::Output; -use crate::lang::expr::{to_expr2, to_expr_id, Env}; -use crate::lang::pattern::{ - symbols_and_variables_from_pattern, symbols_from_pattern, to_pattern_id, Pattern2, PatternId, -}; -use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone}; -use crate::lang::scope::Scope; -use crate::lang::types::{to_annotation2, Alias, Annotation2, Signature, Type2, TypeId}; use roc_collections::all::{default_hasher, ImMap, MutMap, MutSet, SendMap}; use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; @@ -33,6 +24,22 @@ use std::collections::HashMap; use std::fmt::Debug; use ven_graph::{strongly_connected_components, topological_sort_into_groups}; +use crate::{ + lang::{ + core::{ + expr::{expr2::Expr2, expr_to_expr2::to_expr2, output::Output}, + fun_def::FunctionDef, + pattern::{self, symbols_from_pattern, to_pattern_id, Pattern2, PatternId}, + types::{to_annotation2, Alias, Annotation2, Signature, Type2, TypeId}, + val_def::ValueDef, + }, + env::Env, + rigids::Rigids, + scope::Scope, + }, + mem_pool::{pool::Pool, pool_vec::PoolVec, shallow_clone::ShallowClone}, +}; + #[derive(Debug)] pub enum Def { AnnotationOnly { rigids: Rigids, annotation: TypeId }, @@ -127,7 +134,7 @@ fn to_pending_def<'a>( match def { Annotation(loc_pattern, loc_ann) => { // This takes care of checking for shadowing and adding idents to scope. - let (output, loc_can_pattern) = crate::lang::pattern::to_pattern_id( + let (output, loc_can_pattern) = pattern::to_pattern_id( env, scope, pattern_type, @@ -142,7 +149,7 @@ fn to_pending_def<'a>( } Body(loc_pattern, loc_expr) => { // This takes care of checking for shadowing and adding idents to scope. - let (output, loc_can_pattern) = crate::lang::pattern::to_pattern_id( + let (output, loc_can_pattern) = pattern::to_pattern_id( env, scope, pattern_type, diff --git a/ast/src/lang/core/def/def2.rs b/ast/src/lang/core/def/def2.rs new file mode 100644 index 0000000000..023a39760d --- /dev/null +++ b/ast/src/lang/core/def/def2.rs @@ -0,0 +1,43 @@ +use crate::{ + lang::core::{ + expr::{expr2::Expr2, expr2_to_string::expr2_to_string}, + pattern::Pattern2, + }, + mem_pool::pool::{NodeId, Pool}, +}; + +// A top level definition, not inside a function. For example: `main = "Hello, world!"` +#[derive(Debug)] +pub enum Def2 { + // ValueDef example: `main = "Hello, world!"`. identifier -> `main`, expr -> "Hello, world!" + ValueDef { + identifier_id: NodeId, + expr_id: NodeId, + }, + Blank, +} + +pub type DefId = NodeId; + +pub fn def2_to_string(node_id: DefId, pool: &Pool) -> String { + let mut full_string = String::new(); + let def2 = pool.get(node_id); + + match def2 { + Def2::ValueDef { + identifier_id, + expr_id, + } => { + full_string.push_str(&format!( + "Def2::ValueDef(identifier_id: >>{:?}), expr_id: >>{:?})", + pool.get(*identifier_id), + expr2_to_string(*expr_id, pool) + )); + } + Def2::Blank => { + full_string.push_str("Def2::Blank"); + } + } + + full_string +} diff --git a/ast/src/lang/core/def/def_to_def2.rs b/ast/src/lang/core/def/def_to_def2.rs new file mode 100644 index 0000000000..05cf2747a6 --- /dev/null +++ b/ast/src/lang/core/def/def_to_def2.rs @@ -0,0 +1,97 @@ +use bumpalo::collections::Vec as BumpVec; +use bumpalo::Bump; +use roc_parse::{parser::SyntaxError, pattern::PatternType}; +use roc_region::all::Region; + +use crate::lang::{ + core::{expr::expr_to_expr2::loc_expr_to_expr2, pattern::to_pattern2}, + env::Env, + scope::Scope, +}; + +use super::def2::Def2; + +pub fn defs_to_defs2<'a>( + arena: &'a Bump, + env: &mut Env<'a>, + scope: &mut Scope, + parsed_defs: &'a BumpVec>>, + region: Region, +) -> Vec { + parsed_defs + .iter() + .map(|loc| to_def2_from_def(arena, env, scope, &loc.value, region)) + .collect() +} + +pub fn to_def2_from_def<'a>( + arena: &'a Bump, + env: &mut Env<'a>, + scope: &mut Scope, + parsed_def: &'a roc_parse::ast::Def<'a>, + region: Region, +) -> Def2 { + use roc_parse::ast::Def::*; + + match parsed_def { + SpaceBefore(inner_def, _) => to_def2_from_def(arena, env, scope, inner_def, region), + SpaceAfter(inner_def, _) => to_def2_from_def(arena, env, scope, inner_def, region), + Body(&loc_pattern, &loc_expr) => { + // TODO loc_pattern use identifier + let expr2 = loc_expr_to_expr2(arena, loc_expr, env, scope, region).0; + let expr_id = env.pool.add(expr2); + + use roc_parse::ast::Pattern::*; + + match loc_pattern.value { + Identifier(_) => { + let (_, pattern2) = to_pattern2( + env, + scope, + PatternType::TopLevelDef, + &loc_pattern.value, + region, + ); + let pattern_id = env.pool.add(pattern2); + + // TODO support with annotation + Def2::ValueDef { + identifier_id: pattern_id, + expr_id, + } + } + other => { + unimplemented!( + "I don't yet know how to convert the pattern {:?} into an expr2", + other + ) + } + } + } + other => { + unimplemented!( + "I don't know how to make an expr2 from this def yet: {:?}", + other + ) + } + } +} + +pub fn str_to_def2<'a>( + arena: &'a Bump, + input: &'a str, + env: &mut Env<'a>, + scope: &mut Scope, + region: Region, +) -> Result, SyntaxError<'a>> { + match roc_parse::test_helpers::parse_defs_with(arena, input.trim()) { + Ok(vec_loc_def) => Ok(defs_to_defs2( + arena, + env, + scope, + arena.alloc(vec_loc_def), + region, + )), + Err(fail) => Err(fail), + } +} diff --git a/ast/src/lang/core/def/mod.rs b/ast/src/lang/core/def/mod.rs new file mode 100644 index 0000000000..7ab541b811 --- /dev/null +++ b/ast/src/lang/core/def/mod.rs @@ -0,0 +1,3 @@ +pub mod def; +pub mod def2; +pub mod def_to_def2; diff --git a/ast/src/lang/core/expr/expr2.rs b/ast/src/lang/core/expr/expr2.rs new file mode 100644 index 0000000000..e06d169ece --- /dev/null +++ b/ast/src/lang/core/expr/expr2.rs @@ -0,0 +1,233 @@ +use arraystring::{typenum::U30, ArrayString}; +use roc_types::subs::Variable; + +use crate::{ + lang::core::{fun_def::FunctionDef, pattern::Pattern2, val_def::ValueDef}, + mem_pool::{pool::NodeId, pool_str::PoolStr, pool_vec::PoolVec}, +}; +use roc_can::expr::Recursive; +use roc_module::low_level::LowLevel; +use roc_module::operator::CalledVia; +use roc_module::symbol::Symbol; + +use super::record_field::RecordField; + +pub type ArrString = ArrayString; + +// TODO make the inner types private? +pub type ExprId = NodeId; + +/// An Expr that fits in 32B. +/// It has a 1B discriminant and variants which hold payloads of at most 31B. +#[derive(Debug)] +pub enum Expr2 { + /// A negative number literal without a dot + SmallInt { + number: IntVal, // 16B + var: Variable, // 4B + style: IntStyle, // 1B + text: PoolStr, // 8B + }, + // TODO(rvcas): rename this eventually + /// A large (over 64-bit) negative number literal without a dot. + /// This variant can't use IntVal because if IntVal stored 128-bit + /// integers, it would be 32B on its own because of alignment. + I128 { + number: i128, // 16B + var: Variable, // 4B + style: IntStyle, // 1B + text: PoolStr, // 8B + }, + // TODO(rvcas): rename this eventually + /// A large (over 64-bit) nonnegative number literal without a dot + /// This variant can't use IntVal because if IntVal stored 128-bit + /// integers, it would be 32B on its own because of alignment. + U128 { + number: u128, // 16B + var: Variable, // 4B + style: IntStyle, // 1B + text: PoolStr, // 8B + }, + /// A floating-point literal (with a dot) + Float { + number: FloatVal, // 16B + var: Variable, // 4B + text: PoolStr, // 8B + }, + /// string literals of length up to 30B + SmallStr(ArrString), // 31B + /// string literals of length 31B or more + Str(PoolStr), // 8B + // Lookups + Var(Symbol), // 8B + InvalidLookup(PoolStr), // 8B + + List { + elem_var: Variable, // 4B + elems: PoolVec, // 8B + }, + If { + cond_var: Variable, // 4B + expr_var: Variable, // 4B + branches: PoolVec<(ExprId, ExprId)>, // 8B + final_else: ExprId, // 4B + }, + When { + cond_var: Variable, // 4B + expr_var: Variable, // 4B + branches: PoolVec, // 8B + cond: ExprId, // 4B + }, + LetRec { + defs: PoolVec, // 8B + body_var: Variable, // 8B + body_id: ExprId, // 4B + }, + LetFunction { + def_id: NodeId, // 4B + body_var: Variable, // 8B + body_id: ExprId, // 4B + }, + LetValue { + def_id: NodeId, // 4B + body_id: ExprId, // 4B + body_var: Variable, // 4B + }, + Call { + args: PoolVec<(Variable, ExprId)>, // 8B + expr: ExprId, // 4B + expr_var: Variable, // 4B + fn_var: Variable, // 4B + closure_var: Variable, // 4B + called_via: CalledVia, // 2B + }, + RunLowLevel { + op: LowLevel, // 1B + args: PoolVec<(Variable, ExprId)>, // 8B + ret_var: Variable, // 4B + }, + Closure { + args: PoolVec<(Variable, NodeId)>, // 8B + name: Symbol, // 8B + body: ExprId, // 4B + function_type: Variable, // 4B + recursive: Recursive, // 1B + extra: NodeId, // 4B + }, + // Product Types + Record { + record_var: Variable, // 4B + fields: PoolVec, // 8B + }, + /// Empty record constant + EmptyRecord, + /// Look up exactly one field on a record, e.g. (expr).foo. + Access { + field: PoolStr, // 4B + expr: ExprId, // 4B + record_var: Variable, // 4B + ext_var: Variable, // 4B + field_var: Variable, // 4B + }, + + /// field accessor as a function, e.g. (.foo) expr + Accessor { + function_var: Variable, // 4B + closure_var: Variable, // 4B + field: PoolStr, // 4B + record_var: Variable, // 4B + ext_var: Variable, // 4B + field_var: Variable, // 4B + }, + Update { + symbol: Symbol, // 8B + updates: PoolVec, // 8B + record_var: Variable, // 4B + ext_var: Variable, // 4B + }, + + // Sum Types + GlobalTag { + name: PoolStr, // 4B + variant_var: Variable, // 4B + ext_var: Variable, // 4B + arguments: PoolVec<(Variable, ExprId)>, // 8B + }, + PrivateTag { + name: Symbol, // 8B + variant_var: Variable, // 4B + ext_var: Variable, // 4B + arguments: PoolVec<(Variable, ExprId)>, // 8B + }, + Blank, // Rendered as empty box in editor + + // Compiles, but will crash if reached + RuntimeError(/* TODO make a version of RuntimeError that fits in 15B */), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum Problem { + RanOutOfNodeIds, +} + +pub type Res = Result; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum IntStyle { + Decimal, + Octal, + Hex, + Binary, +} + +impl IntStyle { + pub fn from_base(base: roc_parse::ast::Base) -> Self { + use roc_parse::ast::Base; + match base { + Base::Decimal => Self::Decimal, + Base::Octal => Self::Octal, + Base::Hex => Self::Hex, + Base::Binary => Self::Binary, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum IntVal { + I64(i64), + U64(u64), + I32(i32), + U32(u32), + I16(i16), + U16(u16), + I8(i8), + U8(u8), +} + +#[test] +fn size_of_intval() { + assert_eq!(std::mem::size_of::(), 16); +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum FloatVal { + F64(f64), + F32(f32), +} + +#[derive(Debug)] +pub struct WhenBranch { + pub patterns: PoolVec, // 4B + pub body: ExprId, // 3B + pub guard: Option, // 4B +} + +/// This is overflow data from a Closure variant, which needs to store +/// more than 32B of total data +#[derive(Debug)] +pub struct ClosureExtra { + pub return_type: Variable, // 4B + pub captured_symbols: PoolVec<(Symbol, Variable)>, // 8B + pub closure_type: Variable, // 4B + pub closure_ext_var: Variable, // 4B +} diff --git a/ast/src/lang/core/expr/expr2_to_string.rs b/ast/src/lang/core/expr/expr2_to_string.rs new file mode 100644 index 0000000000..cbd2967766 --- /dev/null +++ b/ast/src/lang/core/expr/expr2_to_string.rs @@ -0,0 +1,139 @@ +use crate::{ + lang::core::{expr::record_field::RecordField, val_def::value_def_to_string}, + mem_pool::pool::Pool, +}; + +use super::expr2::{Expr2, ExprId}; +use roc_types::subs::Variable; + +pub fn expr2_to_string(node_id: ExprId, pool: &Pool) -> String { + let mut full_string = String::new(); + let expr2 = pool.get(node_id); + + expr2_to_string_helper(expr2, 0, pool, &mut full_string); + + full_string +} + +fn get_spacing(indent_level: usize) -> String { + std::iter::repeat(" ") + .take(indent_level) + .collect::>() + .join("") +} + +fn expr2_to_string_helper( + expr2: &Expr2, + indent_level: usize, + pool: &Pool, + out_string: &mut String, +) { + out_string.push_str(&get_spacing(indent_level)); + + match expr2 { + Expr2::SmallStr(arr_string) => out_string.push_str(&format!( + "{}{}{}", + "SmallStr(\"", + arr_string.as_str(), + "\")", + )), + Expr2::Str(pool_str) => { + out_string.push_str(&format!("{}{}{}", "Str(\"", pool_str.as_str(pool), "\")",)) + } + Expr2::Blank => out_string.push_str("Blank"), + Expr2::EmptyRecord => out_string.push_str("EmptyRecord"), + Expr2::Record { record_var, fields } => { + out_string.push_str("Record:\n"); + out_string.push_str(&var_to_string(record_var, indent_level + 1)); + + out_string.push_str(&format!("{}fields: [\n", get_spacing(indent_level + 1))); + + let mut first_child = true; + + for field in fields.iter(pool) { + if !first_child { + out_string.push_str(", ") + } else { + first_child = false; + } + + match field { + RecordField::InvalidLabelOnly(pool_str, var) => { + out_string.push_str(&format!( + "{}({}, Var({:?})", + get_spacing(indent_level + 2), + pool_str.as_str(pool), + var, + )); + } + RecordField::LabelOnly(pool_str, var, symbol) => { + out_string.push_str(&format!( + "{}({}, Var({:?}), Symbol({:?})", + get_spacing(indent_level + 2), + pool_str.as_str(pool), + var, + symbol + )); + } + RecordField::LabeledValue(pool_str, var, val_node_id) => { + out_string.push_str(&format!( + "{}({}, Var({:?}), Expr2(\n", + get_spacing(indent_level + 2), + pool_str.as_str(pool), + var, + )); + + let val_expr2 = pool.get(*val_node_id); + expr2_to_string_helper(val_expr2, indent_level + 3, pool, out_string); + out_string.push_str(&format!("{})\n", get_spacing(indent_level + 2))); + } + } + } + + out_string.push_str(&format!("{}]\n", get_spacing(indent_level + 1))); + } + Expr2::List { elem_var, elems } => { + out_string.push_str("List:\n"); + out_string.push_str(&var_to_string(elem_var, indent_level + 1)); + out_string.push_str(&format!("{}elems: [\n", get_spacing(indent_level + 1))); + + let mut first_elt = true; + + for elem_expr2_id in elems.iter(pool) { + if !first_elt { + out_string.push_str(", ") + } else { + first_elt = false; + } + + let elem_expr2 = pool.get(*elem_expr2_id); + + expr2_to_string_helper(elem_expr2, indent_level + 2, pool, out_string) + } + + out_string.push_str(&format!("{}]\n", get_spacing(indent_level + 1))); + } + Expr2::InvalidLookup(pool_str) => { + out_string.push_str(&format!("InvalidLookup({})", pool_str.as_str(pool))); + } + Expr2::SmallInt { text, .. } => { + out_string.push_str(&format!("SmallInt({})", text.as_str(pool))); + } + Expr2::LetValue { + def_id, body_id, .. + } => { + out_string.push_str(&format!( + "LetValue(def_id: >>{:?}), body_id: >>{:?})", + value_def_to_string(pool.get(*def_id), pool), + pool.get(*body_id) + )); + } + other => todo!("Implement for {:?}", other), + } + + out_string.push('\n'); +} + +fn var_to_string(some_var: &Variable, indent_level: usize) -> String { + format!("{}Var({:?})\n", get_spacing(indent_level + 1), some_var) +} diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs new file mode 100644 index 0000000000..febad6b13a --- /dev/null +++ b/ast/src/lang/core/expr/expr_to_expr2.rs @@ -0,0 +1,710 @@ +use bumpalo::Bump; +use roc_can::expr::Recursive; +use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; +use roc_can::operator::desugar_expr; +use roc_collections::all::MutSet; +use roc_module::symbol::Symbol; +use roc_parse::{ast::Expr, pattern::PatternType}; +use roc_problem::can::{Problem, RuntimeError}; +use roc_region::all::{Located, Region}; + +use super::{expr2::Expr2, output::Output}; +use crate::canonicalization::canonicalize::{ + canonicalize_fields, canonicalize_lookup, canonicalize_when_branch, CanonicalizeRecordProblem, +}; +use crate::lang::core::declaration::decl_to_let; +use crate::lang::core::def::def::{canonicalize_defs, sort_can_defs}; +use crate::lang::core::expr::expr2::ClosureExtra; +use crate::lang::core::pattern::to_pattern2; +use crate::lang::core::str::flatten_str_literal; +use crate::mem_pool::shallow_clone::ShallowClone; +use crate::{ + lang::{ + core::expr::expr2::{ExprId, FloatVal, IntStyle, IntVal}, + env::Env, + scope::Scope, + }, + mem_pool::{pool_str::PoolStr, pool_vec::PoolVec}, +}; + +pub fn loc_expr_to_expr2<'a>( + arena: &'a Bump, + loc_expr: Located>, + env: &mut Env<'a>, + scope: &mut Scope, + region: Region, +) -> (Expr2, Output) { + let desugared_loc_expr = desugar_expr(arena, arena.alloc(loc_expr)); + + to_expr2(env, scope, arena.alloc(desugared_loc_expr.value), region) +} + +const ZERO: Region = Region::zero(); + +pub fn to_expr2<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + parse_expr: &'a roc_parse::ast::Expr<'a>, + region: Region, +) -> (Expr2, self::Output) { + use roc_parse::ast::Expr::*; + + match parse_expr { + Float(string) => { + match finish_parsing_float(string) { + Ok(float) => { + let expr = Expr2::Float { + number: FloatVal::F64(float), + var: env.var_store.fresh(), + text: PoolStr::new(string, &mut env.pool), + }; + + (expr, Output::default()) + } + Err((raw, error)) => { + // emit runtime error + let runtime_error = RuntimeError::InvalidFloat(error, ZERO, raw.into()); + + env.problem(Problem::RuntimeError(runtime_error)); + // + // Expr::RuntimeError(runtime_error) + todo!() + } + } + } + Num(string) => { + match finish_parsing_int(string) { + Ok(int) => { + let expr = Expr2::SmallInt { + number: IntVal::I64(int), + var: env.var_store.fresh(), + // TODO non-hardcode + style: IntStyle::Decimal, + text: PoolStr::new(string, &mut env.pool), + }; + + (expr, Output::default()) + } + Err((raw, error)) => { + // emit runtime error + let runtime_error = RuntimeError::InvalidInt( + error, + roc_parse::ast::Base::Decimal, + ZERO, + raw.into(), + ); + + env.problem(Problem::RuntimeError(runtime_error)); + // + // Expr::RuntimeError(runtime_error) + todo!() + } + } + } + NonBase10Int { + string, + base, + is_negative, + } => { + match finish_parsing_base(string, *base, *is_negative) { + Ok(int) => { + let expr = Expr2::SmallInt { + number: IntVal::I64(int), + var: env.var_store.fresh(), + // TODO non-hardcode + style: IntStyle::from_base(*base), + text: PoolStr::new(string, &mut env.pool), + }; + + (expr, Output::default()) + } + Err((raw, error)) => { + // emit runtime error + let runtime_error = RuntimeError::InvalidInt(error, *base, ZERO, raw.into()); + + env.problem(Problem::RuntimeError(runtime_error)); + // + // Expr::RuntimeError(runtime_error) + todo!() + } + } + } + + Str(literal) => flatten_str_literal(env, scope, literal), + + List { items, .. } => { + let mut output = Output::default(); + let output_ref = &mut output; + + let elems: PoolVec = PoolVec::with_capacity(items.len() as u32, env.pool); + + for (node_id, item) in elems.iter_node_ids().zip(items.iter()) { + let (expr, sub_output) = to_expr2(env, scope, &item.value, item.region); + + output_ref.union(sub_output); + + let expr_id = env.pool.add(expr); + env.pool[node_id] = expr_id; + } + + let expr = Expr2::List { + elem_var: env.var_store.fresh(), + elems, + }; + + (expr, output) + } + + GlobalTag(tag) => { + // a global tag without any arguments + ( + Expr2::GlobalTag { + name: PoolStr::new(tag, env.pool), + variant_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + arguments: PoolVec::empty(env.pool), + }, + Output::default(), + ) + } + PrivateTag(name) => { + // a private tag without any arguments + let ident_id = env.ident_ids.get_or_insert(&(*name).into()); + let name = Symbol::new(env.home, ident_id); + ( + Expr2::PrivateTag { + name, + variant_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + arguments: PoolVec::empty(env.pool), + }, + Output::default(), + ) + } + + RecordUpdate { + fields, + update: loc_update, + final_comments: _, + } => { + let (can_update, update_out) = + to_expr2(env, scope, &loc_update.value, loc_update.region); + + if let Expr2::Var(symbol) = &can_update { + match canonicalize_fields(env, scope, fields) { + Ok((can_fields, mut output)) => { + output.references.union_mut(update_out.references); + + let answer = Expr2::Update { + record_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + symbol: *symbol, + updates: can_fields, + }; + + (answer, output) + } + Err(CanonicalizeRecordProblem::InvalidOptionalValue { + field_name: _, + field_region: _, + record_region: _, + }) => { + // let runtime_error = roc_problem::can::RuntimeError::InvalidOptionalValue { + // field_name, + // field_region, + // record_region, + // }; + // + // env.problem(Problem::RuntimeError(runtime_error)); + + todo!() + } + } + } else { + // only (optionally qualified) variables can be updated, not arbitrary expressions + + // let error = roc_problem::can::RuntimeError::InvalidRecordUpdate { + // region: can_update.region, + // }; + // + // let answer = Expr::RuntimeError(error.clone()); + // + // env.problems.push(Problem::RuntimeError(error)); + // + // (answer, Output::default()) + todo!() + } + } + + Record { + fields, + final_comments: _, + } => { + if fields.is_empty() { + (Expr2::EmptyRecord, Output::default()) + } else { + match canonicalize_fields(env, scope, fields) { + Ok((can_fields, output)) => ( + Expr2::Record { + record_var: env.var_store.fresh(), + fields: can_fields, + }, + output, + ), + Err(CanonicalizeRecordProblem::InvalidOptionalValue { + field_name: _, + field_region: _, + record_region: _, + }) => { + // let runtime_error = RuntimeError::InvalidOptionalValue { + // field_name, + // field_region, + // record_region, + // }; + // + // env.problem(runtime_error); + // ( + // Expr::RuntimeError( + // ), + // Output::default(), + // + // ) + todo!() + } + } + } + } + + Access(record_expr, field) => { + // TODO + let region = ZERO; + let (record_expr_id, output) = to_expr_id(env, scope, record_expr, region); + + ( + Expr2::Access { + record_var: env.var_store.fresh(), + field_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + expr: record_expr_id, + field: PoolStr::new(field, env.pool), + }, + output, + ) + } + + AccessorFunction(field) => ( + Expr2::Accessor { + function_var: env.var_store.fresh(), + record_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + closure_var: env.var_store.fresh(), + field_var: env.var_store.fresh(), + field: PoolStr::new(field, env.pool), + }, + Output::default(), + ), + + If(branches, final_else) => { + let mut new_branches = Vec::with_capacity(branches.len()); + let mut output = Output::default(); + + for (condition, then_branch) in branches.iter() { + let (cond, cond_output) = to_expr2(env, scope, &condition.value, condition.region); + + let (then_expr, then_output) = + to_expr2(env, scope, &then_branch.value, then_branch.region); + + output.references.union_mut(cond_output.references); + output.references.union_mut(then_output.references); + + new_branches.push((env.pool.add(cond), env.pool.add(then_expr))); + } + + let (else_expr, else_output) = + to_expr2(env, scope, &final_else.value, final_else.region); + + output.references.union_mut(else_output.references); + + let expr = Expr2::If { + cond_var: env.var_store.fresh(), + expr_var: env.var_store.fresh(), + branches: PoolVec::new(new_branches.into_iter(), env.pool), + final_else: env.pool.add(else_expr), + }; + + (expr, output) + } + + When(loc_cond, branches) => { + // Infer the condition expression's type. + let cond_var = env.var_store.fresh(); + let (can_cond, mut output) = to_expr2(env, scope, &loc_cond.value, loc_cond.region); + + // the condition can never be a tail-call + output.tail_call = None; + + let can_branches = PoolVec::with_capacity(branches.len() as u32, env.pool); + + for (node_id, branch) in can_branches.iter_node_ids().zip(branches.iter()) { + let (can_when_branch, branch_references) = + canonicalize_when_branch(env, scope, *branch, &mut output); + + output.references.union_mut(branch_references); + + env.pool[node_id] = can_when_branch; + } + + // A "when" with no branches is a runtime error, but it will mess things up + // if code gen mistakenly thinks this is a tail call just because its condition + // happened to be one. (The condition gave us our initial output value.) + if branches.is_empty() { + output.tail_call = None; + } + + // Incorporate all three expressions into a combined Output value. + let expr = Expr2::When { + expr_var: env.var_store.fresh(), + cond_var, + cond: env.pool.add(can_cond), + branches: can_branches, + }; + + (expr, output) + } + + Closure(loc_arg_patterns, loc_body_expr) => { + // The globally unique symbol that will refer to this closure once it gets converted + // into a top-level procedure for code gen. + // + // In the Foo module, this will look something like Foo.$1 or Foo.$2. + let symbol = env + .closure_name_symbol + .unwrap_or_else(|| env.gen_unique_symbol()); + env.closure_name_symbol = None; + + // The body expression gets a new scope for canonicalization. + // Shadow `scope` to make sure we don't accidentally use the original one for the + // rest of this block, but keep the original around for later diffing. + let original_scope = scope; + let mut scope = original_scope.shallow_clone(); + let can_args = PoolVec::with_capacity(loc_arg_patterns.len() as u32, env.pool); + let mut output = Output::default(); + + let mut bound_by_argument_patterns = MutSet::default(); + + for (node_id, loc_pattern) in can_args.iter_node_ids().zip(loc_arg_patterns.iter()) { + let (new_output, can_arg) = to_pattern2( + env, + &mut scope, + roc_parse::pattern::PatternType::FunctionArg, + &loc_pattern.value, + loc_pattern.region, + ); + + bound_by_argument_patterns + .extend(new_output.references.bound_symbols.iter().copied()); + + output.union(new_output); + + let pattern_id = env.add(can_arg, loc_pattern.region); + env.pool[node_id] = (env.var_store.fresh(), pattern_id); + } + + let (body_expr, new_output) = + to_expr2(env, &mut scope, &loc_body_expr.value, loc_body_expr.region); + + let mut captured_symbols: MutSet = + new_output.references.lookups.iter().copied().collect(); + + // filter out the closure's name itself + captured_symbols.remove(&symbol); + + // symbols bound either in this pattern or deeper down are not captured! + captured_symbols.retain(|s| !new_output.references.bound_symbols.contains(s)); + captured_symbols.retain(|s| !bound_by_argument_patterns.contains(s)); + + // filter out top-level symbols + // those will be globally available, and don't need to be captured + captured_symbols.retain(|s| !env.top_level_symbols.contains(s)); + + // filter out imported symbols + // those will be globally available, and don't need to be captured + captured_symbols.retain(|s| s.module_id() == env.home); + + // TODO any Closure that has an empty `captured_symbols` list could be excluded! + + output.union(new_output); + + // filter out aliases + captured_symbols.retain(|s| !output.references.referenced_aliases.contains(s)); + + // filter out functions that don't close over anything + captured_symbols.retain(|s| !output.non_closures.contains(s)); + + // Now that we've collected all the references, check to see if any of the args we defined + // went unreferenced. If any did, report them as unused arguments. + for (sub_symbol, region) in scope.symbols() { + if !original_scope.contains_symbol(sub_symbol) { + if !output.references.has_lookup(sub_symbol) { + // The body never referenced this argument we declared. It's an unused argument! + env.problem(Problem::UnusedArgument(symbol, sub_symbol, region)); + } + + // We shouldn't ultimately count arguments as referenced locals. Otherwise, + // we end up with weird conclusions like the expression (\x -> x + 1) + // references the (nonexistant) local variable x! + output.references.lookups.remove(&sub_symbol); + } + } + + env.register_closure(symbol, output.references.clone()); + + let mut captured_symbols: Vec<_> = captured_symbols + .into_iter() + .map(|s| (s, env.var_store.fresh())) + .collect(); + + // sort symbols, so we know the order in which they're stored in the closure record + captured_symbols.sort(); + + // store that this function doesn't capture anything. It will be promoted to a + // top-level function, and does not need to be captured by other surrounding functions. + if captured_symbols.is_empty() { + output.non_closures.insert(symbol); + } + + let captured_symbols = PoolVec::new(captured_symbols.into_iter(), env.pool); + + let extra = ClosureExtra { + return_type: env.var_store.fresh(), // 4B + captured_symbols, // 8B + closure_type: env.var_store.fresh(), // 4B + closure_ext_var: env.var_store.fresh(), // 4B + }; + + ( + Expr2::Closure { + function_type: env.var_store.fresh(), + name: symbol, + recursive: Recursive::NotRecursive, + args: can_args, + body: env.add(body_expr, loc_body_expr.region), + extra: env.pool.add(extra), + }, + output, + ) + } + + Apply(loc_fn, loc_args, application_style) => { + // The expression that evaluates to the function being called, e.g. `foo` in + // (foo) bar baz + let fn_region = loc_fn.region; + + // Canonicalize the function expression and its arguments + let (fn_expr, mut output) = to_expr2(env, scope, &loc_fn.value, fn_region); + + // The function's return type + let args = PoolVec::with_capacity(loc_args.len() as u32, env.pool); + + for (node_id, loc_arg) in args.iter_node_ids().zip(loc_args.iter()) { + let (arg_expr_id, arg_out) = to_expr_id(env, scope, &loc_arg.value, loc_arg.region); + + env.pool[node_id] = (env.var_store.fresh(), arg_expr_id); + + output.references.union_mut(arg_out.references); + } + + // Default: We're not tail-calling a symbol (by name), we're tail-calling a function value. + output.tail_call = None; + + let expr = match fn_expr { + Expr2::Var(ref symbol) => { + output.references.calls.insert(*symbol); + + // we're tail-calling a symbol by name, check if it's the tail-callable symbol + output.tail_call = match &env.tailcallable_symbol { + Some(tc_sym) if *tc_sym == *symbol => Some(*symbol), + Some(_) | None => None, + }; + + // IDEA: Expr2::CallByName? + let fn_expr_id = env.add(fn_expr, fn_region); + Expr2::Call { + args, + expr: fn_expr_id, + expr_var: env.var_store.fresh(), + fn_var: env.var_store.fresh(), + closure_var: env.var_store.fresh(), + called_via: *application_style, + } + } + Expr2::RuntimeError() => { + // We can't call a runtime error; bail out by propagating it! + return (fn_expr, output); + } + Expr2::GlobalTag { + variant_var, + ext_var, + name, + .. + } => Expr2::GlobalTag { + variant_var, + ext_var, + name, + arguments: args, + }, + Expr2::PrivateTag { + variant_var, + ext_var, + name, + .. + } => Expr2::PrivateTag { + variant_var, + ext_var, + name, + arguments: args, + }, + _ => { + // This could be something like ((if True then fn1 else fn2) arg1 arg2). + let fn_expr_id = env.add(fn_expr, fn_region); + Expr2::Call { + args, + expr: fn_expr_id, + expr_var: env.var_store.fresh(), + fn_var: env.var_store.fresh(), + closure_var: env.var_store.fresh(), + called_via: *application_style, + } + } + }; + + (expr, output) + } + + Defs(loc_defs, loc_ret) => { + let (unsorted, mut scope, defs_output, symbols_introduced) = canonicalize_defs( + env, + Output::default(), + scope, + loc_defs, + PatternType::DefExpr, + ); + + // The def as a whole is a tail call iff its return expression is a tail call. + // Use its output as a starting point because its tail_call already has the right answer! + let (ret_expr, mut output) = to_expr2(env, &mut scope, &loc_ret.value, loc_ret.region); + + output + .introduced_variables + .union(&defs_output.introduced_variables); + + output.references.union_mut(defs_output.references); + + // Now that we've collected all the references, check to see if any of the new idents + // we defined went unused by the return expression. If any were unused, report it. + for (symbol, region) in symbols_introduced { + if !output.references.has_lookup(symbol) { + env.problem(Problem::UnusedDef(symbol, region)); + } + } + + let (can_defs, output) = sort_can_defs(env, unsorted, output); + + match can_defs { + Ok(decls) => { + let mut expr = ret_expr; + + for declaration in decls.into_iter().rev() { + expr = decl_to_let(env.pool, env.var_store, declaration, expr); + } + + (expr, output) + } + Err(_err) => { + // TODO: fix this to be something from Expr2 + // (RuntimeError(err), output) + todo!() + } + } + } + + PrecedenceConflict { .. } => { + // use roc_problem::can::RuntimeError::*; + // + // let problem = PrecedenceProblem::BothNonAssociative( + // *whole_region, + // binop1.clone(), + // binop2.clone(), + // ); + // + // env.problem(Problem::PrecedenceProblem(problem.clone())); + // + // ( + // RuntimeError(InvalidPrecedence(problem, region)), + // Output::default(), + // ) + todo!() + } + MalformedClosure => { + // use roc_problem::can::RuntimeError::*; + // (RuntimeError(MalformedClosure(region)), Output::default()) + todo!() + } + MalformedIdent(_name, _problem) => { + // use roc_problem::can::RuntimeError::*; + // + // let problem = MalformedIdentifier((*name).into(), region); + // env.problem(Problem::RuntimeError(problem.clone())); + // + // (RuntimeError(problem), Output::default()) + todo!() + } + Var { module_name, ident } => canonicalize_lookup(env, scope, module_name, ident, region), + + // Below this point, we shouln't see any of these nodes anymore because + // operator desugaring should have removed them! + bad_expr @ ParensAround(_) => { + panic!( + "A ParensAround did not get removed during operator desugaring somehow: {:#?}", + bad_expr + ); + } + bad_expr @ SpaceBefore(_, _) => { + panic!( + "A SpaceBefore did not get removed during operator desugaring somehow: {:#?}", + bad_expr + ); + } + bad_expr @ SpaceAfter(_, _) => { + panic!( + "A SpaceAfter did not get removed during operator desugaring somehow: {:#?}", + bad_expr + ); + } + bad_expr @ BinOps { .. } => { + panic!( + "A binary operator chain did not get desugared somehow: {:#?}", + bad_expr + ); + } + bad_expr @ UnaryOp(_, _) => { + panic!( + "A unary operator did not get desugared somehow: {:#?}", + bad_expr + ); + } + + rest => todo!("not yet implemented {:?}", rest), + } +} + +pub fn to_expr_id<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + parse_expr: &'a roc_parse::ast::Expr<'a>, + region: Region, +) -> (ExprId, Output) { + let (expr, output) = to_expr2(env, scope, parse_expr, region); + + (env.add(expr, region), output) +} diff --git a/ast/src/lang/core/expr/introduced_vars.rs b/ast/src/lang/core/expr/introduced_vars.rs new file mode 100644 index 0000000000..0abb087815 --- /dev/null +++ b/ast/src/lang/core/expr/introduced_vars.rs @@ -0,0 +1,51 @@ +use roc_collections::all::MutMap; +use roc_module::ident::Lowercase; +use roc_module::symbol::Symbol; +use roc_types::subs::Variable; + +#[derive(Clone, Debug, PartialEq, Default)] +pub struct IntroducedVariables { + // Rigids must be unique within a type annoation. + // E.g. in `identity : a -> a`, there should only be one + // variable (a rigid one, with name "a"). + // Hence `rigids : Map` + // + // But then between annotations, the same name can occur multiple times, + // but a variable can only have one name. Therefore + // `ftv : Map`. + pub wildcards: Vec, + pub var_by_name: MutMap, + pub name_by_var: MutMap, + pub host_exposed_aliases: MutMap, +} + +impl IntroducedVariables { + pub fn insert_named(&mut self, name: Lowercase, var: Variable) { + self.var_by_name.insert(name.clone(), var); + self.name_by_var.insert(var, name); + } + + pub fn insert_wildcard(&mut self, var: Variable) { + self.wildcards.push(var); + } + + pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) { + self.host_exposed_aliases.insert(symbol, var); + } + + pub fn union(&mut self, other: &Self) { + self.wildcards.extend(other.wildcards.iter().cloned()); + self.var_by_name.extend(other.var_by_name.clone()); + self.name_by_var.extend(other.name_by_var.clone()); + self.host_exposed_aliases + .extend(other.host_exposed_aliases.clone()); + } + + pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> { + self.var_by_name.get(name) + } + + pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> { + self.name_by_var.get(&var) + } +} diff --git a/ast/src/lang/core/expr/mod.rs b/ast/src/lang/core/expr/mod.rs new file mode 100644 index 0000000000..32d768c4c9 --- /dev/null +++ b/ast/src/lang/core/expr/mod.rs @@ -0,0 +1,6 @@ +pub mod expr2; +pub mod expr2_to_string; +pub(crate) mod expr_to_expr2; +mod introduced_vars; +pub(crate) mod output; +pub mod record_field; diff --git a/ast/src/lang/core/expr/output.rs b/ast/src/lang/core/expr/output.rs new file mode 100644 index 0000000000..4287e3f72f --- /dev/null +++ b/ast/src/lang/core/expr/output.rs @@ -0,0 +1,30 @@ +use crate::{ + lang::core::{def::def::References, types::Alias}, + mem_pool::pool::NodeId, +}; +use roc_collections::all::{MutMap, MutSet}; +use roc_module::symbol::Symbol; + +use super::introduced_vars::IntroducedVariables; + +#[derive(Clone, Default, Debug, PartialEq)] +pub struct Output { + pub references: References, + pub tail_call: Option, + pub introduced_variables: IntroducedVariables, + pub aliases: MutMap>, + pub non_closures: MutSet, +} + +impl Output { + pub fn union(&mut self, other: Self) { + self.references.union_mut(other.references); + + if let (None, Some(later)) = (self.tail_call, other.tail_call) { + self.tail_call = Some(later); + } + + self.aliases.extend(other.aliases); + self.non_closures.extend(other.non_closures); + } +} diff --git a/ast/src/lang/core/expr/record_field.rs b/ast/src/lang/core/expr/record_field.rs new file mode 100644 index 0000000000..aaf464799f --- /dev/null +++ b/ast/src/lang/core/expr/record_field.rs @@ -0,0 +1,49 @@ +use roc_types::subs::Variable; + +use crate::mem_pool::pool_str::PoolStr; +use roc_module::symbol::Symbol; + +use super::expr2::ExprId; + +#[derive(Debug)] +pub enum RecordField { + InvalidLabelOnly(PoolStr, Variable), + LabelOnly(PoolStr, Variable, Symbol), + LabeledValue(PoolStr, Variable, ExprId), +} + +use RecordField::*; + +impl RecordField { + pub fn get_record_field_var(&self) -> &Variable { + match self { + InvalidLabelOnly(_, var) => var, + LabelOnly(_, var, _) => var, + LabeledValue(_, var, _) => var, + } + } + + pub fn get_record_field_pool_str(&self) -> &PoolStr { + match self { + InvalidLabelOnly(pool_str, _) => pool_str, + LabelOnly(pool_str, _, _) => pool_str, + LabeledValue(pool_str, _, _) => pool_str, + } + } + + pub fn get_record_field_pool_str_mut(&mut self) -> &mut PoolStr { + match self { + InvalidLabelOnly(pool_str, _) => pool_str, + LabelOnly(pool_str, _, _) => pool_str, + LabeledValue(pool_str, _, _) => pool_str, + } + } + + pub fn get_record_field_val_node_id(&self) -> Option { + match self { + InvalidLabelOnly(_, _) => None, + LabelOnly(_, _, _) => None, + LabeledValue(_, _, field_val_id) => Some(*field_val_id), + } + } +} diff --git a/ast/src/lang/core/fun_def.rs b/ast/src/lang/core/fun_def.rs new file mode 100644 index 0000000000..588d07d996 --- /dev/null +++ b/ast/src/lang/core/fun_def.rs @@ -0,0 +1,61 @@ +use crate::{ + lang::rigids::Rigids, + mem_pool::{pool::NodeId, pool_vec::PoolVec, shallow_clone::ShallowClone}, +}; +use roc_module::symbol::Symbol; +use roc_types::subs::Variable; + +use super::{ + expr::expr2::ExprId, + pattern::PatternId, + types::{Type2, TypeId}, +}; + +#[derive(Debug)] +pub enum FunctionDef { + WithAnnotation { + name: Symbol, // 8B + arguments: PoolVec<(PatternId, Type2)>, // 8B + rigids: NodeId, // 4B + return_type: TypeId, // 4B + body: ExprId, // 4B + }, + NoAnnotation { + name: Symbol, // 8B + arguments: PoolVec<(PatternId, Variable)>, // 8B + return_var: Variable, // 4B + body: ExprId, // 4B + }, +} + +impl ShallowClone for FunctionDef { + fn shallow_clone(&self) -> Self { + match self { + Self::WithAnnotation { + name, + arguments, + rigids, + return_type, + body, + } => Self::WithAnnotation { + name: *name, + arguments: arguments.shallow_clone(), + rigids: *rigids, + return_type: *return_type, + body: *body, + }, + + Self::NoAnnotation { + name, + arguments, + return_var, + body, + } => Self::NoAnnotation { + name: *name, + arguments: arguments.shallow_clone(), + return_var: *return_var, + body: *body, + }, + } + } +} diff --git a/ast/src/lang/core/header.rs b/ast/src/lang/core/header.rs new file mode 100644 index 0000000000..4b10dad537 --- /dev/null +++ b/ast/src/lang/core/header.rs @@ -0,0 +1,10 @@ +use super::expr::expr2::ExprId; + +#[derive(Debug)] +pub struct AppHeader { + pub app_name: String, + pub packages_base: String, + pub imports: Vec, + pub provides: Vec, + pub ast_node_id: ExprId, // TODO probably want to create and use HeaderId +} diff --git a/ast/src/lang/core/mod.rs b/ast/src/lang/core/mod.rs new file mode 100644 index 0000000000..74300dab4f --- /dev/null +++ b/ast/src/lang/core/mod.rs @@ -0,0 +1,10 @@ +pub mod ast; +mod declaration; +pub mod def; +pub mod expr; +mod fun_def; +pub mod header; +pub mod pattern; +pub mod str; +pub mod types; +pub mod val_def; diff --git a/editor/src/lang/pattern.rs b/ast/src/lang/core/pattern.rs similarity index 97% rename from editor/src/lang/pattern.rs rename to ast/src/lang/core/pattern.rs index aae7797e8a..19a956bcd8 100644 --- a/editor/src/lang/pattern.rs +++ b/ast/src/lang/core/pattern.rs @@ -1,11 +1,7 @@ #![allow(clippy::all)] #![allow(dead_code)] #![allow(unused_imports)] -use crate::editor::ed_error::{EdResult, UnexpectedPattern2Variant}; -use crate::lang::ast::{ExprId, FloatVal, IntVal}; -use crate::lang::expr::{to_expr_id, Env, Output}; -use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone}; -use crate::lang::scope::Scope; + use bumpalo::collections::Vec as BumpVec; use roc_can::expr::unescape_char; use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; @@ -17,7 +13,18 @@ use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError}; use roc_region::all::Region; use roc_types::subs::Variable; -use super::constrain::Constraint; +use crate::ast_error::{ASTResult, UnexpectedPattern2Variant}; +use crate::constrain::Constraint; +use crate::lang::core::expr::expr_to_expr2::to_expr_id; +use crate::lang::env::Env; +use crate::lang::scope::Scope; +use crate::mem_pool::pool::{NodeId, Pool}; +use crate::mem_pool::pool_str::PoolStr; +use crate::mem_pool::pool_vec::PoolVec; +use crate::mem_pool::shallow_clone::ShallowClone; + +use super::expr::expr2::{ExprId, FloatVal, IntVal}; +use super::expr::output::Output; use super::types::Type2; pub type PatternId = NodeId; @@ -483,7 +490,7 @@ pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec { symbols } -pub fn get_identifier_string(pattern: &Pattern2, interns: &Interns) -> EdResult { +pub fn get_identifier_string(pattern: &Pattern2, interns: &Interns) -> ASTResult { match pattern { Pattern2::Identifier(symbol) => Ok(symbol.ident_str(interns).to_string()), other => UnexpectedPattern2Variant { @@ -569,7 +576,7 @@ fn underscore_in_def<'a>(env: &mut Env<'a>, region: Region) -> Pattern2 { Pattern2::UnsupportedPattern(region) } -fn flatten_str_literal(pool: &mut Pool, literal: &StrLiteral<'_>) -> Pattern2 { +pub(crate) fn flatten_str_literal(pool: &mut Pool, literal: &StrLiteral<'_>) -> Pattern2 { use roc_parse::ast::StrLiteral::*; match literal { @@ -579,7 +586,7 @@ fn flatten_str_literal(pool: &mut Pool, literal: &StrLiteral<'_>) -> Pattern2 { } } -fn flatten_str_lines(pool: &mut Pool, lines: &[&[StrSegment<'_>]]) -> Pattern2 { +pub(crate) fn flatten_str_lines(pool: &mut Pool, lines: &[&[StrSegment<'_>]]) -> Pattern2 { use StrSegment::*; let mut buf = String::new(); diff --git a/ast/src/lang/core/str.rs b/ast/src/lang/core/str.rs new file mode 100644 index 0000000000..53b0a999cf --- /dev/null +++ b/ast/src/lang/core/str.rs @@ -0,0 +1,228 @@ +use roc_module::{operator::CalledVia, symbol::Symbol}; +use roc_parse::ast::StrLiteral; + +use crate::{ + ast_error::{ASTResult, UnexpectedASTNode}, + lang::{core::expr::expr_to_expr2::to_expr2, env::Env, scope::Scope}, + mem_pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec}, +}; + +use super::expr::{ + expr2::{Expr2, ExprId}, + output::Output, +}; + +pub(crate) fn flatten_str_literal<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + literal: &StrLiteral<'a>, +) -> (Expr2, Output) { + use roc_parse::ast::StrLiteral::*; + + match literal { + PlainLine(str_slice) => { + // TODO use smallstr + let expr = Expr2::Str(PoolStr::new(str_slice, &mut env.pool)); + + (expr, Output::default()) + } + Line(segments) => flatten_str_lines(env, scope, &[segments]), + Block(lines) => flatten_str_lines(env, scope, lines), + } +} + +enum StrSegment { + Interpolation(Expr2), + Plaintext(PoolStr), +} + +fn flatten_str_lines<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + lines: &[&[roc_parse::ast::StrSegment<'a>]], +) -> (Expr2, Output) { + use roc_parse::ast::StrSegment::*; + + let mut buf = String::new(); + let mut segments = Vec::new(); + let mut output = Output::default(); + + for line in lines { + for segment in line.iter() { + match segment { + Plaintext(string) => { + buf.push_str(string); + } + Unicode(loc_hex_digits) => match u32::from_str_radix(loc_hex_digits.value, 16) { + Ok(code_pt) => match std::char::from_u32(code_pt) { + Some(ch) => { + buf.push(ch); + } + None => { + // env.problem(Problem::InvalidUnicodeCodePt(loc_hex_digits.region)); + // + // return ( + // Expr::RuntimeError(RuntimeError::InvalidUnicodeCodePt( + // loc_hex_digits.region, + // )), + // output, + // ); + todo!() + } + }, + Err(_) => { + // env.problem(Problem::InvalidHexadecimal(loc_hex_digits.region)); + // + // return ( + // Expr::RuntimeError(RuntimeError::InvalidHexadecimal( + // loc_hex_digits.region, + // )), + // output, + // ); + todo!() + } + }, + Interpolated(loc_expr) => { + if roc_can::expr::is_valid_interpolation(loc_expr.value) { + // Interpolations desugar to Str.concat calls + output.references.calls.insert(Symbol::STR_CONCAT); + + if !buf.is_empty() { + segments.push(StrSegment::Plaintext(PoolStr::new(&buf, &mut env.pool))); + + buf = String::new(); + } + + let (loc_expr, new_output) = + to_expr2(env, scope, loc_expr.value, loc_expr.region); + + output.union(new_output); + + segments.push(StrSegment::Interpolation(loc_expr)); + } else { + // env.problem(Problem::InvalidInterpolation(loc_expr.region)); + // + // return ( + // Expr::RuntimeError(RuntimeError::InvalidInterpolation(loc_expr.region)), + // output, + // ); + todo!() + } + } + EscapedChar(escaped) => buf.push(roc_can::expr::unescape_char(escaped)), + } + } + } + + if !buf.is_empty() { + segments.push(StrSegment::Plaintext(PoolStr::new(&buf, &mut env.pool))); + } + + (desugar_str_segments(env, segments), output) +} + +/// Resolve string interpolations by desugaring a sequence of StrSegments +/// into nested calls to Str.concat +fn desugar_str_segments(env: &mut Env, segments: Vec) -> Expr2 { + use StrSegment::*; + + let pool = &mut env.pool; + let var_store = &mut env.var_store; + + let mut iter = segments.into_iter().rev(); + let mut expr = match iter.next() { + Some(Plaintext(pool_str)) => Expr2::Str(pool_str), + Some(Interpolation(expr_id)) => expr_id, + None => { + // No segments? Empty string! + + let pool_str = PoolStr::new("", pool); + Expr2::Str(pool_str) + } + }; + + for seg in iter { + let new_expr = match seg { + Plaintext(string) => Expr2::Str(string), + Interpolation(expr_id) => expr_id, + }; + + let concat_expr_id = pool.add(Expr2::Var(Symbol::STR_CONCAT)); + + let args = vec![ + (var_store.fresh(), pool.add(new_expr)), + (var_store.fresh(), pool.add(expr)), + ]; + let args = PoolVec::new(args.into_iter(), pool); + + let new_call = Expr2::Call { + args, + expr: concat_expr_id, + expr_var: var_store.fresh(), + fn_var: var_store.fresh(), + closure_var: var_store.fresh(), + called_via: CalledVia::Space, + }; + + expr = new_call + } + + expr +} + +pub fn update_str_expr( + node_id: ExprId, + new_char: char, + insert_index: usize, + pool: &mut Pool, +) -> ASTResult<()> { + let str_expr = pool.get_mut(node_id); + + enum Either { + MyString(String), + MyPoolStr(PoolStr), + Done, + } + + let insert_either = match str_expr { + Expr2::SmallStr(arr_string) => { + let insert_res = arr_string.try_insert(insert_index as u8, new_char); + + match insert_res { + Ok(_) => Either::Done, + _ => { + let mut new_string = arr_string.as_str().to_string(); + new_string.insert(insert_index, new_char); + + Either::MyString(new_string) + } + } + } + Expr2::Str(old_pool_str) => Either::MyPoolStr(*old_pool_str), + other => UnexpectedASTNode { + required_node_type: "SmallStr or Str", + encountered_node_type: format!("{:?}", other), + } + .fail()?, + }; + + match insert_either { + Either::MyString(new_string) => { + let new_pool_str = PoolStr::new(&new_string, pool); + + pool.set(node_id, Expr2::Str(new_pool_str)) + } + Either::MyPoolStr(old_pool_str) => { + let mut new_string = old_pool_str.as_str(pool).to_owned(); + + new_string.insert(insert_index, new_char); + + let new_pool_str = PoolStr::new(&new_string, pool); + + pool.set(node_id, Expr2::Str(new_pool_str)) + } + Either::Done => (), + } + + Ok(()) +} diff --git a/editor/src/lang/types.rs b/ast/src/lang/core/types.rs similarity index 99% rename from editor/src/lang/types.rs rename to ast/src/lang/core/types.rs index c935901acd..107a6e7946 100644 --- a/editor/src/lang/types.rs +++ b/ast/src/lang/core/types.rs @@ -1,9 +1,6 @@ #![allow(clippy::all)] #![allow(dead_code)] #![allow(unused_imports)] -use crate::lang::expr::Env; -use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone}; -use crate::lang::scope::Scope; // use roc_can::expr::Output; use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{Ident, TagName}; @@ -12,6 +9,13 @@ use roc_region::all::{Located, Region}; use roc_types::types::{Problem, RecordField}; use roc_types::{subs::Variable, types::ErrorType}; +use crate::lang::env::Env; +use crate::lang::scope::Scope; +use crate::mem_pool::pool::{NodeId, Pool}; +use crate::mem_pool::pool_str::PoolStr; +use crate::mem_pool::pool_vec::PoolVec; +use crate::mem_pool::shallow_clone::ShallowClone; + pub type TypeId = NodeId; #[derive(Debug)] diff --git a/ast/src/lang/core/val_def.rs b/ast/src/lang/core/val_def.rs new file mode 100644 index 0000000000..b30bfb3ace --- /dev/null +++ b/ast/src/lang/core/val_def.rs @@ -0,0 +1,101 @@ +use crate::{ + lang::{core::expr::expr2_to_string::expr2_to_string, rigids::Rigids}, + mem_pool::{ + pool::{NodeId, Pool}, + shallow_clone::ShallowClone, + }, +}; +use roc_types::subs::Variable; + +use super::{ + expr::expr2::ExprId, + pattern::{Pattern2, PatternId}, + types::TypeId, +}; + +#[derive(Debug)] +pub enum ValueDef { + WithAnnotation { + pattern_id: PatternId, // 4B + expr_id: ExprId, // 4B + type_id: TypeId, + rigids: Rigids, + expr_var: Variable, // 4B + }, + NoAnnotation { + pattern_id: PatternId, // 4B + expr_id: ExprId, // 4B + expr_var: Variable, // 4B + }, +} + +impl ShallowClone for ValueDef { + fn shallow_clone(&self) -> Self { + match self { + Self::WithAnnotation { + pattern_id, + expr_id, + type_id, + rigids, + expr_var, + } => Self::WithAnnotation { + pattern_id: *pattern_id, + expr_id: *expr_id, + type_id: *type_id, + rigids: rigids.shallow_clone(), + expr_var: *expr_var, + }, + Self::NoAnnotation { + pattern_id, + expr_id, + expr_var, + } => Self::NoAnnotation { + pattern_id: *pattern_id, + expr_id: *expr_id, + expr_var: *expr_var, + }, + } + } +} + +impl ValueDef { + pub fn get_expr_id(&self) -> ExprId { + match self { + ValueDef::WithAnnotation { expr_id, .. } => *expr_id, + ValueDef::NoAnnotation { expr_id, .. } => *expr_id, + } + } + + pub fn get_pattern_id(&self) -> NodeId { + match self { + ValueDef::WithAnnotation { pattern_id, .. } => *pattern_id, + ValueDef::NoAnnotation { pattern_id, .. } => *pattern_id, + } + } +} + +pub fn value_def_to_string(val_def: &ValueDef, pool: &Pool) -> String { + match val_def { + ValueDef::WithAnnotation { + pattern_id, + expr_id, + type_id, + rigids, + expr_var, + } => { + format!("WithAnnotation {{ pattern_id: {:?}, expr_id: {:?}, type_id: {:?}, rigids: {:?}, expr_var: {:?}}}", pool.get(*pattern_id), expr2_to_string(*expr_id, pool), pool.get(*type_id), rigids, expr_var) + } + ValueDef::NoAnnotation { + pattern_id, + expr_id, + expr_var, + } => { + format!( + "NoAnnotation {{ pattern_id: {:?}, expr_id: {:?}, expr_var: {:?}}}", + pool.get(*pattern_id), + expr2_to_string(*expr_id, pool), + expr_var + ) + } + } +} diff --git a/ast/src/lang/env.rs b/ast/src/lang/env.rs new file mode 100644 index 0000000000..87a048151e --- /dev/null +++ b/ast/src/lang/env.rs @@ -0,0 +1,168 @@ +use bumpalo::{collections::Vec as BumpVec, Bump}; +use roc_collections::all::{MutMap, MutSet}; +use roc_module::ident::{Ident, ModuleName}; +use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; +use roc_problem::can::{Problem, RuntimeError}; +use roc_region::all::{Located, Region}; +use roc_types::subs::VarStore; + +use crate::mem_pool::pool::{NodeId, Pool}; + +use super::core::def::def::References; + +#[derive(Debug)] +pub struct Env<'a> { + pub home: ModuleId, + pub var_store: &'a mut VarStore, + pub pool: &'a mut Pool, + pub arena: &'a Bump, + + pub problems: BumpVec<'a, Problem>, + + pub dep_idents: MutMap, + pub module_ids: &'a ModuleIds, + pub ident_ids: IdentIds, + pub exposed_ident_ids: IdentIds, + + pub closures: MutMap, + /// Symbols which were referenced by qualified lookups. + pub qualified_lookups: MutSet, + + pub top_level_symbols: MutSet, + + pub closure_name_symbol: Option, + pub tailcallable_symbol: Option, +} + +impl<'a> Env<'a> { + pub fn new( + home: ModuleId, + arena: &'a Bump, + pool: &'a mut Pool, + var_store: &'a mut VarStore, + dep_idents: MutMap, + module_ids: &'a ModuleIds, + exposed_ident_ids: IdentIds, + ) -> Env<'a> { + Env { + home, + arena, + pool, + problems: BumpVec::new_in(arena), + var_store, + dep_idents, + module_ids, + ident_ids: exposed_ident_ids.clone(), // we start with these, but will add more later + exposed_ident_ids, + closures: MutMap::default(), + qualified_lookups: MutSet::default(), + tailcallable_symbol: None, + closure_name_symbol: None, + top_level_symbols: MutSet::default(), + } + } + + pub fn add(&mut self, item: T, region: Region) -> NodeId { + let id = self.pool.add(item); + self.set_region(id, region); + + id + } + + pub fn problem(&mut self, problem: Problem) { + self.problems.push(problem); + } + + pub fn set_region(&mut self, _node_id: NodeId, _region: Region) { + dbg!("Don't Forget to set the region eventually"); + } + + pub fn register_closure(&mut self, symbol: Symbol, references: References) { + self.closures.insert(symbol, references); + } + + /// Generates a unique, new symbol like "$1" or "$5", + /// using the home module as the module_id. + /// + /// This is used, for example, during canonicalization of an Expr::Closure + /// to generate a unique symbol to refer to that closure. + pub fn gen_unique_symbol(&mut self) -> Symbol { + let ident_id = self.ident_ids.gen_unique(); + + Symbol::new(self.home, ident_id) + } + + /// Returns Err if the symbol resolved, but it was not exposed by the given module + pub fn qualified_lookup( + &mut self, + module_name: &str, + ident: &str, + region: Region, + ) -> Result { + debug_assert!( + !module_name.is_empty(), + "Called env.qualified_lookup with an unqualified ident: {:?}", + ident + ); + + let module_name: ModuleName = module_name.into(); + + match self.module_ids.get_id(&module_name) { + Some(&module_id) => { + let ident: Ident = ident.into(); + + // You can do qualified lookups on your own module, e.g. + // if I'm in the Foo module, I can do a `Foo.bar` lookup. + if module_id == self.home { + match self.ident_ids.get_id(&ident) { + Some(ident_id) => { + let symbol = Symbol::new(module_id, *ident_id); + + self.qualified_lookups.insert(symbol); + + Ok(symbol) + } + None => Err(RuntimeError::LookupNotInScope( + Located { + value: ident, + region, + }, + self.ident_ids + .idents() + .map(|(_, string)| string.as_ref().into()) + .collect(), + )), + } + } 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); + + self.qualified_lookups.insert(symbol); + + Ok(symbol) + } + None => Err(RuntimeError::ValueNotExposed { + module_name, + ident, + region, + }), + } + } + } + None => Err(RuntimeError::ModuleNotImported { + module_name, + imported_modules: self + .module_ids + .available_modules() + .map(|string| string.as_ref().into()) + .collect(), + region, + }), + } + } +} diff --git a/ast/src/lang/mod.rs b/ast/src/lang/mod.rs new file mode 100644 index 0000000000..fa21ea740d --- /dev/null +++ b/ast/src/lang/mod.rs @@ -0,0 +1,4 @@ +pub mod core; +pub mod env; +mod rigids; +pub mod scope; diff --git a/ast/src/lang/rigids.rs b/ast/src/lang/rigids.rs new file mode 100644 index 0000000000..a10dce0016 --- /dev/null +++ b/ast/src/lang/rigids.rs @@ -0,0 +1,81 @@ +use std::{ + collections::{HashMap, HashSet}, + hash::BuildHasherDefault, +}; + +use crate::mem_pool::{ + pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone, +}; +use roc_collections::all::WyHash; +use roc_types::subs::Variable; + +#[derive(Debug)] +pub struct Rigids { + pub names: PoolVec<(Option, Variable)>, // 8B + padding: [u8; 1], +} + +#[allow(clippy::needless_collect)] +impl Rigids { + pub fn new( + named: HashMap<&str, Variable, BuildHasherDefault>, + unnamed: HashSet>, + pool: &mut Pool, + ) -> Self { + let names = PoolVec::with_capacity((named.len() + unnamed.len()) as u32, pool); + + let mut temp_names = Vec::new(); + + temp_names.extend(named.iter().map(|(name, var)| (Some(*name), *var))); + + temp_names.extend(unnamed.iter().map(|var| (None, *var))); + + for (node_id, (opt_name, variable)) in names.iter_node_ids().zip(temp_names) { + let poolstr = opt_name.map(|name| PoolStr::new(name, pool)); + + pool[node_id] = (poolstr, variable); + } + + Self { + names, + padding: Default::default(), + } + } + + pub fn named(&self, pool: &mut Pool) -> PoolVec<(PoolStr, Variable)> { + let named = self + .names + .iter(pool) + .filter_map(|(opt_pool_str, var)| { + opt_pool_str.as_ref().map(|pool_str| (*pool_str, *var)) + }) + .collect::>(); + + PoolVec::new(named.into_iter(), pool) + } + + pub fn unnamed(&self, pool: &mut Pool) -> PoolVec { + let unnamed = self + .names + .iter(pool) + .filter_map(|(opt_pool_str, var)| { + if opt_pool_str.is_none() { + Some(*var) + } else { + None + } + }) + .collect::>(); + + PoolVec::new(unnamed.into_iter(), pool) + } +} + +impl ShallowClone for Rigids { + fn shallow_clone(&self) -> Self { + Self { + names: self.names.shallow_clone(), + padding: self.padding, + } + } +} diff --git a/editor/src/lang/scope.rs b/ast/src/lang/scope.rs similarity index 97% rename from editor/src/lang/scope.rs rename to ast/src/lang/scope.rs index e17f3e09a9..9f3a11c60c 100644 --- a/editor/src/lang/scope.rs +++ b/ast/src/lang/scope.rs @@ -1,8 +1,11 @@ #![allow(clippy::all)] #![allow(dead_code)] #![allow(unused_imports)] -use crate::lang::pool::{Pool, PoolStr, PoolVec, ShallowClone}; -use crate::lang::types::{Alias, Type2, TypeId}; + +use crate::mem_pool::pool::Pool; +use crate::mem_pool::pool_str::PoolStr; +use crate::mem_pool::pool_vec::PoolVec; +use crate::mem_pool::shallow_clone::ShallowClone; use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{Ident, Lowercase}; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; @@ -14,6 +17,8 @@ use roc_types::{ subs::{VarId, VarStore, Variable}, }; +use super::core::types::{Alias, Type2, TypeId}; + fn solved_type_to_type_id( pool: &mut Pool, solved_type: &SolvedType, diff --git a/ast/src/lib.rs b/ast/src/lib.rs new file mode 100644 index 0000000000..b3d987f99f --- /dev/null +++ b/ast/src/lib.rs @@ -0,0 +1,7 @@ +pub mod ast_error; +mod canonicalization; +pub mod constrain; +pub mod lang; +pub mod mem_pool; +pub mod parse; +pub mod solve_type; diff --git a/ast/src/mem_pool/mod.rs b/ast/src/mem_pool/mod.rs new file mode 100644 index 0000000000..b6c8c83b8f --- /dev/null +++ b/ast/src/mem_pool/mod.rs @@ -0,0 +1,4 @@ +pub mod pool; +pub mod pool_str; +pub mod pool_vec; +pub mod shallow_clone; diff --git a/ast/src/mem_pool/pool.rs b/ast/src/mem_pool/pool.rs new file mode 100644 index 0000000000..ab4ae5548d --- /dev/null +++ b/ast/src/mem_pool/pool.rs @@ -0,0 +1,228 @@ +/// A memory pool of 32-byte nodes. The node value 0 is reserved for the pool's +/// use, and valid nodes may never have that value. +/// +/// Internally, the pool is divided into pages of 4096 bytes. It stores nodes +/// into one page at a time, and when it runs out, it uses mmap to reserve an +/// anonymous memory page in which to store nodes. +/// +/// Since nodes are 32 bytes, one page can store 128 nodes; you can access a +/// particular node by its NodeId, which is an opaque wrapper around a pointer. +/// +/// Pages also use the node value 0 (all 0 bits) to mark nodes as unoccupied. +/// This is important for performance. +use libc::{c_void, MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE}; +use std::any::type_name; +use std::marker::PhantomData; +use std::mem::size_of; +use std::ptr::null; + +pub const NODE_BYTES: usize = 32; + +// Each page has 128 slots. Each slot holds one 32B node +// This means each page is 4096B, which is the size of a memory page +// on typical systems where the compiler will be run. +// +// Nice things about this system include: +// * Allocating a new page is as simple as asking the OS for a memory page. +// * Since each node is 32B, each node's memory address will be a multiple of 16. +// * Thanks to the free lists and our consistent chunk sizes, we should +// end up with very little fragmentation. +// * Finding a slot for a given node should be very fast: see if the relevant +// free list has any openings; if not, try the next size up. +// +// Less nice things include: +// * This system makes it very hard to ever give a page back to the OS. +// We could try doing the Mesh Allocator strategy: whenever we allocate +// something, assign it to a random slot in the page, and then periodically +// try to merge two pages into one (by locking and remapping them in the OS) +// and then returning the redundant physical page back to the OS. This should +// work in theory, but is pretty complicated, and we'd need to schedule it. +// Keep in mind that we can't use the Mesh Allocator itself because it returns +// usize pointers, which would be too big for us to have 16B nodes. +// On the plus side, we could be okay with higher memory usage early on, +// and then later use the Mesh strategy to reduce long-running memory usage. +// +// With this system, we can allocate up to 4B nodes. If we wanted to keep +// a generational index in there, like https://crates.io/crates/sharded-slab +// does, we could use some of the 32 bits for that. For example, if we wanted +// to have a 5-bit generational index (supporting up to 32 generations), then +// we would have 27 bits remaining, meaning we could only support at most +// 134M nodes. Since the editor has a separate Pool for each module, is that +// enough for any single module we'll encounter in practice? Probably, and +// especially if we allocate super large collection literals on the heap instead +// of in the pool. +// +// Another possible design is to try to catch reuse bugs using an "ASan" like +// approach: in development builds, whenever we "free" a particular slot, we +// can add it to a dev-build-only "freed nodes" list and don't hand it back +// out (so, we leak the memory.) Then we can (again, in development builds only) +// check to see if we're about to store something in zeroed-out memory; if so, check +// to see if it was + +#[derive(Debug, Eq)] +pub struct NodeId { + pub(super) index: u32, + pub(super) _phantom: PhantomData, +} + +impl Clone for NodeId { + fn clone(&self) -> Self { + NodeId { + index: self.index, + _phantom: PhantomData::default(), + } + } +} + +impl PartialEq for NodeId { + fn eq(&self, other: &Self) -> bool { + self.index == other.index + } +} + +impl Copy for NodeId {} + +#[derive(Debug)] +pub struct Pool { + pub(super) nodes: *mut [u8; NODE_BYTES], + num_nodes: u32, + capacity: u32, + // free_1node_slots: Vec>, +} + +impl Pool { + pub fn with_capacity(nodes: u32) -> Self { + // round up number of nodes requested to nearest page size in bytes + let bytes_per_page = page_size::get(); + let node_bytes = NODE_BYTES * nodes as usize; + let leftover = node_bytes % bytes_per_page; + let bytes_to_mmap = if leftover == 0 { + node_bytes + } else { + node_bytes + bytes_per_page - leftover + }; + + let nodes = unsafe { + // mmap anonymous memory pages - that is, contiguous virtual memory + // addresses from the OS which will be lazily translated into + // physical memory one 4096-byte page at a time, once we actually + // try to read or write in that page's address range. + libc::mmap( + null::() as *mut c_void, + bytes_to_mmap, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + 0, + 0, + ) + } as *mut [u8; NODE_BYTES]; + + // This is our actual capacity, in nodes. + // It might be higher than the requested capacity due to rounding up + // to nearest page size. + let capacity = (bytes_to_mmap / NODE_BYTES) as u32; + + Pool { + nodes, + num_nodes: 0, + capacity, + } + } + + pub fn add(&mut self, node: T) -> NodeId { + // It's only safe to store this if T fits in S. + debug_assert!( + size_of::() <= NODE_BYTES, + "{} has a size of {}, but it needs to be at most {}", + type_name::(), + size_of::(), + NODE_BYTES + ); + + let node_id = self.reserve(1); + let node_ptr = unsafe { self.nodes.offset(node_id.index as isize) } as *mut T; + + unsafe { *node_ptr = node }; + + node_id + } + + /// Reserves the given number of contiguous node slots, and returns + /// the NodeId of the first one. We only allow reserving 2^32 in a row. + pub(super) fn reserve(&mut self, nodes: u32) -> NodeId { + // TODO once we have a free list, look in there for an open slot first! + let index = self.num_nodes; + + if index < self.capacity { + self.num_nodes = index + nodes; + + NodeId { + index, + _phantom: PhantomData::default(), + } + } else { + todo!("pool ran out of capacity. TODO reallocate the nodes pointer to map to a bigger space. Can use mremap on Linux, but must memcpy lots of bytes on macOS and Windows."); + } + } + + pub fn get<'a, 'b, T>(&'a self, node_id: NodeId) -> &'b T { + unsafe { + let node_ptr = self.nodes.offset(node_id.index as isize) as *const T; + + &*node_ptr + } + } + + pub fn get_mut(&mut self, node_id: NodeId) -> &mut T { + unsafe { + let node_ptr = self.nodes.offset(node_id.index as isize) as *mut T; + + &mut *node_ptr + } + } + + pub fn set(&mut self, node_id: NodeId, element: T) { + unsafe { + let node_ptr = self.nodes.offset(node_id.index as isize) as *mut T; + + *node_ptr = element; + } + } + + // A node is available iff its bytes are all zeroes + #[allow(dead_code)] + fn is_available(&self, node_id: NodeId) -> bool { + debug_assert_eq!(size_of::(), NODE_BYTES); + + unsafe { + let node_ptr = self.nodes.offset(node_id.index as isize) as *const [u8; NODE_BYTES]; + + *node_ptr == [0; NODE_BYTES] + } + } +} + +impl std::ops::Index> for Pool { + type Output = T; + + fn index(&self, node_id: NodeId) -> &Self::Output { + self.get(node_id) + } +} + +impl std::ops::IndexMut> for Pool { + fn index_mut(&mut self, node_id: NodeId) -> &mut Self::Output { + self.get_mut(node_id) + } +} + +impl Drop for Pool { + fn drop(&mut self) { + unsafe { + libc::munmap( + self.nodes as *mut c_void, + NODE_BYTES * self.capacity as usize, + ); + } + } +} diff --git a/ast/src/mem_pool/pool_str.rs b/ast/src/mem_pool/pool_str.rs new file mode 100644 index 0000000000..435d4586bb --- /dev/null +++ b/ast/src/mem_pool/pool_str.rs @@ -0,0 +1,86 @@ +use super::pool::{NodeId, Pool, NODE_BYTES}; +use super::shallow_clone::ShallowClone; +use libc::c_void; +use std::marker::PhantomData; +use std::mem::size_of; + +/// A string containing at most 2^32 pool-allocated bytes. +#[derive(Debug, Copy, Clone)] +pub struct PoolStr { + first_node_id: NodeId<()>, + len: u32, +} + +#[test] +fn pool_str_size() { + assert_eq!(size_of::(), 8); +} + +impl PoolStr { + pub fn new(string: &str, pool: &mut Pool) -> Self { + debug_assert!(string.len() <= u32::MAX as usize); + + let chars_per_node = NODE_BYTES / size_of::(); + + let number_of_nodes = f64::ceil(string.len() as f64 / chars_per_node as f64) as u32; + + if number_of_nodes > 0 { + let first_node_id = pool.reserve(number_of_nodes); + let index = first_node_id.index as isize; + let next_node_ptr = unsafe { pool.nodes.offset(index) } as *mut c_void; + + unsafe { + libc::memcpy( + next_node_ptr, + string.as_ptr() as *const c_void, + string.len(), + ); + } + + PoolStr { + first_node_id, + len: string.len() as u32, + } + } else { + PoolStr { + first_node_id: NodeId { + index: 0, + _phantom: PhantomData::default(), + }, + len: 0, + } + } + } + + pub fn as_str(&self, pool: &Pool) -> &str { + unsafe { + let node_ptr = pool.nodes.offset(self.first_node_id.index as isize) as *const u8; + + let node_slice: &[u8] = std::slice::from_raw_parts(node_ptr, self.len as usize); + + std::str::from_utf8_unchecked(&node_slice[0..self.len as usize]) + } + } + + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, pool: &Pool) -> usize { + let contents = self.as_str(pool); + + contents.len() + } + + pub fn is_empty(&self, pool: &Pool) -> bool { + self.len(pool) == 0 + } +} + +impl ShallowClone for PoolStr { + fn shallow_clone(&self) -> Self { + // Question: should this fully clone, or is a shallow copy + // (and the aliasing it entails) OK? + Self { + first_node_id: self.first_node_id, + len: self.len, + } + } +} diff --git a/ast/src/mem_pool/pool_vec.rs b/ast/src/mem_pool/pool_vec.rs new file mode 100644 index 0000000000..65c9e89b1b --- /dev/null +++ b/ast/src/mem_pool/pool_vec.rs @@ -0,0 +1,323 @@ +use super::pool::{NodeId, Pool, NODE_BYTES}; +use super::shallow_clone::ShallowClone; +use libc::c_void; +use std::any::type_name; +use std::cmp::Ordering; +use std::marker::PhantomData; +use std::mem::size_of; + +/// An array of at most 2^32 pool-allocated nodes. +#[derive(Debug)] +pub struct PoolVec { + first_node_id: NodeId, + len: u32, +} + +#[test] +fn pool_vec_size() { + assert_eq!(size_of::>(), 8); +} + +impl<'a, T: 'a + Sized> PoolVec { + pub fn empty(pool: &mut Pool) -> Self { + Self::new(std::iter::empty(), pool) + } + + pub fn with_capacity(len: u32, pool: &mut Pool) -> Self { + debug_assert!( + size_of::() <= NODE_BYTES, + "{} has a size of {}", + type_name::(), + size_of::() + ); + + if len == 0 { + Self::empty(pool) + } else { + let first_node_id = pool.reserve(len); + + PoolVec { first_node_id, len } + } + } + + pub fn len(&self) -> usize { + self.len as usize + } + + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + pub fn new>(nodes: I, pool: &mut Pool) -> Self { + debug_assert!(nodes.len() <= u32::MAX as usize); + debug_assert!(size_of::() <= NODE_BYTES); + + let len = nodes.len() as u32; + + if len > 0 { + let first_node_id = pool.reserve(len); + let index = first_node_id.index as isize; + let mut next_node_ptr = unsafe { pool.nodes.offset(index) } as *mut T; + + for (indx_inc, node) in nodes.enumerate() { + unsafe { + *next_node_ptr = node; + + next_node_ptr = pool.nodes.offset(index + (indx_inc as isize) + 1) as *mut T; + } + } + + PoolVec { first_node_id, len } + } else { + PoolVec { + first_node_id: NodeId { + index: 0, + _phantom: PhantomData::default(), + }, + len: 0, + } + } + } + + pub fn iter(&self, pool: &'a Pool) -> impl ExactSizeIterator { + self.pool_list_iter(pool) + } + + pub fn iter_mut(&self, pool: &'a mut Pool) -> impl ExactSizeIterator { + self.pool_list_iter_mut(pool) + } + + pub fn iter_node_ids(&self) -> impl ExactSizeIterator> { + self.pool_list_iter_node_ids() + } + + /// Private version of into_iter which exposes the implementation detail + /// of PoolVecIter. We don't want that struct to be public, but we + /// actually do want to have this separate function for code reuse + /// in the iterator's next() method. + #[inline(always)] + fn pool_list_iter(&self, pool: &'a Pool) -> PoolVecIter<'a, T> { + PoolVecIter { + pool, + current_node_id: self.first_node_id, + len_remaining: self.len, + } + } + + #[inline(always)] + fn pool_list_iter_mut(&self, pool: &'a Pool) -> PoolVecIterMut<'a, T> { + PoolVecIterMut { + pool, + current_node_id: self.first_node_id, + len_remaining: self.len, + } + } + + #[inline(always)] + fn pool_list_iter_node_ids(&self) -> PoolVecIterNodeIds { + PoolVecIterNodeIds { + current_node_id: self.first_node_id, + len_remaining: self.len, + } + } + + pub fn free(self, pool: &'a mut Pool) { + // zero out the memory + unsafe { + let index = self.first_node_id.index as isize; + let node_ptr = pool.nodes.offset(index) as *mut c_void; + let bytes = self.len as usize * NODE_BYTES; + + libc::memset(node_ptr, 0, bytes); + } + + // TODO insert it into the pool's free list + } +} + +impl ShallowClone for PoolVec { + fn shallow_clone(&self) -> Self { + // Question: should this fully clone, or is a shallow copy + // (and the aliasing it entails) OK? + Self { + first_node_id: self.first_node_id, + len: self.len, + } + } +} + +struct PoolVecIter<'a, T> { + pool: &'a Pool, + current_node_id: NodeId, + len_remaining: u32, +} + +impl<'a, T> ExactSizeIterator for PoolVecIter<'a, T> +where + T: 'a, +{ + fn len(&self) -> usize { + self.len_remaining as usize + } +} + +impl<'a, T> Iterator for PoolVecIter<'a, T> +where + T: 'a, +{ + type Item = &'a T; + + fn next(&mut self) -> Option { + let len_remaining = self.len_remaining; + + match len_remaining.cmp(&1) { + Ordering::Greater => { + // Get the current node + let index = self.current_node_id.index; + let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *const T; + + // Advance the node pointer to the next node in the current page + self.current_node_id = NodeId { + index: index + 1, + _phantom: PhantomData::default(), + }; + self.len_remaining = len_remaining - 1; + + Some(unsafe { &*node_ptr }) + } + Ordering::Equal => { + self.len_remaining = 0; + + // Don't advance the node pointer's node, because that might + // advance past the end of the page! + + let index = self.current_node_id.index; + let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *const T; + + Some(unsafe { &*node_ptr }) + } + Ordering::Less => { + // len_remaining was 0 + None + } + } + } +} + +struct PoolVecIterMut<'a, T> { + pool: &'a Pool, + current_node_id: NodeId, + len_remaining: u32, +} + +impl<'a, T> ExactSizeIterator for PoolVecIterMut<'a, T> +where + T: 'a, +{ + fn len(&self) -> usize { + self.len_remaining as usize + } +} + +impl<'a, T> Iterator for PoolVecIterMut<'a, T> +where + T: 'a, +{ + type Item = &'a mut T; + + fn next(&mut self) -> Option { + let len_remaining = self.len_remaining; + + match len_remaining.cmp(&1) { + Ordering::Greater => { + // Get the current node + let index = self.current_node_id.index; + let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *mut T; + + // Advance the node pointer to the next node in the current page + self.current_node_id = NodeId { + index: index + 1, + _phantom: PhantomData::default(), + }; + self.len_remaining = len_remaining - 1; + + Some(unsafe { &mut *node_ptr }) + } + Ordering::Equal => { + self.len_remaining = 0; + + // Don't advance the node pointer's node, because that might + // advance past the end of the page! + + let index = self.current_node_id.index; + let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *mut T; + + Some(unsafe { &mut *node_ptr }) + } + Ordering::Less => { + // len_remaining was 0 + None + } + } + } +} + +struct PoolVecIterNodeIds { + current_node_id: NodeId, + len_remaining: u32, +} + +impl ExactSizeIterator for PoolVecIterNodeIds { + fn len(&self) -> usize { + self.len_remaining as usize + } +} + +impl Iterator for PoolVecIterNodeIds { + type Item = NodeId; + + fn next(&mut self) -> Option { + let len_remaining = self.len_remaining; + + match len_remaining.cmp(&1) { + Ordering::Greater => { + // Get the current node + let current = self.current_node_id; + let index = current.index; + + // Advance the node pointer to the next node in the current page + self.current_node_id = NodeId { + index: index + 1, + _phantom: PhantomData::default(), + }; + self.len_remaining = len_remaining - 1; + + Some(current) + } + Ordering::Equal => { + self.len_remaining = 0; + + // Don't advance the node pointer's node, because that might + // advance past the end of the page! + + Some(self.current_node_id) + } + Ordering::Less => { + // len_remaining was 0 + None + } + } + } +} + +#[test] +fn pool_vec_iter_test() { + let expected_vec: Vec = vec![2, 4, 8, 16]; + + let mut test_pool = Pool::with_capacity(1024); + let pool_vec = PoolVec::new(expected_vec.clone().into_iter(), &mut test_pool); + + let current_vec: Vec = pool_vec.iter(&test_pool).copied().collect(); + + assert_eq!(current_vec, expected_vec); +} diff --git a/ast/src/mem_pool/shallow_clone.rs b/ast/src/mem_pool/shallow_clone.rs new file mode 100644 index 0000000000..f444b1f897 --- /dev/null +++ b/ast/src/mem_pool/shallow_clone.rs @@ -0,0 +1,32 @@ +use roc_can::expected::Expected; +use roc_can::expected::PExpected; + +/// Clones the outer node, but does not clone any nodeids +pub trait ShallowClone { + fn shallow_clone(&self) -> Self; +} + +impl ShallowClone for Expected { + fn shallow_clone(&self) -> Self { + use Expected::*; + + match self { + NoExpectation(t) => NoExpectation(t.shallow_clone()), + ForReason(reason, t, region) => ForReason(reason.clone(), t.shallow_clone(), *region), + FromAnnotation(loc_pat, n, source, t) => { + FromAnnotation(loc_pat.clone(), *n, *source, t.shallow_clone()) + } + } + } +} + +impl ShallowClone for PExpected { + fn shallow_clone(&self) -> Self { + use PExpected::*; + + match self { + NoExpectation(t) => NoExpectation(t.shallow_clone()), + ForReason(reason, t, region) => ForReason(reason.clone(), t.shallow_clone(), *region), + } + } +} diff --git a/ast/src/parse/mod.rs b/ast/src/parse/mod.rs new file mode 100644 index 0000000000..31bf78fd5e --- /dev/null +++ b/ast/src/parse/mod.rs @@ -0,0 +1,2 @@ +pub mod parse_ast; +pub mod parse_header; diff --git a/ast/src/parse/parse_ast.rs b/ast/src/parse/parse_ast.rs new file mode 100644 index 0000000000..3ae3cca0d7 --- /dev/null +++ b/ast/src/parse/parse_ast.rs @@ -0,0 +1,48 @@ +use bumpalo::Bump; +use roc_parse::parser::SyntaxError; +use roc_region::all::Region; + +use crate::lang::{ + core::{ + ast::AST, + def::{def2::DefId, def_to_def2::str_to_def2}, + expr::expr2::Expr2, + }, + env::Env, + scope::Scope, +}; + +use super::parse_header; + +pub fn parse_from_string<'a>( + code_str: &'a str, + env: &mut Env<'a>, + ast_arena: &'a Bump, +) -> Result> { + let blank_line_indx = code_str + .find("\n\n") + .expect("I was expecting a double newline to split header and rest of code."); + + let header_str = &code_str[0..blank_line_indx]; + let tail_str = &code_str[blank_line_indx..]; + + let mut scope = Scope::new(env.home, env.pool, env.var_store); + let region = Region::new(0, 0, 0, 0); + + let mut def_ids = Vec::::new(); + + let def2_vec = str_to_def2(ast_arena, tail_str, env, &mut scope, region)?; + + for def2 in def2_vec { + let def_id = env.pool.add(def2); + + def_ids.push(def_id); + } + + let ast_node_id = env.pool.add(Expr2::Blank); + + Ok(AST { + header: parse_header::parse_from_string(header_str, ast_node_id), + def_ids, + }) +} diff --git a/ast/src/parse/parse_header.rs b/ast/src/parse/parse_header.rs new file mode 100644 index 0000000000..e387fea026 --- /dev/null +++ b/ast/src/parse/parse_header.rs @@ -0,0 +1,12 @@ +use crate::lang::core::{expr::expr2::ExprId, header::AppHeader}; + +// TODO don't use mock struct and actually parse string +pub fn parse_from_string(_header_str: &str, ast_node_id: ExprId) -> AppHeader { + AppHeader { + app_name: "\"untitled-app\"".to_owned(), + packages_base: "\"platform\"".to_owned(), + imports: vec![], + provides: vec!["main".to_owned()], + ast_node_id, + } +} diff --git a/editor/src/lang/roc_file.rs b/ast/src/roc_file.rs similarity index 100% rename from editor/src/lang/roc_file.rs rename to ast/src/roc_file.rs diff --git a/editor/src/lang/solve.rs b/ast/src/solve_type.rs similarity index 99% rename from editor/src/lang/solve.rs rename to ast/src/solve_type.rs index 68b29c7635..2b39c606b3 100644 --- a/editor/src/lang/solve.rs +++ b/ast/src/solve_type.rs @@ -1,8 +1,5 @@ #![allow(clippy::all)] #![allow(dead_code)] -use crate::lang::constrain::Constraint::{self, *}; -use crate::lang::pool::{Pool, PoolVec, ShallowClone}; -use crate::lang::types::Type2; use bumpalo::Bump; use roc_can::expected::{Expected, PExpected}; use roc_collections::all::{BumpMap, BumpMapDefault, MutMap}; @@ -20,6 +17,12 @@ use roc_types::types::{ use roc_unify::unify::unify; use roc_unify::unify::Unified::*; +use crate::constrain::Constraint; +use crate::lang::core::types::Type2; +use crate::mem_pool::pool::Pool; +use crate::mem_pool::pool_vec::PoolVec; +use crate::mem_pool::shallow_clone::ShallowClone; + // Type checking system adapted from Elm by Evan Czaplicki, BSD-3-Clause Licensed // https://github.com/elm/compiler // Thank you, Evan! @@ -197,6 +200,8 @@ fn solve<'a>( subs: &mut Subs, constraint: &Constraint, ) -> State { + use crate::solve_type::Constraint::*; + match constraint { True => state, // SaveTheEnvironment => { diff --git a/code_markup/Cargo.toml b/code_markup/Cargo.toml new file mode 100644 index 0000000000..2583ee0006 --- /dev/null +++ b/code_markup/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "roc_code_markup" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" +description = "Our own markup language for Roc code. Used by the editor and (soon) the docs." + +[dependencies] +roc_ast = { path = "../ast" } +roc_module = { path = "../compiler/module" } +roc_utils = { path = "../utils" } +serde = { version = "1.0.123", features = ["derive"] } +palette = "0.5" +snafu = { version = "0.6", features = ["backtraces"] } +bumpalo = { version = "3.2", features = ["collections"] } + +[dev-dependencies] \ No newline at end of file diff --git a/code_markup/src/colors.rs b/code_markup/src/colors.rs new file mode 100644 index 0000000000..93c6c452b6 --- /dev/null +++ b/code_markup/src/colors.rs @@ -0,0 +1,22 @@ +use palette::{Hsv, LinSrgb}; + +pub type RgbaTup = (f32, f32, f32, f32); +pub const WHITE: RgbaTup = (1.0, 1.0, 1.0, 1.0); + +pub fn to_slice((r, g, b, a): RgbaTup) -> [f32; 4] { + [r, g, b, a] +} + +pub fn from_hsb(hue: usize, saturation: usize, brightness: usize) -> RgbaTup { + from_hsba(hue, saturation, brightness, 1.0) +} + +pub fn from_hsba(hue: usize, saturation: usize, brightness: usize, alpha: f32) -> RgbaTup { + let rgb = LinSrgb::from(Hsv::new( + hue as f32, + (saturation as f32) / 100.0, + (brightness as f32) / 100.0, + )); + + (rgb.red, rgb.green, rgb.blue, alpha) +} diff --git a/code_markup/src/lib.rs b/code_markup/src/lib.rs new file mode 100644 index 0000000000..9e5d220855 --- /dev/null +++ b/code_markup/src/lib.rs @@ -0,0 +1,5 @@ +pub mod colors; +pub mod markup; +pub mod markup_error; +pub mod slow_pool; +pub mod syntax_highlight; diff --git a/editor/src/editor/markup/attribute.rs b/code_markup/src/markup/attribute.rs similarity index 95% rename from editor/src/editor/markup/attribute.rs rename to code_markup/src/markup/attribute.rs index 304c4aadce..7df80cbc70 100644 --- a/editor/src/editor/markup/attribute.rs +++ b/code_markup/src/markup/attribute.rs @@ -1,8 +1,8 @@ #![allow(dead_code)] - -use crate::editor::ed_error::{CaretNotFound, EdResult}; use snafu::ensure; +use crate::markup_error::{CaretNotFound, MarkResult}; + #[derive(Debug, Copy, Clone)] pub struct Caret { pub offset_col: usize, @@ -65,10 +65,6 @@ pub struct Attributes { } impl Attributes { - pub fn new() -> Attributes { - Attributes { all: Vec::new() } - } - pub fn add(&mut self, attr: Attribute) { self.all.push(attr); } @@ -103,7 +99,7 @@ impl Attributes { carets } - pub fn delete_caret(&mut self, offset_col: usize, node_id: usize) -> EdResult<()> { + pub fn delete_caret(&mut self, offset_col: usize, node_id: usize) -> MarkResult<()> { let old_len = self.all.len(); self.all.retain(|attr| { @@ -121,3 +117,9 @@ impl Attributes { Ok(()) } } + +impl Default for Attributes { + fn default() -> Self { + Attributes { all: Vec::new() } + } +} diff --git a/editor/src/editor/markup/common_nodes.rs b/code_markup/src/markup/common_nodes.rs similarity index 84% rename from editor/src/editor/markup/common_nodes.rs rename to code_markup/src/markup/common_nodes.rs index f74648f3fa..89a8a8c2d9 100644 --- a/editor/src/editor/markup/common_nodes.rs +++ b/code_markup/src/markup/common_nodes.rs @@ -1,7 +1,6 @@ -use crate::{ - editor::{slow_pool::MarkNodeId, syntax_highlight::HighlightStyle}, - lang::{ast::ExprId, parse::ASTNodeId}, -}; +use roc_ast::lang::core::{ast::ASTNodeId, expr::expr2::ExprId}; + +use crate::{slow_pool::MarkNodeId, syntax_highlight::HighlightStyle}; use super::{attribute::Attributes, nodes, nodes::MarkupNode}; @@ -10,7 +9,7 @@ pub fn new_equals_mn(ast_node_id: ASTNodeId, parent_id_opt: Option) content: nodes::EQUALS.to_owned(), ast_node_id, syn_high_style: HighlightStyle::Operator, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } @@ -21,7 +20,7 @@ pub fn new_comma_mn(expr_id: ExprId, parent_id_opt: Option) -> Marku content: nodes::COMMA.to_owned(), ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::Blank, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } @@ -31,7 +30,7 @@ pub fn new_blank_mn(ast_node_id: ASTNodeId, parent_id_opt: Option) - MarkupNode::Blank { ast_node_id, syn_high_style: HighlightStyle::Blank, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } @@ -45,7 +44,7 @@ pub fn new_blank_mn_w_nls( MarkupNode::Blank { ast_node_id, syn_high_style: HighlightStyle::Blank, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: nr_of_newlines, } @@ -56,7 +55,7 @@ pub fn new_colon_mn(expr_id: ExprId, parent_id_opt: Option) -> Marku content: nodes::COLON.to_owned(), ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::Operator, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } @@ -67,7 +66,7 @@ pub fn new_left_accolade_mn(expr_id: ExprId, parent_id_opt: Option) content: nodes::LEFT_ACCOLADE.to_owned(), ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } @@ -78,7 +77,7 @@ pub fn new_right_accolade_mn(expr_id: ExprId, parent_id_opt: Option) content: nodes::RIGHT_ACCOLADE.to_owned(), ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } @@ -89,7 +88,7 @@ pub fn new_left_square_mn(expr_id: ExprId, parent_id_opt: Option) -> content: nodes::LEFT_SQUARE_BR.to_owned(), ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } @@ -100,7 +99,7 @@ pub fn new_right_square_mn(expr_id: ExprId, parent_id_opt: Option) - content: nodes::RIGHT_SQUARE_BR.to_owned(), ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } diff --git a/editor/src/editor/markup/mod.rs b/code_markup/src/markup/mod.rs similarity index 70% rename from editor/src/editor/markup/mod.rs rename to code_markup/src/markup/mod.rs index de015ebd80..e3ce137f80 100644 --- a/editor/src/editor/markup/mod.rs +++ b/code_markup/src/markup/mod.rs @@ -1,3 +1,4 @@ pub mod attribute; pub mod common_nodes; pub mod nodes; +pub mod top_level_def; diff --git a/editor/src/editor/markup/nodes.rs b/code_markup/src/markup/nodes.rs similarity index 92% rename from editor/src/editor/markup/nodes.rs rename to code_markup/src/markup/nodes.rs index 00b5358e88..cea08f288b 100644 --- a/editor/src/editor/markup/nodes.rs +++ b/code_markup/src/markup/nodes.rs @@ -1,33 +1,39 @@ -use super::attribute::Attributes; -use crate::editor::ed_error::EdResult; -use crate::editor::ed_error::ExpectedTextNode; -use crate::editor::ed_error::{NestedNodeMissingChild, NestedNodeRequired}; -use crate::editor::markup::common_nodes::new_blank_mn; -use crate::editor::markup::common_nodes::new_blank_mn_w_nls; -use crate::editor::markup::common_nodes::new_colon_mn; -use crate::editor::markup::common_nodes::new_comma_mn; -use crate::editor::markup::common_nodes::new_equals_mn; -use crate::editor::markup::common_nodes::new_left_accolade_mn; -use crate::editor::markup::common_nodes::new_left_square_mn; -use crate::editor::markup::common_nodes::new_right_accolade_mn; -use crate::editor::markup::common_nodes::new_right_square_mn; -use crate::editor::mvc::tld_value_update::tld_mark_node; -use crate::editor::slow_pool::MarkNodeId; -use crate::editor::slow_pool::SlowPool; -use crate::editor::syntax_highlight::HighlightStyle; -use crate::editor::util::index_of; -use crate::lang::ast::Def2; -use crate::lang::ast::DefId; -use crate::lang::ast::ExprId; -use crate::lang::ast::RecordField; -use crate::lang::ast::ValueDef; -use crate::lang::parse::ASTNodeId; -use crate::lang::parse::{AppHeader, AST}; -use crate::lang::pattern::get_identifier_string; -use crate::lang::{ast::Expr2, expr::Env, pool::PoolStr}; -use crate::ui::util::slice_get; +use crate::{ + markup::common_nodes::{ + new_blank_mn, new_colon_mn, new_comma_mn, new_equals_mn, new_left_accolade_mn, + new_left_square_mn, new_right_accolade_mn, new_right_square_mn, + }, + markup_error::MarkResult, + slow_pool::{MarkNodeId, SlowPool}, + syntax_highlight::HighlightStyle, +}; + +use super::{ + attribute::Attributes, common_nodes::new_blank_mn_w_nls, top_level_def::tld_mark_node, +}; + +use crate::markup_error::{ExpectedTextNode, NestedNodeMissingChild, NestedNodeRequired}; use bumpalo::Bump; +use roc_ast::{ + ast_error::ASTResult, + lang::{ + core::{ + ast::{ASTNodeId, AST}, + def::def2::{Def2, DefId}, + expr::{ + expr2::{Expr2, ExprId}, + record_field::RecordField, + }, + header::AppHeader, + pattern::get_identifier_string, + val_def::ValueDef, + }, + env::Env, + }, + mem_pool::pool_str::PoolStr, +}; use roc_module::symbol::Interns; +use roc_utils::{index_of, slice_get}; use std::fmt; #[derive(Debug)] @@ -95,7 +101,7 @@ impl MarkupNode { &self, child_id: MarkNodeId, mark_node_pool: &SlowPool, - ) -> EdResult<(usize, usize)> { + ) -> MarkResult<(usize, usize)> { match self { MarkupNode::Nested { children_ids, .. } => { let mut mark_child_index_opt: Option = None; @@ -122,12 +128,11 @@ impl MarkupNode { Ok((child_index, ast_child_index)) } else { // we want to find the index of the closest ast mark node to child_index - let indices_in_mark_res: EdResult> = child_ids_with_ast - .iter() - .map(|c_id| index_of(*c_id, children_ids)) - .collect(); + let mut indices_in_mark = vec![]; - let indices_in_mark = indices_in_mark_res?; + for &c_id in child_ids_with_ast.iter() { + indices_in_mark.push(index_of(c_id, children_ids)?); + } let mut last_diff = usize::MAX; let mut best_index = 0; @@ -186,7 +191,7 @@ impl MarkupNode { full_content } - pub fn get_content_mut(&mut self) -> EdResult<&mut String> { + pub fn get_content_mut(&mut self) -> MarkResult<&mut String> { match self { MarkupNode::Nested { .. } => ExpectedTextNode { function_name: "set_content".to_owned(), @@ -208,7 +213,7 @@ impl MarkupNode { .all(|chr| chr.is_ascii_alphanumeric()) } - pub fn add_child_at_index(&mut self, index: usize, child_id: MarkNodeId) -> EdResult<()> { + pub fn add_child_at_index(&mut self, index: usize, child_id: MarkNodeId) -> MarkResult<()> { if let MarkupNode::Nested { children_ids, .. } = self { children_ids.splice(index..index, vec![child_id]); } else { @@ -292,7 +297,7 @@ fn new_markup_node( content: text, ast_node_id: node_id, syn_high_style: highlight_style, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, }; @@ -307,7 +312,7 @@ pub fn def2_to_markup<'a, 'b>( def2_node_id: DefId, mark_node_pool: &mut SlowPool, interns: &Interns, -) -> EdResult { +) -> ASTResult { let ast_node_id = ASTNodeId::ADefId(def2_node_id); let mark_node_id = match def2 { @@ -349,7 +354,7 @@ pub fn expr2_to_markup<'a, 'b>( expr2_node_id: ExprId, mark_node_pool: &mut SlowPool, interns: &Interns, -) -> EdResult { +) -> ASTResult { let ast_node_id = ASTNodeId::AExprId(expr2_node_id); let mark_node_id = match expr2 { @@ -497,7 +502,7 @@ pub fn expr2_to_markup<'a, 'b>( content: val_name, ast_node_id, syn_high_style: HighlightStyle::Variable, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, }; @@ -606,7 +611,7 @@ fn header_mn(content: String, expr_id: ExprId, mark_node_pool: &mut SlowPool) -> content, ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::PackageRelated, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, }; @@ -624,7 +629,7 @@ fn header_val_mn( content, ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: highlight_style, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, }; @@ -798,7 +803,7 @@ pub fn ast_to_mark_nodes<'a, 'b>( ast: &AST, mark_node_pool: &mut SlowPool, interns: &Interns, -) -> EdResult> { +) -> ASTResult> { let mut all_mark_node_ids = vec![header_to_markup(&ast.header, mark_node_pool)]; for &def_id in ast.def_ids.iter() { diff --git a/code_markup/src/markup/top_level_def.rs b/code_markup/src/markup/top_level_def.rs new file mode 100644 index 0000000000..d2122fc790 --- /dev/null +++ b/code_markup/src/markup/top_level_def.rs @@ -0,0 +1,51 @@ +use roc_ast::{ + ast_error::ASTResult, + lang::{ + core::{ + ast::ASTNodeId, + pattern::{get_identifier_string, PatternId}, + }, + env::Env, + }, +}; +use roc_module::symbol::Interns; + +use crate::{ + markup::{attribute::Attributes, common_nodes::new_equals_mn, nodes::MarkupNode}, + slow_pool::{MarkNodeId, SlowPool}, + syntax_highlight::HighlightStyle, +}; + +pub fn tld_mark_node<'a>( + identifier_id: PatternId, + expr_mark_node_id: MarkNodeId, + ast_node_id: ASTNodeId, + mark_node_pool: &mut SlowPool, + env: &Env<'a>, + interns: &Interns, +) -> ASTResult { + let pattern2 = env.pool.get(identifier_id); + let val_name = get_identifier_string(pattern2, interns)?; + + let val_name_mn = MarkupNode::Text { + content: val_name, + ast_node_id, + syn_high_style: HighlightStyle::Variable, + attributes: Attributes::default(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + let val_name_mn_id = mark_node_pool.add(val_name_mn); + + let equals_mn_id = mark_node_pool.add(new_equals_mn(ast_node_id, None)); + + let full_let_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![val_name_mn_id, equals_mn_id, expr_mark_node_id], + parent_id_opt: None, + newlines_at_end: 2, + }; + + Ok(full_let_node) +} diff --git a/code_markup/src/markup_error.rs b/code_markup/src/markup_error.rs new file mode 100644 index 0000000000..5de9768fba --- /dev/null +++ b/code_markup/src/markup_error.rs @@ -0,0 +1,55 @@ +use roc_utils::util_error::UtilError; +use snafu::{Backtrace, NoneError, ResultExt, Snafu}; + +use crate::slow_pool::MarkNodeId; + +#[derive(Debug, Snafu)] +#[snafu(visibility(pub))] +pub enum MarkError { + #[snafu(display( + "CaretNotFound: No carets were found in the expected node with id {}", + node_id + ))] + CaretNotFound { + node_id: MarkNodeId, + backtrace: Backtrace, + }, + #[snafu(display( + "ExpectedTextNode: the function {} expected a Text node, got {} instead.", + function_name, + node_type + ))] + ExpectedTextNode { + function_name: String, + node_type: String, + backtrace: Backtrace, + }, + #[snafu(display("NestedNodeMissingChild: expected to find child with id {} in Nested MarkupNode, but it was missing. Id's of the children are {:?}.", node_id, children_ids))] + NestedNodeMissingChild { + node_id: MarkNodeId, + children_ids: Vec, + backtrace: Backtrace, + }, + #[snafu(display( + "NestedNodeRequired: required a Nested node at this position, node was a {}.", + node_type + ))] + NestedNodeRequired { + node_type: String, + backtrace: Backtrace, + }, + #[snafu(display("UIError: {}", msg))] + UtilErrorBacktrace { msg: String, backtrace: Backtrace }, +} + +pub type MarkResult = std::result::Result; + +impl From for MarkError { + fn from(util_err: UtilError) -> Self { + let msg = format!("{}", util_err); + + // hack to handle MarkError derive + let dummy_res: Result<(), NoneError> = Err(NoneError {}); + dummy_res.context(UtilErrorBacktrace { msg }).unwrap_err() + } +} diff --git a/editor/src/editor/slow_pool.rs b/code_markup/src/slow_pool.rs similarity index 95% rename from editor/src/editor/slow_pool.rs rename to code_markup/src/slow_pool.rs index 72dc43336c..d69e1a1a9c 100644 --- a/editor/src/editor/slow_pool.rs +++ b/code_markup/src/slow_pool.rs @@ -1,6 +1,7 @@ -use crate::editor::markup::nodes::MarkupNode; use std::fmt; +use crate::markup::nodes::MarkupNode; + pub type MarkNodeId = usize; #[derive(Debug)] @@ -9,10 +10,6 @@ pub struct SlowPool { } impl SlowPool { - pub fn new() -> SlowPool { - SlowPool { nodes: Vec::new() } - } - pub fn add(&mut self, node: MarkupNode) -> MarkNodeId { let id = self.nodes.len(); @@ -72,3 +69,9 @@ impl fmt::Display for SlowPool { Ok(()) } } + +impl Default for SlowPool { + fn default() -> Self { + SlowPool { nodes: Vec::new() } + } +} diff --git a/editor/src/editor/syntax_highlight.rs b/code_markup/src/syntax_highlight.rs similarity index 78% rename from editor/src/editor/syntax_highlight.rs rename to code_markup/src/syntax_highlight.rs index 602a335b48..12c0ef33e1 100644 --- a/editor/src/editor/syntax_highlight.rs +++ b/code_markup/src/syntax_highlight.rs @@ -1,8 +1,8 @@ -use crate::graphics::colors as gr_colors; -use gr_colors::{from_hsb, RgbaTup}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use crate::colors::{self, from_hsb, RgbaTup}; + #[derive(Hash, Eq, PartialEq, Copy, Clone, Debug, Deserialize, Serialize)] pub enum HighlightStyle { Operator, // =+-<>... @@ -24,14 +24,14 @@ pub fn default_highlight_map() -> HashMap { let mut highlight_map = HashMap::new(); [ - (Operator, gr_colors::WHITE), + (Operator, colors::WHITE), (String, from_hsb(346, 65, 97)), - (FunctionName, gr_colors::WHITE), - (Type, gr_colors::WHITE), + (FunctionName, colors::WHITE), + (Type, colors::WHITE), (Bracket, from_hsb(347, 80, 100)), (Number, from_hsb(185, 50, 75)), - (PackageRelated, gr_colors::WHITE), - (Variable, gr_colors::WHITE), + (PackageRelated, colors::WHITE), + (Variable, colors::WHITE), (RecordField, from_hsb(258, 50, 90)), (Import, from_hsb(185, 50, 75)), (Provides, from_hsb(185, 50, 75)), diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 603498ce87..d24dadded6 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -5,13 +5,14 @@ authors = ["The Roc Contributors"] license = "UPL-1.0" edition = "2018" description = "An editor for Roc" -exclude = ["src/shaders/*.spv"] [dependencies] +roc_ast = { path = "../ast" } roc_collections = { path = "../compiler/collections" } roc_load = { path = "../compiler/load" } roc_builtins = { path = "../compiler/builtins" } roc_can = { path = "../compiler/can" } +roc_code_markup = { path = "../code_markup"} roc_parse = { path = "../compiler/parse" } roc_region = { path = "../compiler/region" } roc_module = { path = "../compiler/module" } diff --git a/editor/src/editor/ed_error.rs b/editor/src/editor/ed_error.rs index 4ed692eefa..357693bb69 100644 --- a/editor/src/editor/ed_error.rs +++ b/editor/src/editor/ed_error.rs @@ -1,6 +1,9 @@ -use crate::lang::parse::ASTNodeId; -use crate::{editor::slow_pool::MarkNodeId, ui::text::text_pos::TextPos}; +use crate::ui::text::text_pos::TextPos; use colored::*; +use roc_ast::ast_error::ASTError; +use roc_ast::lang::core::ast::ASTNodeId; +use roc_code_markup::markup_error::MarkError; +use roc_code_markup::slow_pool::MarkNodeId; use snafu::{Backtrace, ErrorCompat, NoneError, ResultExt, Snafu}; //import errors as follows: @@ -211,8 +214,12 @@ pub enum EdError { #[snafu(display("StringParseError: {}", msg))] StringParseError { msg: String, backtrace: Backtrace }, + #[snafu(display("ASTError: {}", msg))] + ASTErrorBacktrace { msg: String, backtrace: Backtrace }, #[snafu(display("UIError: {}", msg))] UIErrorBacktrace { msg: String, backtrace: Backtrace }, + #[snafu(display("MarkError: {}", msg))] + MarkErrorBacktrace { msg: String, backtrace: Backtrace }, } pub type EdResult = std::result::Result; @@ -283,3 +290,23 @@ impl From for EdError { dummy_res.context(UIErrorBacktrace { msg }).unwrap_err() } } + +impl From for EdError { + fn from(mark_err: MarkError) -> Self { + let msg = format!("{}", mark_err); + + // hack to handle EdError derive + let dummy_res: Result<(), NoneError> = Err(NoneError {}); + dummy_res.context(MarkErrorBacktrace { msg }).unwrap_err() + } +} + +impl From for EdError { + fn from(ast_err: ASTError) -> Self { + let msg = format!("{}", ast_err); + + // hack to handle EdError derive + let dummy_res: Result<(), NoneError> = Err(NoneError {}); + dummy_res.context(ASTErrorBacktrace { msg }).unwrap_err() + } +} diff --git a/editor/src/editor/grid_node_map.rs b/editor/src/editor/grid_node_map.rs index 2cece54f43..e342d624c1 100644 --- a/editor/src/editor/grid_node_map.rs +++ b/editor/src/editor/grid_node_map.rs @@ -2,21 +2,20 @@ use crate::editor::ed_error::EdResult; use crate::editor::ed_error::NestedNodeWithoutChildren; use crate::editor::ed_error::{NoDefMarkNodeBeforeLineNr, NodeIdNotInGridNodeMap}; use crate::editor::mvc::ed_model::EdModel; -use crate::editor::slow_pool::MarkNodeId; -use crate::editor::slow_pool::SlowPool; use crate::editor::util::first_last_index_of; use crate::editor::util::index_of; -use crate::lang::parse::ASTNodeId; use crate::ui::text::selection::Selection; use crate::ui::text::text_pos::TextPos; use crate::ui::ui_error::{LineInsertionFailed, OutOfBounds, UIResult}; use crate::ui::util::{slice_get, slice_get_mut}; +use roc_ast::lang::core::ast::ASTNodeId; +use roc_code_markup::markup::nodes::get_root_mark_node_id; +use roc_code_markup::slow_pool::MarkNodeId; +use roc_code_markup::slow_pool::SlowPool; use snafu::OptionExt; use std::cmp::Ordering; use std::fmt; -use super::markup::nodes::get_root_mark_node_id; - #[derive(Debug)] pub struct GridNodeMap { pub lines: Vec>, diff --git a/editor/src/editor/main.rs b/editor/src/editor/main.rs index ba31393054..a3a091508a 100644 --- a/editor/src/editor/main.rs +++ b/editor/src/editor/main.rs @@ -17,14 +17,14 @@ use crate::graphics::{ primitives::rect::Rect, primitives::text::{build_glyph_brush, example_code_glyph_rect, queue_text_draw, Text}, }; -use crate::lang::expr::Env; -use crate::lang::pool::Pool; use crate::ui::text::caret_w_select::CaretPos; use crate::ui::util::path_to_string; use bumpalo::Bump; use cgmath::Vector2; use fs_extra::dir::{copy, ls, CopyOptions, DirEntryAttr, DirEntryValue}; use pipelines::RectResources; +use roc_ast::lang::env::Env; +use roc_ast::mem_pool::pool::Pool; use roc_can::builtins::builtin_defs_map; use roc_collections::all::MutMap; use roc_load; diff --git a/editor/src/editor/mod.rs b/editor/src/editor/mod.rs index 89efb3a318..f776acc44c 100644 --- a/editor/src/editor/mod.rs +++ b/editor/src/editor/mod.rs @@ -4,13 +4,10 @@ pub mod ed_error; mod grid_node_map; mod keyboard_input; pub mod main; -mod markup; mod mvc; mod render_ast; mod render_debug; mod resources; -mod slow_pool; mod style; -mod syntax_highlight; mod theme; mod util; diff --git a/editor/src/editor/mvc/break_line.rs b/editor/src/editor/mvc/break_line.rs index 6fc8c7ca13..b8de1c1077 100644 --- a/editor/src/editor/mvc/break_line.rs +++ b/editor/src/editor/mvc/break_line.rs @@ -1,10 +1,11 @@ +use roc_ast::lang::core::ast::ASTNodeId; +use roc_ast::lang::core::def::def2::Def2; +use roc_code_markup::markup::common_nodes::new_blank_mn_w_nls; + use crate::editor::ed_error::EdResult; -use crate::editor::markup::common_nodes::new_blank_mn_w_nls; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; use crate::editor::util::index_of; -use crate::lang::ast::Def2; -use crate::lang::parse::ASTNodeId; use crate::ui::text::text_pos::TextPos; // put everything after caret on new line, create a Def2::Blank if there was nothing after the caret. diff --git a/editor/src/editor/mvc/ed_model.rs b/editor/src/editor/mvc/ed_model.rs index a522bc6553..d255bdbeb5 100644 --- a/editor/src/editor/mvc/ed_model.rs +++ b/editor/src/editor/mvc/ed_model.rs @@ -1,21 +1,22 @@ use crate::editor::code_lines::CodeLines; use crate::editor::grid_node_map::GridNodeMap; -use crate::editor::markup::nodes::ast_to_mark_nodes; -use crate::editor::slow_pool::{MarkNodeId, SlowPool}; use crate::editor::{ ed_error::SrcParseError, ed_error::{EdResult, EmptyCodeString, MissingParent, NoNodeAtCaretPosition}, }; use crate::graphics::primitives::rect::Rect; -use crate::lang::expr::Env; -use crate::lang::parse::{ASTNodeId, AST}; -use crate::lang::pool::PoolStr; use crate::ui::text::caret_w_select::{CaretPos, CaretWSelect}; use crate::ui::text::lines::SelectableLines; use crate::ui::text::text_pos::TextPos; use crate::ui::ui_error::UIResult; use bumpalo::Bump; use nonempty::NonEmpty; +use roc_ast::lang::core::ast::{ASTNodeId, AST}; +use roc_ast::lang::env::Env; +use roc_ast::mem_pool::pool_str::PoolStr; +use roc_ast::parse::parse_ast; +use roc_code_markup::markup::nodes::ast_to_mark_nodes; +use roc_code_markup::slow_pool::{MarkNodeId, SlowPool}; use roc_load::file::LoadedModule; use std::path::Path; @@ -56,18 +57,18 @@ pub fn init_model<'a>( ) -> EdResult> { let mut module = EdModule::new(code_str, env, code_arena)?; - let mut mark_node_pool = SlowPool::new(); + let mut mark_node_pool = SlowPool::default(); let markup_ids = if code_str.is_empty() { EmptyCodeString {}.fail() } else { - ast_to_mark_nodes( + Ok(ast_to_mark_nodes( code_arena, &mut module.env, &module.ast, &mut mark_node_pool, &loaded_module.interns, - ) + )?) }?; let mut code_lines = CodeLines::default(); @@ -152,7 +153,7 @@ impl<'a> EdModel<'a> { if let Some(parent_id) = curr_mark_node.get_parent_id_opt() { let parent = self.mark_node_pool.get(parent_id); - parent.get_child_indices(curr_mark_node_id, &self.mark_node_pool) + Ok(parent.get_child_indices(curr_mark_node_id, &self.mark_node_pool)?) } else { MissingParent { node_id: curr_mark_node_id, @@ -180,7 +181,7 @@ pub struct EdModule<'a> { impl<'a> EdModule<'a> { pub fn new(code_str: &'a str, mut env: Env<'a>, ast_arena: &'a Bump) -> EdResult> { if !code_str.is_empty() { - let parse_res = AST::parse_from_string(code_str, &mut env, ast_arena); + let parse_res = parse_ast::parse_from_string(code_str, &mut env, ast_arena); match parse_res { Ok(ast) => Ok(EdModule { env, ast }), @@ -201,8 +202,6 @@ pub mod test_ed_model { use crate::editor::main::load_module; use crate::editor::mvc::ed_model; use crate::editor::resources::strings::HELLO_WORLD; - use crate::lang::expr::Env; - use crate::lang::pool::Pool; use crate::ui::text::caret_w_select::test_caret_w_select::convert_dsl_to_selection; use crate::ui::text::caret_w_select::test_caret_w_select::convert_selection_to_dsl; use crate::ui::text::caret_w_select::CaretPos; @@ -211,6 +210,8 @@ pub mod test_ed_model { use crate::ui::ui_error::UIResult; use bumpalo::Bump; use ed_model::EdModel; + use roc_ast::lang::env::Env; + use roc_ast::mem_pool::pool::Pool; use roc_load::file::LoadedModule; use roc_module::symbol::IdentIds; use roc_module::symbol::ModuleIds; diff --git a/editor/src/editor/mvc/ed_update.rs b/editor/src/editor/mvc/ed_update.rs index 9a515d55c4..a0384d0bc0 100644 --- a/editor/src/editor/mvc/ed_update.rs +++ b/editor/src/editor/mvc/ed_update.rs @@ -7,10 +7,10 @@ use crate::editor::code_lines::CodeLines; use crate::editor::ed_error::EdResult; use crate::editor::ed_error::MissingSelection; use crate::editor::grid_node_map::GridNodeMap; -use crate::editor::markup::attribute::Attributes; +/*use crate::editor::markup::attribute::Attributes; use crate::editor::markup::nodes; use crate::editor::markup::nodes::MarkupNode; -use crate::editor::markup::nodes::EQUALS; +use crate::editor::markup::nodes::EQUALS;*/ use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_model::SelectedBlock; @@ -26,7 +26,7 @@ use crate::editor::mvc::string_update::start_new_string; use crate::editor::mvc::string_update::update_small_string; use crate::editor::mvc::string_update::update_string; use crate::editor::mvc::tld_value_update::{start_new_tld_value, update_tld_val_name}; -use crate::editor::slow_pool::MarkNodeId; +/*use crate::editor::slow_pool::MarkNodeId; use crate::editor::slow_pool::SlowPool; use crate::editor::syntax_highlight::HighlightStyle; use crate::lang::ast::Def2; @@ -37,7 +37,7 @@ use crate::lang::parse::ASTNodeId; use crate::lang::pool::Pool; use crate::lang::pool::PoolStr; use crate::lang::types::Type2; -use crate::lang::{constrain::Constraint, solve}; +use crate::lang::{constrain::Constraint, solve};*/ use crate::ui::text::caret_w_select::CaretWSelect; use crate::ui::text::lines::MoveCaretFun; use crate::ui::text::selection::validate_raw_sel; @@ -50,7 +50,25 @@ use crate::ui::util::path_to_string; use crate::ui::util::write_to_file; use crate::window::keyboard_input::Modifiers; use bumpalo::Bump; +use roc_ast::constrain::constrain_expr; +use roc_ast::constrain::Constraint; +use roc_ast::lang::core::ast::ASTNodeId; +use roc_ast::lang::core::def::def2::Def2; +use roc_ast::lang::core::def::def2::DefId; +use roc_ast::lang::core::expr::expr2::Expr2; +use roc_ast::lang::core::expr::expr2::ExprId; +use roc_ast::lang::core::types::Type2; +use roc_ast::mem_pool::pool::Pool; +use roc_ast::mem_pool::pool_str::PoolStr; +use roc_ast::solve_type; use roc_can::expected::Expected; +use roc_code_markup::markup::attribute::Attributes; +use roc_code_markup::markup::nodes; +use roc_code_markup::markup::nodes::MarkupNode; +use roc_code_markup::markup::nodes::EQUALS; +use roc_code_markup::slow_pool::MarkNodeId; +use roc_code_markup::slow_pool::SlowPool; +use roc_code_markup::syntax_highlight::HighlightStyle; use roc_collections::all::MutMap; use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; @@ -491,8 +509,8 @@ impl<'a> EdModel<'a> { rigid_variables: MutMap, constraint: Constraint, var_store: VarStore, - ) -> (Solved, solve::Env, Vec) { - let env = solve::Env { + ) -> (Solved, solve_type::Env, Vec) { + let env = solve_type::Env { vars_by_symbol: MutMap::default(), aliases, }; @@ -510,7 +528,7 @@ impl<'a> EdModel<'a> { // Run the solver to populate Subs. let (solved_subs, solved_env) = - solve::run(&arena, mempool, &env, &mut problems, subs, &constraint); + solve_type::run(&arena, mempool, &env, &mut problems, subs, &constraint); (solved_subs, solved_env, problems) } @@ -570,7 +588,7 @@ impl<'a> EdModel<'a> { let blank_replacement = MarkupNode::Blank { ast_node_id: sel_block.ast_node_id, - attributes: Attributes::new(), + attributes: Attributes::default(), syn_high_style: HighlightStyle::Blank, parent_id_opt: expr2_level_mark_node.get_parent_id_opt(), newlines_at_end, @@ -801,6 +819,14 @@ pub fn get_node_context<'a>(ed_model: &'a EdModel) -> EdResult> }) } +fn if_modifiers(modifiers: &Modifiers, shortcut_result: UIResult<()>) -> EdResult<()> { + if modifiers.cmd_or_ctrl() { + Ok(shortcut_result?) + } else { + Ok(()) + } +} + // current(=caret is here) MarkupNode corresponds to a Def2 in the AST pub fn handle_new_char_def( received_char: &char, diff --git a/editor/src/editor/mvc/ed_view.rs b/editor/src/editor/mvc/ed_view.rs index 6fee1301f5..b9d4693b9e 100644 --- a/editor/src/editor/mvc/ed_view.rs +++ b/editor/src/editor/mvc/ed_view.rs @@ -7,7 +7,6 @@ use crate::editor::render_debug::build_debug_graphics; use crate::editor::resources::strings::START_TIP; use crate::graphics::primitives::rect::Rect; use crate::graphics::primitives::text::{owned_section_from_text, Text}; -use crate::lang::pool::Pool; use crate::ui::text::caret_w_select::make_caret_rect; use crate::ui::text::caret_w_select::make_selection_rect; use crate::ui::text::caret_w_select::CaretWSelect; @@ -15,6 +14,7 @@ use crate::ui::text::selection::Selection; use crate::ui::tooltip::ToolTip; use crate::ui::ui_error::MissingGlyphDims; use cgmath::Vector2; +use roc_ast::mem_pool::pool::Pool; use snafu::OptionExt; use winit::dpi::PhysicalSize; diff --git a/editor/src/editor/mvc/int_update.rs b/editor/src/editor/mvc/int_update.rs index 237da57d86..75a8354b60 100644 --- a/editor/src/editor/mvc/int_update.rs +++ b/editor/src/editor/mvc/int_update.rs @@ -1,17 +1,18 @@ +use roc_ast::lang::core::expr::expr2::Expr2::SmallInt; +use roc_ast::lang::core::expr::expr2::IntStyle; +use roc_ast::lang::core::expr::expr2::IntVal; +use roc_ast::mem_pool::pool_str::PoolStr; +use roc_code_markup::markup::attribute::Attributes; +use roc_code_markup::markup::nodes::MarkupNode; +use roc_code_markup::slow_pool::MarkNodeId; +use roc_code_markup::syntax_highlight::HighlightStyle; + use crate::editor::ed_error::EdResult; use crate::editor::ed_error::StringParseError; -use crate::editor::markup::attribute::Attributes; -use crate::editor::markup::nodes::MarkupNode; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_update::get_node_context; use crate::editor::mvc::ed_update::NodeContext; -use crate::editor::slow_pool::MarkNodeId; -use crate::editor::syntax_highlight::HighlightStyle; -use crate::lang::ast::Expr2::SmallInt; -use crate::lang::ast::IntVal; -use crate::lang::ast::{IntStyle, IntVal::*}; -use crate::lang::pool::PoolStr; use crate::ui::text::lines::SelectableLines; // digit_char should be verified to be a digit before calling this function @@ -48,7 +49,7 @@ pub fn start_new_int(ed_model: &mut EdModel, digit_char: &char) -> EdResult EdResult<()> { + use IntVal::*; + *number = match number { I64(_) => I64(check_parse_res(updated_str.parse::())?), U64(_) => U64(check_parse_res(updated_str.parse::())?), diff --git a/editor/src/editor/mvc/let_update.rs b/editor/src/editor/mvc/let_update.rs index 697531bb5a..a678c0dbfb 100644 --- a/editor/src/editor/mvc/let_update.rs +++ b/editor/src/editor/mvc/let_update.rs @@ -1,18 +1,19 @@ +use roc_ast::lang::core::ast::ASTNodeId; +use roc_ast::lang::core::expr::expr2::Expr2; +use roc_ast::lang::core::pattern::Pattern2; +use roc_ast::lang::core::val_def::ValueDef; +use roc_code_markup::markup::attribute::Attributes; +use roc_code_markup::markup::common_nodes::new_blank_mn_w_nls; +use roc_code_markup::markup::common_nodes::new_equals_mn; +use roc_code_markup::markup::nodes::MarkupNode; +use roc_code_markup::syntax_highlight::HighlightStyle; use roc_module::symbol::Symbol; use crate::editor::ed_error::EdResult; -use crate::editor::markup::attribute::Attributes; -use crate::editor::markup::common_nodes::new_blank_mn_w_nls; -use crate::editor::markup::common_nodes::new_equals_mn; -use crate::editor::markup::nodes::MarkupNode; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_update::get_node_context; use crate::editor::mvc::ed_update::NodeContext; -use crate::editor::syntax_highlight::HighlightStyle; -use crate::lang::ast::{Expr2, ValueDef}; -use crate::lang::parse::ASTNodeId; -use crate::lang::pattern::Pattern2; pub fn start_new_let_value(ed_model: &mut EdModel, new_char: &char) -> EdResult { let NodeContext { @@ -66,7 +67,7 @@ pub fn start_new_let_value(ed_model: &mut EdModel, new_char: &char) -> EdResult< content: val_name_string, ast_node_id, syn_high_style: HighlightStyle::Variable, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: Some(curr_mark_node_id), newlines_at_end: curr_mark_node_nls, }; diff --git a/editor/src/editor/mvc/list_update.rs b/editor/src/editor/mvc/list_update.rs index 65e6fce10b..fef31b7627 100644 --- a/editor/src/editor/mvc/list_update.rs +++ b/editor/src/editor/mvc/list_update.rs @@ -1,19 +1,18 @@ -use crate::editor::ed_error::EdResult; -use crate::editor::ed_error::{MissingParent, UnexpectedASTNode}; -use crate::editor::markup::common_nodes::{ +use roc_ast::lang::core::ast::{ast_node_to_string, ASTNodeId}; +use roc_ast::lang::core::expr::expr2::{Expr2, ExprId}; +use roc_ast::mem_pool::pool_vec::PoolVec; +use roc_code_markup::markup::common_nodes::{ new_blank_mn, new_comma_mn, new_left_square_mn, new_right_square_mn, }; -use crate::editor::markup::nodes; -use crate::editor::markup::nodes::MarkupNode; +use roc_code_markup::markup::nodes::{self, MarkupNode}; +use roc_code_markup::slow_pool::MarkNodeId; + +use crate::editor::ed_error::EdResult; +use crate::editor::ed_error::{MissingParent, UnexpectedASTNode}; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_update::get_node_context; use crate::editor::mvc::ed_update::NodeContext; -use crate::editor::slow_pool::MarkNodeId; -use crate::lang::ast::ExprId; -use crate::lang::ast::{ast_node_to_string, Expr2}; -use crate::lang::parse::ASTNodeId; -use crate::lang::pool::PoolVec; use crate::ui::text::text_pos::TextPos; pub fn start_new_list(ed_model: &mut EdModel) -> EdResult { diff --git a/editor/src/editor/mvc/lookup_update.rs b/editor/src/editor/mvc/lookup_update.rs index 03a4905942..5d5c9f5396 100644 --- a/editor/src/editor/mvc/lookup_update.rs +++ b/editor/src/editor/mvc/lookup_update.rs @@ -1,9 +1,10 @@ +use roc_ast::lang::core::expr::expr2::{Expr2, ExprId}; +use roc_ast::mem_pool::pool_str::PoolStr; +use roc_code_markup::slow_pool::MarkNodeId; + use crate::editor::ed_error::EdResult; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; -use crate::editor::slow_pool::MarkNodeId; -use crate::lang::ast::{Expr2, ExprId}; -use crate::lang::pool::PoolStr; use crate::ui::text::lines::SelectableLines; pub fn update_invalid_lookup( diff --git a/editor/src/editor/mvc/record_update.rs b/editor/src/editor/mvc/record_update.rs index 0b8c911ab6..d27bb0c5d0 100644 --- a/editor/src/editor/mvc/record_update.rs +++ b/editor/src/editor/mvc/record_update.rs @@ -1,23 +1,26 @@ use crate::editor::ed_error::EdResult; use crate::editor::ed_error::MissingParent; use crate::editor::ed_error::RecordWithoutFields; -use crate::editor::markup::attribute::Attributes; -use crate::editor::markup::common_nodes::new_blank_mn; -use crate::editor::markup::common_nodes::new_left_accolade_mn; -use crate::editor::markup::common_nodes::new_right_accolade_mn; -use crate::editor::markup::nodes; -use crate::editor::markup::nodes::MarkupNode; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_update::get_node_context; use crate::editor::mvc::ed_update::NodeContext; -use crate::editor::slow_pool::MarkNodeId; -use crate::editor::syntax_highlight::HighlightStyle; use crate::editor::util::index_of; -use crate::lang::ast::{Expr2, ExprId, RecordField}; -use crate::lang::parse::ASTNodeId; -use crate::lang::pool::{PoolStr, PoolVec}; use crate::ui::text::text_pos::TextPos; +use roc_ast::lang::core::ast::ASTNodeId; +use roc_ast::lang::core::expr::expr2::Expr2; +use roc_ast::lang::core::expr::expr2::ExprId; +use roc_ast::lang::core::expr::record_field::RecordField; +use roc_ast::mem_pool::pool_str::PoolStr; +use roc_ast::mem_pool::pool_vec::PoolVec; +use roc_code_markup::markup::attribute::Attributes; +use roc_code_markup::markup::common_nodes::new_blank_mn; +use roc_code_markup::markup::common_nodes::new_left_accolade_mn; +use roc_code_markup::markup::common_nodes::new_right_accolade_mn; +use roc_code_markup::markup::nodes; +use roc_code_markup::markup::nodes::MarkupNode; +use roc_code_markup::slow_pool::MarkNodeId; +use roc_code_markup::syntax_highlight::HighlightStyle; use snafu::OptionExt; pub fn start_new_record(ed_model: &mut EdModel) -> EdResult { @@ -123,7 +126,7 @@ pub fn update_empty_record( content: new_input.to_owned(), ast_node_id, syn_high_style: HighlightStyle::RecordField, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, }; @@ -232,7 +235,7 @@ pub fn update_record_colon( content: record_colon.to_owned(), ast_node_id: ASTNodeId::AExprId(record_ast_node_id), syn_high_style: HighlightStyle::Operator, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: Some(parent_id), newlines_at_end: 0, }; diff --git a/editor/src/editor/mvc/string_update.rs b/editor/src/editor/mvc/string_update.rs index 2c35ca89fd..8ee2ddd881 100644 --- a/editor/src/editor/mvc/string_update.rs +++ b/editor/src/editor/mvc/string_update.rs @@ -1,16 +1,17 @@ +use roc_ast::lang::core::expr::expr2::ArrString; +use roc_ast::lang::core::expr::expr2::Expr2; +use roc_ast::lang::core::str::update_str_expr; +use roc_ast::mem_pool::pool_str::PoolStr; +use roc_code_markup::markup::attribute::Attributes; +use roc_code_markup::markup::nodes; +use roc_code_markup::markup::nodes::MarkupNode; +use roc_code_markup::syntax_highlight::HighlightStyle; + use crate::editor::ed_error::EdResult; -use crate::editor::markup::attribute::Attributes; -use crate::editor::markup::nodes; -use crate::editor::markup::nodes::MarkupNode; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_update::get_node_context; use crate::editor::mvc::ed_update::NodeContext; -use crate::editor::syntax_highlight::HighlightStyle; -use crate::lang::ast::update_str_expr; -use crate::lang::ast::ArrString; -use crate::lang::ast::Expr2; -use crate::lang::pool::PoolStr; pub fn update_small_string( new_char: &char, @@ -149,7 +150,7 @@ pub fn start_new_string(ed_model: &mut EdModel) -> EdResult { content: nodes::STRING_QUOTES.to_owned(), ast_node_id, syn_high_style: HighlightStyle::String, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: curr_mark_node_nls, }; diff --git a/editor/src/editor/mvc/tld_value_update.rs b/editor/src/editor/mvc/tld_value_update.rs index 8cdb85feba..6ca2f8e0fd 100644 --- a/editor/src/editor/mvc/tld_value_update.rs +++ b/editor/src/editor/mvc/tld_value_update.rs @@ -1,23 +1,28 @@ +use roc_ast::{ + lang::{ + core::{ + ast::ASTNodeId, + def::def2::Def2, + expr::expr2::Expr2, + pattern::{get_identifier_string, Pattern2}, + }, + env::Env, + }, + mem_pool::pool::NodeId, +}; +use roc_code_markup::{ + markup::{ + attribute::Attributes, + common_nodes::{new_blank_mn_w_nls, new_equals_mn}, + nodes::{set_parent_for_all, MarkupNode}, + }, + slow_pool::{MarkNodeId, SlowPool}, + syntax_highlight::HighlightStyle, +}; use roc_module::symbol::{Interns, Symbol}; use crate::{ - editor::{ - ed_error::{EdResult, FailedToUpdateIdentIdName, KeyNotFound}, - markup::{ - attribute::Attributes, - common_nodes::{new_blank_mn_w_nls, new_equals_mn}, - nodes::{set_parent_for_all, MarkupNode}, - }, - slow_pool::{MarkNodeId, SlowPool}, - syntax_highlight::HighlightStyle, - }, - lang::{ - ast::{Def2, Expr2}, - expr::Env, - parse::ASTNodeId, - pattern::{get_identifier_string, Pattern2}, - pool::NodeId, - }, + editor::ed_error::{EdResult, FailedToUpdateIdentIdName, KeyNotFound}, ui::text::text_pos::TextPos, }; @@ -44,7 +49,7 @@ pub fn tld_mark_node<'a>( content: val_name, ast_node_id, syn_high_style: HighlightStyle::Variable, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, }; diff --git a/editor/src/editor/render_ast.rs b/editor/src/editor/render_ast.rs index 3fa42bc1de..00d035a692 100644 --- a/editor/src/editor/render_ast.rs +++ b/editor/src/editor/render_ast.rs @@ -1,11 +1,10 @@ -use super::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER}; -use super::slow_pool::MarkNodeId; use crate::editor::mvc::ed_view::RenderedWgpu; -use crate::editor::slow_pool::SlowPool; use crate::editor::{ed_error::EdResult, theme::EdTheme, util::map_get}; use crate::graphics::primitives::rect::Rect; use crate::graphics::primitives::text as gr_text; use cgmath::Vector2; +use roc_code_markup::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER}; +use roc_code_markup::slow_pool::{MarkNodeId, SlowPool}; use winit::dpi::PhysicalSize; use crate::{editor::config::Config, graphics::colors}; diff --git a/editor/src/editor/render_debug.rs b/editor/src/editor/render_debug.rs index 36e4377d80..2fc1f23863 100644 --- a/editor/src/editor/render_debug.rs +++ b/editor/src/editor/render_debug.rs @@ -1,11 +1,11 @@ use crate::editor::ed_error::EdResult; -use crate::editor::markup::nodes::tree_as_string; use crate::editor::mvc::ed_model::EdModel; use crate::graphics::colors; use crate::graphics::colors::from_hsb; use crate::graphics::primitives::text as gr_text; -use crate::lang::ast::def2_to_string; use cgmath::Vector2; +use roc_ast::lang::core::def::def2::def2_to_string; +use roc_code_markup::markup::nodes::tree_as_string; use winit::dpi::PhysicalSize; use crate::editor::config::Config; diff --git a/editor/src/editor/theme.rs b/editor/src/editor/theme.rs index f0809cd890..30e45a47a9 100644 --- a/editor/src/editor/theme.rs +++ b/editor/src/editor/theme.rs @@ -1,8 +1,8 @@ use gr_colors::{from_hsb, RgbaTup}; +use roc_code_markup::syntax_highlight::{default_highlight_map, HighlightStyle}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use crate::editor::syntax_highlight::{default_highlight_map, HighlightStyle}; use crate::graphics::colors as gr_colors; use crate::ui::theme::UITheme; diff --git a/editor/src/lang/ast.rs b/editor/src/lang/ast.rs deleted file mode 100644 index 3fffb3cdad..0000000000 --- a/editor/src/lang/ast.rs +++ /dev/null @@ -1,742 +0,0 @@ -#![allow(clippy::manual_map)] - -use std::collections::{HashMap, HashSet}; -use std::hash::BuildHasherDefault; - -use crate::editor::ed_error::{EdResult, UnexpectedASTNode}; -use crate::lang::pattern::{Pattern2, PatternId}; -use crate::lang::pool::Pool; -use crate::lang::pool::{NodeId, PoolStr, PoolVec, ShallowClone}; -use crate::lang::types::{Type2, TypeId}; -use arraystring::{typenum::U30, ArrayString}; -use roc_can::expr::Recursive; -use roc_collections::all::WyHash; -use roc_module::low_level::LowLevel; -use roc_module::operator::CalledVia; -use roc_module::symbol::Symbol; -use roc_types::subs::Variable; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum Problem { - RanOutOfNodeIds, -} - -pub type Res = Result; - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum IntStyle { - Decimal, - Octal, - Hex, - Binary, -} - -impl IntStyle { - pub fn from_base(base: roc_parse::ast::Base) -> Self { - use roc_parse::ast::Base; - match base { - Base::Decimal => Self::Decimal, - Base::Octal => Self::Octal, - Base::Hex => Self::Hex, - Base::Binary => Self::Binary, - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum IntVal { - I64(i64), - U64(u64), - I32(i32), - U32(u32), - I16(i16), - U16(u16), - I8(i8), - U8(u8), -} - -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum FloatVal { - F64(f64), - F32(f32), -} - -#[derive(Debug)] -pub enum RecordField { - InvalidLabelOnly(PoolStr, Variable), - LabelOnly(PoolStr, Variable, Symbol), - LabeledValue(PoolStr, Variable, ExprId), -} - -#[test] -fn size_of_intval() { - assert_eq!(std::mem::size_of::(), 16); -} - -pub type ArrString = ArrayString; - -/// An Expr that fits in 32B. -/// It has a 1B discriminant and variants which hold payloads of at most 31B. -#[derive(Debug)] -pub enum Expr2 { - /// A negative number literal without a dot - SmallInt { - number: IntVal, // 16B - var: Variable, // 4B - style: IntStyle, // 1B - text: PoolStr, // 8B - }, - // TODO(rvcas): rename this eventually - /// A large (over 64-bit) negative number literal without a dot. - /// This variant can't use IntVal because if IntVal stored 128-bit - /// integers, it would be 32B on its own because of alignment. - I128 { - number: i128, // 16B - var: Variable, // 4B - style: IntStyle, // 1B - text: PoolStr, // 8B - }, - // TODO(rvcas): rename this eventually - /// A large (over 64-bit) nonnegative number literal without a dot - /// This variant can't use IntVal because if IntVal stored 128-bit - /// integers, it would be 32B on its own because of alignment. - U128 { - number: u128, // 16B - var: Variable, // 4B - style: IntStyle, // 1B - text: PoolStr, // 8B - }, - /// A floating-point literal (with a dot) - Float { - number: FloatVal, // 16B - var: Variable, // 4B - text: PoolStr, // 8B - }, - /// string literals of length up to 30B - SmallStr(ArrString), // 31B - /// string literals of length 31B or more - Str(PoolStr), // 8B - // Lookups - Var(Symbol), // 8B - InvalidLookup(PoolStr), // 8B - - List { - elem_var: Variable, // 4B - elems: PoolVec, // 8B - }, - If { - cond_var: Variable, // 4B - expr_var: Variable, // 4B - branches: PoolVec<(ExprId, ExprId)>, // 8B - final_else: ExprId, // 4B - }, - When { - cond_var: Variable, // 4B - expr_var: Variable, // 4B - branches: PoolVec, // 8B - cond: ExprId, // 4B - }, - LetRec { - defs: PoolVec, // 8B - body_var: Variable, // 8B - body_id: ExprId, // 4B - }, - LetFunction { - def_id: NodeId, // 4B - body_var: Variable, // 8B - body_id: ExprId, // 4B - }, - LetValue { - def_id: NodeId, // 4B - body_id: ExprId, // 4B - body_var: Variable, // 4B - }, - Call { - args: PoolVec<(Variable, ExprId)>, // 8B - expr: ExprId, // 4B - expr_var: Variable, // 4B - fn_var: Variable, // 4B - closure_var: Variable, // 4B - called_via: CalledVia, // 2B - }, - RunLowLevel { - op: LowLevel, // 1B - args: PoolVec<(Variable, ExprId)>, // 8B - ret_var: Variable, // 4B - }, - Closure { - args: PoolVec<(Variable, NodeId)>, // 8B - name: Symbol, // 8B - body: ExprId, // 4B - function_type: Variable, // 4B - recursive: Recursive, // 1B - extra: NodeId, // 4B - }, - // Product Types - Record { - record_var: Variable, // 4B - fields: PoolVec, // 8B - }, - /// Empty record constant - EmptyRecord, - /// Look up exactly one field on a record, e.g. (expr).foo. - Access { - field: PoolStr, // 4B - expr: ExprId, // 4B - record_var: Variable, // 4B - ext_var: Variable, // 4B - field_var: Variable, // 4B - }, - - /// field accessor as a function, e.g. (.foo) expr - Accessor { - function_var: Variable, // 4B - closure_var: Variable, // 4B - field: PoolStr, // 4B - record_var: Variable, // 4B - ext_var: Variable, // 4B - field_var: Variable, // 4B - }, - Update { - symbol: Symbol, // 8B - updates: PoolVec, // 8B - record_var: Variable, // 4B - ext_var: Variable, // 4B - }, - - // Sum Types - GlobalTag { - name: PoolStr, // 4B - variant_var: Variable, // 4B - ext_var: Variable, // 4B - arguments: PoolVec<(Variable, ExprId)>, // 8B - }, - PrivateTag { - name: Symbol, // 8B - variant_var: Variable, // 4B - ext_var: Variable, // 4B - arguments: PoolVec<(Variable, ExprId)>, // 8B - }, - Blank, // Rendered as empty box in editor - - // Compiles, but will crash if reached - RuntimeError(/* TODO make a version of RuntimeError that fits in 15B */), -} - -// A top level definition, not inside a function. For example: `main = "Hello, world!"` -#[derive(Debug)] -pub enum Def2 { - // ValueDef example: `main = "Hello, world!"`. identifier -> `main`, expr -> "Hello, world!" - ValueDef { - identifier_id: NodeId, - expr_id: NodeId, - }, - Blank, -} - -#[derive(Debug)] -pub enum ValueDef { - WithAnnotation { - pattern_id: PatternId, // 4B - expr_id: ExprId, // 4B - type_id: TypeId, - rigids: Rigids, - expr_var: Variable, // 4B - }, - NoAnnotation { - pattern_id: PatternId, // 4B - expr_id: ExprId, // 4B - expr_var: Variable, // 4B - }, -} - -impl ShallowClone for ValueDef { - fn shallow_clone(&self) -> Self { - match self { - Self::WithAnnotation { - pattern_id, - expr_id, - type_id, - rigids, - expr_var, - } => Self::WithAnnotation { - pattern_id: *pattern_id, - expr_id: *expr_id, - type_id: *type_id, - rigids: rigids.shallow_clone(), - expr_var: *expr_var, - }, - Self::NoAnnotation { - pattern_id, - expr_id, - expr_var, - } => Self::NoAnnotation { - pattern_id: *pattern_id, - expr_id: *expr_id, - expr_var: *expr_var, - }, - } - } -} - -impl ValueDef { - pub fn get_expr_id(&self) -> ExprId { - match self { - ValueDef::WithAnnotation { expr_id, .. } => *expr_id, - ValueDef::NoAnnotation { expr_id, .. } => *expr_id, - } - } - - pub fn get_pattern_id(&self) -> NodeId { - match self { - ValueDef::WithAnnotation { pattern_id, .. } => *pattern_id, - ValueDef::NoAnnotation { pattern_id, .. } => *pattern_id, - } - } -} - -pub fn value_def_to_string(val_def: &ValueDef, pool: &Pool) -> String { - match val_def { - ValueDef::WithAnnotation { - pattern_id, - expr_id, - type_id, - rigids, - expr_var, - } => { - format!("WithAnnotation {{ pattern_id: {:?}, expr_id: {:?}, type_id: {:?}, rigids: {:?}, expr_var: {:?}}}", pool.get(*pattern_id), expr2_to_string(*expr_id, pool), pool.get(*type_id), rigids, expr_var) - } - ValueDef::NoAnnotation { - pattern_id, - expr_id, - expr_var, - } => { - format!( - "NoAnnotation {{ pattern_id: {:?}, expr_id: {:?}, expr_var: {:?}}}", - pool.get(*pattern_id), - expr2_to_string(*expr_id, pool), - expr_var - ) - } - } -} - -#[derive(Debug)] -pub enum FunctionDef { - WithAnnotation { - name: Symbol, // 8B - arguments: PoolVec<(PatternId, Type2)>, // 8B - rigids: NodeId, // 4B - return_type: TypeId, // 4B - body: ExprId, // 4B - }, - NoAnnotation { - name: Symbol, // 8B - arguments: PoolVec<(PatternId, Variable)>, // 8B - return_var: Variable, // 4B - body: ExprId, // 4B - }, -} - -impl ShallowClone for FunctionDef { - fn shallow_clone(&self) -> Self { - match self { - Self::WithAnnotation { - name, - arguments, - rigids, - return_type, - body, - } => Self::WithAnnotation { - name: *name, - arguments: arguments.shallow_clone(), - rigids: *rigids, - return_type: *return_type, - body: *body, - }, - - Self::NoAnnotation { - name, - arguments, - return_var, - body, - } => Self::NoAnnotation { - name: *name, - arguments: arguments.shallow_clone(), - return_var: *return_var, - body: *body, - }, - } - } -} - -#[derive(Debug)] -pub struct Rigids { - pub names: PoolVec<(Option, Variable)>, // 8B - padding: [u8; 1], -} - -#[allow(clippy::needless_collect)] -impl Rigids { - pub fn new( - named: HashMap<&str, Variable, BuildHasherDefault>, - unnamed: HashSet>, - pool: &mut Pool, - ) -> Self { - let names = PoolVec::with_capacity((named.len() + unnamed.len()) as u32, pool); - - let mut temp_names = Vec::new(); - - temp_names.extend(named.iter().map(|(name, var)| (Some(*name), *var))); - - temp_names.extend(unnamed.iter().map(|var| (None, *var))); - - for (node_id, (opt_name, variable)) in names.iter_node_ids().zip(temp_names) { - let poolstr = opt_name.map(|name| PoolStr::new(name, pool)); - - pool[node_id] = (poolstr, variable); - } - - Self { - names, - padding: Default::default(), - } - } - - pub fn named(&self, pool: &mut Pool) -> PoolVec<(PoolStr, Variable)> { - let named = self - .names - .iter(pool) - .filter_map(|(opt_pool_str, var)| { - if let Some(pool_str) = opt_pool_str { - Some((*pool_str, *var)) - } else { - None - } - }) - .collect::>(); - - PoolVec::new(named.into_iter(), pool) - } - - pub fn unnamed(&self, pool: &mut Pool) -> PoolVec { - let unnamed = self - .names - .iter(pool) - .filter_map(|(opt_pool_str, var)| { - if opt_pool_str.is_none() { - Some(*var) - } else { - None - } - }) - .collect::>(); - - PoolVec::new(unnamed.into_iter(), pool) - } -} - -/// This is overflow data from a Closure variant, which needs to store -/// more than 32B of total data -#[derive(Debug)] -pub struct ClosureExtra { - pub return_type: Variable, // 4B - pub captured_symbols: PoolVec<(Symbol, Variable)>, // 8B - pub closure_type: Variable, // 4B - pub closure_ext_var: Variable, // 4B -} - -#[derive(Debug)] -pub struct WhenBranch { - pub patterns: PoolVec, // 4B - pub body: ExprId, // 3B - pub guard: Option, // 4B -} - -// TODO make the inner types private? -pub type ExprId = NodeId; - -pub type DefId = NodeId; - -use RecordField::*; - -use super::parse::ASTNodeId; -impl RecordField { - pub fn get_record_field_var(&self) -> &Variable { - match self { - InvalidLabelOnly(_, var) => var, - LabelOnly(_, var, _) => var, - LabeledValue(_, var, _) => var, - } - } - - pub fn get_record_field_pool_str(&self) -> &PoolStr { - match self { - InvalidLabelOnly(pool_str, _) => pool_str, - LabelOnly(pool_str, _, _) => pool_str, - LabeledValue(pool_str, _, _) => pool_str, - } - } - - pub fn get_record_field_pool_str_mut(&mut self) -> &mut PoolStr { - match self { - InvalidLabelOnly(pool_str, _) => pool_str, - LabelOnly(pool_str, _, _) => pool_str, - LabeledValue(pool_str, _, _) => pool_str, - } - } - - pub fn get_record_field_val_node_id(&self) -> Option { - match self { - InvalidLabelOnly(_, _) => None, - LabelOnly(_, _, _) => None, - LabeledValue(_, _, field_val_id) => Some(*field_val_id), - } - } -} - -pub fn ast_node_to_string(node_id: ASTNodeId, pool: &Pool) -> String { - match node_id { - ASTNodeId::ADefId(def_id) => def2_to_string(def_id, pool), - ASTNodeId::AExprId(expr_id) => expr2_to_string(expr_id, pool), - } -} - -pub fn expr2_to_string(node_id: ExprId, pool: &Pool) -> String { - let mut full_string = String::new(); - let expr2 = pool.get(node_id); - - expr2_to_string_helper(expr2, 0, pool, &mut full_string); - - full_string -} - -fn get_spacing(indent_level: usize) -> String { - std::iter::repeat(" ") - .take(indent_level) - .collect::>() - .join("") -} - -fn expr2_to_string_helper( - expr2: &Expr2, - indent_level: usize, - pool: &Pool, - out_string: &mut String, -) { - out_string.push_str(&get_spacing(indent_level)); - - match expr2 { - Expr2::SmallStr(arr_string) => out_string.push_str(&format!( - "{}{}{}", - "SmallStr(\"", - arr_string.as_str(), - "\")", - )), - Expr2::Str(pool_str) => { - out_string.push_str(&format!("{}{}{}", "Str(\"", pool_str.as_str(pool), "\")",)) - } - Expr2::Blank => out_string.push_str("Blank"), - Expr2::EmptyRecord => out_string.push_str("EmptyRecord"), - Expr2::Record { record_var, fields } => { - out_string.push_str("Record:\n"); - out_string.push_str(&var_to_string(record_var, indent_level + 1)); - - out_string.push_str(&format!("{}fields: [\n", get_spacing(indent_level + 1))); - - let mut first_child = true; - - for field in fields.iter(pool) { - if !first_child { - out_string.push_str(", ") - } else { - first_child = false; - } - - match field { - RecordField::InvalidLabelOnly(pool_str, var) => { - out_string.push_str(&format!( - "{}({}, Var({:?})", - get_spacing(indent_level + 2), - pool_str.as_str(pool), - var, - )); - } - RecordField::LabelOnly(pool_str, var, symbol) => { - out_string.push_str(&format!( - "{}({}, Var({:?}), Symbol({:?})", - get_spacing(indent_level + 2), - pool_str.as_str(pool), - var, - symbol - )); - } - RecordField::LabeledValue(pool_str, var, val_node_id) => { - out_string.push_str(&format!( - "{}({}, Var({:?}), Expr2(\n", - get_spacing(indent_level + 2), - pool_str.as_str(pool), - var, - )); - - let val_expr2 = pool.get(*val_node_id); - expr2_to_string_helper(val_expr2, indent_level + 3, pool, out_string); - out_string.push_str(&format!("{})\n", get_spacing(indent_level + 2))); - } - } - } - - out_string.push_str(&format!("{}]\n", get_spacing(indent_level + 1))); - } - Expr2::List { elem_var, elems } => { - out_string.push_str("List:\n"); - out_string.push_str(&var_to_string(elem_var, indent_level + 1)); - out_string.push_str(&format!("{}elems: [\n", get_spacing(indent_level + 1))); - - let mut first_elt = true; - - for elem_expr2_id in elems.iter(pool) { - if !first_elt { - out_string.push_str(", ") - } else { - first_elt = false; - } - - let elem_expr2 = pool.get(*elem_expr2_id); - - expr2_to_string_helper(elem_expr2, indent_level + 2, pool, out_string) - } - - out_string.push_str(&format!("{}]\n", get_spacing(indent_level + 1))); - } - Expr2::InvalidLookup(pool_str) => { - out_string.push_str(&format!("InvalidLookup({})", pool_str.as_str(pool))); - } - Expr2::SmallInt { text, .. } => { - out_string.push_str(&format!("SmallInt({})", text.as_str(pool))); - } - Expr2::LetValue { - def_id, body_id, .. - } => { - out_string.push_str(&format!( - "LetValue(def_id: >>{:?}), body_id: >>{:?})", - value_def_to_string(pool.get(*def_id), pool), - pool.get(*body_id) - )); - } - other => todo!("Implement for {:?}", other), - } - - out_string.push('\n'); -} - -pub fn def2_to_string(node_id: DefId, pool: &Pool) -> String { - let mut full_string = String::new(); - let def2 = pool.get(node_id); - - match def2 { - Def2::ValueDef { - identifier_id, - expr_id, - } => { - full_string.push_str(&format!( - "Def2::ValueDef(identifier_id: >>{:?}), expr_id: >>{:?})", - pool.get(*identifier_id), - expr2_to_string(*expr_id, pool) - )); - } - Def2::Blank => { - full_string.push_str("Def2::Blank"); - } - } - - full_string -} - -fn var_to_string(some_var: &Variable, indent_level: usize) -> String { - format!("{}Var({:?})\n", get_spacing(indent_level + 1), some_var) -} - -// get string from SmallStr or Str -pub fn get_string_from_expr2(node_id: ExprId, pool: &Pool) -> EdResult { - match pool.get(node_id) { - Expr2::SmallStr(arr_string) => Ok(arr_string.as_str().to_string()), - Expr2::Str(pool_str) => Ok(pool_str.as_str(pool).to_owned()), - other => UnexpectedASTNode { - required_node_type: "SmallStr or Str", - encountered_node_type: format!("{:?}", other), - } - .fail()?, - } -} - -pub fn update_str_expr( - node_id: ExprId, - new_char: char, - insert_index: usize, - pool: &mut Pool, -) -> EdResult<()> { - let str_expr = pool.get_mut(node_id); - - enum Either { - MyString(String), - MyPoolStr(PoolStr), - Done, - } - - let insert_either = match str_expr { - Expr2::SmallStr(arr_string) => { - let insert_res = arr_string.try_insert(insert_index as u8, new_char); - - match insert_res { - Ok(_) => Either::Done, - _ => { - let mut new_string = arr_string.as_str().to_string(); - new_string.insert(insert_index, new_char); - - Either::MyString(new_string) - } - } - } - Expr2::Str(old_pool_str) => Either::MyPoolStr(*old_pool_str), - other => UnexpectedASTNode { - required_node_type: "SmallStr or Str", - encountered_node_type: format!("{:?}", other), - } - .fail()?, - }; - - match insert_either { - Either::MyString(new_string) => { - let new_pool_str = PoolStr::new(&new_string, pool); - - pool.set(node_id, Expr2::Str(new_pool_str)) - } - Either::MyPoolStr(old_pool_str) => { - let mut new_string = old_pool_str.as_str(pool).to_owned(); - - new_string.insert(insert_index, new_char); - - let new_pool_str = PoolStr::new(&new_string, pool); - - pool.set(node_id, Expr2::Str(new_pool_str)) - } - Either::Done => (), - } - - Ok(()) -} - -#[test] -fn size_of_expr() { - assert_eq!(std::mem::size_of::(), crate::lang::pool::NODE_BYTES); -} - -impl ShallowClone for Rigids { - fn shallow_clone(&self) -> Self { - Self { - names: self.names.shallow_clone(), - padding: self.padding, - } - } -} diff --git a/editor/src/lang/expr.rs b/editor/src/lang/expr.rs deleted file mode 100644 index a50c315f94..0000000000 --- a/editor/src/lang/expr.rs +++ /dev/null @@ -1,1555 +0,0 @@ -#![allow(clippy::all)] -#![allow(dead_code)] -#![allow(unused_imports)] -use bumpalo::{collections::Vec as BumpVec, Bump}; -use std::collections::HashMap; -use std::iter::FromIterator; - -use crate::lang::ast::{ - expr2_to_string, value_def_to_string, ClosureExtra, Def2, Expr2, ExprId, FloatVal, IntStyle, - IntVal, RecordField, ValueDef, WhenBranch, -}; -use crate::lang::def::{ - canonicalize_defs, sort_can_defs, CanDefs, Declaration, Def, PendingDef, References, -}; -use crate::lang::pattern::{to_pattern2, Pattern2, PatternId}; -use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone}; -use crate::lang::scope::Scope; -use crate::lang::types::{Alias, Annotation2, Type2, TypeId}; - -use roc_can::expr::Recursive; -use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; -use roc_can::operator::desugar_expr; -use roc_collections::all::default_hasher; -use roc_collections::all::{MutMap, MutSet}; -use roc_module::ident::{Ident, Lowercase, ModuleName}; -use roc_module::low_level::LowLevel; -use roc_module::operator::CalledVia; -use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; -use roc_parse::ast; -use roc_parse::ast::Expr; -use roc_parse::ast::StrLiteral; -use roc_parse::parser::{loc, Parser, State, SyntaxError}; -use roc_parse::pattern::PatternType; -use roc_problem::can::{Problem, RuntimeError}; -use roc_region::all::{Located, Region}; -use roc_types::subs::{VarStore, Variable}; - -#[derive(Clone, Debug, PartialEq, Default)] -pub struct IntroducedVariables { - // Rigids must be unique within a type annoation. - // E.g. in `identity : a -> a`, there should only be one - // variable (a rigid one, with name "a"). - // Hence `rigids : Map` - // - // But then between annotations, the same name can occur multiple times, - // but a variable can only have one name. Therefore - // `ftv : Map`. - pub wildcards: Vec, - pub var_by_name: MutMap, - pub name_by_var: MutMap, - pub host_exposed_aliases: MutMap, -} - -impl IntroducedVariables { - pub fn insert_named(&mut self, name: Lowercase, var: Variable) { - self.var_by_name.insert(name.clone(), var); - self.name_by_var.insert(var, name); - } - - pub fn insert_wildcard(&mut self, var: Variable) { - self.wildcards.push(var); - } - - pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) { - self.host_exposed_aliases.insert(symbol, var); - } - - pub fn union(&mut self, other: &Self) { - self.wildcards.extend(other.wildcards.iter().cloned()); - self.var_by_name.extend(other.var_by_name.clone()); - self.name_by_var.extend(other.name_by_var.clone()); - self.host_exposed_aliases - .extend(other.host_exposed_aliases.clone()); - } - - pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> { - self.var_by_name.get(name) - } - - pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> { - self.name_by_var.get(&var) - } -} - -#[derive(Clone, Default, Debug, PartialEq)] -pub struct Output { - pub references: References, - pub tail_call: Option, - pub introduced_variables: IntroducedVariables, - pub aliases: MutMap>, - pub non_closures: MutSet, -} - -impl Output { - pub fn union(&mut self, other: Self) { - self.references.union_mut(other.references); - - if let (None, Some(later)) = (self.tail_call, other.tail_call) { - self.tail_call = Some(later); - } - - self.aliases.extend(other.aliases); - self.non_closures.extend(other.non_closures); - } -} - -#[derive(Debug)] -pub struct Env<'a> { - pub home: ModuleId, - pub var_store: &'a mut VarStore, - pub pool: &'a mut Pool, - pub arena: &'a Bump, - - pub problems: BumpVec<'a, Problem>, - - pub dep_idents: MutMap, - pub module_ids: &'a ModuleIds, - pub ident_ids: IdentIds, - pub exposed_ident_ids: IdentIds, - - pub closures: MutMap, - /// Symbols which were referenced by qualified lookups. - pub qualified_lookups: MutSet, - - pub top_level_symbols: MutSet, - - pub closure_name_symbol: Option, - pub tailcallable_symbol: Option, -} - -impl<'a> Env<'a> { - pub fn new( - home: ModuleId, - arena: &'a Bump, - pool: &'a mut Pool, - var_store: &'a mut VarStore, - dep_idents: MutMap, - module_ids: &'a ModuleIds, - exposed_ident_ids: IdentIds, - ) -> Env<'a> { - Env { - home, - arena, - pool, - problems: BumpVec::new_in(arena), - var_store, - dep_idents, - module_ids, - ident_ids: exposed_ident_ids.clone(), // we start with these, but will add more later - exposed_ident_ids, - closures: MutMap::default(), - qualified_lookups: MutSet::default(), - tailcallable_symbol: None, - closure_name_symbol: None, - top_level_symbols: MutSet::default(), - } - } - - pub fn add(&mut self, item: T, region: Region) -> NodeId { - let id = self.pool.add(item); - self.set_region(id, region); - - id - } - - pub fn problem(&mut self, problem: Problem) { - self.problems.push(problem); - } - - pub fn set_region(&mut self, _node_id: NodeId, _region: Region) { - dbg!("Don't Forget to set the region eventually"); - } - - pub fn register_closure(&mut self, symbol: Symbol, references: References) { - self.closures.insert(symbol, references); - } - - /// Generates a unique, new symbol like "$1" or "$5", - /// using the home module as the module_id. - /// - /// This is used, for example, during canonicalization of an Expr::Closure - /// to generate a unique symbol to refer to that closure. - pub fn gen_unique_symbol(&mut self) -> Symbol { - let ident_id = self.ident_ids.gen_unique(); - - Symbol::new(self.home, ident_id) - } - - /// Returns Err if the symbol resolved, but it was not exposed by the given module - pub fn qualified_lookup( - &mut self, - module_name: &str, - ident: &str, - region: Region, - ) -> Result { - debug_assert!( - !module_name.is_empty(), - "Called env.qualified_lookup with an unqualified ident: {:?}", - ident - ); - - let module_name: ModuleName = module_name.into(); - - match self.module_ids.get_id(&module_name) { - Some(&module_id) => { - let ident: Ident = ident.into(); - - // You can do qualified lookups on your own module, e.g. - // if I'm in the Foo module, I can do a `Foo.bar` lookup. - if module_id == self.home { - match self.ident_ids.get_id(&ident) { - Some(ident_id) => { - let symbol = Symbol::new(module_id, *ident_id); - - self.qualified_lookups.insert(symbol); - - Ok(symbol) - } - None => Err(RuntimeError::LookupNotInScope( - Located { - value: ident, - region, - }, - self.ident_ids - .idents() - .map(|(_, string)| string.as_ref().into()) - .collect(), - )), - } - } 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); - - self.qualified_lookups.insert(symbol); - - Ok(symbol) - } - None => Err(RuntimeError::ValueNotExposed { - module_name: ModuleName::from(module_name), - ident, - region, - }), - } - } - } - None => Err(RuntimeError::ModuleNotImported { - module_name, - imported_modules: self - .module_ids - .available_modules() - .map(|string| string.as_ref().into()) - .collect(), - region, - }), - } - } -} - -const ZERO: Region = Region::zero(); - -pub fn as_expr_id<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - expr_id: ExprId, - parse_expr: &'a roc_parse::ast::Expr<'a>, - region: Region, -) -> Output { - let (expr, output) = to_expr2(env, scope, parse_expr, region); - - env.pool[expr_id] = expr; - env.set_region(expr_id, region); - - output -} - -pub fn to_expr_id<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - parse_expr: &'a roc_parse::ast::Expr<'a>, - region: Region, -) -> (ExprId, Output) { - let (expr, output) = to_expr2(env, scope, parse_expr, region); - - (env.add(expr, region), output) -} - -pub fn str_to_def2<'a>( - arena: &'a Bump, - input: &'a str, - env: &mut Env<'a>, - scope: &mut Scope, - region: Region, -) -> Result, SyntaxError<'a>> { - match roc_parse::test_helpers::parse_defs_with(arena, input.trim()) { - Ok(vec_loc_def) => Ok(defs_to_defs2( - arena, - env, - scope, - arena.alloc(vec_loc_def), - region, - )), - Err(fail) => Err(fail), - } -} - -pub fn str_to_expr2<'a>( - arena: &'a Bump, - input: &'a str, - env: &mut Env<'a>, - scope: &mut Scope, - region: Region, -) -> Result<(Expr2, self::Output), SyntaxError<'a>> { - match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) { - Ok(loc_expr) => Ok(loc_expr_to_expr2(arena, loc_expr, env, scope, region)), - Err(fail) => Err(fail), - } -} - -fn loc_expr_to_expr2<'a>( - arena: &'a Bump, - loc_expr: Located>, - env: &mut Env<'a>, - scope: &mut Scope, - region: Region, -) -> (Expr2, self::Output) { - let desugared_loc_expr = desugar_expr(arena, arena.alloc(loc_expr)); - - to_expr2(env, scope, arena.alloc(desugared_loc_expr.value), region) -} - -pub fn to_expr2<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - parse_expr: &'a roc_parse::ast::Expr<'a>, - region: Region, -) -> (Expr2, self::Output) { - use roc_parse::ast::Expr::*; - - match parse_expr { - Float(string) => { - match finish_parsing_float(string) { - Ok(float) => { - let expr = Expr2::Float { - number: FloatVal::F64(float), - var: env.var_store.fresh(), - text: PoolStr::new(string, &mut env.pool), - }; - - (expr, Output::default()) - } - Err((raw, error)) => { - // emit runtime error - let runtime_error = RuntimeError::InvalidFloat(error, ZERO, raw.into()); - - env.problem(Problem::RuntimeError(runtime_error.clone())); - // - // Expr::RuntimeError(runtime_error) - todo!() - } - } - } - Num(string) => { - match finish_parsing_int(string) { - Ok(int) => { - let expr = Expr2::SmallInt { - number: IntVal::I64(int), - var: env.var_store.fresh(), - // TODO non-hardcode - style: IntStyle::Decimal, - text: PoolStr::new(string, &mut env.pool), - }; - - (expr, Output::default()) - } - Err((raw, error)) => { - // emit runtime error - let runtime_error = RuntimeError::InvalidInt( - error, - roc_parse::ast::Base::Decimal, - ZERO, - raw.into(), - ); - - env.problem(Problem::RuntimeError(runtime_error.clone())); - // - // Expr::RuntimeError(runtime_error) - todo!() - } - } - } - NonBase10Int { - string, - base, - is_negative, - } => { - match finish_parsing_base(string, *base, *is_negative) { - Ok(int) => { - let expr = Expr2::SmallInt { - number: IntVal::I64(int), - var: env.var_store.fresh(), - // TODO non-hardcode - style: IntStyle::from_base(*base), - text: PoolStr::new(string, &mut env.pool), - }; - - (expr, Output::default()) - } - Err((raw, error)) => { - // emit runtime error - let runtime_error = RuntimeError::InvalidInt(error, *base, ZERO, raw.into()); - - env.problem(Problem::RuntimeError(runtime_error.clone())); - // - // Expr::RuntimeError(runtime_error) - todo!() - } - } - } - - Str(literal) => flatten_str_literal(env, scope, &literal), - - List { items, .. } => { - let mut output = Output::default(); - let output_ref = &mut output; - - let elems: PoolVec = PoolVec::with_capacity(items.len() as u32, env.pool); - - for (node_id, item) in elems.iter_node_ids().zip(items.iter()) { - let (expr, sub_output) = to_expr2(env, scope, &item.value, item.region); - - output_ref.union(sub_output); - - let expr_id = env.pool.add(expr); - env.pool[node_id] = expr_id; - } - - let expr = Expr2::List { - elem_var: env.var_store.fresh(), - elems, - }; - - (expr, output) - } - - GlobalTag(tag) => { - // a global tag without any arguments - ( - Expr2::GlobalTag { - name: PoolStr::new(tag, env.pool), - variant_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - arguments: PoolVec::empty(env.pool), - }, - Output::default(), - ) - } - PrivateTag(name) => { - // a private tag without any arguments - let ident_id = env.ident_ids.get_or_insert(&(*name).into()); - let name = Symbol::new(env.home, ident_id); - ( - Expr2::PrivateTag { - name, - variant_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - arguments: PoolVec::empty(env.pool), - }, - Output::default(), - ) - } - - RecordUpdate { - fields, - update: loc_update, - final_comments: _, - } => { - let (can_update, update_out) = - to_expr2(env, scope, &loc_update.value, loc_update.region); - - if let Expr2::Var(symbol) = &can_update { - match canonicalize_fields(env, scope, fields) { - Ok((can_fields, mut output)) => { - output.references.union_mut(update_out.references); - - let answer = Expr2::Update { - record_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - symbol: *symbol, - updates: can_fields, - }; - - (answer, output) - } - Err(CanonicalizeRecordProblem::InvalidOptionalValue { - field_name: _, - field_region: _, - record_region: _, - }) => { - // let runtime_error = roc_problem::can::RuntimeError::InvalidOptionalValue { - // field_name, - // field_region, - // record_region, - // }; - // - // env.problem(Problem::RuntimeError(runtime_error)); - - todo!() - } - } - } else { - // only (optionally qualified) variables can be updated, not arbitrary expressions - - // let error = roc_problem::can::RuntimeError::InvalidRecordUpdate { - // region: can_update.region, - // }; - // - // let answer = Expr::RuntimeError(error.clone()); - // - // env.problems.push(Problem::RuntimeError(error)); - // - // (answer, Output::default()) - todo!() - } - } - - Record { - fields, - final_comments: _, - } => { - if fields.is_empty() { - (Expr2::EmptyRecord, Output::default()) - } else { - match canonicalize_fields(env, scope, fields) { - Ok((can_fields, output)) => ( - Expr2::Record { - record_var: env.var_store.fresh(), - fields: can_fields, - }, - output, - ), - Err(CanonicalizeRecordProblem::InvalidOptionalValue { - field_name: _, - field_region: _, - record_region: _, - }) => { - // let runtime_error = RuntimeError::InvalidOptionalValue { - // field_name, - // field_region, - // record_region, - // }; - // - // env.problem(runtime_error); - // ( - // Expr::RuntimeError( - // ), - // Output::default(), - // - // ) - todo!() - } - } - } - } - - Access(record_expr, field) => { - // TODO - let region = ZERO; - let (record_expr_id, output) = to_expr_id(env, scope, record_expr, region); - - ( - Expr2::Access { - record_var: env.var_store.fresh(), - field_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - expr: record_expr_id, - field: PoolStr::new(field, env.pool), - }, - output, - ) - } - - AccessorFunction(field) => ( - Expr2::Accessor { - function_var: env.var_store.fresh(), - record_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - closure_var: env.var_store.fresh(), - field_var: env.var_store.fresh(), - field: PoolStr::new(field, env.pool), - }, - Output::default(), - ), - - If(branches, final_else) => { - let mut new_branches = Vec::with_capacity(branches.len()); - let mut output = Output::default(); - - for (condition, then_branch) in branches.iter() { - let (cond, cond_output) = to_expr2(env, scope, &condition.value, condition.region); - - let (then_expr, then_output) = - to_expr2(env, scope, &then_branch.value, then_branch.region); - - output.references.union_mut(cond_output.references); - output.references.union_mut(then_output.references); - - new_branches.push((env.pool.add(cond), env.pool.add(then_expr))); - } - - let (else_expr, else_output) = - to_expr2(env, scope, &final_else.value, final_else.region); - - output.references.union_mut(else_output.references); - - let expr = Expr2::If { - cond_var: env.var_store.fresh(), - expr_var: env.var_store.fresh(), - branches: PoolVec::new(new_branches.into_iter(), env.pool), - final_else: env.pool.add(else_expr), - }; - - (expr, output) - } - - When(loc_cond, branches) => { - // Infer the condition expression's type. - let cond_var = env.var_store.fresh(); - let (can_cond, mut output) = to_expr2(env, scope, &loc_cond.value, loc_cond.region); - - // the condition can never be a tail-call - output.tail_call = None; - - let can_branches = PoolVec::with_capacity(branches.len() as u32, env.pool); - - for (node_id, branch) in can_branches.iter_node_ids().zip(branches.iter()) { - let (can_when_branch, branch_references) = - canonicalize_when_branch(env, scope, *branch, &mut output); - - output.references.union_mut(branch_references); - - env.pool[node_id] = can_when_branch; - } - - // A "when" with no branches is a runtime error, but it will mess things up - // if code gen mistakenly thinks this is a tail call just because its condition - // happened to be one. (The condition gave us our initial output value.) - if branches.is_empty() { - output.tail_call = None; - } - - // Incorporate all three expressions into a combined Output value. - let expr = Expr2::When { - expr_var: env.var_store.fresh(), - cond_var, - cond: env.pool.add(can_cond), - branches: can_branches, - }; - - (expr, output) - } - - Closure(loc_arg_patterns, loc_body_expr) => { - // The globally unique symbol that will refer to this closure once it gets converted - // into a top-level procedure for code gen. - // - // In the Foo module, this will look something like Foo.$1 or Foo.$2. - let symbol = env - .closure_name_symbol - .unwrap_or_else(|| env.gen_unique_symbol()); - env.closure_name_symbol = None; - - // The body expression gets a new scope for canonicalization. - // Shadow `scope` to make sure we don't accidentally use the original one for the - // rest of this block, but keep the original around for later diffing. - let original_scope = scope; - let mut scope = original_scope.shallow_clone(); - let can_args = PoolVec::with_capacity(loc_arg_patterns.len() as u32, env.pool); - let mut output = Output::default(); - - let mut bound_by_argument_patterns = MutSet::default(); - - for (node_id, loc_pattern) in can_args.iter_node_ids().zip(loc_arg_patterns.iter()) { - let (new_output, can_arg) = to_pattern2( - env, - &mut scope, - roc_parse::pattern::PatternType::FunctionArg, - &loc_pattern.value, - loc_pattern.region, - ); - - bound_by_argument_patterns - .extend(new_output.references.bound_symbols.iter().copied()); - - output.union(new_output); - - let pattern_id = env.add(can_arg, loc_pattern.region); - env.pool[node_id] = (env.var_store.fresh(), pattern_id); - } - - let (body_expr, new_output) = - to_expr2(env, &mut scope, &loc_body_expr.value, loc_body_expr.region); - - let mut captured_symbols: MutSet = - new_output.references.lookups.iter().copied().collect(); - - // filter out the closure's name itself - captured_symbols.remove(&symbol); - - // symbols bound either in this pattern or deeper down are not captured! - captured_symbols.retain(|s| !new_output.references.bound_symbols.contains(s)); - captured_symbols.retain(|s| !bound_by_argument_patterns.contains(s)); - - // filter out top-level symbols - // those will be globally available, and don't need to be captured - captured_symbols.retain(|s| !env.top_level_symbols.contains(s)); - - // filter out imported symbols - // those will be globally available, and don't need to be captured - captured_symbols.retain(|s| s.module_id() == env.home); - - // TODO any Closure that has an empty `captured_symbols` list could be excluded! - - output.union(new_output); - - // filter out aliases - captured_symbols.retain(|s| !output.references.referenced_aliases.contains(s)); - - // filter out functions that don't close over anything - captured_symbols.retain(|s| !output.non_closures.contains(s)); - - // Now that we've collected all the references, check to see if any of the args we defined - // went unreferenced. If any did, report them as unused arguments. - for (sub_symbol, region) in scope.symbols() { - if !original_scope.contains_symbol(sub_symbol) { - if !output.references.has_lookup(sub_symbol) { - // The body never referenced this argument we declared. It's an unused argument! - env.problem(Problem::UnusedArgument(symbol, sub_symbol, region)); - } - - // We shouldn't ultimately count arguments as referenced locals. Otherwise, - // we end up with weird conclusions like the expression (\x -> x + 1) - // references the (nonexistant) local variable x! - output.references.lookups.remove(&sub_symbol); - } - } - - env.register_closure(symbol, output.references.clone()); - - let mut captured_symbols: Vec<_> = captured_symbols - .into_iter() - .map(|s| (s, env.var_store.fresh())) - .collect(); - - // sort symbols, so we know the order in which they're stored in the closure record - captured_symbols.sort(); - - // store that this function doesn't capture anything. It will be promoted to a - // top-level function, and does not need to be captured by other surrounding functions. - if captured_symbols.is_empty() { - output.non_closures.insert(symbol); - } - - let captured_symbols = PoolVec::new(captured_symbols.into_iter(), env.pool); - - let extra = ClosureExtra { - return_type: env.var_store.fresh(), // 4B - captured_symbols, // 8B - closure_type: env.var_store.fresh(), // 4B - closure_ext_var: env.var_store.fresh(), // 4B - }; - - ( - Expr2::Closure { - function_type: env.var_store.fresh(), - name: symbol, - recursive: Recursive::NotRecursive, - args: can_args, - body: env.add(body_expr, loc_body_expr.region), - extra: env.pool.add(extra), - }, - output, - ) - } - - Apply(loc_fn, loc_args, application_style) => { - // The expression that evaluates to the function being called, e.g. `foo` in - // (foo) bar baz - let fn_region = loc_fn.region; - - // Canonicalize the function expression and its arguments - let (fn_expr, mut output) = to_expr2(env, scope, &loc_fn.value, fn_region); - - // The function's return type - let args = PoolVec::with_capacity(loc_args.len() as u32, env.pool); - - for (node_id, loc_arg) in args.iter_node_ids().zip(loc_args.iter()) { - let (arg_expr_id, arg_out) = to_expr_id(env, scope, &loc_arg.value, loc_arg.region); - - env.pool[node_id] = (env.var_store.fresh(), arg_expr_id); - - output.references.union_mut(arg_out.references); - } - - // Default: We're not tail-calling a symbol (by name), we're tail-calling a function value. - output.tail_call = None; - - let expr = match fn_expr { - Expr2::Var(ref symbol) => { - output.references.calls.insert(*symbol); - - // we're tail-calling a symbol by name, check if it's the tail-callable symbol - output.tail_call = match &env.tailcallable_symbol { - Some(tc_sym) if *tc_sym == *symbol => Some(*symbol), - Some(_) | None => None, - }; - - // IDEA: Expr2::CallByName? - let fn_expr_id = env.add(fn_expr, fn_region); - Expr2::Call { - args, - expr: fn_expr_id, - expr_var: env.var_store.fresh(), - fn_var: env.var_store.fresh(), - closure_var: env.var_store.fresh(), - called_via: *application_style, - } - } - Expr2::RuntimeError() => { - // We can't call a runtime error; bail out by propagating it! - return (fn_expr, output); - } - Expr2::GlobalTag { - variant_var, - ext_var, - name, - .. - } => Expr2::GlobalTag { - variant_var, - ext_var, - name, - arguments: args, - }, - Expr2::PrivateTag { - variant_var, - ext_var, - name, - .. - } => Expr2::PrivateTag { - variant_var, - ext_var, - name, - arguments: args, - }, - _ => { - // This could be something like ((if True then fn1 else fn2) arg1 arg2). - let fn_expr_id = env.add(fn_expr, fn_region); - Expr2::Call { - args, - expr: fn_expr_id, - expr_var: env.var_store.fresh(), - fn_var: env.var_store.fresh(), - closure_var: env.var_store.fresh(), - called_via: *application_style, - } - } - }; - - (expr, output) - } - - Defs(loc_defs, loc_ret) => { - let (unsorted, mut scope, defs_output, symbols_introduced) = canonicalize_defs( - env, - Output::default(), - &scope, - loc_defs, - PatternType::DefExpr, - ); - - // The def as a whole is a tail call iff its return expression is a tail call. - // Use its output as a starting point because its tail_call already has the right answer! - let (ret_expr, mut output) = to_expr2(env, &mut scope, &loc_ret.value, loc_ret.region); - - output - .introduced_variables - .union(&defs_output.introduced_variables); - - output.references.union_mut(defs_output.references); - - // Now that we've collected all the references, check to see if any of the new idents - // we defined went unused by the return expression. If any were unused, report it. - for (symbol, region) in symbols_introduced { - if !output.references.has_lookup(symbol) { - env.problem(Problem::UnusedDef(symbol, region)); - } - } - - let (can_defs, output) = sort_can_defs(env, unsorted, output); - - match can_defs { - Ok(decls) => { - let mut expr = ret_expr; - - for declaration in decls.into_iter().rev() { - expr = decl_to_let(env.pool, env.var_store, declaration, expr); - } - - (expr, output) - } - Err(_err) => { - // TODO: fix this to be something from Expr2 - // (RuntimeError(err), output) - todo!() - } - } - } - - PrecedenceConflict { .. } => { - // use roc_problem::can::RuntimeError::*; - // - // let problem = PrecedenceProblem::BothNonAssociative( - // *whole_region, - // binop1.clone(), - // binop2.clone(), - // ); - // - // env.problem(Problem::PrecedenceProblem(problem.clone())); - // - // ( - // RuntimeError(InvalidPrecedence(problem, region)), - // Output::default(), - // ) - todo!() - } - MalformedClosure => { - // use roc_problem::can::RuntimeError::*; - // (RuntimeError(MalformedClosure(region)), Output::default()) - todo!() - } - MalformedIdent(_name, _problem) => { - // use roc_problem::can::RuntimeError::*; - // - // let problem = MalformedIdentifier((*name).into(), region); - // env.problem(Problem::RuntimeError(problem.clone())); - // - // (RuntimeError(problem), Output::default()) - todo!() - } - Var { module_name, ident } => canonicalize_lookup(env, scope, module_name, ident, region), - - // Below this point, we shouln't see any of these nodes anymore because - // operator desugaring should have removed them! - bad_expr @ ParensAround(_) => { - panic!( - "A ParensAround did not get removed during operator desugaring somehow: {:#?}", - bad_expr - ); - } - bad_expr @ SpaceBefore(_, _) => { - panic!( - "A SpaceBefore did not get removed during operator desugaring somehow: {:#?}", - bad_expr - ); - } - bad_expr @ SpaceAfter(_, _) => { - panic!( - "A SpaceAfter did not get removed during operator desugaring somehow: {:#?}", - bad_expr - ); - } - bad_expr @ BinOps { .. } => { - panic!( - "A binary operator chain did not get desugared somehow: {:#?}", - bad_expr - ); - } - bad_expr @ UnaryOp(_, _) => { - panic!( - "A unary operator did not get desugared somehow: {:#?}", - bad_expr - ); - } - - rest => todo!("not yet implemented {:?}", rest), - } -} - -pub fn defs_to_defs2<'a>( - arena: &'a Bump, - env: &mut Env<'a>, - scope: &mut Scope, - parsed_defs: &'a BumpVec>>, - region: Region, -) -> Vec { - use roc_parse::ast::Expr::*; - - parsed_defs - .iter() - .map(|loc| to_def2_from_def(arena, env, scope, &loc.value, region)) - .collect() -} - -pub fn to_def2_from_def<'a>( - arena: &'a Bump, - env: &mut Env<'a>, - scope: &mut Scope, - parsed_def: &'a roc_parse::ast::Def<'a>, - region: Region, -) -> Def2 { - use roc_parse::ast::Def::*; - - match parsed_def { - SpaceBefore(inner_def, _) => to_def2_from_def(arena, env, scope, inner_def, region), - SpaceAfter(inner_def, _) => to_def2_from_def(arena, env, scope, inner_def, region), - Body(&loc_pattern, &loc_expr) => { - // TODO loc_pattern use identifier - let expr2 = loc_expr_to_expr2(arena, loc_expr, env, scope, region).0; - let expr_id = env.pool.add(expr2); - - use roc_parse::ast::Pattern::*; - - match loc_pattern.value { - Identifier(_) => { - let (_, pattern2) = to_pattern2( - env, - scope, - PatternType::TopLevelDef, - &loc_pattern.value, - region, - ); - let pattern_id = env.pool.add(pattern2); - - // TODO support with annotation - Def2::ValueDef { - identifier_id: pattern_id, - expr_id, - } - } - other => { - unimplemented!( - "I don't yet know how to convert the pattern {:?} into an expr2", - other - ) - } - } - } - other => { - unimplemented!( - "I don't know how to make an expr2 from this def yet: {:?}", - other - ) - } - } -} - -fn flatten_str_literal<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - literal: &StrLiteral<'a>, -) -> (Expr2, Output) { - use roc_parse::ast::StrLiteral::*; - - match literal { - PlainLine(str_slice) => { - // TODO use smallstr - let expr = Expr2::Str(PoolStr::new(str_slice, &mut env.pool)); - - (expr, Output::default()) - } - Line(segments) => flatten_str_lines(env, scope, &[segments]), - Block(lines) => flatten_str_lines(env, scope, lines), - } -} - -enum StrSegment { - Interpolation(Expr2), - Plaintext(PoolStr), -} - -fn flatten_str_lines<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - lines: &[&[roc_parse::ast::StrSegment<'a>]], -) -> (Expr2, Output) { - use roc_parse::ast::StrSegment::*; - - let mut buf = String::new(); - let mut segments = Vec::new(); - let mut output = Output::default(); - - for line in lines { - for segment in line.iter() { - match segment { - Plaintext(string) => { - buf.push_str(string); - } - Unicode(loc_hex_digits) => match u32::from_str_radix(loc_hex_digits.value, 16) { - Ok(code_pt) => match std::char::from_u32(code_pt) { - Some(ch) => { - buf.push(ch); - } - None => { - // env.problem(Problem::InvalidUnicodeCodePt(loc_hex_digits.region)); - // - // return ( - // Expr::RuntimeError(RuntimeError::InvalidUnicodeCodePt( - // loc_hex_digits.region, - // )), - // output, - // ); - todo!() - } - }, - Err(_) => { - // env.problem(Problem::InvalidHexadecimal(loc_hex_digits.region)); - // - // return ( - // Expr::RuntimeError(RuntimeError::InvalidHexadecimal( - // loc_hex_digits.region, - // )), - // output, - // ); - todo!() - } - }, - Interpolated(loc_expr) => { - if roc_can::expr::is_valid_interpolation(loc_expr.value) { - // Interpolations desugar to Str.concat calls - output.references.calls.insert(Symbol::STR_CONCAT); - - if !buf.is_empty() { - segments.push(StrSegment::Plaintext(PoolStr::new(&buf, &mut env.pool))); - - buf = String::new(); - } - - let (loc_expr, new_output) = - to_expr2(env, scope, loc_expr.value, loc_expr.region); - - output.union(new_output); - - segments.push(StrSegment::Interpolation(loc_expr)); - } else { - // env.problem(Problem::InvalidInterpolation(loc_expr.region)); - // - // return ( - // Expr::RuntimeError(RuntimeError::InvalidInterpolation(loc_expr.region)), - // output, - // ); - todo!() - } - } - EscapedChar(escaped) => buf.push(roc_can::expr::unescape_char(escaped)), - } - } - } - - if !buf.is_empty() { - segments.push(StrSegment::Plaintext(PoolStr::new(&buf, &mut env.pool))); - } - - (desugar_str_segments(env, segments), output) -} - -/// Resolve string interpolations by desugaring a sequence of StrSegments -/// into nested calls to Str.concat -fn desugar_str_segments<'a>(env: &mut Env<'a>, segments: Vec) -> Expr2 { - use StrSegment::*; - - let pool = &mut env.pool; - let var_store = &mut env.var_store; - - let mut iter = segments.into_iter().rev(); - let mut expr = match iter.next() { - Some(Plaintext(pool_str)) => Expr2::Str(pool_str), - Some(Interpolation(expr_id)) => expr_id, - None => { - // No segments? Empty string! - - let pool_str = PoolStr::new("", pool); - Expr2::Str(pool_str) - } - }; - - for seg in iter { - let new_expr = match seg { - Plaintext(string) => Expr2::Str(string), - Interpolation(expr_id) => expr_id, - }; - - let concat_expr_id = pool.add(Expr2::Var(Symbol::STR_CONCAT)); - - let args = vec![ - (var_store.fresh(), pool.add(new_expr)), - (var_store.fresh(), pool.add(expr)), - ]; - let args = PoolVec::new(args.into_iter(), pool); - - let new_call = Expr2::Call { - args, - expr: concat_expr_id, - expr_var: var_store.fresh(), - fn_var: var_store.fresh(), - closure_var: var_store.fresh(), - called_via: CalledVia::Space, - }; - - expr = new_call - } - - expr -} - -enum CanonicalizeRecordProblem { - InvalidOptionalValue { - field_name: PoolStr, - field_region: Region, - record_region: Region, - }, -} - -enum FieldVar { - VarAndExprId(Variable, ExprId), - OnlyVar(Variable), -} - -fn canonicalize_fields<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - fields: &'a [Located>>], -) -> Result<(PoolVec, Output), CanonicalizeRecordProblem> { - let mut can_fields: MutMap<&'a str, FieldVar> = MutMap::default(); - let mut output = Output::default(); - - for loc_field in fields.iter() { - match canonicalize_field(env, scope, &loc_field.value) { - Ok(can_field) => { - match can_field { - CanonicalField::LabelAndValue { - label, - value_expr, - value_output, - var, - } => { - let expr_id = env.pool.add(value_expr); - - let replaced = - can_fields.insert(label, FieldVar::VarAndExprId(var, expr_id)); - - if let Some(_old) = replaced { - // env.problems.push(Problem::DuplicateRecordFieldValue { - // field_name: label, - // field_region: loc_field.region, - // record_region: region, - // replaced_region: old.region, - // }); - todo!() - } - - output.references.union_mut(value_output.references); - } - CanonicalField::InvalidLabelOnly { label, var } => { - let replaced = can_fields.insert(label, FieldVar::OnlyVar(var)); - - if let Some(_old) = replaced { - todo!() - } - } - } - } - - Err(CanonicalizeFieldProblem::InvalidOptionalValue { - field_name: _, - field_region: _, - }) => { - // env.problem(Problem::InvalidOptionalValue { - // field_name: field_name.clone(), - // field_region, - // record_region: region, - // }); - // return Err(CanonicalizeRecordProblem::InvalidOptionalValue { - // field_name, - // field_region, - // record_region: region, - // }); - todo!() - } - } - } - - let pool_vec = PoolVec::with_capacity(can_fields.len() as u32, env.pool); - - for (node_id, (string, field_var)) in pool_vec.iter_node_ids().zip(can_fields.into_iter()) { - let name = PoolStr::new(string, env.pool); - - match field_var { - FieldVar::VarAndExprId(var, expr_id) => { - env.pool[node_id] = RecordField::LabeledValue(name, var, expr_id); - } - FieldVar::OnlyVar(var) => { - env.pool[node_id] = RecordField::InvalidLabelOnly(name, var); - } // TODO RecordField::LabelOnly - } - } - - Ok((pool_vec, output)) -} - -enum CanonicalizeFieldProblem { - InvalidOptionalValue { - field_name: PoolStr, - field_region: Region, - }, -} -enum CanonicalField<'a> { - LabelAndValue { - label: &'a str, - value_expr: Expr2, - value_output: Output, - var: Variable, - }, - InvalidLabelOnly { - label: &'a str, - var: Variable, - }, // TODO make ValidLabelOnly -} -fn canonicalize_field<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - field: &'a roc_parse::ast::AssignedField<'a, roc_parse::ast::Expr<'a>>, -) -> Result, CanonicalizeFieldProblem> { - use roc_parse::ast::AssignedField::*; - - match field { - // Both a label and a value, e.g. `{ name: "blah" }` - RequiredValue(label, _, loc_expr) => { - let field_var = env.var_store.fresh(); - let (loc_can_expr, output) = to_expr2(env, scope, &loc_expr.value, loc_expr.region); - - Ok(CanonicalField::LabelAndValue { - label: label.value, - value_expr: loc_can_expr, - value_output: output, - var: field_var, - }) - } - - OptionalValue(label, _, loc_expr) => Err(CanonicalizeFieldProblem::InvalidOptionalValue { - field_name: PoolStr::new(label.value, env.pool), - field_region: Region::span_across(&label.region, &loc_expr.region), - }), - - // A label with no value, e.g. `{ name }` (this is sugar for { name: name }) - LabelOnly(label) => { - let field_var = env.var_store.fresh(); - // TODO return ValidLabel if label points to in scope variable - Ok(CanonicalField::InvalidLabelOnly { - label: label.value, - var: field_var, - }) - } - - SpaceBefore(sub_field, _) | SpaceAfter(sub_field, _) => { - canonicalize_field(env, scope, sub_field) - } - - Malformed(_string) => { - panic!("TODO canonicalize malformed record field"); - } - } -} - -#[inline(always)] -fn canonicalize_when_branch<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - branch: &'a roc_parse::ast::WhenBranch<'a>, - output: &mut Output, -) -> (WhenBranch, References) { - let patterns = PoolVec::with_capacity(branch.patterns.len() as u32, env.pool); - - let original_scope = scope; - let mut scope = original_scope.shallow_clone(); - - // TODO report symbols not bound in all patterns - for (node_id, loc_pattern) in patterns.iter_node_ids().zip(branch.patterns.iter()) { - let (new_output, can_pattern) = to_pattern2( - env, - &mut scope, - roc_parse::pattern::PatternType::WhenBranch, - &loc_pattern.value, - loc_pattern.region, - ); - - output.union(new_output); - - env.set_region(node_id, loc_pattern.region); - env.pool[node_id] = can_pattern; - } - - let (value, mut branch_output) = - to_expr2(env, &mut scope, &branch.value.value, branch.value.region); - let value_id = env.pool.add(value); - env.set_region(value_id, branch.value.region); - - let guard = match &branch.guard { - None => None, - Some(loc_expr) => { - let (can_guard, guard_branch_output) = - to_expr2(env, &mut scope, &loc_expr.value, loc_expr.region); - - let expr_id = env.pool.add(can_guard); - env.set_region(expr_id, loc_expr.region); - - branch_output.union(guard_branch_output); - Some(expr_id) - } - }; - - // Now that we've collected all the references for this branch, check to see if - // any of the new idents it defined were unused. If any were, report it. - for (symbol, region) in scope.symbols() { - let symbol = symbol; - - if !output.references.has_lookup(symbol) - && !branch_output.references.has_lookup(symbol) - && !original_scope.contains_symbol(symbol) - { - env.problem(Problem::UnusedDef(symbol, region)); - } - } - - let references = branch_output.references.clone(); - output.union(branch_output); - - ( - WhenBranch { - patterns, - body: value_id, - guard, - }, - references, - ) -} - -fn canonicalize_lookup( - env: &mut Env<'_>, - scope: &mut Scope, - module_name: &str, - ident: &str, - region: Region, -) -> (Expr2, Output) { - use Expr2::*; - - let mut output = Output::default(); - let can_expr = if module_name.is_empty() { - // Since module_name was empty, this is an unqualified var. - // Look it up in scope! - match scope.lookup(&(*ident).into(), region) { - Ok(symbol) => { - output.references.lookups.insert(symbol); - - Var(symbol) - } - Err(problem) => { - env.problem(Problem::RuntimeError(problem.clone())); - - RuntimeError() - } - } - } else { - // Since module_name was nonempty, this is a qualified var. - // Look it up in the env! - match env.qualified_lookup(module_name, ident, region) { - Ok(symbol) => { - output.references.lookups.insert(symbol); - - Var(symbol) - } - Err(problem) => { - // Either the module wasn't imported, or - // it was imported but it doesn't expose this ident. - env.problem(Problem::RuntimeError(problem.clone())); - - RuntimeError() - } - } - }; - - // If it's valid, this ident should be in scope already. - - (can_expr, output) -} - -fn decl_to_let(pool: &mut Pool, var_store: &mut VarStore, decl: Declaration, ret: Expr2) -> Expr2 { - match decl { - Declaration::Declare(def) => match def { - Def::AnnotationOnly { .. } => todo!(), - Def::Value(value_def) => { - let def_id = pool.add(value_def); - - let body_id = pool.add(ret); - - Expr2::LetValue { - def_id, - body_id, - body_var: var_store.fresh(), - } - } - Def::Function(function_def) => { - let def_id = pool.add(function_def); - let body_id = pool.add(ret); - - Expr2::LetFunction { - def_id, - body_id, - body_var: var_store.fresh(), - } - } - }, - Declaration::DeclareRec(defs) => { - let mut function_defs = vec![]; - - for def in defs { - match def { - Def::AnnotationOnly { .. } => todo!(), - Def::Function(function_def) => function_defs.push(function_def), - Def::Value(_) => unreachable!(), - } - } - - let body_id = pool.add(ret); - - Expr2::LetRec { - defs: PoolVec::new(function_defs.into_iter(), pool), - body_var: var_store.fresh(), - body_id, - } - } - Declaration::InvalidCycle(_entries, _) => { - // TODO: replace with something from Expr2 - // Expr::RuntimeError(RuntimeError::CircularDef(entries)) - todo!() - } - Declaration::Builtin(_) => { - // Builtins should only be added to top-level decls, not to let-exprs! - unreachable!() - } - } -} diff --git a/editor/src/lang/mod.rs b/editor/src/lang/mod.rs deleted file mode 100644 index fa747386e1..0000000000 --- a/editor/src/lang/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -pub mod ast; -pub mod constrain; -mod def; -pub mod expr; -mod module; -pub mod parse; -pub mod pattern; -pub mod pool; -pub mod roc_file; -pub mod scope; -pub mod solve; -pub mod types; diff --git a/editor/src/lang/parse.rs b/editor/src/lang/parse.rs deleted file mode 100644 index 9c7253bb75..0000000000 --- a/editor/src/lang/parse.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::fmt::Debug; - -use crate::{ - editor::ed_error::ASTNodeIdWithoutExprId, editor::ed_error::EdResult, lang::scope::Scope, -}; -use bumpalo::Bump; -use roc_parse::parser::SyntaxError; -use roc_region::all::Region; - -use super::{ - ast::{DefId, Expr2, ExprId}, - expr::{str_to_def2, Env}, -}; - -#[derive(Debug)] -pub struct AST { - pub header: AppHeader, - pub def_ids: Vec, -} - -#[derive(Debug, PartialEq, Copy, Clone)] -pub enum ASTNodeId { - ADefId(DefId), - AExprId(ExprId), -} - -impl ASTNodeId { - pub fn to_expr_id(&self) -> EdResult { - match self { - ASTNodeId::AExprId(expr_id) => Ok(*expr_id), - _ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?, - } - } - - pub fn to_def_id(&self) -> EdResult { - match self { - ASTNodeId::ADefId(def_id) => Ok(*def_id), - _ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?, - } - } -} - -#[derive(Debug)] -pub struct AppHeader { - pub app_name: String, - pub packages_base: String, - pub imports: Vec, - pub provides: Vec, - pub ast_node_id: ExprId, // TODO probably want to use HeaderId -} - -impl AST { - pub fn parse_from_string<'a>( - code_str: &'a str, - env: &mut Env<'a>, - ast_arena: &'a Bump, - ) -> Result> { - let blank_line_indx = code_str - .find("\n\n") - .expect("I was expecting a double newline to split header and rest of code."); - - let header_str = &code_str[0..blank_line_indx]; - let tail_str = &code_str[blank_line_indx..]; - - let mut scope = Scope::new(env.home, env.pool, env.var_store); - let region = Region::new(0, 0, 0, 0); - - let mut def_ids = Vec::::new(); - - let def2_vec = str_to_def2(ast_arena, tail_str, env, &mut scope, region)?; - - for def2 in def2_vec { - let def_id = env.pool.add(def2); - - def_ids.push(def_id); - } - - let ast_node_id = env.pool.add(Expr2::Blank); - - Ok(AST { - header: AppHeader::parse_from_string(header_str, ast_node_id), - def_ids, - }) - } -} - -impl AppHeader { - // TODO don't use mock struct and actually parse string - pub fn parse_from_string(_header_str: &str, ast_node_id: ExprId) -> Self { - AppHeader { - app_name: "\"untitled-app\"".to_owned(), - packages_base: "\"platform\"".to_owned(), - imports: vec![], - provides: vec!["main".to_owned()], - ast_node_id, - } - } -} diff --git a/editor/src/lang/pool.rs b/editor/src/lang/pool.rs deleted file mode 100644 index 2504cfcffc..0000000000 --- a/editor/src/lang/pool.rs +++ /dev/null @@ -1,657 +0,0 @@ -/// A pool of 32-byte nodes. The node value 0 is reserved for the pool's -/// use, and valid nodes may never have that value. -/// -/// Internally, the pool is divided into pages of 4096 bytes. It stores nodes -/// into one page at a time, and when it runs out, it uses mmap to reserve an -/// anonymous memory page in which to store nodes. -/// -/// Since nodes are 32 bytes, one page can store 128 nodes; you can access a -/// particular node by its NodeId, which is an opaque wrapper around a pointer. -/// -/// Pages also use the node value 0 (all 0 bits) to mark nodes as unoccupied. -/// This is important for performance. -use libc::{c_void, MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE}; -use roc_can::expected::Expected; -use roc_can::expected::PExpected; -use std::any::type_name; -use std::cmp::Ordering; -use std::marker::PhantomData; -use std::mem::size_of; -use std::ptr::null; - -pub const NODE_BYTES: usize = 32; - -// Each page has 128 slots. Each slot holds one 32B node -// This means each page is 4096B, which is the size of a memory page -// on typical systems where the compiler will be run. -// -// Nice things about this system include: -// * Allocating a new page is as simple as asking the OS for a memory page. -// * Since each node is 32B, each node's memory address will be a multiple of 16. -// * Thanks to the free lists and our consistent chunk sizes, we should -// end up with very little fragmentation. -// * Finding a slot for a given node should be very fast: see if the relevant -// free list has any openings; if not, try the next size up. -// -// Less nice things include: -// * This system makes it very hard to ever give a page back to the OS. -// We could try doing the Mesh Allocator strategy: whenever we allocate -// something, assign it to a random slot in the page, and then periodically -// try to merge two pages into one (by locking and remapping them in the OS) -// and then returning the redundant physical page back to the OS. This should -// work in theory, but is pretty complicated, and we'd need to schedule it. -// Keep in mind that we can't use the Mesh Allocator itself because it returns -// usize pointers, which would be too big for us to have 16B nodes. -// On the plus side, we could be okay with higher memory usage early on, -// and then later use the Mesh strategy to reduce long-running memory usage. -// -// With this system, we can allocate up to 4B nodes. If we wanted to keep -// a generational index in there, like https://crates.io/crates/sharded-slab -// does, we could use some of the 32 bits for that. For example, if we wanted -// to have a 5-bit generational index (supporting up to 32 generations), then -// we would have 27 bits remaining, meaning we could only support at most -// 134M nodes. Since the editor has a separate Pool for each module, is that -// enough for any single module we'll encounter in practice? Probably, and -// especially if we allocate super large collection literals on the heap instead -// of in the pool. -// -// Another possible design is to try to catch reuse bugs using an "ASan" like -// approach: in development builds, whenever we "free" a particular slot, we -// can add it to a dev-build-only "freed nodes" list and don't hand it back -// out (so, we leak the memory.) Then we can (again, in development builds only) -// check to see if we're about to store something in zeroed-out memory; if so, check -// to see if it was - -#[derive(Debug, Eq)] -pub struct NodeId { - index: u32, - _phantom: PhantomData, -} - -impl Clone for NodeId { - fn clone(&self) -> Self { - NodeId { - index: self.index, - _phantom: PhantomData::default(), - } - } -} - -impl PartialEq for NodeId { - fn eq(&self, other: &Self) -> bool { - self.index == other.index - } -} - -impl Copy for NodeId {} - -#[derive(Debug)] -pub struct Pool { - nodes: *mut [u8; NODE_BYTES], - num_nodes: u32, - capacity: u32, - // free_1node_slots: Vec>, -} - -impl Pool { - pub fn with_capacity(nodes: u32) -> Self { - // round up number of nodes requested to nearest page size in bytes - let bytes_per_page = page_size::get(); - let node_bytes = NODE_BYTES * nodes as usize; - let leftover = node_bytes % bytes_per_page; - let bytes_to_mmap = if leftover == 0 { - node_bytes - } else { - node_bytes + bytes_per_page - leftover - }; - - let nodes = unsafe { - // mmap anonymous memory pages - that is, contiguous virtual memory - // addresses from the OS which will be lazily translated into - // physical memory one 4096-byte page at a time, once we actually - // try to read or write in that page's address range. - libc::mmap( - null::() as *mut c_void, - bytes_to_mmap, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, - 0, - 0, - ) - } as *mut [u8; NODE_BYTES]; - - // This is our actual capacity, in nodes. - // It might be higher than the requested capacity due to rounding up - // to nearest page size. - let capacity = (bytes_to_mmap / NODE_BYTES) as u32; - - Pool { - nodes, - num_nodes: 0, - capacity, - } - } - - pub fn add(&mut self, node: T) -> NodeId { - // It's only safe to store this if T fits in S. - debug_assert!( - size_of::() <= NODE_BYTES, - "{} has a size of {}, but it needs to be at most {}", - type_name::(), - size_of::(), - NODE_BYTES - ); - - let node_id = self.reserve(1); - let node_ptr = unsafe { self.nodes.offset(node_id.index as isize) } as *mut T; - - unsafe { *node_ptr = node }; - - node_id - } - - /// Reserves the given number of contiguous node slots, and returns - /// the NodeId of the first one. We only allow reserving 2^32 in a row. - fn reserve(&mut self, nodes: u32) -> NodeId { - // TODO once we have a free list, look in there for an open slot first! - let index = self.num_nodes; - - if index < self.capacity { - self.num_nodes = index + nodes; - - NodeId { - index, - _phantom: PhantomData::default(), - } - } else { - todo!("pool ran out of capacity. TODO reallocate the nodes pointer to map to a bigger space. Can use mremap on Linux, but must memcpy lots of bytes on macOS and Windows."); - } - } - - pub fn get<'a, 'b, T>(&'a self, node_id: NodeId) -> &'b T { - unsafe { - let node_ptr = self.nodes.offset(node_id.index as isize) as *const T; - - &*node_ptr - } - } - - pub fn get_mut(&mut self, node_id: NodeId) -> &mut T { - unsafe { - let node_ptr = self.nodes.offset(node_id.index as isize) as *mut T; - - &mut *node_ptr - } - } - - pub fn set(&mut self, node_id: NodeId, element: T) { - unsafe { - let node_ptr = self.nodes.offset(node_id.index as isize) as *mut T; - - *node_ptr = element; - } - } - - // A node is available iff its bytes are all zeroes - #[allow(dead_code)] - fn is_available(&self, node_id: NodeId) -> bool { - debug_assert_eq!(size_of::(), NODE_BYTES); - - unsafe { - let node_ptr = self.nodes.offset(node_id.index as isize) as *const [u8; NODE_BYTES]; - - *node_ptr == [0; NODE_BYTES] - } - } -} - -impl std::ops::Index> for Pool { - type Output = T; - - fn index(&self, node_id: NodeId) -> &Self::Output { - self.get(node_id) - } -} - -impl std::ops::IndexMut> for Pool { - fn index_mut(&mut self, node_id: NodeId) -> &mut Self::Output { - self.get_mut(node_id) - } -} - -impl Drop for Pool { - fn drop(&mut self) { - unsafe { - libc::munmap( - self.nodes as *mut c_void, - NODE_BYTES * self.capacity as usize, - ); - } - } -} - -/// A string containing at most 2^32 pool-allocated bytes. -#[derive(Debug, Copy, Clone)] -pub struct PoolStr { - first_node_id: NodeId<()>, - len: u32, -} - -#[test] -fn pool_str_size() { - assert_eq!(size_of::(), 8); -} - -impl PoolStr { - pub fn new(string: &str, pool: &mut Pool) -> Self { - debug_assert!(string.len() <= u32::MAX as usize); - - let chars_per_node = NODE_BYTES / size_of::(); - - let number_of_nodes = f64::ceil(string.len() as f64 / chars_per_node as f64) as u32; - - if number_of_nodes > 0 { - let first_node_id = pool.reserve(number_of_nodes); - let index = first_node_id.index as isize; - let next_node_ptr = unsafe { pool.nodes.offset(index) } as *mut c_void; - - unsafe { - libc::memcpy( - next_node_ptr, - string.as_ptr() as *const c_void, - string.len(), - ); - } - - PoolStr { - first_node_id, - len: string.len() as u32, - } - } else { - PoolStr { - first_node_id: NodeId { - index: 0, - _phantom: PhantomData::default(), - }, - len: 0, - } - } - } - - pub fn as_str(&self, pool: &Pool) -> &str { - unsafe { - let node_ptr = pool.nodes.offset(self.first_node_id.index as isize) as *const u8; - - let node_slice: &[u8] = std::slice::from_raw_parts(node_ptr, self.len as usize); - - std::str::from_utf8_unchecked(&node_slice[0..self.len as usize]) - } - } - - #[allow(clippy::len_without_is_empty)] - pub fn len(&self, pool: &Pool) -> usize { - let contents = self.as_str(pool); - - contents.len() - } - - pub fn is_empty(&self, pool: &Pool) -> bool { - self.len(pool) == 0 - } -} - -impl ShallowClone for PoolStr { - fn shallow_clone(&self) -> Self { - // Question: should this fully clone, or is a shallow copy - // (and the aliasing it entails) OK? - Self { - first_node_id: self.first_node_id, - len: self.len, - } - } -} - -/// An array of at most 2^32 pool-allocated nodes. -#[derive(Debug)] -pub struct PoolVec { - first_node_id: NodeId, - len: u32, -} - -#[test] -fn pool_vec_size() { - assert_eq!(size_of::>(), 8); -} - -impl<'a, T: 'a + Sized> PoolVec { - pub fn empty(pool: &mut Pool) -> Self { - Self::new(std::iter::empty(), pool) - } - - pub fn with_capacity(len: u32, pool: &mut Pool) -> Self { - debug_assert!( - size_of::() <= NODE_BYTES, - "{} has a size of {}", - type_name::(), - size_of::() - ); - - if len == 0 { - Self::empty(pool) - } else { - let first_node_id = pool.reserve(len); - - PoolVec { first_node_id, len } - } - } - - pub fn len(&self) -> usize { - self.len as usize - } - - pub fn is_empty(&self) -> bool { - self.len == 0 - } - - pub fn new>(nodes: I, pool: &mut Pool) -> Self { - debug_assert!(nodes.len() <= u32::MAX as usize); - debug_assert!(size_of::() <= NODE_BYTES); - - let len = nodes.len() as u32; - - if len > 0 { - let first_node_id = pool.reserve(len); - let index = first_node_id.index as isize; - let mut next_node_ptr = unsafe { pool.nodes.offset(index) } as *mut T; - - for (indx_inc, node) in nodes.enumerate() { - unsafe { - *next_node_ptr = node; - - next_node_ptr = pool.nodes.offset(index + (indx_inc as isize) + 1) as *mut T; - } - } - - PoolVec { first_node_id, len } - } else { - PoolVec { - first_node_id: NodeId { - index: 0, - _phantom: PhantomData::default(), - }, - len: 0, - } - } - } - - pub fn iter(&self, pool: &'a Pool) -> impl ExactSizeIterator { - self.pool_list_iter(pool) - } - - pub fn iter_mut(&self, pool: &'a mut Pool) -> impl ExactSizeIterator { - self.pool_list_iter_mut(pool) - } - - pub fn iter_node_ids(&self) -> impl ExactSizeIterator> { - self.pool_list_iter_node_ids() - } - - /// Private version of into_iter which exposes the implementation detail - /// of PoolVecIter. We don't want that struct to be public, but we - /// actually do want to have this separate function for code reuse - /// in the iterator's next() method. - #[inline(always)] - fn pool_list_iter(&self, pool: &'a Pool) -> PoolVecIter<'a, T> { - PoolVecIter { - pool, - current_node_id: self.first_node_id, - len_remaining: self.len, - } - } - - #[inline(always)] - fn pool_list_iter_mut(&self, pool: &'a Pool) -> PoolVecIterMut<'a, T> { - PoolVecIterMut { - pool, - current_node_id: self.first_node_id, - len_remaining: self.len, - } - } - - #[inline(always)] - fn pool_list_iter_node_ids(&self) -> PoolVecIterNodeIds { - PoolVecIterNodeIds { - current_node_id: self.first_node_id, - len_remaining: self.len, - } - } - - pub fn free(self, pool: &'a mut Pool) { - // zero out the memory - unsafe { - let index = self.first_node_id.index as isize; - let node_ptr = pool.nodes.offset(index) as *mut c_void; - let bytes = self.len as usize * NODE_BYTES; - - libc::memset(node_ptr, 0, bytes); - } - - // TODO insert it into the pool's free list - } -} - -impl ShallowClone for PoolVec { - fn shallow_clone(&self) -> Self { - // Question: should this fully clone, or is a shallow copy - // (and the aliasing it entails) OK? - Self { - first_node_id: self.first_node_id, - len: self.len, - } - } -} - -struct PoolVecIter<'a, T> { - pool: &'a Pool, - current_node_id: NodeId, - len_remaining: u32, -} - -impl<'a, T> ExactSizeIterator for PoolVecIter<'a, T> -where - T: 'a, -{ - fn len(&self) -> usize { - self.len_remaining as usize - } -} - -impl<'a, T> Iterator for PoolVecIter<'a, T> -where - T: 'a, -{ - type Item = &'a T; - - fn next(&mut self) -> Option { - let len_remaining = self.len_remaining; - - match len_remaining.cmp(&1) { - Ordering::Greater => { - // Get the current node - let index = self.current_node_id.index; - let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *const T; - - // Advance the node pointer to the next node in the current page - self.current_node_id = NodeId { - index: index + 1, - _phantom: PhantomData::default(), - }; - self.len_remaining = len_remaining - 1; - - Some(unsafe { &*node_ptr }) - } - Ordering::Equal => { - self.len_remaining = 0; - - // Don't advance the node pointer's node, because that might - // advance past the end of the page! - - let index = self.current_node_id.index; - let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *const T; - - Some(unsafe { &*node_ptr }) - } - Ordering::Less => { - // len_remaining was 0 - None - } - } - } -} - -struct PoolVecIterMut<'a, T> { - pool: &'a Pool, - current_node_id: NodeId, - len_remaining: u32, -} - -impl<'a, T> ExactSizeIterator for PoolVecIterMut<'a, T> -where - T: 'a, -{ - fn len(&self) -> usize { - self.len_remaining as usize - } -} - -impl<'a, T> Iterator for PoolVecIterMut<'a, T> -where - T: 'a, -{ - type Item = &'a mut T; - - fn next(&mut self) -> Option { - let len_remaining = self.len_remaining; - - match len_remaining.cmp(&1) { - Ordering::Greater => { - // Get the current node - let index = self.current_node_id.index; - let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *mut T; - - // Advance the node pointer to the next node in the current page - self.current_node_id = NodeId { - index: index + 1, - _phantom: PhantomData::default(), - }; - self.len_remaining = len_remaining - 1; - - Some(unsafe { &mut *node_ptr }) - } - Ordering::Equal => { - self.len_remaining = 0; - - // Don't advance the node pointer's node, because that might - // advance past the end of the page! - - let index = self.current_node_id.index; - let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *mut T; - - Some(unsafe { &mut *node_ptr }) - } - Ordering::Less => { - // len_remaining was 0 - None - } - } - } -} - -struct PoolVecIterNodeIds { - current_node_id: NodeId, - len_remaining: u32, -} - -impl ExactSizeIterator for PoolVecIterNodeIds { - fn len(&self) -> usize { - self.len_remaining as usize - } -} - -impl Iterator for PoolVecIterNodeIds { - type Item = NodeId; - - fn next(&mut self) -> Option { - let len_remaining = self.len_remaining; - - match len_remaining.cmp(&1) { - Ordering::Greater => { - // Get the current node - let current = self.current_node_id; - let index = current.index; - - // Advance the node pointer to the next node in the current page - self.current_node_id = NodeId { - index: index + 1, - _phantom: PhantomData::default(), - }; - self.len_remaining = len_remaining - 1; - - Some(current) - } - Ordering::Equal => { - self.len_remaining = 0; - - // Don't advance the node pointer's node, because that might - // advance past the end of the page! - - Some(self.current_node_id) - } - Ordering::Less => { - // len_remaining was 0 - None - } - } - } -} - -#[test] -fn pool_vec_iter_test() { - let expected_vec: Vec = vec![2, 4, 8, 16]; - - let mut test_pool = Pool::with_capacity(1024); - let pool_vec = PoolVec::new(expected_vec.clone().into_iter(), &mut test_pool); - - let current_vec: Vec = pool_vec.iter(&test_pool).copied().collect(); - - assert_eq!(current_vec, expected_vec); -} -/// Clones the outer node, but does not clone any nodeids -pub trait ShallowClone { - fn shallow_clone(&self) -> Self; -} - -impl ShallowClone for Expected { - fn shallow_clone(&self) -> Self { - use Expected::*; - - match self { - NoExpectation(t) => NoExpectation(t.shallow_clone()), - ForReason(reason, t, region) => ForReason(reason.clone(), t.shallow_clone(), *region), - FromAnnotation(loc_pat, n, source, t) => { - FromAnnotation(loc_pat.clone(), *n, *source, t.shallow_clone()) - } - } - } -} - -impl ShallowClone for PExpected { - fn shallow_clone(&self) -> Self { - use PExpected::*; - - match self { - NoExpectation(t) => NoExpectation(t.shallow_clone()), - ForReason(reason, t, region) => ForReason(reason.clone(), t.shallow_clone(), *region), - } - } -} diff --git a/editor/src/lib.rs b/editor/src/lib.rs index f6f3053840..5ee7ea9fd5 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -3,14 +3,12 @@ #![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)] #[cfg_attr(test, macro_use)] -extern crate indoc; extern crate pest; #[cfg_attr(test, macro_use)] extern crate pest_derive; mod editor; mod graphics; -pub mod lang; //TODO remove pub for unused warnings mod ui; mod window; diff --git a/editor/tests/solve_expr2.rs b/editor/tests/solve_expr2.rs deleted file mode 100644 index 88f5e5248b..0000000000 --- a/editor/tests/solve_expr2.rs +++ /dev/null @@ -1,372 +0,0 @@ -#[macro_use] -extern crate pretty_assertions; -#[macro_use] -extern crate indoc; - -use bumpalo::Bump; -use roc_can::expected::Expected; -use roc_collections::all::MutMap; -use roc_editor::lang::solve; -use roc_editor::lang::{ - constrain::constrain_expr, - constrain::Constraint, - expr::{str_to_expr2, Env}, - pool::Pool, - scope::Scope, - types::Type2, -}; -use roc_module::ident::Lowercase; -use roc_module::symbol::Interns; -use roc_module::symbol::Symbol; -use roc_module::symbol::{IdentIds, ModuleIds}; -use roc_region::all::Region; -use roc_types::solved_types::Solved; -use roc_types::subs::{Subs, Variable}; -use roc_types::{pretty_print::content_to_string, subs::VarStore}; - -fn run_solve<'a>( - arena: &'a Bump, - mempool: &mut Pool, - aliases: MutMap, - rigid_variables: MutMap, - constraint: Constraint, - var_store: VarStore, -) -> (Solved, solve::Env, Vec) { - let env = solve::Env { - vars_by_symbol: MutMap::default(), - aliases, - }; - - let mut subs = Subs::new(var_store); - - for (var, name) in rigid_variables { - subs.rigid_var(var, name); - } - - // Now that the module is parsed, canonicalized, and constrained, - // we need to type check it. - let mut problems = Vec::new(); - - // Run the solver to populate Subs. - let (solved_subs, solved_env) = - solve::run(arena, mempool, &env, &mut problems, subs, &constraint); - - (solved_subs, solved_env, problems) -} - -fn infer_eq(actual: &str, expected_str: &str) { - let mut env_pool = Pool::with_capacity(1024); - let env_arena = Bump::new(); - let code_arena = Bump::new(); - - let mut var_store = VarStore::default(); - let var = var_store.fresh(); - let dep_idents = IdentIds::exposed_builtins(8); - let exposed_ident_ids = IdentIds::default(); - let mut module_ids = ModuleIds::default(); - let mod_id = module_ids.get_or_insert(&"ModId123".into()); - - let mut env = Env::new( - mod_id, - &env_arena, - &mut env_pool, - &mut var_store, - dep_idents, - &module_ids, - exposed_ident_ids, - ); - - let mut scope = Scope::new(env.home, env.pool, env.var_store); - - let region = Region::zero(); - - let expr2_result = str_to_expr2(&code_arena, actual, &mut env, &mut scope, region); - - match expr2_result { - Ok((expr, _)) => { - let constraint = constrain_expr( - &code_arena, - &mut env, - &expr, - Expected::NoExpectation(Type2::Variable(var)), - Region::zero(), - ); - - let Env { - pool, - var_store: ref_var_store, - mut dep_idents, - .. - } = env; - - // extract the var_store out of the env again - let mut var_store = VarStore::default(); - std::mem::swap(ref_var_store, &mut var_store); - - let (mut solved, _, _) = run_solve( - &code_arena, - pool, - Default::default(), - Default::default(), - constraint, - var_store, - ); - - let subs = solved.inner_mut(); - - let content = subs.get_content_without_compacting(var); - - // Connect the ModuleId to it's IdentIds - dep_idents.insert(mod_id, env.ident_ids); - - let interns = Interns { - module_ids: env.module_ids.clone(), - all_ident_ids: dep_idents, - }; - - let actual_str = content_to_string(content, subs, mod_id, &interns); - - assert_eq!(actual_str, expected_str); - } - Err(e) => panic!("syntax error {:?}", e), - } -} - -#[test] -fn constrain_str() { - infer_eq( - indoc!( - r#" - "type inference!" - "# - ), - "Str", - ) -} - -// This will be more useful once we actually map -// strings less than 15 chars to SmallStr -#[test] -fn constrain_small_str() { - infer_eq( - indoc!( - r#" - "a" - "# - ), - "Str", - ) -} - -#[test] -fn constrain_empty_record() { - infer_eq( - indoc!( - r#" - {} - "# - ), - "{}", - ) -} - -#[test] -fn constrain_small_int() { - infer_eq( - indoc!( - r#" - 12 - "# - ), - "Num *", - ) -} - -#[test] -fn constrain_float() { - infer_eq( - indoc!( - r#" - 3.14 - "# - ), - "Float *", - ) -} - -#[test] -fn constrain_record() { - infer_eq( - indoc!( - r#" - { x : 1, y : "hi" } - "# - ), - "{ x : Num *, y : Str }", - ) -} - -#[test] -fn constrain_empty_list() { - infer_eq( - indoc!( - r#" - [] - "# - ), - "List *", - ) -} - -#[test] -fn constrain_list() { - infer_eq( - indoc!( - r#" - [ 1, 2 ] - "# - ), - "List (Num *)", - ) -} - -#[test] -fn constrain_list_of_records() { - infer_eq( - indoc!( - r#" - [ { x: 1 }, { x: 3 } ] - "# - ), - "List { x : Num * }", - ) -} - -#[test] -fn constrain_global_tag() { - infer_eq( - indoc!( - r#" - Foo - "# - ), - "[ Foo ]*", - ) -} - -#[test] -fn constrain_private_tag() { - infer_eq( - indoc!( - r#" - @Foo - "# - ), - "[ @Foo ]*", - ) -} - -#[test] -fn constrain_call_and_accessor() { - infer_eq( - indoc!( - r#" - .foo { foo: "bar" } - "# - ), - "Str", - ) -} - -#[test] -fn constrain_access() { - infer_eq( - indoc!( - r#" - { foo: "bar" }.foo - "# - ), - "Str", - ) -} - -#[test] -fn constrain_if() { - infer_eq( - indoc!( - r#" - if True then Green else Red - "# - ), - "[ Green, Red ]*", - ) -} - -#[test] -fn constrain_when() { - infer_eq( - indoc!( - r#" - when if True then Green else Red is - Green -> Blue - Red -> Purple - "# - ), - "[ Blue, Purple ]*", - ) -} - -#[test] -fn constrain_let_value() { - infer_eq( - indoc!( - r#" - person = { name: "roc" } - - person - "# - ), - "{ name : Str }", - ) -} - -#[test] -fn constrain_update() { - infer_eq( - indoc!( - r#" - person = { name: "roc" } - - { person & name: "bird" } - "# - ), - "{ name : Str }", - ) -} - -#[ignore = "TODO: implement builtins in the editor"] -#[test] -fn constrain_run_low_level() { - infer_eq( - indoc!( - r#" - List.map [ { name: "roc" }, { name: "bird" } ] .name - "# - ), - "List Str", - ) -} - -#[test] -fn constrain_closure() { - infer_eq( - indoc!( - r#" - x = 1 - - \{} -> x - "# - ), - "{}* -> Num *", - ) -} diff --git a/utils/Cargo.toml b/utils/Cargo.toml new file mode 100644 index 0000000000..75c3f928a4 --- /dev/null +++ b/utils/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "roc_utils" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" +description = "Utility functions used all over the code base." + +[dependencies] +snafu = { version = "0.6", features = ["backtraces"] } + +[dev-dependencies] \ No newline at end of file diff --git a/utils/src/lib.rs b/utils/src/lib.rs new file mode 100644 index 0000000000..bbab75e59b --- /dev/null +++ b/utils/src/lib.rs @@ -0,0 +1,95 @@ +use snafu::OptionExt; +use std::{collections::HashMap, slice::SliceIndex}; +use util_error::{IndexOfFailed, KeyNotFound, OutOfBounds, UtilResult}; + +pub mod util_error; + +// replace HashMap method that returns Option with one that returns Result and proper Error +pub fn map_get<'a, K: ::std::fmt::Debug + std::hash::Hash + std::cmp::Eq, V>( + hash_map: &'a HashMap, + key: &K, +) -> UtilResult<&'a V> { + let value = hash_map.get(key).context(KeyNotFound { + key_str: format!("{:?}", key), + })?; + + Ok(value) +} + +pub fn index_of(elt: T, slice: &[T]) -> UtilResult { + let index = slice + .iter() + .position(|slice_elt| *slice_elt == elt) + .with_context(|| { + let elt_str = format!("{:?}", elt); + let collection_str = format!("{:?}", slice); + + IndexOfFailed { + elt_str, + collection_str, + } + })?; + + Ok(index) +} + +// replace slice method that return Option with one that return Result and proper Error +pub fn slice_get(index: usize, slice: &[T]) -> UtilResult<&>::Output> { + let elt_ref = slice.get(index).context(OutOfBounds { + index, + collection_name: "Slice", + len: slice.len(), + })?; + + Ok(elt_ref) +} + +pub fn slice_get_mut( + index: usize, + slice: &mut [T], +) -> UtilResult<&mut >::Output> { + let slice_len = slice.len(); + + let elt_ref = slice.get_mut(index).context(OutOfBounds { + index, + collection_name: "Slice", + len: slice_len, + })?; + + Ok(elt_ref) +} + +// returns the index of the first occurrence of element and index of the last occurrence +pub fn first_last_index_of( + elt: T, + slice: &[T], +) -> UtilResult<(usize, usize)> { + let mut first_index_opt = None; + let mut last_index_opt = None; + + for (index, list_elt) in slice.iter().enumerate() { + if *list_elt == elt { + if first_index_opt.is_none() { + first_index_opt = Some(index); + last_index_opt = Some(index); + } else { + last_index_opt = Some(index) + } + } else if last_index_opt.is_some() { + break; + } + } + + if let (Some(first_index), Some(last_index)) = (first_index_opt, last_index_opt) { + Ok((first_index, last_index)) + } else { + let elt_str = format!("{:?}", elt); + let collection_str = format!("{:?}", slice); + + IndexOfFailed { + elt_str, + collection_str, + } + .fail() + } +} diff --git a/utils/src/util_error.rs b/utils/src/util_error.rs new file mode 100644 index 0000000000..d19c230f11 --- /dev/null +++ b/utils/src/util_error.rs @@ -0,0 +1,35 @@ +use snafu::{Backtrace, Snafu}; + +#[derive(Debug, Snafu)] +#[snafu(visibility(pub))] +pub enum UtilError { + #[snafu(display( + "IndexOfFailed: Element {} was not found in collection {}.", + elt_str, + collection_str + ))] + IndexOfFailed { + elt_str: String, + collection_str: String, + backtrace: Backtrace, + }, + #[snafu(display("KeyNotFound: key {} was not found in HashMap.", key_str,))] + KeyNotFound { + key_str: String, + backtrace: Backtrace, + }, + #[snafu(display( + "OutOfBounds: index {} was out of bounds for {} with length {}.", + index, + collection_name, + len + ))] + OutOfBounds { + index: usize, + collection_name: String, + len: usize, + backtrace: Backtrace, + }, +} + +pub type UtilResult = std::result::Result;