mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 23:04:49 +00:00
Merge pull request #1743 from rtfeldman/crate-expr2-markupnode
separate crates for markup and lang folders.
This commit is contained in:
commit
931ce765c5
84 changed files with 4339 additions and 3673 deletions
44
Cargo.lock
generated
44
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -29,9 +29,12 @@ members = [
|
|||
"vendor/pathfinding",
|
||||
"vendor/pretty",
|
||||
"editor",
|
||||
"ast",
|
||||
"cli",
|
||||
"cli/cli_utils",
|
||||
"code_markup",
|
||||
"roc_std",
|
||||
"utils",
|
||||
"docs",
|
||||
"linker",
|
||||
]
|
||||
|
|
|
@ -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:
|
||||
|
|
27
ast/Cargo.toml
Normal file
27
ast/Cargo.toml
Normal file
|
@ -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"
|
38
ast/src/ast_error.rs
Normal file
38
ast/src/ast_error.rs
Normal file
|
@ -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<T, E = ASTError> = std::result::Result<T, E>;
|
305
ast/src/canonicalization/canonicalize.rs
Normal file
305
ast/src/canonicalization/canonicalize.rs
Normal file
|
@ -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<roc_parse::ast::AssignedField<'a, roc_parse::ast::Expr<'a>>>],
|
||||
) -> Result<(PoolVec<RecordField>, 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<CanonicalField<'a>, 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)
|
||||
}
|
2
ast/src/canonicalization/mod.rs
Normal file
2
ast/src/canonicalization/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod canonicalize;
|
||||
pub mod module;
|
|
@ -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<Symbol, NodeId<Alias>>,
|
||||
pub rigid_variables: MutMap<Variable, Lowercase>,
|
|
@ -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<Type2>, 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<Symbol, roc_types::types::Alias>,
|
||||
rigid_variables: MutMap<Variable, Lowercase>,
|
||||
constraint: Constraint,
|
||||
var_store: VarStore,
|
||||
) -> (Solved<Subs>, solve_type::Env, Vec<solve_type::TypeError>) {
|
||||
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 *",
|
||||
)
|
||||
}
|
||||
}
|
45
ast/src/lang/core/ast.rs
Normal file
45
ast/src/lang/core/ast.rs
Normal file
|
@ -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<DefId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
pub enum ASTNodeId {
|
||||
ADefId(DefId),
|
||||
AExprId(ExprId),
|
||||
}
|
||||
|
||||
impl ASTNodeId {
|
||||
pub fn to_expr_id(&self) -> ASTResult<ExprId> {
|
||||
match self {
|
||||
ASTNodeId::AExprId(expr_id) => Ok(*expr_id),
|
||||
_ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_def_id(&self) -> ASTResult<DefId> {
|
||||
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),
|
||||
}
|
||||
}
|
70
ast/src/lang/core/declaration.rs
Normal file
70
ast/src/lang/core/declaration.rs
Normal file
|
@ -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!()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
43
ast/src/lang/core/def/def2.rs
Normal file
43
ast/src/lang/core/def/def2.rs
Normal file
|
@ -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<Pattern2>,
|
||||
expr_id: NodeId<Expr2>,
|
||||
},
|
||||
Blank,
|
||||
}
|
||||
|
||||
pub type DefId = NodeId<Def2>;
|
||||
|
||||
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
|
||||
}
|
97
ast/src/lang/core/def/def_to_def2.rs
Normal file
97
ast/src/lang/core/def/def_to_def2.rs
Normal file
|
@ -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<roc_region::all::Loc<roc_parse::ast::Def<'a>>>,
|
||||
region: Region,
|
||||
) -> Vec<Def2> {
|
||||
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<Vec<Def2>, 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),
|
||||
}
|
||||
}
|
3
ast/src/lang/core/def/mod.rs
Normal file
3
ast/src/lang/core/def/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub mod def;
|
||||
pub mod def2;
|
||||
pub mod def_to_def2;
|
233
ast/src/lang/core/expr/expr2.rs
Normal file
233
ast/src/lang/core/expr/expr2.rs
Normal file
|
@ -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<U30>;
|
||||
|
||||
// TODO make the inner types private?
|
||||
pub type ExprId = NodeId<Expr2>;
|
||||
|
||||
/// 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<ExprId>, // 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<WhenBranch>, // 8B
|
||||
cond: ExprId, // 4B
|
||||
},
|
||||
LetRec {
|
||||
defs: PoolVec<FunctionDef>, // 8B
|
||||
body_var: Variable, // 8B
|
||||
body_id: ExprId, // 4B
|
||||
},
|
||||
LetFunction {
|
||||
def_id: NodeId<FunctionDef>, // 4B
|
||||
body_var: Variable, // 8B
|
||||
body_id: ExprId, // 4B
|
||||
},
|
||||
LetValue {
|
||||
def_id: NodeId<ValueDef>, // 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<Pattern2>)>, // 8B
|
||||
name: Symbol, // 8B
|
||||
body: ExprId, // 4B
|
||||
function_type: Variable, // 4B
|
||||
recursive: Recursive, // 1B
|
||||
extra: NodeId<ClosureExtra>, // 4B
|
||||
},
|
||||
// Product Types
|
||||
Record {
|
||||
record_var: Variable, // 4B
|
||||
fields: PoolVec<RecordField>, // 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<RecordField>, // 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<T> = Result<T, Problem>;
|
||||
|
||||
#[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::<IntVal>(), 16);
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum FloatVal {
|
||||
F64(f64),
|
||||
F32(f32),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WhenBranch {
|
||||
pub patterns: PoolVec<Pattern2>, // 4B
|
||||
pub body: ExprId, // 3B
|
||||
pub guard: Option<ExprId>, // 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
|
||||
}
|
139
ast/src/lang/core/expr/expr2_to_string.rs
Normal file
139
ast/src/lang/core/expr/expr2_to_string.rs
Normal file
|
@ -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::<Vec<&str>>()
|
||||
.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)
|
||||
}
|
710
ast/src/lang/core/expr/expr_to_expr2.rs
Normal file
710
ast/src/lang/core/expr/expr_to_expr2.rs
Normal file
|
@ -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<Expr<'a>>,
|
||||
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<ExprId> = 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<Symbol> =
|
||||
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)
|
||||
}
|
51
ast/src/lang/core/expr/introduced_vars.rs
Normal file
51
ast/src/lang/core/expr/introduced_vars.rs
Normal file
|
@ -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<Lowercase, Variable>`
|
||||
//
|
||||
// But then between annotations, the same name can occur multiple times,
|
||||
// but a variable can only have one name. Therefore
|
||||
// `ftv : Map<Variable, Lowercase>`.
|
||||
pub wildcards: Vec<Variable>,
|
||||
pub var_by_name: MutMap<Lowercase, Variable>,
|
||||
pub name_by_var: MutMap<Variable, Lowercase>,
|
||||
pub host_exposed_aliases: MutMap<Symbol, Variable>,
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
6
ast/src/lang/core/expr/mod.rs
Normal file
6
ast/src/lang/core/expr/mod.rs
Normal file
|
@ -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;
|
30
ast/src/lang/core/expr/output.rs
Normal file
30
ast/src/lang/core/expr/output.rs
Normal file
|
@ -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<Symbol>,
|
||||
pub introduced_variables: IntroducedVariables,
|
||||
pub aliases: MutMap<Symbol, NodeId<Alias>>,
|
||||
pub non_closures: MutSet<Symbol>,
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
49
ast/src/lang/core/expr/record_field.rs
Normal file
49
ast/src/lang/core/expr/record_field.rs
Normal file
|
@ -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<ExprId> {
|
||||
match self {
|
||||
InvalidLabelOnly(_, _) => None,
|
||||
LabelOnly(_, _, _) => None,
|
||||
LabeledValue(_, _, field_val_id) => Some(*field_val_id),
|
||||
}
|
||||
}
|
||||
}
|
61
ast/src/lang/core/fun_def.rs
Normal file
61
ast/src/lang/core/fun_def.rs
Normal file
|
@ -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<Rigids>, // 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,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
10
ast/src/lang/core/header.rs
Normal file
10
ast/src/lang/core/header.rs
Normal file
|
@ -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<String>,
|
||||
pub provides: Vec<String>,
|
||||
pub ast_node_id: ExprId, // TODO probably want to create and use HeaderId
|
||||
}
|
10
ast/src/lang/core/mod.rs
Normal file
10
ast/src/lang/core/mod.rs
Normal file
|
@ -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;
|
|
@ -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<Pattern2>;
|
||||
|
@ -483,7 +490,7 @@ pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec<Symbol> {
|
|||
symbols
|
||||
}
|
||||
|
||||
pub fn get_identifier_string(pattern: &Pattern2, interns: &Interns) -> EdResult<String> {
|
||||
pub fn get_identifier_string(pattern: &Pattern2, interns: &Interns) -> ASTResult<String> {
|
||||
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();
|
228
ast/src/lang/core/str.rs
Normal file
228
ast/src/lang/core/str.rs
Normal file
|
@ -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<StrSegment>) -> 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(())
|
||||
}
|
|
@ -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<Type2>;
|
||||
|
||||
#[derive(Debug)]
|
101
ast/src/lang/core/val_def.rs
Normal file
101
ast/src/lang/core/val_def.rs
Normal file
|
@ -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<Pattern2> {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
168
ast/src/lang/env.rs
Normal file
168
ast/src/lang/env.rs
Normal file
|
@ -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<ModuleId, IdentIds>,
|
||||
pub module_ids: &'a ModuleIds,
|
||||
pub ident_ids: IdentIds,
|
||||
pub exposed_ident_ids: IdentIds,
|
||||
|
||||
pub closures: MutMap<Symbol, References>,
|
||||
/// Symbols which were referenced by qualified lookups.
|
||||
pub qualified_lookups: MutSet<Symbol>,
|
||||
|
||||
pub top_level_symbols: MutSet<Symbol>,
|
||||
|
||||
pub closure_name_symbol: Option<Symbol>,
|
||||
pub tailcallable_symbol: Option<Symbol>,
|
||||
}
|
||||
|
||||
impl<'a> Env<'a> {
|
||||
pub fn new(
|
||||
home: ModuleId,
|
||||
arena: &'a Bump,
|
||||
pool: &'a mut Pool,
|
||||
var_store: &'a mut VarStore,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
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<T>(&mut self, item: T, region: Region) -> NodeId<T> {
|
||||
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<T>(&mut self, _node_id: NodeId<T>, _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<Symbol, RuntimeError> {
|
||||
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,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
4
ast/src/lang/mod.rs
Normal file
4
ast/src/lang/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
pub mod core;
|
||||
pub mod env;
|
||||
mod rigids;
|
||||
pub mod scope;
|
81
ast/src/lang/rigids.rs
Normal file
81
ast/src/lang/rigids.rs
Normal file
|
@ -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<PoolStr>, Variable)>, // 8B
|
||||
padding: [u8; 1],
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_collect)]
|
||||
impl Rigids {
|
||||
pub fn new(
|
||||
named: HashMap<&str, Variable, BuildHasherDefault<WyHash>>,
|
||||
unnamed: HashSet<Variable, BuildHasherDefault<WyHash>>,
|
||||
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::<Vec<(PoolStr, Variable)>>();
|
||||
|
||||
PoolVec::new(named.into_iter(), pool)
|
||||
}
|
||||
|
||||
pub fn unnamed(&self, pool: &mut Pool) -> PoolVec<Variable> {
|
||||
let unnamed = self
|
||||
.names
|
||||
.iter(pool)
|
||||
.filter_map(|(opt_pool_str, var)| {
|
||||
if opt_pool_str.is_none() {
|
||||
Some(*var)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Variable>>();
|
||||
|
||||
PoolVec::new(unnamed.into_iter(), pool)
|
||||
}
|
||||
}
|
||||
|
||||
impl ShallowClone for Rigids {
|
||||
fn shallow_clone(&self) -> Self {
|
||||
Self {
|
||||
names: self.names.shallow_clone(),
|
||||
padding: self.padding,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
7
ast/src/lib.rs
Normal file
7
ast/src/lib.rs
Normal file
|
@ -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;
|
4
ast/src/mem_pool/mod.rs
Normal file
4
ast/src/mem_pool/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
pub mod pool;
|
||||
pub mod pool_str;
|
||||
pub mod pool_vec;
|
||||
pub mod shallow_clone;
|
228
ast/src/mem_pool/pool.rs
Normal file
228
ast/src/mem_pool/pool.rs
Normal file
|
@ -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<T> {
|
||||
pub(super) index: u32,
|
||||
pub(super) _phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> Clone for NodeId<T> {
|
||||
fn clone(&self) -> Self {
|
||||
NodeId {
|
||||
index: self.index,
|
||||
_phantom: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq for NodeId<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.index == other.index
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for NodeId<T> {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Pool {
|
||||
pub(super) nodes: *mut [u8; NODE_BYTES],
|
||||
num_nodes: u32,
|
||||
capacity: u32,
|
||||
// free_1node_slots: Vec<NodeId<T>>,
|
||||
}
|
||||
|
||||
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::<c_void>() 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<T>(&mut self, node: T) -> NodeId<T> {
|
||||
// It's only safe to store this if T fits in S.
|
||||
debug_assert!(
|
||||
size_of::<T>() <= NODE_BYTES,
|
||||
"{} has a size of {}, but it needs to be at most {}",
|
||||
type_name::<T>(),
|
||||
size_of::<T>(),
|
||||
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<T>(&mut self, nodes: u32) -> NodeId<T> {
|
||||
// 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<T>) -> &'b T {
|
||||
unsafe {
|
||||
let node_ptr = self.nodes.offset(node_id.index as isize) as *const T;
|
||||
|
||||
&*node_ptr
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut<T>(&mut self, node_id: NodeId<T>) -> &mut T {
|
||||
unsafe {
|
||||
let node_ptr = self.nodes.offset(node_id.index as isize) as *mut T;
|
||||
|
||||
&mut *node_ptr
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set<T>(&mut self, node_id: NodeId<T>, 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<T>(&self, node_id: NodeId<T>) -> bool {
|
||||
debug_assert_eq!(size_of::<T>(), 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<T> std::ops::Index<NodeId<T>> for Pool {
|
||||
type Output = T;
|
||||
|
||||
fn index(&self, node_id: NodeId<T>) -> &Self::Output {
|
||||
self.get(node_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::IndexMut<NodeId<T>> for Pool {
|
||||
fn index_mut(&mut self, node_id: NodeId<T>) -> &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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
86
ast/src/mem_pool/pool_str.rs
Normal file
86
ast/src/mem_pool/pool_str.rs
Normal file
|
@ -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::<PoolStr>(), 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::<char>();
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
323
ast/src/mem_pool/pool_vec.rs
Normal file
323
ast/src/mem_pool/pool_vec.rs
Normal file
|
@ -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<T> {
|
||||
first_node_id: NodeId<T>,
|
||||
len: u32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_vec_size() {
|
||||
assert_eq!(size_of::<PoolVec<()>>(), 8);
|
||||
}
|
||||
|
||||
impl<'a, T: 'a + Sized> PoolVec<T> {
|
||||
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::<T>() <= NODE_BYTES,
|
||||
"{} has a size of {}",
|
||||
type_name::<T>(),
|
||||
size_of::<T>()
|
||||
);
|
||||
|
||||
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<I: ExactSizeIterator<Item = T>>(nodes: I, pool: &mut Pool) -> Self {
|
||||
debug_assert!(nodes.len() <= u32::MAX as usize);
|
||||
debug_assert!(size_of::<T>() <= 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<Item = &'a T> {
|
||||
self.pool_list_iter(pool)
|
||||
}
|
||||
|
||||
pub fn iter_mut(&self, pool: &'a mut Pool) -> impl ExactSizeIterator<Item = &'a mut T> {
|
||||
self.pool_list_iter_mut(pool)
|
||||
}
|
||||
|
||||
pub fn iter_node_ids(&self) -> impl ExactSizeIterator<Item = NodeId<T>> {
|
||||
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<T> {
|
||||
PoolVecIterNodeIds {
|
||||
current_node_id: self.first_node_id,
|
||||
len_remaining: self.len,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn free<S>(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<T> ShallowClone for PoolVec<T> {
|
||||
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<T>,
|
||||
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<Self::Item> {
|
||||
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<T>,
|
||||
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<Self::Item> {
|
||||
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<T> {
|
||||
current_node_id: NodeId<T>,
|
||||
len_remaining: u32,
|
||||
}
|
||||
|
||||
impl<T> ExactSizeIterator for PoolVecIterNodeIds<T> {
|
||||
fn len(&self) -> usize {
|
||||
self.len_remaining as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Iterator for PoolVecIterNodeIds<T> {
|
||||
type Item = NodeId<T>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
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<usize> = 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<usize> = pool_vec.iter(&test_pool).copied().collect();
|
||||
|
||||
assert_eq!(current_vec, expected_vec);
|
||||
}
|
32
ast/src/mem_pool/shallow_clone.rs
Normal file
32
ast/src/mem_pool/shallow_clone.rs
Normal file
|
@ -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<T: ShallowClone> ShallowClone for Expected<T> {
|
||||
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<T: ShallowClone> ShallowClone for PExpected<T> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
2
ast/src/parse/mod.rs
Normal file
2
ast/src/parse/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod parse_ast;
|
||||
pub mod parse_header;
|
48
ast/src/parse/parse_ast.rs
Normal file
48
ast/src/parse/parse_ast.rs
Normal file
|
@ -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<AST, SyntaxError<'a>> {
|
||||
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::<DefId>::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,
|
||||
})
|
||||
}
|
12
ast/src/parse/parse_header.rs
Normal file
12
ast/src/parse/parse_header.rs
Normal file
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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 => {
|
18
code_markup/Cargo.toml
Normal file
18
code_markup/Cargo.toml
Normal file
|
@ -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]
|
22
code_markup/src/colors.rs
Normal file
22
code_markup/src/colors.rs
Normal file
|
@ -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)
|
||||
}
|
5
code_markup/src/lib.rs
Normal file
5
code_markup/src/lib.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
pub mod colors;
|
||||
pub mod markup;
|
||||
pub mod markup_error;
|
||||
pub mod slow_pool;
|
||||
pub mod syntax_highlight;
|
|
@ -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() }
|
||||
}
|
||||
}
|
|
@ -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<MarkNodeId>)
|
|||
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<MarkNodeId>) -> 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<MarkNodeId>) -
|
|||
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<MarkNodeId>) -> 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<MarkNodeId>)
|
|||
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<MarkNodeId>)
|
|||
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<MarkNodeId>) ->
|
|||
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<MarkNodeId>) -
|
|||
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,
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
pub mod attribute;
|
||||
pub mod common_nodes;
|
||||
pub mod nodes;
|
||||
pub mod top_level_def;
|
|
@ -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<usize> = 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<Vec<usize>> = 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<MarkNodeId> {
|
||||
) -> ASTResult<MarkNodeId> {
|
||||
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<MarkNodeId> {
|
||||
) -> ASTResult<MarkNodeId> {
|
||||
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<Vec<MarkNodeId>> {
|
||||
) -> ASTResult<Vec<MarkNodeId>> {
|
||||
let mut all_mark_node_ids = vec![header_to_markup(&ast.header, mark_node_pool)];
|
||||
|
||||
for &def_id in ast.def_ids.iter() {
|
51
code_markup/src/markup/top_level_def.rs
Normal file
51
code_markup/src/markup/top_level_def.rs
Normal file
|
@ -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<MarkupNode> {
|
||||
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)
|
||||
}
|
55
code_markup/src/markup_error.rs
Normal file
55
code_markup/src/markup_error.rs
Normal file
|
@ -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<MarkNodeId>,
|
||||
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<T, E = MarkError> = std::result::Result<T, E>;
|
||||
|
||||
impl From<UtilError> 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()
|
||||
}
|
||||
}
|
|
@ -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() }
|
||||
}
|
||||
}
|
|
@ -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<HighlightStyle, RgbaTup> {
|
|||
|
||||
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)),
|
|
@ -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" }
|
||||
|
|
|
@ -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<T, E = EdError> = std::result::Result<T, E>;
|
||||
|
@ -283,3 +290,23 @@ impl From<UIError> for EdError {
|
|||
dummy_res.context(UIErrorBacktrace { msg }).unwrap_err()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MarkError> 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<ASTError> 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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Vec<MarkNodeId>>,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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<EdModel<'a>> {
|
||||
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<EdModule<'a>> {
|
||||
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;
|
||||
|
|
|
@ -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<Variable, Lowercase>,
|
||||
constraint: Constraint,
|
||||
var_store: VarStore,
|
||||
) -> (Solved<Subs>, solve::Env, Vec<solve::TypeError>) {
|
||||
let env = solve::Env {
|
||||
) -> (Solved<Subs>, solve_type::Env, Vec<solve_type::TypeError>) {
|
||||
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<NodeContext<'a>>
|
|||
})
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<Inpu
|
|||
content: digit_string,
|
||||
ast_node_id,
|
||||
syn_high_style: HighlightStyle::Number,
|
||||
attributes: Attributes::new(),
|
||||
attributes: Attributes::default(),
|
||||
parent_id_opt,
|
||||
newlines_at_end: curr_mark_node_nls,
|
||||
};
|
||||
|
@ -145,6 +146,8 @@ pub fn update_int(
|
|||
}
|
||||
|
||||
fn update_small_int_num(number: &mut IntVal, updated_str: &str) -> EdResult<()> {
|
||||
use IntVal::*;
|
||||
|
||||
*number = match number {
|
||||
I64(_) => I64(check_parse_res(updated_str.parse::<i64>())?),
|
||||
U64(_) => U64(check_parse_res(updated_str.parse::<u64>())?),
|
||||
|
|
|
@ -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<InputOutcome> {
|
||||
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,
|
||||
};
|
||||
|
|
|
@ -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<InputOutcome> {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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<InputOutcome> {
|
||||
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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<InputOutcome> {
|
|||
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,
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<T> = Result<T, Problem>;
|
||||
|
||||
#[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::<IntVal>(), 16);
|
||||
}
|
||||
|
||||
pub type ArrString = ArrayString<U30>;
|
||||
|
||||
/// 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<ExprId>, // 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<WhenBranch>, // 8B
|
||||
cond: ExprId, // 4B
|
||||
},
|
||||
LetRec {
|
||||
defs: PoolVec<FunctionDef>, // 8B
|
||||
body_var: Variable, // 8B
|
||||
body_id: ExprId, // 4B
|
||||
},
|
||||
LetFunction {
|
||||
def_id: NodeId<FunctionDef>, // 4B
|
||||
body_var: Variable, // 8B
|
||||
body_id: ExprId, // 4B
|
||||
},
|
||||
LetValue {
|
||||
def_id: NodeId<ValueDef>, // 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<Pattern2>)>, // 8B
|
||||
name: Symbol, // 8B
|
||||
body: ExprId, // 4B
|
||||
function_type: Variable, // 4B
|
||||
recursive: Recursive, // 1B
|
||||
extra: NodeId<ClosureExtra>, // 4B
|
||||
},
|
||||
// Product Types
|
||||
Record {
|
||||
record_var: Variable, // 4B
|
||||
fields: PoolVec<RecordField>, // 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<RecordField>, // 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<Pattern2>,
|
||||
expr_id: NodeId<Expr2>,
|
||||
},
|
||||
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<Pattern2> {
|
||||
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<Rigids>, // 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<PoolStr>, Variable)>, // 8B
|
||||
padding: [u8; 1],
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_collect)]
|
||||
impl Rigids {
|
||||
pub fn new(
|
||||
named: HashMap<&str, Variable, BuildHasherDefault<WyHash>>,
|
||||
unnamed: HashSet<Variable, BuildHasherDefault<WyHash>>,
|
||||
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::<Vec<(PoolStr, Variable)>>();
|
||||
|
||||
PoolVec::new(named.into_iter(), pool)
|
||||
}
|
||||
|
||||
pub fn unnamed(&self, pool: &mut Pool) -> PoolVec<Variable> {
|
||||
let unnamed = self
|
||||
.names
|
||||
.iter(pool)
|
||||
.filter_map(|(opt_pool_str, var)| {
|
||||
if opt_pool_str.is_none() {
|
||||
Some(*var)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Variable>>();
|
||||
|
||||
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<Pattern2>, // 4B
|
||||
pub body: ExprId, // 3B
|
||||
pub guard: Option<ExprId>, // 4B
|
||||
}
|
||||
|
||||
// TODO make the inner types private?
|
||||
pub type ExprId = NodeId<Expr2>;
|
||||
|
||||
pub type DefId = NodeId<Def2>;
|
||||
|
||||
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<ExprId> {
|
||||
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::<Vec<&str>>()
|
||||
.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<String> {
|
||||
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::<Expr2>(), crate::lang::pool::NODE_BYTES);
|
||||
}
|
||||
|
||||
impl ShallowClone for Rigids {
|
||||
fn shallow_clone(&self) -> Self {
|
||||
Self {
|
||||
names: self.names.shallow_clone(),
|
||||
padding: self.padding,
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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;
|
|
@ -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<DefId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||
pub enum ASTNodeId {
|
||||
ADefId(DefId),
|
||||
AExprId(ExprId),
|
||||
}
|
||||
|
||||
impl ASTNodeId {
|
||||
pub fn to_expr_id(&self) -> EdResult<ExprId> {
|
||||
match self {
|
||||
ASTNodeId::AExprId(expr_id) => Ok(*expr_id),
|
||||
_ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_def_id(&self) -> EdResult<DefId> {
|
||||
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<String>,
|
||||
pub provides: Vec<String>,
|
||||
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<AST, SyntaxError<'a>> {
|
||||
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::<DefId>::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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<T> {
|
||||
index: u32,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> Clone for NodeId<T> {
|
||||
fn clone(&self) -> Self {
|
||||
NodeId {
|
||||
index: self.index,
|
||||
_phantom: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq for NodeId<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.index == other.index
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for NodeId<T> {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Pool {
|
||||
nodes: *mut [u8; NODE_BYTES],
|
||||
num_nodes: u32,
|
||||
capacity: u32,
|
||||
// free_1node_slots: Vec<NodeId<T>>,
|
||||
}
|
||||
|
||||
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::<c_void>() 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<T>(&mut self, node: T) -> NodeId<T> {
|
||||
// It's only safe to store this if T fits in S.
|
||||
debug_assert!(
|
||||
size_of::<T>() <= NODE_BYTES,
|
||||
"{} has a size of {}, but it needs to be at most {}",
|
||||
type_name::<T>(),
|
||||
size_of::<T>(),
|
||||
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<T>(&mut self, nodes: u32) -> NodeId<T> {
|
||||
// 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<T>) -> &'b T {
|
||||
unsafe {
|
||||
let node_ptr = self.nodes.offset(node_id.index as isize) as *const T;
|
||||
|
||||
&*node_ptr
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut<T>(&mut self, node_id: NodeId<T>) -> &mut T {
|
||||
unsafe {
|
||||
let node_ptr = self.nodes.offset(node_id.index as isize) as *mut T;
|
||||
|
||||
&mut *node_ptr
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set<T>(&mut self, node_id: NodeId<T>, 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<T>(&self, node_id: NodeId<T>) -> bool {
|
||||
debug_assert_eq!(size_of::<T>(), 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<T> std::ops::Index<NodeId<T>> for Pool {
|
||||
type Output = T;
|
||||
|
||||
fn index(&self, node_id: NodeId<T>) -> &Self::Output {
|
||||
self.get(node_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::IndexMut<NodeId<T>> for Pool {
|
||||
fn index_mut(&mut self, node_id: NodeId<T>) -> &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::<PoolStr>(), 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::<char>();
|
||||
|
||||
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<T> {
|
||||
first_node_id: NodeId<T>,
|
||||
len: u32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pool_vec_size() {
|
||||
assert_eq!(size_of::<PoolVec<()>>(), 8);
|
||||
}
|
||||
|
||||
impl<'a, T: 'a + Sized> PoolVec<T> {
|
||||
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::<T>() <= NODE_BYTES,
|
||||
"{} has a size of {}",
|
||||
type_name::<T>(),
|
||||
size_of::<T>()
|
||||
);
|
||||
|
||||
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<I: ExactSizeIterator<Item = T>>(nodes: I, pool: &mut Pool) -> Self {
|
||||
debug_assert!(nodes.len() <= u32::MAX as usize);
|
||||
debug_assert!(size_of::<T>() <= 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<Item = &'a T> {
|
||||
self.pool_list_iter(pool)
|
||||
}
|
||||
|
||||
pub fn iter_mut(&self, pool: &'a mut Pool) -> impl ExactSizeIterator<Item = &'a mut T> {
|
||||
self.pool_list_iter_mut(pool)
|
||||
}
|
||||
|
||||
pub fn iter_node_ids(&self) -> impl ExactSizeIterator<Item = NodeId<T>> {
|
||||
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<T> {
|
||||
PoolVecIterNodeIds {
|
||||
current_node_id: self.first_node_id,
|
||||
len_remaining: self.len,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn free<S>(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<T> ShallowClone for PoolVec<T> {
|
||||
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<T>,
|
||||
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<Self::Item> {
|
||||
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<T>,
|
||||
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<Self::Item> {
|
||||
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<T> {
|
||||
current_node_id: NodeId<T>,
|
||||
len_remaining: u32,
|
||||
}
|
||||
|
||||
impl<T> ExactSizeIterator for PoolVecIterNodeIds<T> {
|
||||
fn len(&self) -> usize {
|
||||
self.len_remaining as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Iterator for PoolVecIterNodeIds<T> {
|
||||
type Item = NodeId<T>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
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<usize> = 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<usize> = 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<T: ShallowClone> ShallowClone for Expected<T> {
|
||||
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<T: ShallowClone> ShallowClone for PExpected<T> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<Symbol, roc_types::types::Alias>,
|
||||
rigid_variables: MutMap<Variable, Lowercase>,
|
||||
constraint: Constraint,
|
||||
var_store: VarStore,
|
||||
) -> (Solved<Subs>, solve::Env, Vec<solve::TypeError>) {
|
||||
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 *",
|
||||
)
|
||||
}
|
12
utils/Cargo.toml
Normal file
12
utils/Cargo.toml
Normal file
|
@ -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]
|
95
utils/src/lib.rs
Normal file
95
utils/src/lib.rs
Normal file
|
@ -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<K, V>,
|
||||
key: &K,
|
||||
) -> UtilResult<&'a V> {
|
||||
let value = hash_map.get(key).context(KeyNotFound {
|
||||
key_str: format!("{:?}", key),
|
||||
})?;
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub fn index_of<T: ::std::fmt::Debug + std::cmp::Eq>(elt: T, slice: &[T]) -> UtilResult<usize> {
|
||||
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<T>(index: usize, slice: &[T]) -> UtilResult<&<usize as SliceIndex<[T]>>::Output> {
|
||||
let elt_ref = slice.get(index).context(OutOfBounds {
|
||||
index,
|
||||
collection_name: "Slice",
|
||||
len: slice.len(),
|
||||
})?;
|
||||
|
||||
Ok(elt_ref)
|
||||
}
|
||||
|
||||
pub fn slice_get_mut<T>(
|
||||
index: usize,
|
||||
slice: &mut [T],
|
||||
) -> UtilResult<&mut <usize as SliceIndex<[T]>>::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<T: ::std::fmt::Debug + std::cmp::Eq>(
|
||||
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()
|
||||
}
|
||||
}
|
35
utils/src/util_error.rs
Normal file
35
utils/src/util_error.rs
Normal file
|
@ -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<T, E = UtilError> = std::result::Result<T, E>;
|
Loading…
Add table
Add a link
Reference in a new issue