From bc2714aad81f1e1d2490af0372fc316a43f254ad Mon Sep 17 00:00:00 2001 From: Lawrence Bethlenfalvy Date: Thu, 25 May 2023 19:14:24 +0100 Subject: [PATCH] Preparation for sharing - rustfmt - clippy - comments - README --- README.md | 28 +- notes.md | 0 notes/macros.md | 27 ++ orchid.code-workspace | 2 +- rustfmt.toml | 34 +++ src/cli.rs | 13 +- src/external/assertion_error.rs | 18 +- src/external/bool/boolean.rs | 18 +- src/external/bool/equals.rs | 54 ++-- src/external/bool/ifthenelse.rs | 57 ++-- src/external/bool/mod.rs | 10 +- src/external/conv/mod.rs | 9 +- src/external/conv/parse_float.rs | 52 ++-- src/external/conv/parse_uint.rs | 61 ++-- src/external/conv/to_string.rs | 35 +-- src/external/cpsio/debug.rs | 29 +- src/external/cpsio/io.rs | 45 +-- src/external/cpsio/mod.rs | 15 +- src/external/cpsio/panic.rs | 24 +- src/external/cpsio/print.rs | 37 ++- src/external/cpsio/readline.rs | 29 +- src/external/litconv.rs | 31 +- src/external/mod.rs | 14 +- src/external/num/mod.rs | 7 +- src/external/num/numeric.rs | 47 ++- src/external/num/operators/add.rs | 31 +- src/external/num/operators/divide.rs | 33 +- src/external/num/operators/mod.rs | 2 +- src/external/num/operators/multiply.rs | 34 +-- src/external/num/operators/remainder.rs | 34 +-- src/external/num/operators/subtract.rs | 34 +-- src/external/runtime_error.rs | 15 +- src/external/std.rs | 17 +- src/external/str/char_at.rs | 41 +-- src/external/str/concatenate.rs | 36 ++- src/external/str/mod.rs | 14 +- src/foreign.rs | 47 +-- src/foreign_macros/atomic_defaults.rs | 13 +- src/foreign_macros/atomic_impl.rs | 89 +++--- src/foreign_macros/atomic_inert.rs | 30 +- src/foreign_macros/atomic_redirect.rs | 25 +- src/foreign_macros/externfn_impl.rs | 54 ++-- src/foreign_macros/mod.rs | 2 +- src/interner/display.rs | 26 +- src/interner/mod.rs | 18 +- src/interner/monotype.rs | 75 ++--- src/interner/multitype.rs | 61 ++-- src/interner/token.rs | 42 +-- src/interpreter/apply.rs | 157 ++++++---- src/interpreter/context.rs | 24 +- src/interpreter/error.rs | 7 +- src/interpreter/mod.rs | 4 +- src/interpreter/run.rs | 175 +++++++---- src/main.rs | 21 +- src/parse/comment.rs | 20 +- src/parse/context.rs | 39 ++- src/parse/decls.rs | 14 + src/parse/enum_filter.rs | 12 +- src/parse/expression.rs | 127 ++++---- src/parse/{parse.rs => facade.rs} | 57 ++-- src/parse/import.rs | 132 ++++---- src/parse/lexer.rs | 128 ++++---- src/parse/mod.rs | 29 +- src/parse/name.rs | 51 ++-- src/parse/number.rs | 99 +++--- src/parse/placeholder.rs | 26 +- src/parse/sourcefile.rs | 160 +++++----- src/parse/string.rs | 51 ++-- src/pipeline/error/mod.rs | 14 +- src/pipeline/error/module_not_found.rs | 12 +- src/pipeline/error/not_exported.rs | 40 +-- src/pipeline/error/parse_error_with_path.rs | 33 +- src/pipeline/error/project_error.rs | 17 +- src/pipeline/error/too_many_supers.rs | 17 +- src/pipeline/error/unexpected_directory.rs | 15 +- src/pipeline/error/visibility_mismatch.rs | 13 +- src/pipeline/file_loader.rs | 96 +++--- src/pipeline/import_abs_path.rs | 32 +- src/pipeline/import_resolution/alias_map.rs | 31 +- .../import_resolution/apply_aliases.rs | 137 +++++---- .../import_resolution/collect_aliases.rs | 117 ++++--- src/pipeline/import_resolution/decls.rs | 6 +- src/pipeline/import_resolution/mod.rs | 4 +- .../import_resolution/resolve_imports.rs | 28 +- src/pipeline/mod.rs | 17 +- src/pipeline/parse_layer.rs | 44 ++- src/pipeline/project_tree/add_prelude.rs | 46 ++- src/pipeline/project_tree/build_tree.rs | 205 +++++++------ .../project_tree/collect_ops/exported_ops.rs | 85 +++--- src/pipeline/project_tree/collect_ops/mod.rs | 6 +- .../project_tree/collect_ops/ops_for.rs | 28 +- src/pipeline/project_tree/const_tree.rs | 58 ++-- src/pipeline/project_tree/mod.rs | 50 ++- .../project_tree/normalize_imports.rs | 92 +++--- src/pipeline/project_tree/parse_file.rs | 37 ++- src/pipeline/project_tree/prefix.rs | 82 +++-- src/pipeline/project_tree/tree.rs | 55 ++-- src/pipeline/source_loader/load_source.rs | 81 ++--- src/pipeline/source_loader/loaded_source.rs | 8 +- src/pipeline/source_loader/mod.rs | 37 ++- src/pipeline/source_loader/preparse.rs | 138 +++++---- src/pipeline/split_name.rs | 14 +- src/representations/ast.rs | 289 ++++++++++-------- src/representations/ast_to_postmacro.rs | 109 +++---- src/representations/interpreted.rs | 81 ++--- src/representations/literal.rs | 24 +- src/representations/location.rs | 28 +- src/representations/mod.rs | 11 +- src/representations/path_set.rs | 46 +-- src/representations/postmacro.rs | 69 +++-- .../postmacro_to_interpreted.rs | 47 +-- src/representations/primitive.rs | 15 +- src/representations/sourcefile.rs | 118 +++---- src/representations/tree.rs | 85 +++--- src/representations/typed.rs | 121 +++++--- src/rule/matcher.rs | 5 +- src/rule/matcher_second/any_match.rs | 25 +- src/rule/matcher_second/build.rs | 129 ++++---- src/rule/matcher_second/mod.rs | 32 +- src/rule/matcher_second/scal_match.rs | 44 +-- src/rule/matcher_second/shared.rs | 90 +++--- src/rule/matcher_second/vec_match.rs | 68 +++-- src/rule/mod.rs | 17 +- src/rule/prepare_rule.rs | 99 +++--- src/rule/repository.rs | 132 ++++---- src/rule/rule_error.rs | 35 ++- src/rule/state.rs | 49 +-- src/rule/update_first_seq.rs | 45 +-- src/rule/vec_attrs.rs | 20 +- src/run_dir.rs | 103 ++++--- src/utils/cache.rs | 61 +--- src/utils/coprefix.rs | 0 src/utils/iter.rs | 32 +- src/utils/mod.rs | 28 +- src/utils/print_nname.rs | 14 +- src/utils/protomap.rs | 172 ----------- src/utils/pushed.rs | 4 +- src/utils/replace_first.rs | 22 +- src/utils/side.rs | 45 ++- src/utils/string_from_charset.rs | 13 +- src/utils/substack.rs | 74 ++--- src/utils/unwrap_or.rs | 13 +- src/utils/variant.rs | 19 -- src/utils/xloop.rs | 47 +-- 144 files changed, 3734 insertions(+), 3243 deletions(-) delete mode 100644 notes.md create mode 100644 notes/macros.md create mode 100644 rustfmt.toml create mode 100644 src/parse/decls.rs rename src/parse/{parse.rs => facade.rs} (54%) delete mode 100644 src/utils/coprefix.rs delete mode 100644 src/utils/protomap.rs delete mode 100644 src/utils/variant.rs diff --git a/README.md b/README.md index 5a3cd47..bea6990 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,29 @@ -All you need to run the project is a nighly rust toolchain. Go to one of the folders within `examples` and run +Orchid is an experimental lazy, pure functional programming language designed to be embeddable in a Rust application for scripting. + +# Usage + +TODO + +I need to write a few articles explaining individual fragments of the language, and accurately document everything. Writing tutorials at this stage is not really worth it. + +# Design + +The execution model is lambda calculus, with call by name and copy tracking to avoid repeating steps. This leads to the minimal number of necessary reduction steps. + +To make the syntax more intuitive, completely hygienic macros can be used which are applied to expressions after all imports are resolved and all tokens are namespaced both in the macro and in the referencing expression. + +Namespaces are inspired by Rust modules and ES6. Every file and directory is implicitly a public module. Files can `export` names of constants or namespaces, all names in a substitution rule, or explicitly export some names. Names are implicitly created when they're referenced. `import` syntax is similar to Rust except with `(` parentheses `)` and no semicolons. + +# Try it out + +The project uses the nighly rust toolchain. Go to one of the folders within `examples` and run ```sh cargo run -- -p . -``` \ No newline at end of file +``` + +you can try modifying the examples, but error reporting for the time being is pretty terrible. + +# Contribution + +All contributions are welcome. For the time being, use the issue tracker to discuss ideas. \ No newline at end of file diff --git a/notes.md b/notes.md deleted file mode 100644 index e69de29..0000000 diff --git a/notes/macros.md b/notes/macros.md new file mode 100644 index 0000000..c6821c9 --- /dev/null +++ b/notes/macros.md @@ -0,0 +1,27 @@ +Substitution rules are represented by the `=prio=>` arrow where `prio` is a floating point literal. They are tested form highest priority to lowest. When one matches, the substitution is executed and all macros are re-checked from the beginning. + +Wildcards either match a single token `$foo`, at least one token `...$bar` or any number of tokens `..$baz`. The latter two forms can also have an unsigned integer growth priority `...$quz:3` which influences their order in deciding the precedence of matches. + +# Match priority + +When a macro matches the program more than once, matches in ancestors take precedence. If there's no direct ancestry, the left branch takes precedence. When two matches are found in the same token sequence, the order is determined by the number of tokens allocated to the highest priority variable length wildcard where this number differs. + +Variable length placeholders outside parens always have a higher priority than those inside. On the same level, the numbers decide the priority. In case of a tie, the placeholder to the left is preferred. + +# Writing macros + +Macro programs are systems consisting of substitution rules which reinterpret the tree produced by the previous rules. A good example for how this works can be found in ../examples/list-processing/fn.orc + +Priority numbers are written in hexadecimal normal form to avoid precision bugs, and they're divided into bands throughout the f64 value range: (the numbers represent powers of 16) + +- **32-39**: Binary operators, in inverse priority order +- **80-87**: Expression-like structures such as if/then/else +- **128-135**: Anything that creates lambdas + Programs triggered by a lower priority pattern than this can assume that all names are correctly bound +- **200**: Aliases extracted for readability + The user-accessible entry points of all macro programs must be lower priority than this, so any arbitrary syntax can be extracted into an alias with no side effects +- **224-231**: Integration; documented hooks exposed by a macro package to allow third party packages to extend its functionality + The `statement` pattern produced by `do{}` blocks and matched by `let` and `cps` is a good example of this. When any of these are triggered, all macro programs are in a documented state. +- **248-255**: Transitional states within macro programs get the highest priority + +The numbers are arbitrary and up for debate. These are just the ones I came up with when writing the examples. \ No newline at end of file diff --git a/orchid.code-workspace b/orchid.code-workspace index 1e0c1fc..e18e705 100644 --- a/orchid.code-workspace +++ b/orchid.code-workspace @@ -24,7 +24,7 @@ "editor.formatOnType": true, }, "[rust]": { - "editor.rulers": [74] + "editor.rulers": [80], }, "rust-analyzer.showUnlinkedFileNotification": false, "rust-analyzer.checkOnSave": true, diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..62f1d15 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,34 @@ +# meta +format_code_in_doc_comments = true +unstable_features = true +version = "Two" + +# space +tab_spaces = 2 +max_width = 80 +error_on_line_overflow = true +format_macro_matchers = true +newline_style = "Unix" +normalize_comments = true +wrap_comments = true +overflow_delimited_expr = true +single_line_if_else_max_width = 50 +use_small_heuristics = "Max" + +# literals +hex_literal_case = "Lower" +format_strings = true + +# delimiters +match_arm_blocks = false +match_block_trailing_comma = true + +# structure +condense_wildcard_suffixes = true +use_field_init_shorthand = true +use_try_shorthand = true + +# Modules +group_imports = "StdExternalCrate" +imports_granularity = "Module" +reorder_modules = true \ No newline at end of file diff --git a/src/cli.rs b/src/cli.rs index e80b132..e961ed3 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,19 +1,22 @@ -use std::{fmt::Display, io::{stdin, BufRead, stdout, Write}}; +use std::fmt::Display; +use std::io::{stdin, stdout, BufRead, Write}; pub fn prompt( prompt: &str, default: T, - mut try_cast: impl FnMut(String) -> Result + mut try_cast: impl FnMut(String) -> Result, ) -> T { loop { print!("{prompt} ({default}): "); stdout().lock().flush().unwrap(); let mut input = String::with_capacity(100); stdin().lock().read_line(&mut input).unwrap(); - if input.is_empty() {return default} + if input.is_empty() { + return default; + } match try_cast(input) { Ok(t) => return t, - Err(e) => println!("Error: {e}") + Err(e) => println!("Error: {e}"), } } -} \ No newline at end of file +} diff --git a/src/external/assertion_error.rs b/src/external/assertion_error.rs index 5ea4e03..85e554b 100644 --- a/src/external/assertion_error.rs +++ b/src/external/assertion_error.rs @@ -1,23 +1,27 @@ -use std::rc::Rc; use std::fmt::Display; +use std::rc::Rc; use crate::foreign::ExternError; use crate::representations::interpreted::ExprInst; - +/// Some expectation (usually about the argument types of a function) did not +/// hold. #[derive(Clone)] -pub struct AssertionError{ +pub struct AssertionError { pub value: ExprInst, pub assertion: &'static str, } impl AssertionError { - pub fn fail(value: ExprInst, assertion: &'static str) -> Result> { - return Err(Self { value, assertion }.into_extern()) + pub fn fail( + value: ExprInst, + assertion: &'static str, + ) -> Result> { + return Err(Self { value, assertion }.into_extern()); } pub fn ext(value: ExprInst, assertion: &'static str) -> Rc { - return Self { value, assertion }.into_extern() + return Self { value, assertion }.into_extern(); } } @@ -27,4 +31,4 @@ impl Display for AssertionError { } } -impl ExternError for AssertionError{} \ No newline at end of file +impl ExternError for AssertionError {} diff --git a/src/external/bool/boolean.rs b/src/external/bool/boolean.rs index 366d56c..8726db9 100644 --- a/src/external/bool/boolean.rs +++ b/src/external/bool/boolean.rs @@ -1,12 +1,18 @@ -use crate::foreign::Atom; -use crate::representations::{interpreted::{Clause, ExprInst}, Primitive}; use crate::atomic_inert; +use crate::foreign::Atom; +use crate::representations::interpreted::{Clause, ExprInst}; +use crate::representations::Primitive; +/// Booleans exposed to Orchid #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Boolean(pub bool); atomic_inert!(Boolean); -impl From for Boolean { fn from(value: bool) -> Self { Self(value) } } +impl From for Boolean { + fn from(value: bool) -> Self { + Self(value) + } +} impl TryFrom for Boolean { type Error = (); @@ -15,9 +21,9 @@ impl TryFrom for Boolean { let expr = value.expr(); if let Clause::P(Primitive::Atom(Atom(a))) = &expr.clause { if let Some(b) = a.as_any().downcast_ref::() { - return Ok(*b) + return Ok(*b); } } - return Err(()) + Err(()) } -} \ No newline at end of file +} diff --git a/src/external/bool/equals.rs b/src/external/bool/equals.rs index f0281e1..3b9fd57 100644 --- a/src/external/bool/equals.rs +++ b/src/external/bool/equals.rs @@ -1,48 +1,54 @@ use std::fmt::Debug; -use crate::external::litconv::with_lit; -use crate::representations::{interpreted::ExprInst, Literal}; -use crate::{atomic_impl, atomic_redirect, externfn_impl}; - use super::super::assertion_error::AssertionError; use super::boolean::Boolean; +use crate::external::litconv::with_lit; +use crate::representations::interpreted::ExprInst; +use crate::representations::Literal; +use crate::{atomic_impl, atomic_redirect, externfn_impl}; -/// Equals function +/// Compares the inner values if /// +/// - both values are char, +/// - both are string, +/// - both are either uint or num +/// /// Next state: [Equals1] #[derive(Clone)] pub struct Equals2; -externfn_impl!(Equals2, |_: &Self, x: ExprInst| Ok(Equals1{x})); +externfn_impl!(Equals2, |_: &Self, x: ExprInst| Ok(Equals1 { x })); -/// Partially applied Equals function -/// /// Prev state: [Equals2]; Next state: [Equals0] #[derive(Debug, Clone)] -pub struct Equals1{ x: ExprInst } +pub struct Equals1 { + x: ExprInst, +} atomic_redirect!(Equals1, x); atomic_impl!(Equals1); externfn_impl!(Equals1, |this: &Self, x: ExprInst| { - with_lit(&this.x, |l| Ok(Equals0{ a: l.clone(), x })) + with_lit(&this.x, |l| Ok(Equals0 { a: l.clone(), x })) }); -/// Fully applied Equals function. -/// /// Prev state: [Equals1] - #[derive(Debug, Clone)] -pub struct Equals0 { a: Literal, x: ExprInst } +pub struct Equals0 { + a: Literal, + x: ExprInst, +} atomic_redirect!(Equals0, x); -atomic_impl!(Equals0, |Self{ a, x }: &Self, _| { - let eqls = with_lit(x, |l| Ok(match (a, l) { - (Literal::Char(c1), Literal::Char(c2)) => c1 == c2, - (Literal::Num(n1), Literal::Num(n2)) => n1 == n2, - (Literal::Str(s1), Literal::Str(s2)) => s1 == s2, - (Literal::Uint(i1), Literal::Uint(i2)) => i1 == i2, - (Literal::Num(n1), Literal::Uint(u1)) => *n1 == (*u1 as f64), - (Literal::Uint(u1), Literal::Num(n1)) => *n1 == (*u1 as f64), - (_, _) => AssertionError::fail(x.clone(), "the expected type")?, - }))?; +atomic_impl!(Equals0, |Self { a, x }: &Self, _| { + let eqls = with_lit(x, |l| { + Ok(match (a, l) { + (Literal::Char(c1), Literal::Char(c2)) => c1 == c2, + (Literal::Num(n1), Literal::Num(n2)) => n1 == n2, + (Literal::Str(s1), Literal::Str(s2)) => s1 == s2, + (Literal::Uint(i1), Literal::Uint(i2)) => i1 == i2, + (Literal::Num(n1), Literal::Uint(u1)) => *n1 == (*u1 as f64), + (Literal::Uint(u1), Literal::Num(n1)) => *n1 == (*u1 as f64), + (..) => AssertionError::fail(x.clone(), "the expected type")?, + }) + })?; Ok(Boolean::from(eqls).to_atom_cls()) }); diff --git a/src/external/bool/ifthenelse.rs b/src/external/bool/ifthenelse.rs index db920a0..df4ce03 100644 --- a/src/external/bool/ifthenelse.rs +++ b/src/external/bool/ifthenelse.rs @@ -1,41 +1,46 @@ use std::fmt::Debug; use std::rc::Rc; +use super::Boolean; use crate::external::assertion_error::AssertionError; -use crate::representations::{PathSet, interpreted::{Clause, ExprInst}}; +use crate::representations::interpreted::{Clause, ExprInst}; +use crate::representations::PathSet; use crate::{atomic_impl, atomic_redirect, externfn_impl}; -use super::Boolean; - -/// IfThenElse function -/// +/// Takes a boolean and two branches, runs the first if the bool is true, the +/// second if it's false. +/// /// Next state: [IfThenElse0] - #[derive(Clone)] pub struct IfThenElse1; -externfn_impl!(IfThenElse1, |_: &Self, x: ExprInst| Ok(IfThenElse0{x})); - -/// Partially applied IfThenElse function -/// -/// Prev state: [IfThenElse1]; Next state: [IfThenElse0] +externfn_impl!(IfThenElse1, |_: &Self, x: ExprInst| Ok(IfThenElse0 { x })); +/// Prev state: [IfThenElse1] #[derive(Debug, Clone)] -pub struct IfThenElse0{ x: ExprInst } +pub struct IfThenElse0 { + x: ExprInst, +} atomic_redirect!(IfThenElse0, x); atomic_impl!(IfThenElse0, |this: &Self, _| { - let Boolean(b) = this.x.clone().try_into() + let Boolean(b) = this + .x + .clone() + .try_into() .map_err(|_| AssertionError::ext(this.x.clone(), "a boolean"))?; - Ok(if b { Clause::Lambda { - args: Some(PathSet { steps: Rc::new(vec![]), next: None }), - body: Clause::Lambda { - args: None, - body: Clause::LambdaArg.wrap() - }.wrap() - }} else { Clause::Lambda { - args: None, - body: Clause::Lambda { + Ok(if b { + Clause::Lambda { args: Some(PathSet { steps: Rc::new(vec![]), next: None }), - body: Clause::LambdaArg.wrap() - }.wrap() - }}) -}); \ No newline at end of file + body: Clause::Lambda { args: None, body: Clause::LambdaArg.wrap() } + .wrap(), + } + } else { + Clause::Lambda { + args: None, + body: Clause::Lambda { + args: Some(PathSet { steps: Rc::new(vec![]), next: None }), + body: Clause::LambdaArg.wrap(), + } + .wrap(), + } + }) +}); diff --git a/src/external/bool/mod.rs b/src/external/bool/mod.rs index 31aad3d..13e288b 100644 --- a/src/external/bool/mod.rs +++ b/src/external/bool/mod.rs @@ -1,16 +1,16 @@ -mod equals; mod boolean; +mod equals; mod ifthenelse; pub use boolean::Boolean; -use crate::{pipeline::ConstTree, interner::Interner}; - +use crate::interner::Interner; +use crate::pipeline::ConstTree; pub fn bool(i: &Interner) -> ConstTree { ConstTree::tree([ (i.i("ifthenelse"), ConstTree::xfn(ifthenelse::IfThenElse1)), (i.i("equals"), ConstTree::xfn(equals::Equals2)), (i.i("true"), ConstTree::atom(Boolean(true))), - (i.i("false"), ConstTree::atom(Boolean(false))) + (i.i("false"), ConstTree::atom(Boolean(false))), ]) -} \ No newline at end of file +} diff --git a/src/external/conv/mod.rs b/src/external/conv/mod.rs index c272e1c..e11f76a 100644 --- a/src/external/conv/mod.rs +++ b/src/external/conv/mod.rs @@ -1,13 +1,14 @@ -use crate::{interner::Interner, pipeline::ConstTree}; +use crate::interner::Interner; +use crate::pipeline::ConstTree; -mod to_string; mod parse_float; mod parse_uint; +mod to_string; pub fn conv(i: &Interner) -> ConstTree { ConstTree::tree([ (i.i("parse_float"), ConstTree::xfn(parse_float::ParseFloat1)), (i.i("parse_uint"), ConstTree::xfn(parse_uint::ParseUint1)), - (i.i("to_string"), ConstTree::xfn(to_string::ToString1)) + (i.i("to_string"), ConstTree::xfn(to_string::ToString1)), ]) -} \ No newline at end of file +} diff --git a/src/external/conv/parse_float.rs b/src/external/conv/parse_float.rs index 639f2a9..9048ec6 100644 --- a/src/external/conv/parse_float.rs +++ b/src/external/conv/parse_float.rs @@ -1,41 +1,43 @@ +use std::fmt::Debug; use chumsky::Parser; -use std::fmt::Debug; - use super::super::assertion_error::AssertionError; use crate::external::litconv::with_lit; use crate::parse::float_parser; -use crate::representations::{interpreted::ExprInst, Literal}; +use crate::representations::interpreted::ExprInst; +use crate::representations::Literal; use crate::{atomic_impl, atomic_redirect, externfn_impl}; -/// ParseFloat a number -/// +/// parse a number. Accepts the same syntax Orchid does +/// /// Next state: [ParseFloat0] - #[derive(Clone)] pub struct ParseFloat1; -externfn_impl!(ParseFloat1, |_: &Self, x: ExprInst| Ok(ParseFloat0{x})); +externfn_impl!(ParseFloat1, |_: &Self, x: ExprInst| Ok(ParseFloat0 { x })); -/// Applied to_string function -/// /// Prev state: [ParseFloat1] - #[derive(Debug, Clone)] -pub struct ParseFloat0{ x: ExprInst } +pub struct ParseFloat0 { + x: ExprInst, +} atomic_redirect!(ParseFloat0, x); -atomic_impl!(ParseFloat0, |Self{ x }: &Self, _| { - let number = with_lit(x, |l| Ok(match l { - Literal::Str(s) => { - let parser = float_parser(); - parser.parse(s.as_str()) - .map_err(|_| AssertionError::ext(x.clone(), "cannot be parsed into a float"))? - } - Literal::Num(n) => *n, - Literal::Uint(i) => (*i as u32).into(), - Literal::Char(char) => char.to_digit(10) - .ok_or(AssertionError::ext(x.clone(), "is not a decimal digit"))? - .into() - }))?; +atomic_impl!(ParseFloat0, |Self { x }: &Self, _| { + let number = with_lit(x, |l| { + Ok(match l { + Literal::Str(s) => { + let parser = float_parser(); + parser.parse(s.as_str()).map_err(|_| { + AssertionError::ext(x.clone(), "cannot be parsed into a float") + })? + }, + Literal::Num(n) => *n, + Literal::Uint(i) => (*i as u32).into(), + Literal::Char(char) => char + .to_digit(10) + .ok_or(AssertionError::ext(x.clone(), "is not a decimal digit"))? + .into(), + }) + })?; Ok(number.into()) -}); \ No newline at end of file +}); diff --git a/src/external/conv/parse_uint.rs b/src/external/conv/parse_uint.rs index 2e848c3..029a20e 100644 --- a/src/external/conv/parse_uint.rs +++ b/src/external/conv/parse_uint.rs @@ -1,40 +1,47 @@ +use std::fmt::Debug; use chumsky::Parser; -use std::fmt::Debug; - -use crate::external::{litconv::with_lit, assertion_error::AssertionError}; -use crate::representations::{interpreted::ExprInst, Literal}; -use crate::{atomic_impl, atomic_redirect, externfn_impl}; +use crate::external::assertion_error::AssertionError; +use crate::external::litconv::with_lit; use crate::parse::int_parser; +use crate::representations::interpreted::ExprInst; +use crate::representations::Literal; +use crate::{atomic_impl, atomic_redirect, externfn_impl}; -/// Parse a number -/// +/// Parse an unsigned integer. Accepts the same formats Orchid does. If the +/// input is a number, floors it. +/// /// Next state: [ParseUint0] - #[derive(Clone)] pub struct ParseUint1; -externfn_impl!(ParseUint1, |_: &Self, x: ExprInst| Ok(ParseUint0{x})); +externfn_impl!(ParseUint1, |_: &Self, x: ExprInst| Ok(ParseUint0 { x })); -/// Applied ParseUint function -/// /// Prev state: [ParseUint1] - #[derive(Debug, Clone)] -pub struct ParseUint0{ x: ExprInst } +pub struct ParseUint0 { + x: ExprInst, +} atomic_redirect!(ParseUint0, x); -atomic_impl!(ParseUint0, |Self{ x }: &Self, _| { - let uint = with_lit(x, |l| Ok(match l { - Literal::Str(s) => { - let parser = int_parser(); - parser.parse(s.as_str()) - .map_err(|_| AssertionError::ext(x.clone(), "cannot be parsed into an unsigned int"))? - } - Literal::Num(n) => n.floor() as u64, - Literal::Uint(i) => *i, - Literal::Char(char) => char.to_digit(10) - .ok_or(AssertionError::ext(x.clone(), "is not a decimal digit"))? - .into() - }))?; +atomic_impl!(ParseUint0, |Self { x }: &Self, _| { + let uint = with_lit(x, |l| { + Ok(match l { + Literal::Str(s) => { + let parser = int_parser(); + parser.parse(s.as_str()).map_err(|_| { + AssertionError::ext( + x.clone(), + "cannot be parsed into an unsigned int", + ) + })? + }, + Literal::Num(n) => n.floor() as u64, + Literal::Uint(i) => *i, + Literal::Char(char) => char + .to_digit(10) + .ok_or(AssertionError::ext(x.clone(), "is not a decimal digit"))? + .into(), + }) + })?; Ok(uint.into()) -}); \ No newline at end of file +}); diff --git a/src/external/conv/to_string.rs b/src/external/conv/to_string.rs index 731129e..f9aa89d 100644 --- a/src/external/conv/to_string.rs +++ b/src/external/conv/to_string.rs @@ -1,31 +1,32 @@ - use std::fmt::Debug; use crate::external::litconv::with_lit; -use crate::representations::{interpreted::ExprInst, Literal}; +use crate::representations::interpreted::ExprInst; +use crate::representations::Literal; use crate::{atomic_impl, atomic_redirect, externfn_impl}; -/// ToString a clause -/// +/// Convert a literal to a string using Rust's conversions for floats, chars and +/// uints respectively +/// /// Next state: [ToString0] - #[derive(Clone)] pub struct ToString1; -externfn_impl!(ToString1, |_: &Self, x: ExprInst| Ok(ToString0{x})); +externfn_impl!(ToString1, |_: &Self, x: ExprInst| Ok(ToString0 { x })); -/// Applied ToString function -/// /// Prev state: [ToString1] - #[derive(Debug, Clone)] -pub struct ToString0{ x: ExprInst } +pub struct ToString0 { + x: ExprInst, +} atomic_redirect!(ToString0, x); -atomic_impl!(ToString0, |Self{ x }: &Self, _| { - let string = with_lit(x, |l| Ok(match l { - Literal::Char(c) => c.to_string(), - Literal::Uint(i) => i.to_string(), - Literal::Num(n) => n.to_string(), - Literal::Str(s) => s.clone() - }))?; +atomic_impl!(ToString0, |Self { x }: &Self, _| { + let string = with_lit(x, |l| { + Ok(match l { + Literal::Char(c) => c.to_string(), + Literal::Uint(i) => i.to_string(), + Literal::Num(n) => n.to_string(), + Literal::Str(s) => s.clone(), + }) + })?; Ok(string.into()) }); diff --git a/src/external/cpsio/debug.rs b/src/external/cpsio/debug.rs index cba1bff..b309fa2 100644 --- a/src/external/cpsio/debug.rs +++ b/src/external/cpsio/debug.rs @@ -3,31 +3,30 @@ use std::fmt::Debug; use crate::foreign::{Atomic, AtomicReturn}; use crate::interner::InternedDisplay; use crate::interpreter::Context; -use crate::{externfn_impl, atomic_defaults}; use crate::representations::interpreted::ExprInst; +use crate::{atomic_defaults, externfn_impl}; -/// Debug function -/// -/// Next state: [Debug0] - +/// Print and return whatever expression is in the argument without normalizing +/// it. +/// +/// Next state: [Debug1] #[derive(Clone)] pub struct Debug2; -externfn_impl!(Debug2, |_: &Self, x: ExprInst| Ok(Debug1{x})); - -/// Partially applied Print function -/// -/// Prev state: [Debug1] +externfn_impl!(Debug2, |_: &Self, x: ExprInst| Ok(Debug1 { x })); +/// Prev state: [Debug2] #[derive(Debug, Clone)] -pub struct Debug1{ x: ExprInst } +pub struct Debug1 { + x: ExprInst, +} impl Atomic for Debug1 { atomic_defaults!(); fn run(&self, ctx: Context) -> crate::foreign::AtomicResult { - println!("{}", self.x.bundle(&ctx.interner)); - Ok(AtomicReturn{ + println!("{}", self.x.bundle(ctx.interner)); + Ok(AtomicReturn { clause: self.x.expr().clause.clone(), gas: ctx.gas.map(|g| g - 1), - inert: false + inert: false, }) } -} \ No newline at end of file +} diff --git a/src/external/cpsio/io.rs b/src/external/cpsio/io.rs index a59f78d..4597d19 100644 --- a/src/external/cpsio/io.rs +++ b/src/external/cpsio/io.rs @@ -1,35 +1,40 @@ -use std::io::{self, Write, stdin}; +use std::io::{self, Write}; -use crate::{representations::{interpreted::{ExprInst, Clause}, Primitive, Literal}, atomic_inert, interpreter::{HandlerParm, HandlerRes}, unwrap_or, external::runtime_error::RuntimeError}; +use crate::external::runtime_error::RuntimeError; +use crate::interpreter::{HandlerParm, HandlerRes}; +use crate::representations::interpreted::{Clause, ExprInst}; +use crate::representations::{Literal, Primitive}; +use crate::{atomic_inert, unwrap_or}; +/// An IO command to be handled by the host application. #[derive(Clone, Debug)] pub enum IO { Print(String, ExprInst), - Readline(ExprInst) + Readline(ExprInst), } atomic_inert!(IO); +/// Default xommand handler for IO actions pub fn handle(effect: HandlerParm) -> HandlerRes { - let io: &IO = unwrap_or!( - effect.as_any().downcast_ref(); - return Err(effect) - ); - match io { + // Downcast command + let io: &IO = unwrap_or!(effect.as_any().downcast_ref(); Err(effect)?); + // Interpret and execute + Ok(match io { IO::Print(str, cont) => { print!("{}", str); - io::stdout().flush().unwrap(); - Ok(Ok(cont.clone())) + io::stdout() + .flush() + .map_err(|e| RuntimeError::ext(e.to_string(), "writing to stdout"))?; + cont.clone() }, IO::Readline(cont) => { let mut buf = String::new(); - if let Err(e) = stdin().read_line(&mut buf) { - return Ok(Err(RuntimeError::ext(e.to_string(), "reading from stdin"))); - } + io::stdin() + .read_line(&mut buf) + .map_err(|e| RuntimeError::ext(e.to_string(), "reading from stdin"))?; buf.pop(); - Ok(Ok(Clause::Apply { - f: cont.clone(), - x: Clause::P(Primitive::Literal(Literal::Str(buf))).wrap() - }.wrap())) - } - } -} \ No newline at end of file + let x = Clause::P(Primitive::Literal(Literal::Str(buf))).wrap(); + Clause::Apply { f: cont.clone(), x }.wrap() + }, + }) +} diff --git a/src/external/cpsio/mod.rs b/src/external/cpsio/mod.rs index 14e4c72..25123ce 100644 --- a/src/external/cpsio/mod.rs +++ b/src/external/cpsio/mod.rs @@ -1,18 +1,19 @@ -use crate::{interner::Interner, pipeline::ConstTree}; +use crate::interner::Interner; +use crate::pipeline::ConstTree; +mod debug; +mod io; +mod panic; mod print; mod readline; -mod debug; -mod panic; -mod io; -pub use io::{IO, handle}; +pub use io::{handle, IO}; pub fn cpsio(i: &Interner) -> ConstTree { ConstTree::tree([ (i.i("print"), ConstTree::xfn(print::Print2)), (i.i("readline"), ConstTree::xfn(readline::Readln2)), (i.i("debug"), ConstTree::xfn(debug::Debug2)), - (i.i("panic"), ConstTree::xfn(panic::Panic1)) + (i.i("panic"), ConstTree::xfn(panic::Panic1)), ]) -} \ No newline at end of file +} diff --git a/src/external/cpsio/panic.rs b/src/external/cpsio/panic.rs index b845f8a..8c5b8c6 100644 --- a/src/external/cpsio/panic.rs +++ b/src/external/cpsio/panic.rs @@ -1,23 +1,29 @@ use std::fmt::Display; -use crate::{atomic_impl, atomic_redirect, externfn_impl}; use crate::external::litconv::with_str; -use crate::representations::interpreted::ExprInst; use crate::foreign::ExternError; +use crate::representations::interpreted::ExprInst; +use crate::{atomic_impl, atomic_redirect, externfn_impl}; +/// Takes a message, returns an [ExternError] unconditionally. +/// +/// Next state: [Panic0] #[derive(Clone)] pub struct Panic1; -externfn_impl!(Panic1, |_: &Self, x: ExprInst| Ok(Panic0{ x })); +externfn_impl!(Panic1, |_: &Self, x: ExprInst| Ok(Panic0 { x })); +/// Prev state: [Panic1] #[derive(Debug, Clone)] -pub struct Panic0{ x: ExprInst } +pub struct Panic0 { + x: ExprInst, +} atomic_redirect!(Panic0, x); -atomic_impl!(Panic0, |Self{ x }: &Self, _| { - with_str(x, |s| { - Err(OrchidPanic(s.clone()).into_extern()) - }) +atomic_impl!(Panic0, |Self { x }: &Self, _| { + with_str(x, |s| Err(OrchidPanic(s.clone()).into_extern())) }); +/// An unrecoverable error in Orchid land. Of course, because Orchid is lazy, it +/// only applies to the expressions that use the one that generated it. pub struct OrchidPanic(String); impl Display for OrchidPanic { @@ -26,4 +32,4 @@ impl Display for OrchidPanic { } } -impl ExternError for OrchidPanic {} \ No newline at end of file +impl ExternError for OrchidPanic {} diff --git a/src/external/cpsio/print.rs b/src/external/cpsio/print.rs index dcf0a5a..df194ce 100644 --- a/src/external/cpsio/print.rs +++ b/src/external/cpsio/print.rs @@ -1,43 +1,40 @@ use std::fmt::Debug; +use super::io::IO; use crate::external::litconv::with_str; use crate::foreign::{Atomic, AtomicResult, AtomicReturn}; use crate::interpreter::Context; -use crate::{atomic_impl, atomic_redirect, externfn_impl, atomic_defaults}; use crate::representations::interpreted::ExprInst; +use crate::{atomic_defaults, atomic_impl, atomic_redirect, externfn_impl}; -use super::io::IO; - -/// Print function -/// +/// Wrap a string and the continuation into an [IO] event to be evaluated by the +/// embedder. +/// /// Next state: [Print1] - #[derive(Clone)] pub struct Print2; -externfn_impl!(Print2, |_: &Self, x: ExprInst| Ok(Print1{x})); +externfn_impl!(Print2, |_: &Self, x: ExprInst| Ok(Print1 { x })); -/// Partially applied Print function -/// /// Prev state: [Print2]; Next state: [Print0] - #[derive(Debug, Clone)] -pub struct Print1{ x: ExprInst } +pub struct Print1 { + x: ExprInst, +} atomic_redirect!(Print1, x); atomic_impl!(Print1); externfn_impl!(Print1, |this: &Self, x: ExprInst| { - with_str(&this.x, |s| { - Ok(Print0{ s: s.clone(), x }) - }) + with_str(&this.x, |s| Ok(Print0 { s: s.clone(), x })) }); +/// Prev state: [Print1] #[derive(Debug, Clone)] -pub struct Print0{ s: String, x: ExprInst } +pub struct Print0 { + s: String, + x: ExprInst, +} impl Atomic for Print0 { atomic_defaults!(); fn run(&self, ctx: Context) -> AtomicResult { - Ok(AtomicReturn::from_data( - IO::Print(self.s.clone(), self.x.clone()), - ctx - )) + Ok(AtomicReturn::from_data(IO::Print(self.s.clone(), self.x.clone()), ctx)) } -} \ No newline at end of file +} diff --git a/src/external/cpsio/readline.rs b/src/external/cpsio/readline.rs index 3fdc3d2..83ad57f 100644 --- a/src/external/cpsio/readline.rs +++ b/src/external/cpsio/readline.rs @@ -1,32 +1,27 @@ use std::fmt::Debug; +use super::io::IO; use crate::foreign::{Atomic, AtomicResult, AtomicReturn}; use crate::interpreter::Context; -use crate::{externfn_impl, atomic_defaults}; use crate::representations::interpreted::ExprInst; +use crate::{atomic_defaults, externfn_impl}; -use super::io::IO; - -/// Readln function -/// +/// Create an [IO] event that reads a line form standard input and calls the +/// continuation with it. +/// /// Next state: [Readln1] - #[derive(Clone)] pub struct Readln2; -externfn_impl!(Readln2, |_: &Self, x: ExprInst| Ok(Readln1{x})); - -/// Partially applied Readln function -/// -/// Prev state: [Readln2]; Next state: [Readln0] +externfn_impl!(Readln2, |_: &Self, x: ExprInst| Ok(Readln1 { x })); +/// Prev state: [Readln2] #[derive(Debug, Clone)] -pub struct Readln1{ x: ExprInst } +pub struct Readln1 { + x: ExprInst, +} impl Atomic for Readln1 { atomic_defaults!(); fn run(&self, ctx: Context) -> AtomicResult { - Ok(AtomicReturn::from_data( - IO::Readline(self.x.clone()), - ctx - )) + Ok(AtomicReturn::from_data(IO::Readline(self.x.clone()), ctx)) } -} \ No newline at end of file +} diff --git a/src/external/litconv.rs b/src/external/litconv.rs index 7f24de6..34d563b 100644 --- a/src/external/litconv.rs +++ b/src/external/litconv.rs @@ -1,34 +1,45 @@ use std::rc::Rc; -use crate::foreign::ExternError; use crate::external::assertion_error::AssertionError; +use crate::foreign::ExternError; use crate::representations::interpreted::ExprInst; use crate::representations::Literal; -pub fn with_lit(x: &ExprInst, - predicate: impl FnOnce(&Literal) -> Result> +/// Tries to cast the [ExprInst] as a [Literal], calls the provided function on +/// it if successful. Returns a generic [AssertionError] if not. +pub fn with_lit( + x: &ExprInst, + predicate: impl FnOnce(&Literal) -> Result>, ) -> Result> { x.with_literal(predicate) .map_err(|()| AssertionError::ext(x.clone(), "a literal value")) .and_then(|r| r) } -pub fn with_str(x: &ExprInst, - predicate: impl FnOnce(&String) -> Result> +/// Like [with_lit] but also unwraps [Literal::Str] +pub fn with_str( + x: &ExprInst, + predicate: impl FnOnce(&String) -> Result>, ) -> Result> { with_lit(x, |l| { - if let Literal::Str(s) = l {predicate(&s)} else { + if let Literal::Str(s) = l { + predicate(s) + } else { AssertionError::fail(x.clone(), "a string")? } }) } -pub fn with_uint(x: &ExprInst, - predicate: impl FnOnce(u64) -> Result> +/// Like [with_lit] but also unwraps [Literal::Uint] +pub fn with_uint( + x: &ExprInst, + predicate: impl FnOnce(u64) -> Result>, ) -> Result> { with_lit(x, |l| { - if let Literal::Uint(u) = l {predicate(*u)} else { + if let Literal::Uint(u) = l { + predicate(*u) + } else { AssertionError::fail(x.clone(), "an uint")? } }) -} \ No newline at end of file +} diff --git a/src/external/mod.rs b/src/external/mod.rs index e9b1dc2..e5f610b 100644 --- a/src/external/mod.rs +++ b/src/external/mod.rs @@ -1,11 +1,11 @@ -mod num; mod assertion_error; -pub mod std; -mod conv; -mod str; -mod cpsio; -mod runtime_error; mod bool; +mod conv; +mod cpsio; mod litconv; +mod num; +mod runtime_error; +pub mod std; +mod str; -pub use cpsio::{IO, handle}; \ No newline at end of file +pub use cpsio::{handle, IO}; diff --git a/src/external/num/mod.rs b/src/external/num/mod.rs index db44373..74d80e3 100644 --- a/src/external/num/mod.rs +++ b/src/external/num/mod.rs @@ -2,7 +2,8 @@ mod numeric; pub mod operators; pub use numeric::Numeric; -use crate::{interner::Interner, pipeline::ConstTree}; +use crate::interner::Interner; +use crate::pipeline::ConstTree; pub fn num(i: &Interner) -> ConstTree { ConstTree::tree([ @@ -10,6 +11,6 @@ pub fn num(i: &Interner) -> ConstTree { (i.i("subtract"), ConstTree::xfn(operators::subtract::Subtract2)), (i.i("multiply"), ConstTree::xfn(operators::multiply::Multiply2)), (i.i("divide"), ConstTree::xfn(operators::divide::Divide2)), - (i.i("remainder"), ConstTree::xfn(operators::remainder::Remainder2)) + (i.i("remainder"), ConstTree::xfn(operators::remainder::Remainder2)), ]) -} \ No newline at end of file +} diff --git a/src/external/num/numeric.rs b/src/external/num/numeric.rs index 337f490..76de87d 100644 --- a/src/external/num/numeric.rs +++ b/src/external/num/numeric.rs @@ -1,4 +1,4 @@ -use std::ops::{Add, Sub, Mul, Div, Rem}; +use std::ops::{Add, Div, Mul, Rem, Sub}; use std::rc::Rc; use ordered_float::NotNan; @@ -6,21 +6,21 @@ use ordered_float::NotNan; use crate::external::assertion_error::AssertionError; use crate::external::litconv::with_lit; use crate::foreign::ExternError; -use crate::representations::Literal; -use crate::representations::Primitive; use crate::representations::interpreted::{Clause, ExprInst}; +use crate::representations::{Literal, Primitive}; +/// A number, either floating point or unsigned int, visible to Orchid. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Numeric { Uint(u64), - Num(NotNan) + Num(NotNan), } impl Numeric { /// Wrap a f64 in a Numeric - /// + /// /// # Panics - /// + /// /// if the value is NaN or Infinity.try_into() fn num>(value: T) -> Self { let f = value.into(); @@ -36,9 +36,9 @@ impl Add for Numeric { match (self, rhs) { (Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a + b), (Numeric::Num(a), Numeric::Num(b)) => Numeric::num(a + b), - (Numeric::Uint(a), Numeric::Num(b)) | - (Numeric::Num(b), Numeric::Uint(a)) - => Numeric::num::(a as f64 + *b) + (Numeric::Uint(a), Numeric::Num(b)) + | (Numeric::Num(b), Numeric::Uint(a)) => + Numeric::num::(a as f64 + *b), } } } @@ -49,11 +49,10 @@ impl Sub for Numeric { fn sub(self, rhs: Self) -> Self::Output { match (self, rhs) { (Numeric::Uint(a), Numeric::Uint(b)) if b <= a => Numeric::Uint(a - b), - (Numeric::Uint(a), Numeric::Uint(b)) - => Numeric::num(a as f64 - b as f64), + (Numeric::Uint(a), Numeric::Uint(b)) => Numeric::num(a as f64 - b as f64), (Numeric::Num(a), Numeric::Num(b)) => Numeric::num(a - b), (Numeric::Uint(a), Numeric::Num(b)) => Numeric::num(a as f64 - *b), - (Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(*a - b as f64) + (Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(*a - b as f64), } } } @@ -65,9 +64,9 @@ impl Mul for Numeric { match (self, rhs) { (Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a * b), (Numeric::Num(a), Numeric::Num(b)) => Numeric::num(a * b), - (Numeric::Uint(a), Numeric::Num(b)) | - (Numeric::Num(b), Numeric::Uint(a)) - => Numeric::Num(NotNan::new(a as f64).unwrap() * b) + (Numeric::Uint(a), Numeric::Num(b)) + | (Numeric::Num(b), Numeric::Uint(a)) => + Numeric::Num(NotNan::new(a as f64).unwrap() * b), } } } @@ -90,7 +89,7 @@ impl Rem for Numeric { (Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a % b), (Numeric::Num(a), Numeric::Num(b)) => Numeric::num(a % b), (Numeric::Uint(a), Numeric::Num(b)) => Numeric::num(a as f64 % *b), - (Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(*a % b as f64) + (Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(*a % b as f64), } } } @@ -101,7 +100,7 @@ impl TryFrom for Numeric { with_lit(&value.clone(), |l| match l { Literal::Uint(i) => Ok(Numeric::Uint(*i)), Literal::Num(n) => Ok(Numeric::Num(*n)), - _ => AssertionError::fail(value, "an integer or number")? + _ => AssertionError::fail(value, "an integer or number")?, }) } } @@ -110,7 +109,7 @@ impl From for Clause { fn from(value: Numeric) -> Self { Clause::P(Primitive::Literal(match value { Numeric::Uint(i) => Literal::Uint(i), - Numeric::Num(n) => Literal::Num(n) + Numeric::Num(n) => Literal::Num(n), })) } } @@ -119,16 +118,16 @@ impl From for String { fn from(value: Numeric) -> Self { match value { Numeric::Uint(i) => i.to_string(), - Numeric::Num(n) => n.to_string() + Numeric::Num(n) => n.to_string(), } } } -impl Into for Numeric { - fn into(self) -> f64 { - match self { +impl From for f64 { + fn from(val: Numeric) -> Self { + match val { Numeric::Num(n) => *n, - Numeric::Uint(i) => i as f64 + Numeric::Uint(i) => i as f64, } } -} \ No newline at end of file +} diff --git a/src/external/num/operators/add.rs b/src/external/num/operators/add.rs index c430ede..b893bc3 100644 --- a/src/external/num/operators/add.rs +++ b/src/external/num/operators/add.rs @@ -1,39 +1,36 @@ - -use super::super::Numeric; - use std::fmt::Debug; -use crate::{atomic_impl, atomic_redirect, externfn_impl}; +use super::super::Numeric; use crate::representations::interpreted::ExprInst; +use crate::{atomic_impl, atomic_redirect, externfn_impl}; -/// Add function -/// +/// Adds two numbers +/// /// Next state: [Add1] #[derive(Clone)] pub struct Add2; -externfn_impl!(Add2, |_: &Self, x: ExprInst| Ok(Add1{x})); +externfn_impl!(Add2, |_: &Self, x: ExprInst| Ok(Add1 { x })); -/// Partially applied Add function -/// /// Prev state: [Add2]; Next state: [Add0] #[derive(Debug, Clone)] -pub struct Add1{ x: ExprInst } +pub struct Add1 { + x: ExprInst, +} atomic_redirect!(Add1, x); atomic_impl!(Add1); externfn_impl!(Add1, |this: &Self, x: ExprInst| { let a: Numeric = this.x.clone().try_into()?; - Ok(Add0{ a, x }) + Ok(Add0 { a, x }) }); -/// Fully applied Add function. -/// /// Prev state: [Add1] #[derive(Debug, Clone)] -pub struct Add0 { a: Numeric, x: ExprInst } +pub struct Add0 { + a: Numeric, + x: ExprInst, +} atomic_redirect!(Add0, x); -atomic_impl!(Add0, |Self{ a, x }: &Self, _| { +atomic_impl!(Add0, |Self { a, x }: &Self, _| { let b: Numeric = x.clone().try_into()?; Ok((*a + b).into()) }); - - diff --git a/src/external/num/operators/divide.rs b/src/external/num/operators/divide.rs index 259293f..c8cc703 100644 --- a/src/external/num/operators/divide.rs +++ b/src/external/num/operators/divide.rs @@ -1,40 +1,37 @@ - -use super::super::Numeric; - use std::fmt::Debug; -use crate::{atomic_impl, atomic_redirect, externfn_impl}; +use super::super::Numeric; use crate::representations::interpreted::ExprInst; +use crate::{atomic_impl, atomic_redirect, externfn_impl}; -/// Divide function -/// +/// Divides two numbers +/// /// Next state: [Divide1] #[derive(Clone)] pub struct Divide2; -externfn_impl!(Divide2, |_: &Self, x: ExprInst| Ok(Divide1{x})); +externfn_impl!(Divide2, |_: &Self, x: ExprInst| Ok(Divide1 { x })); -/// Partially applied Divide function -/// /// Prev state: [Divide2]; Next state: [Divide0] - #[derive(Debug, Clone)] -pub struct Divide1{ x: ExprInst } +pub struct Divide1 { + x: ExprInst, +} atomic_redirect!(Divide1, x); atomic_impl!(Divide1); externfn_impl!(Divide1, |this: &Self, x: ExprInst| { let a: Numeric = this.x.clone().try_into()?; - Ok(Divide0{ a, x }) + Ok(Divide0 { a, x }) }); -/// Fully applied Divide function. -/// /// Prev state: [Divide1] - #[derive(Debug, Clone)] -pub struct Divide0 { a: Numeric, x: ExprInst } +pub struct Divide0 { + a: Numeric, + x: ExprInst, +} atomic_redirect!(Divide0, x); -atomic_impl!(Divide0, |Self{ a, x }: &Self, _| { +atomic_impl!(Divide0, |Self { a, x }: &Self, _| { let b: Numeric = x.clone().try_into()?; Ok((*a / b).into()) -}); \ No newline at end of file +}); diff --git a/src/external/num/operators/mod.rs b/src/external/num/operators/mod.rs index f85ed42..ee7fb4f 100644 --- a/src/external/num/operators/mod.rs +++ b/src/external/num/operators/mod.rs @@ -2,4 +2,4 @@ pub mod add; pub mod divide; pub mod multiply; pub mod remainder; -pub mod subtract; \ No newline at end of file +pub mod subtract; diff --git a/src/external/num/operators/multiply.rs b/src/external/num/operators/multiply.rs index 85a75b3..ba4aefe 100644 --- a/src/external/num/operators/multiply.rs +++ b/src/external/num/operators/multiply.rs @@ -1,40 +1,36 @@ - -use super::super::Numeric; - use std::fmt::Debug; -use crate::{atomic_impl, atomic_redirect, externfn_impl}; +use super::super::Numeric; use crate::representations::interpreted::ExprInst; +use crate::{atomic_impl, atomic_redirect, externfn_impl}; -/// Multiply function -/// +/// Multiplies two numbers +/// /// Next state: [Multiply1] - #[derive(Clone)] pub struct Multiply2; -externfn_impl!(Multiply2, |_: &Self, x: ExprInst| Ok(Multiply1{x})); +externfn_impl!(Multiply2, |_: &Self, x: ExprInst| Ok(Multiply1 { x })); -/// Partially applied Multiply function -/// /// Prev state: [Multiply2]; Next state: [Multiply0] - #[derive(Debug, Clone)] -pub struct Multiply1{ x: ExprInst } +pub struct Multiply1 { + x: ExprInst, +} atomic_redirect!(Multiply1, x); atomic_impl!(Multiply1); externfn_impl!(Multiply1, |this: &Self, x: ExprInst| { let a: Numeric = this.x.clone().try_into()?; - Ok(Multiply0{ a, x }) + Ok(Multiply0 { a, x }) }); -/// Fully applied Multiply function. -/// /// Prev state: [Multiply1] - #[derive(Debug, Clone)] -pub struct Multiply0 { a: Numeric, x: ExprInst } +pub struct Multiply0 { + a: Numeric, + x: ExprInst, +} atomic_redirect!(Multiply0, x); -atomic_impl!(Multiply0, |Self{ a, x }: &Self, _| { +atomic_impl!(Multiply0, |Self { a, x }: &Self, _| { let b: Numeric = x.clone().try_into()?; Ok((*a * b).into()) -}); \ No newline at end of file +}); diff --git a/src/external/num/operators/remainder.rs b/src/external/num/operators/remainder.rs index cf3621d..736bd43 100644 --- a/src/external/num/operators/remainder.rs +++ b/src/external/num/operators/remainder.rs @@ -1,40 +1,36 @@ - -use super::super::Numeric; - use std::fmt::Debug; -use crate::{atomic_impl, atomic_redirect, externfn_impl}; +use super::super::Numeric; use crate::representations::interpreted::ExprInst; +use crate::{atomic_impl, atomic_redirect, externfn_impl}; -/// Remainder function -/// +/// Takes the modulus of two numbers. +/// /// Next state: [Remainder1] - #[derive(Clone)] pub struct Remainder2; -externfn_impl!(Remainder2, |_: &Self, x: ExprInst| Ok(Remainder1{x})); +externfn_impl!(Remainder2, |_: &Self, x: ExprInst| Ok(Remainder1 { x })); -/// Partially applied Remainder function -/// /// Prev state: [Remainder2]; Next state: [Remainder0] - #[derive(Debug, Clone)] -pub struct Remainder1{ x: ExprInst } +pub struct Remainder1 { + x: ExprInst, +} atomic_redirect!(Remainder1, x); atomic_impl!(Remainder1); externfn_impl!(Remainder1, |this: &Self, x: ExprInst| { let a: Numeric = this.x.clone().try_into()?; - Ok(Remainder0{ a, x }) + Ok(Remainder0 { a, x }) }); -/// Fully applied Remainder function. -/// /// Prev state: [Remainder1] - #[derive(Debug, Clone)] -pub struct Remainder0 { a: Numeric, x: ExprInst } +pub struct Remainder0 { + a: Numeric, + x: ExprInst, +} atomic_redirect!(Remainder0, x); -atomic_impl!(Remainder0, |Self{ a, x }: &Self, _| { +atomic_impl!(Remainder0, |Self { a, x }: &Self, _| { let b: Numeric = x.clone().try_into()?; Ok((*a % b).into()) -}); \ No newline at end of file +}); diff --git a/src/external/num/operators/subtract.rs b/src/external/num/operators/subtract.rs index 9aef1dd..191ffc4 100644 --- a/src/external/num/operators/subtract.rs +++ b/src/external/num/operators/subtract.rs @@ -1,40 +1,36 @@ - -use super::super::Numeric; - use std::fmt::Debug; -use crate::{atomic_impl, atomic_redirect, externfn_impl}; +use super::super::Numeric; use crate::representations::interpreted::ExprInst; +use crate::{atomic_impl, atomic_redirect, externfn_impl}; -/// Subtract function -/// +/// Subtracts two numbers +/// /// Next state: [Subtract1] - #[derive(Clone)] pub struct Subtract2; -externfn_impl!(Subtract2, |_: &Self, x: ExprInst| Ok(Subtract1{x})); +externfn_impl!(Subtract2, |_: &Self, x: ExprInst| Ok(Subtract1 { x })); -/// Partially applied Subtract function -/// /// Prev state: [Subtract2]; Next state: [Subtract0] - #[derive(Debug, Clone)] -pub struct Subtract1{ x: ExprInst } +pub struct Subtract1 { + x: ExprInst, +} atomic_redirect!(Subtract1, x); atomic_impl!(Subtract1); externfn_impl!(Subtract1, |this: &Self, x: ExprInst| { let a: Numeric = this.x.clone().try_into()?; - Ok(Subtract0{ a, x }) + Ok(Subtract0 { a, x }) }); -/// Fully applied Subtract function. -/// /// Prev state: [Subtract1] - #[derive(Debug, Clone)] -pub struct Subtract0 { a: Numeric, x: ExprInst } +pub struct Subtract0 { + a: Numeric, + x: ExprInst, +} atomic_redirect!(Subtract0, x); -atomic_impl!(Subtract0, |Self{ a, x }: &Self, _| { +atomic_impl!(Subtract0, |Self { a, x }: &Self, _| { let b: Numeric = x.clone().try_into()?; Ok((*a - b).into()) -}); \ No newline at end of file +}); diff --git a/src/external/runtime_error.rs b/src/external/runtime_error.rs index 34919cc..d47a45a 100644 --- a/src/external/runtime_error.rs +++ b/src/external/runtime_error.rs @@ -1,7 +1,9 @@ -use std::{rc::Rc, fmt::Display}; +use std::fmt::Display; +use std::rc::Rc; use crate::foreign::ExternError; +/// Some external event prevented the operation from succeeding #[derive(Clone)] pub struct RuntimeError { message: String, @@ -9,12 +11,15 @@ pub struct RuntimeError { } impl RuntimeError { - pub fn fail(message: String, operation: &'static str) -> Result> { - return Err(Self { message, operation }.into_extern()) + pub fn fail( + message: String, + operation: &'static str, + ) -> Result> { + return Err(Self { message, operation }.into_extern()); } pub fn ext(message: String, operation: &'static str) -> Rc { - return Self { message, operation }.into_extern() + return Self { message, operation }.into_extern(); } } @@ -24,4 +29,4 @@ impl Display for RuntimeError { } } -impl ExternError for RuntimeError{} \ No newline at end of file +impl ExternError for RuntimeError {} diff --git a/src/external/std.rs b/src/external/std.rs index f90ee5b..fb84dd9 100644 --- a/src/external/std.rs +++ b/src/external/std.rs @@ -1,16 +1,11 @@ -use crate::pipeline::ConstTree; -use crate::interner::Interner; - use super::bool::bool; -use super::cpsio::cpsio; use super::conv::conv; -use super::str::str; +use super::cpsio::cpsio; use super::num::num; +use super::str::str; +use crate::interner::Interner; +use crate::pipeline::ConstTree; pub fn std(i: &Interner) -> ConstTree { - cpsio(i) - + conv(i) - + bool(i) - + str(i) - + num(i) -} \ No newline at end of file + cpsio(i) + conv(i) + bool(i) + str(i) + num(i) +} diff --git a/src/external/str/char_at.rs b/src/external/str/char_at.rs index 950392b..8fdf6c3 100644 --- a/src/external/str/char_at.rs +++ b/src/external/str/char_at.rs @@ -2,41 +2,44 @@ use std::fmt::Debug; use crate::external::litconv::{with_str, with_uint}; use crate::external::runtime_error::RuntimeError; +use crate::representations::interpreted::{Clause, ExprInst}; use crate::representations::{Literal, Primitive}; use crate::{atomic_impl, atomic_redirect, externfn_impl}; -use crate::representations::interpreted::{Clause, ExprInst}; -/// CharAt function -/// +/// Takes an uint and a string, finds the char in a string at a 0-based index +/// /// Next state: [CharAt1] - #[derive(Clone)] pub struct CharAt2; -externfn_impl!(CharAt2, |_: &Self, x: ExprInst| Ok(CharAt1{x})); +externfn_impl!(CharAt2, |_: &Self, x: ExprInst| Ok(CharAt1 { x })); -/// Partially applied CharAt function -/// /// Prev state: [CharAt2]; Next state: [CharAt0] - #[derive(Debug, Clone)] -pub struct CharAt1{ x: ExprInst } +pub struct CharAt1 { + x: ExprInst, +} atomic_redirect!(CharAt1, x); atomic_impl!(CharAt1); externfn_impl!(CharAt1, |this: &Self, x: ExprInst| { - with_str(&this.x, |s| Ok(CharAt0{ s: s.clone(), x })) + with_str(&this.x, |s| Ok(CharAt0 { s: s.clone(), x })) }); -/// Fully applied CharAt function. -/// /// Prev state: [CharAt1] - #[derive(Debug, Clone)] -pub struct CharAt0 { s: String, x: ExprInst } +pub struct CharAt0 { + s: String, + x: ExprInst, +} atomic_redirect!(CharAt0, x); -atomic_impl!(CharAt0, |Self{ s, x }: &Self, _| { - with_uint(x, |i| if let Some(c) = s.chars().nth(i as usize) { - Ok(Clause::P(Primitive::Literal(Literal::Char(c)))) - } else { - RuntimeError::fail("Character index out of bounds".to_string(), "indexing string")? +atomic_impl!(CharAt0, |Self { s, x }: &Self, _| { + with_uint(x, |i| { + if let Some(c) = s.chars().nth(i as usize) { + Ok(Clause::P(Primitive::Literal(Literal::Char(c)))) + } else { + RuntimeError::fail( + "Character index out of bounds".to_string(), + "indexing string", + )? + } }) }); diff --git a/src/external/str/concatenate.rs b/src/external/str/concatenate.rs index 09bad5a..aad41f1 100644 --- a/src/external/str/concatenate.rs +++ b/src/external/str/concatenate.rs @@ -1,39 +1,37 @@ use std::fmt::Debug; use crate::external::litconv::with_str; -use crate::{atomic_impl, atomic_redirect, externfn_impl}; -use crate::representations::{Primitive, Literal}; use crate::representations::interpreted::{Clause, ExprInst}; +use crate::representations::{Literal, Primitive}; +use crate::{atomic_impl, atomic_redirect, externfn_impl}; -/// Concatenate function -/// +/// Concatenates two strings +/// /// Next state: [Concatenate1] - #[derive(Clone)] pub struct Concatenate2; -externfn_impl!(Concatenate2, |_: &Self, c: ExprInst| Ok(Concatenate1{c})); +externfn_impl!(Concatenate2, |_: &Self, c: ExprInst| Ok(Concatenate1 { c })); -/// Partially applied Concatenate function -/// /// Prev state: [Concatenate2]; Next state: [Concatenate0] - #[derive(Debug, Clone)] -pub struct Concatenate1{ c: ExprInst } +pub struct Concatenate1 { + c: ExprInst, +} atomic_redirect!(Concatenate1, c); atomic_impl!(Concatenate1); externfn_impl!(Concatenate1, |this: &Self, c: ExprInst| { - with_str(&this.c, |a| Ok(Concatenate0{ a: a.clone(), c })) + with_str(&this.c, |a| Ok(Concatenate0 { a: a.clone(), c })) }); -/// Fully applied Concatenate function. -/// /// Prev state: [Concatenate1] - #[derive(Debug, Clone)] -pub struct Concatenate0 { a: String, c: ExprInst } +pub struct Concatenate0 { + a: String, + c: ExprInst, +} atomic_redirect!(Concatenate0, c); -atomic_impl!(Concatenate0, |Self{ a, c }: &Self, _| { - with_str(c, |b| Ok(Clause::P(Primitive::Literal( - Literal::Str(a.to_owned() + b) - )))) +atomic_impl!(Concatenate0, |Self { a, c }: &Self, _| { + with_str(c, |b| { + Ok(Clause::P(Primitive::Literal(Literal::Str(a.to_owned() + b)))) + }) }); diff --git a/src/external/str/mod.rs b/src/external/str/mod.rs index cb6b322..1909d90 100644 --- a/src/external/str/mod.rs +++ b/src/external/str/mod.rs @@ -1,10 +1,12 @@ -mod concatenate; mod char_at; +mod concatenate; -use crate::{pipeline::ConstTree, interner::Interner}; +use crate::interner::Interner; +use crate::pipeline::ConstTree; pub fn str(i: &Interner) -> ConstTree { - ConstTree::tree([ - (i.i("concatenate"), ConstTree::xfn(concatenate::Concatenate2)) - ]) -} \ No newline at end of file + ConstTree::tree([( + i.i("concatenate"), + ConstTree::xfn(concatenate::Concatenate2), + )]) +} diff --git a/src/foreign.rs b/src/foreign.rs index 535b857..2661a98 100644 --- a/src/foreign.rs +++ b/src/foreign.rs @@ -1,30 +1,25 @@ use std::any::Any; use std::error::Error; -use std::fmt::{Display, Debug}; +use std::fmt::{Debug, Display}; use std::hash::Hash; use std::rc::Rc; use dyn_clone::DynClone; -use crate::interpreter::{RuntimeError, Context}; - -use crate::representations::Primitive; +use crate::interpreter::{Context, RuntimeError}; pub use crate::representations::interpreted::Clause; use crate::representations::interpreted::ExprInst; +use crate::representations::Primitive; pub struct AtomicReturn { pub clause: Clause, pub gas: Option, - pub inert: bool + pub inert: bool, } impl AtomicReturn { /// Wrap an inert atomic for delivery to the supervisor pub fn from_data(d: D, c: Context) -> Self { - AtomicReturn { - clause: d.to_atom_cls(), - gas: c.gas, - inert: false - } + AtomicReturn { clause: d.to_atom_cls(), gas: c.gas, inert: false } } } @@ -36,7 +31,9 @@ pub type RcExpr = ExprInst; pub trait ExternError: Display { fn into_extern(self) -> Rc - where Self: 'static + Sized { + where + Self: 'static + Sized, + { Rc::new(self) } } @@ -58,14 +55,19 @@ pub trait ExternFn: DynClone { fn hash(&self, state: &mut dyn std::hash::Hasher) { state.write_str(self.name()) } - fn to_xfn_cls(self) -> Clause where Self: Sized + 'static { + fn to_xfn_cls(self) -> Clause + where + Self: Sized + 'static, + { Clause::P(Primitive::ExternFn(Box::new(self))) } } impl Eq for dyn ExternFn {} impl PartialEq for dyn ExternFn { - fn eq(&self, other: &Self) -> bool { self.name() == other.name() } + fn eq(&self, other: &Self) -> bool { + self.name() == other.name() + } } impl Hash for dyn ExternFn { fn hash(&self, state: &mut H) { @@ -78,10 +80,16 @@ impl Debug for dyn ExternFn { } } -pub trait Atomic: Any + Debug + DynClone where Self: 'static { +pub trait Atomic: Any + Debug + DynClone +where + Self: 'static, +{ fn as_any(&self) -> &dyn Any; fn run(&self, ctx: Context) -> AtomicResult; - fn to_atom_cls(self) -> Clause where Self: Sized { + fn to_atom_cls(self) -> Clause + where + Self: Sized, + { Clause::P(Primitive::Atom(Atom(Box::new(self)))) } } @@ -105,10 +113,11 @@ impl Atom { pub fn try_cast(&self) -> Result<&T, ()> { self.data().as_any().downcast_ref().ok_or(()) } - pub fn is(&self) -> bool { self.data().as_any().is::() } + pub fn is(&self) -> bool { + self.data().as_any().is::() + } pub fn cast(&self) -> &T { - self.data().as_any().downcast_ref() - .expect("Type mismatch on Atom::cast") + self.data().as_any().downcast_ref().expect("Type mismatch on Atom::cast") } pub fn run(&self, ctx: Context) -> AtomicResult { self.0.run(ctx) @@ -125,4 +134,4 @@ impl Debug for Atom { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "##ATOM[{:?}]##", self.data()) } -} \ No newline at end of file +} diff --git a/src/foreign_macros/atomic_defaults.rs b/src/foreign_macros/atomic_defaults.rs index 33348be..1b2dd8a 100644 --- a/src/foreign_macros/atomic_defaults.rs +++ b/src/foreign_macros/atomic_defaults.rs @@ -1,13 +1,16 @@ #[allow(unused)] // for the doc comments use crate::foreign::Atomic; -/// A macro that generates the straightforward, syntactically invariant part of implementing -/// [Atomic]. Implemented fns are [Atomic::as_any], [Atomic::definitely_eq] and [Atomic::hash]. -/// +/// A macro that generates the straightforward, syntactically invariant part of +/// implementing [Atomic]. Implemented fns are [Atomic::as_any], +/// [Atomic::definitely_eq] and [Atomic::hash]. +/// /// It depends on [Eq] and [Hash] #[macro_export] macro_rules! atomic_defaults { () => { - fn as_any(&self) -> &dyn std::any::Any { self } + fn as_any(&self) -> &dyn std::any::Any { + self + } }; -} \ No newline at end of file +} diff --git a/src/foreign_macros/atomic_impl.rs b/src/foreign_macros/atomic_impl.rs index 3d0e266..d075ff9 100644 --- a/src/foreign_macros/atomic_impl.rs +++ b/src/foreign_macros/atomic_impl.rs @@ -1,33 +1,39 @@ #[allow(unused)] // for the doc comments -use crate::representations::Primitive; -#[allow(unused)] // for the doc comments -use crate::foreign::{Atomic, ExternFn}; -#[allow(unused)] // for the doc comments use std::any::Any; #[allow(unused)] // for the doc comments -use dyn_clone::DynClone; -#[allow(unused)] // for the doc comments use std::fmt::Debug; -/// A macro that generates implementations of [Atomic] to simplify the development of -/// external bindings for Orchid. -/// -/// The macro depends on implementations of [AsRef] and [From<(&Self, Clause)>] for -/// extracting the clause to be processed and then reconstructing the [Atomic]. Naturally, -/// supertraits of [Atomic] are also dependencies. These are [Any], [Debug] and [DynClone]. -/// -/// The simplest form just requires the typename to be specified. This additionally depends on an -/// implementation of [ExternFn] because after the clause is fully normalized it returns `Self` -/// wrapped in a [Primitive::ExternFn]. It is intended for intermediary -/// stages of the function where validation and the next state are defined in [ExternFn::apply]. -/// +#[allow(unused)] // for the doc comments +use dyn_clone::DynClone; + +#[allow(unused)] // for the doc comments +use crate::foreign::{Atomic, ExternFn}; +#[allow(unused)] // for the doc comments +use crate::representations::Primitive; + +/// A macro that generates implementations of [Atomic] to simplify the +/// development of external bindings for Orchid. +/// +/// The macro depends on implementations of [AsRef] and [From<(&Self, +/// Clause)>] for extracting the clause to be processed and then reconstructing +/// the [Atomic]. Naturally, supertraits of [Atomic] are also dependencies. +/// These are [Any], [Debug] and [DynClone]. +/// +/// The simplest form just requires the typename to be specified. This +/// additionally depends on an implementation of [ExternFn] because after the +/// clause is fully normalized it returns `Self` wrapped in a +/// [Primitive::ExternFn]. It is intended for intermediary stages of the +/// function where validation and the next state are defined in +/// [ExternFn::apply]. +/// /// ``` /// atomic_impl!(Multiply1) /// ``` -/// -/// The last stage of the function should use the extended form of the macro which takes an -/// additional closure to explicitly describe what happens when the argument is fully processed. -/// +/// +/// The last stage of the function should use the extended form of the macro +/// which takes an additional closure to explicitly describe what happens when +/// the argument is fully processed. +/// /// ``` /// // excerpt from the exact implementation of Multiply /// atomic_impl!(Multiply0, |Self(a, cls): &Self| { @@ -35,45 +41,44 @@ use std::fmt::Debug; /// Ok(*a * b).into()) /// }) /// ``` -/// #[macro_export] macro_rules! atomic_impl { ($typ:ident) => { - atomic_impl!{$typ, |this: &Self, _: $crate::interpreter::Context| { + atomic_impl! {$typ, |this: &Self, _: $crate::interpreter::Context| { use $crate::foreign::ExternFn; Ok(this.clone().to_xfn_cls()) }} }; ($typ:ident, $next_phase:expr) => { impl $crate::foreign::Atomic for $typ { - $crate::atomic_defaults!{} + $crate::atomic_defaults! {} - fn run(&self, ctx: $crate::interpreter::Context) - -> $crate::foreign::AtomicResult - { + fn run( + &self, + ctx: $crate::interpreter::Context, + ) -> $crate::foreign::AtomicResult { // extract the expression - let expr = - >::as_ref(self).clone(); + let expr = + >::as_ref(self).clone(); // run the expression let ret = $crate::interpreter::run(expr, ctx.clone())?; - let $crate::interpreter::Return{ gas, state, inert } = ret; + let $crate::interpreter::Return { gas, state, inert } = ret; // rebuild the atomic - let next_self = - >::from((self, state)); + let next_self = + >::from((self, state)); // branch off or wrap up let clause = if inert { - match ($next_phase)(&next_self, ctx) { + let closure = $next_phase; + match closure(&next_self, ctx) { Ok(r) => r, - Err(e) => return Err( - $crate::interpreter::RuntimeError::Extern(e) - ) + Err(e) => return Err($crate::interpreter::RuntimeError::Extern(e)), } - } else { next_self.to_atom_cls() }; + } else { + next_self.to_atom_cls() + }; // package and return - Ok($crate::foreign::AtomicReturn{ clause, gas, inert: false }) + Ok($crate::foreign::AtomicReturn { clause, gas, inert: false }) } } }; -} \ No newline at end of file +} diff --git a/src/foreign_macros/atomic_inert.rs b/src/foreign_macros/atomic_inert.rs index 559a319..913e55d 100644 --- a/src/foreign_macros/atomic_inert.rs +++ b/src/foreign_macros/atomic_inert.rs @@ -1,29 +1,33 @@ #[allow(unused)] // for the doc comments -use crate::foreign::Atomic; -#[allow(unused)] // for the doc comments use std::any::Any; #[allow(unused)] // for the doc comments -use dyn_clone::DynClone; -#[allow(unused)] // for the doc comments use std::fmt::Debug; -/// Implement [Atomic] for a structure that cannot be transformed any further. This would be optimal -/// for atomics encapsulating raw data. [Atomic] depends on [Any], [Debug] and [DynClone]. +#[allow(unused)] // for the doc comments +use dyn_clone::DynClone; + +#[allow(unused)] // for the doc comments +use crate::foreign::Atomic; + +/// Implement [Atomic] for a structure that cannot be transformed any further. +/// This would be optimal for atomics encapsulating raw data. [Atomic] depends +/// on [Any], [Debug] and [DynClone]. #[macro_export] macro_rules! atomic_inert { ($typ:ident) => { impl $crate::foreign::Atomic for $typ { - $crate::atomic_defaults!{} + $crate::atomic_defaults! {} - fn run(&self, ctx: $crate::interpreter::Context) - -> $crate::foreign::AtomicResult - { - Ok($crate::foreign::AtomicReturn{ + fn run( + &self, + ctx: $crate::interpreter::Context, + ) -> $crate::foreign::AtomicResult { + Ok($crate::foreign::AtomicReturn { clause: self.clone().to_atom_cls(), gas: ctx.gas, - inert: true + inert: true, }) } } }; -} \ No newline at end of file +} diff --git a/src/foreign_macros/atomic_redirect.rs b/src/foreign_macros/atomic_redirect.rs index c6e4f7f..32e3b05 100644 --- a/src/foreign_macros/atomic_redirect.rs +++ b/src/foreign_macros/atomic_redirect.rs @@ -1,30 +1,33 @@ #[allow(unused)] use super::atomic_impl; -/// Implement the traits required by [atomic_impl] to redirect run_* functions to a field -/// with a particular name. +/// Implement the traits required by [atomic_impl] to redirect run_* functions +/// to a field with a particular name. #[macro_export] macro_rules! atomic_redirect { ($typ:ident) => { impl AsRef<$crate::foreign::RcExpr> for $typ { - fn as_ref(&self) -> &Clause { &self.0 } + fn as_ref(&self) -> &Clause { + &self.0 + } } impl From<(&Self, $crate::foreign::RcExpr)> for $typ { fn from((old, clause): (&Self, Clause)) -> Self { - Self{ 0: clause, ..old.clone() } + Self { 0: clause, ..old.clone() } } } }; ($typ:ident, $field:ident) => { - impl AsRef<$crate::foreign::RcExpr> - for $typ { - fn as_ref(&self) -> &$crate::foreign::RcExpr { &self.$field } + impl AsRef<$crate::foreign::RcExpr> for $typ { + fn as_ref(&self) -> &$crate::foreign::RcExpr { + &self.$field + } } - impl From<(&Self, $crate::foreign::RcExpr)> - for $typ { + impl From<(&Self, $crate::foreign::RcExpr)> for $typ { + #[allow(clippy::needless_update)] fn from((old, $field): (&Self, $crate::foreign::RcExpr)) -> Self { - Self{ $field, ..old.clone() } + Self { $field, ..old.clone() } } } }; -} \ No newline at end of file +} diff --git a/src/foreign_macros/externfn_impl.rs b/src/foreign_macros/externfn_impl.rs index 1e00231..36e2f92 100644 --- a/src/foreign_macros/externfn_impl.rs +++ b/src/foreign_macros/externfn_impl.rs @@ -1,41 +1,47 @@ #[allow(unused)] // for the doc comments -use crate::{atomic_impl, atomic_redirect}; -#[allow(unused)] // for the doc comments -use crate::representations::Primitive; -#[allow(unused)] // for the doc comments -use crate::foreign::{Atomic, ExternFn}; -#[allow(unused)] // for the doc comments use std::any::Any; #[allow(unused)] // for the doc comments +use std::fmt::Debug; +#[allow(unused)] // for the doc comments use std::hash::Hash; + #[allow(unused)] // for the doc comments use dyn_clone::DynClone; -#[allow(unused)] // for the doc comments -use std::fmt::Debug; -/// Implement [ExternFn] with a closure that produces an [Atomic] from a reference to self -/// and a closure. This can be used in conjunction with [atomic_impl] and [atomic_redirect] -/// to normalize the argument automatically before using it. +#[allow(unused)] // for the doc comments +use crate::foreign::{Atomic, ExternFn}; +#[allow(unused)] // for the doc comments +use crate::representations::Primitive; +#[allow(unused)] // for the doc comments +use crate::{atomic_impl, atomic_redirect}; + +/// Implement [ExternFn] with a closure that produces an [Atomic] from a +/// reference to self and a closure. This can be used in conjunction with +/// [atomic_impl] and [atomic_redirect] to normalize the argument automatically +/// before using it. #[macro_export] macro_rules! externfn_impl { ($typ:ident, $next_atomic:expr) => { impl $crate::foreign::ExternFn for $typ { - fn name(&self) -> &str {stringify!($typ)} - fn apply(&self, + fn name(&self) -> &str { + stringify!($typ) + } + fn apply( + &self, arg: $crate::foreign::RcExpr, - _ctx: $crate::interpreter::Context + _ctx: $crate::interpreter::Context, ) -> $crate::foreign::XfnResult { - match ($next_atomic)(self, arg) { // ? casts the result but we want to strictly forward it - Ok(r) => Ok( - $crate::representations::interpreted::Clause::P( - $crate::representations::Primitive::Atom( - $crate::foreign::Atom::new(r) - ) - ) - ), - Err(e) => Err(e) + let closure = $next_atomic; + match closure(self, arg) { + // ? casts the result but we want to strictly forward it + Ok(r) => Ok($crate::representations::interpreted::Clause::P( + $crate::representations::Primitive::Atom( + $crate::foreign::Atom::new(r), + ), + )), + Err(e) => Err(e), } } } }; -} \ No newline at end of file +} diff --git a/src/foreign_macros/mod.rs b/src/foreign_macros/mod.rs index 8a3193d..fb8c37e 100644 --- a/src/foreign_macros/mod.rs +++ b/src/foreign_macros/mod.rs @@ -2,4 +2,4 @@ mod atomic_defaults; mod atomic_impl; mod atomic_inert; mod atomic_redirect; -mod externfn_impl; \ No newline at end of file +mod externfn_impl; diff --git a/src/interner/display.rs b/src/interner/display.rs index a9bd707..54a9dcf 100644 --- a/src/interner/display.rs +++ b/src/interner/display.rs @@ -6,13 +6,14 @@ use crate::interner::Interner; /// A variant of [std::fmt::Display] for objects that contain interned /// strings and therefore can only be stringified in the presence of a /// string interner -/// +/// /// The functions defined here are suffixed to distinguish them from /// the ones in Display and ToString respectively, because Rust can't /// identify functions based on arity pub trait InternedDisplay { /// formats the value using the given formatter and string interner - fn fmt_i(&self, + fn fmt_i( + &self, f: &mut std::fmt::Formatter<'_>, i: &Interner, ) -> std::fmt::Result; @@ -28,26 +29,31 @@ pub trait InternedDisplay { buf } - fn bundle<'a>(&'a self, interner: &'a Interner) - -> DisplayBundle<'a, Self> - { + fn bundle<'a>(&'a self, interner: &'a Interner) -> DisplayBundle<'a, Self> { DisplayBundle { interner, data: self } } } -impl InternedDisplay for T where T: Display { - fn fmt_i(&self, f: &mut std::fmt::Formatter<'_>, _i: &Interner) -> std::fmt::Result { - ::fmt(&self, f) +impl InternedDisplay for T +where + T: Display, +{ + fn fmt_i( + &self, + f: &mut std::fmt::Formatter<'_>, + _i: &Interner, + ) -> std::fmt::Result { + ::fmt(self, f) } } pub struct DisplayBundle<'a, T: InternedDisplay + ?Sized> { interner: &'a Interner, - data: &'a T + data: &'a T, } impl<'a, T: InternedDisplay> Display for DisplayBundle<'a, T> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { self.data.fmt_i(f, self.interner) } -} \ No newline at end of file +} diff --git a/src/interner/mod.rs b/src/interner/mod.rs index 53bdacf..54a5e36 100644 --- a/src/interner/mod.rs +++ b/src/interner/mod.rs @@ -1,9 +1,21 @@ +mod display; mod monotype; mod multitype; mod token; -mod display; +pub use display::{DisplayBundle, InternedDisplay}; pub use monotype::TypedInterner; pub use multitype::Interner; -pub use token::Token; -pub use display::{DisplayBundle, InternedDisplay}; +pub use token::Tok; + +/// A symbol, nsname, nname or namespaced name is a sequence of namespaces +/// and an identifier. The [Vec] can never be empty. +/// +/// Throughout different stages of processing, these names can be +/// +/// - local names to be prefixed with the current module +/// - imported names starting with a segment +/// - ending a single import or +/// - defined in one of the glob imported modules +/// - absolute names +pub type Sym = Tok>>; diff --git a/src/interner/monotype.rs b/src/interner/monotype.rs index fca1239..bc22135 100644 --- a/src/interner/monotype.rs +++ b/src/interner/monotype.rs @@ -1,50 +1,54 @@ -use std::num::NonZeroU32; -use std::cell::RefCell; use std::borrow::Borrow; -use std::hash::{Hash, BuildHasher}; +use std::cell::RefCell; +use std::hash::{BuildHasher, Hash}; +use std::num::NonZeroU32; use hashbrown::HashMap; -use super::token::Token; +use super::token::Tok; -pub struct TypedInterner{ - tokens: RefCell>>, - values: RefCell> +/// An interner for any type that implements [Borrow]. This is inspired by +/// Lasso but much simpler, in part because not much can be known about the type. +pub struct TypedInterner { + tokens: RefCell>>, + values: RefCell>, } impl TypedInterner { /// Create a fresh interner instance pub fn new() -> Self { Self { tokens: RefCell::new(HashMap::new()), - values: RefCell::new(Vec::new()) + values: RefCell::new(Vec::new()), } } /// Intern an object, returning a token - pub fn i>(&self, q: &Q) - -> Token where T: Borrow + pub fn i>(&self, q: &Q) -> Tok + where + T: Borrow, { let mut tokens = self.tokens.borrow_mut(); let hash = compute_hash(tokens.hasher(), q); - let raw_entry = tokens.raw_entry_mut().from_hash(hash, |k| { - >::borrow(k) == q - }); + let raw_entry = tokens + .raw_entry_mut() + .from_hash(hash, |k| >::borrow(k) == q); let kv = raw_entry.or_insert_with(|| { let mut values = self.values.borrow_mut(); - let uniq_key: NonZeroU32 = (values.len() as u32 + 1u32) - .try_into().expect("can never be zero"); + let uniq_key: NonZeroU32 = + (values.len() as u32 + 1u32).try_into().expect("can never be zero"); let keybox = Box::new(q.to_owned()); let keyref = Box::leak(keybox); values.push((keyref, true)); - let token = Token::::from_id(uniq_key); + let token = Tok::::from_id(uniq_key); (keyref, token) }); *kv.1 } /// Resolve a token, obtaining an object - /// It is illegal to use a token obtained from one interner with another. - pub fn r(&self, t: Token) -> &T { + /// It is illegal to use a token obtained from one interner with + /// another. + pub fn r(&self, t: Tok) -> &T { let values = self.values.borrow(); let key = t.into_usize() - 1; values[key].0 @@ -52,17 +56,20 @@ impl TypedInterner { /// Intern a static reference without allocating the data on the heap #[allow(unused)] - pub fn intern_static(&self, tref: &'static T) -> Token { + pub fn intern_static(&self, tref: &'static T) -> Tok { let mut tokens = self.tokens.borrow_mut(); - let token = *tokens.raw_entry_mut().from_key(tref) - .or_insert_with(|| { - let mut values = self.values.borrow_mut(); - let uniq_key: NonZeroU32 = (values.len() as u32 + 1u32) - .try_into().expect("can never be zero"); - values.push((tref, false)); - let token = Token::::from_id(uniq_key); - (tref, token) - }).1; + let token = *tokens + .raw_entry_mut() + .from_key(tref) + .or_insert_with(|| { + let mut values = self.values.borrow_mut(); + let uniq_key: NonZeroU32 = + (values.len() as u32 + 1u32).try_into().expect("can never be zero"); + values.push((tref, false)); + let token = Tok::::from_id(uniq_key); + (tref, token) + }) + .1; token } } @@ -74,10 +81,10 @@ impl Drop for TypedInterner { // which negates the need for unsafe here let mut values = self.values.borrow_mut(); for (item, owned) in values.drain(..) { - if !owned {continue} - unsafe { - Box::from_raw((item as *const T).cast_mut()) - }; + if !owned { + continue; + } + unsafe { Box::from_raw((item as *const T).cast_mut()) }; } } } @@ -85,10 +92,10 @@ impl Drop for TypedInterner { /// Helper function to compute hashes outside a hashmap fn compute_hash( hash_builder: &impl BuildHasher, - key: &(impl Hash + ?Sized) + key: &(impl Hash + ?Sized), ) -> u64 { use core::hash::Hasher; let mut state = hash_builder.build_hasher(); key.hash(&mut state); state.finish() -} \ No newline at end of file +} diff --git a/src/interner/multitype.rs b/src/interner/multitype.rs index 9d38724..432ce5d 100644 --- a/src/interner/multitype.rs +++ b/src/interner/multitype.rs @@ -1,14 +1,17 @@ +use std::any::{Any, TypeId}; use std::borrow::Borrow; use std::cell::{RefCell, RefMut}; -use std::any::{TypeId, Any}; use std::hash::Hash; use std::rc::Rc; use hashbrown::HashMap; use super::monotype::TypedInterner; -use super::token::Token; +use super::token::Tok; +/// A collection of interners based on their type. Allows to intern any object +/// that implements [ToOwned]. Objects of the same type are stored together in a +/// [TypedInterner]. pub struct Interner { interners: RefCell>>, } @@ -17,56 +20,59 @@ impl Interner { Self { interners: RefCell::new(HashMap::new()) } } - pub fn i(&self, q: &Q) - -> Token - where Q::Owned: 'static + Eq + Hash + Clone + Borrow + pub fn i(&self, q: &Q) -> Tok + where + Q::Owned: 'static + Eq + Hash + Clone + Borrow, { let mut interners = self.interners.borrow_mut(); let interner = get_interner(&mut interners); interner.i(q) } - pub fn r(&self, t: Token) -> &T { + pub fn r(&self, t: Tok) -> &T { let mut interners = self.interners.borrow_mut(); let interner = get_interner(&mut interners); // TODO: figure this out - unsafe{ (interner.r(t) as *const T).as_ref().unwrap() } + unsafe { (interner.r(t) as *const T).as_ref().unwrap() } } /// Fully resolve /// TODO: make this generic over containers - pub fn extern_vec(&self, - t: Token>> + pub fn extern_vec( + &self, + t: Tok>>, ) -> Vec { let mut interners = self.interners.borrow_mut(); let v_int = get_interner(&mut interners); let t_int = get_interner(&mut interners); let v = v_int.r(t); - v.iter() - .map(|t| t_int.r(*t)) - .cloned() - .collect() + v.iter().map(|t| t_int.r(*t)).cloned().collect() } - pub fn extern_all(&self, - s: &[Token] + pub fn extern_all( + &self, + s: &[Tok], ) -> Vec { - s.iter() - .map(|t| self.r(*t)) - .cloned() - .collect() + s.iter().map(|t| self.r(*t)).cloned().collect() + } +} + +impl Default for Interner { + fn default() -> Self { + Self::new() } } /// Get or create an interner for a given type. fn get_interner( - interners: &mut RefMut>> + interners: &mut RefMut>>, ) -> Rc> { - let boxed = interners.raw_entry_mut().from_key(&TypeId::of::()) - .or_insert_with(|| ( - TypeId::of::(), - Rc::new(TypedInterner::::new()) - )).1.clone(); + let boxed = interners + .raw_entry_mut() + .from_key(&TypeId::of::()) + .or_insert_with(|| (TypeId::of::(), Rc::new(TypedInterner::::new()))) + .1 + .clone(); boxed.downcast().expect("the typeid is supposed to protect from this") } @@ -94,8 +100,9 @@ mod test { #[allow(unused)] pub fn test_str_slice() { let interner = Interner::new(); - let key1 = interner.i(&vec!["a".to_string(), "b".to_string(), "c".to_string()]); + let key1 = + interner.i(&vec!["a".to_string(), "b".to_string(), "c".to_string()]); let key2 = interner.i(&["a", "b", "c"][..]); // assert_eq!(key1, key2); } -} \ No newline at end of file +} diff --git a/src/interner/token.rs b/src/interner/token.rs index 4283720..196e7eb 100644 --- a/src/interner/token.rs +++ b/src/interner/token.rs @@ -1,14 +1,18 @@ -use std::{num::NonZeroU32, marker::PhantomData}; +use std::cmp::PartialEq; use std::fmt::Debug; use std::hash::Hash; +use std::marker::PhantomData; +use std::num::NonZeroU32; -use std::cmp::PartialEq; - -pub struct Token{ +/// A number representing an object of type `T` stored in some interner. It is a +/// logic error to compare tokens obtained from different interners, or to use a +/// token with an interner other than the one that created it, but this is +/// currently not enforced. +pub struct Tok { id: NonZeroU32, - phantom_data: PhantomData + phantom_data: PhantomData, } -impl Token { +impl Tok { pub fn from_id(id: NonZeroU32) -> Self { Self { id, phantom_data: PhantomData } } @@ -21,37 +25,39 @@ impl Token { } } -impl Debug for Token { +impl Debug for Tok { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Token({})", self.id) } } -impl Copy for Token {} -impl Clone for Token { +impl Copy for Tok {} +impl Clone for Tok { fn clone(&self) -> Self { - Self{ id: self.id, phantom_data: PhantomData } + Self { id: self.id, phantom_data: PhantomData } } } -impl Eq for Token {} -impl PartialEq for Token { - fn eq(&self, other: &Self) -> bool { self.id == other.id } +impl Eq for Tok {} +impl PartialEq for Tok { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } } -impl Ord for Token { +impl Ord for Tok { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.id.cmp(&other.id) } } -impl PartialOrd for Token { +impl PartialOrd for Tok { fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(&other)) + Some(self.cmp(other)) } } -impl Hash for Token { +impl Hash for Tok { fn hash(&self, state: &mut H) { state.write_u32(self.id.into()) } -} \ No newline at end of file +} diff --git a/src/interpreter/apply.rs b/src/interpreter/apply.rs index bc6998b..de42931 100644 --- a/src/interpreter/apply.rs +++ b/src/interpreter/apply.rs @@ -1,103 +1,126 @@ +use super::context::Context; +use super::error::RuntimeError; +use super::Return; use crate::foreign::AtomicReturn; -use crate::representations::Primitive; -use crate::representations::PathSet; -use crate::representations::interpreted::{ExprInst, Clause}; +use crate::representations::interpreted::{Clause, ExprInst}; +use crate::representations::{PathSet, Primitive}; use crate::utils::Side; -use super::Return; -use super::error::RuntimeError; -use super::context::Context; - -/// Process the clause at the end of the provided path. -/// Note that paths always point to at least one target. -/// Note also that this is not cached as a normalization step in the -/// intermediate expressions. +/// Process the clause at the end of the provided path. Note that paths always +/// point to at least one target. Note also that this is not cached as a +/// normalization step in the intermediate expressions. fn map_at( - path: &[Side], source: ExprInst, - mapper: &mut impl FnMut(&Clause) -> Result + path: &[Side], + source: ExprInst, + mapper: &mut impl FnMut(&Clause) -> Result, ) -> Result { - source.try_update(|value| { - // Pass right through lambdas - if let Clause::Lambda { args, body } = value { - return Ok((Clause::Lambda { - args: args.clone(), - body: map_at(path, body.clone(), mapper)? - }, ())) - } - // If the path ends here, process the next (non-lambda) node - let (head, tail) = if let Some(sf) = path.split_first() {sf} else { - return Ok((mapper(value)?, ())) - }; - // If it's an Apply, execute the next step in the path - if let Clause::Apply { f, x } = value { - return Ok((match head { - Side::Left => Clause::Apply { - f: map_at(tail, f.clone(), mapper)?, - x: x.clone(), - }, - Side::Right => Clause::Apply { - f: f.clone(), - x: map_at(tail, x.clone(), mapper)?, - } - }, ())) - } - panic!("Invalid path") - }).map(|p| p.0) + source + .try_update(|value| { + // Pass right through lambdas + if let Clause::Lambda { args, body } = value { + return Ok(( + Clause::Lambda { + args: args.clone(), + body: map_at(path, body.clone(), mapper)?, + }, + (), + )); + } + // If the path ends here, process the next (non-lambda) node + let (head, tail) = if let Some(sf) = path.split_first() { + sf + } else { + return Ok((mapper(value)?, ())); + }; + // If it's an Apply, execute the next step in the path + if let Clause::Apply { f, x } = value { + return Ok(( + match head { + Side::Left => Clause::Apply { + f: map_at(tail, f.clone(), mapper)?, + x: x.clone(), + }, + Side::Right => Clause::Apply { + f: f.clone(), + x: map_at(tail, x.clone(), mapper)?, + }, + }, + (), + )); + } + panic!("Invalid path") + }) + .map(|p| p.0) } +/// Replace the [Clause::LambdaArg] placeholders at the ends of the [PathSet] +/// with the value in the body. Note that a path may point to multiple +/// placeholders. fn substitute(paths: &PathSet, value: Clause, body: ExprInst) -> ExprInst { - let PathSet{ steps, next } = paths; - map_at(&steps, body, &mut |checkpoint| -> Result { + let PathSet { steps, next } = paths; + map_at(steps, body, &mut |checkpoint| -> Result { match (checkpoint, next) { - (Clause::Lambda{..}, _) => unreachable!("Handled by map_at"), + (Clause::Lambda { .. }, _) => unreachable!("Handled by map_at"), (Clause::Apply { f, x }, Some((left, right))) => Ok(Clause::Apply { - f: substitute(&left, value.clone(), f.clone()), - x: substitute(&right, value.clone(), x.clone()), + f: substitute(left, value.clone(), f.clone()), + x: substitute(right, value.clone(), x.clone()), }), (Clause::LambdaArg, None) => Ok(value.clone()), - (_, None) => panic!("Substitution path ends in something other than LambdaArg"), - (_, Some(_)) => panic!("Substitution path leads into something other than Apply"), + (_, None) => + panic!("Substitution path ends in something other than LambdaArg"), + (_, Some(_)) => + panic!("Substitution path leads into something other than Apply"), } - }).into_ok() + }) + .into_ok() } /// Apply a function-like expression to a parameter. -/// If any work is being done, gas will be deducted. pub fn apply( - f: ExprInst, x: ExprInst, ctx: Context + f: ExprInst, + x: ExprInst, + ctx: Context, ) -> Result { - let (state, (gas, inert)) = f.clone().try_update(|clause| match clause { + let (state, (gas, inert)) = f.try_update(|clause| match clause { // apply an ExternFn or an internal function Clause::P(Primitive::ExternFn(f)) => { - let clause = f.apply(x, ctx.clone()) - .map_err(|e| RuntimeError::Extern(e))?; + let clause = + f.apply(x, ctx.clone()).map_err(|e| RuntimeError::Extern(e))?; Ok((clause, (ctx.gas.map(|g| g - 1), false))) - } - Clause::Lambda{args, body} => Ok(if let Some(args) = args { + }, + Clause::Lambda { args, body } => Ok(if let Some(args) = args { let x_cls = x.expr().clause.clone(); let new_xpr_inst = substitute(args, x_cls, body.clone()); let new_xpr = new_xpr_inst.expr(); // cost of substitution // XXX: should this be the number of occurrences instead? (new_xpr.clause.clone(), (ctx.gas.map(|x| x - 1), false)) - } else {(body.expr().clause.clone(), (ctx.gas, false))}), + } else { + (body.expr().clause.clone(), (ctx.gas, false)) + }), Clause::Constant(name) => { - let symval = if let Some(sym) = ctx.symbols.get(name) {sym.clone()} - else { panic!("missing symbol for function {}", - ctx.interner.extern_vec(*name).join("::") - )}; - Ok((Clause::Apply { f: symval, x, }, (ctx.gas, false))) - } - Clause::P(Primitive::Atom(atom)) => { // take a step in expanding atom + let symval = if let Some(sym) = ctx.symbols.get(name) { + sym.clone() + } else { + panic!( + "missing symbol for function {}", + ctx.interner.extern_vec(*name).join("::") + ) + }; + Ok((Clause::Apply { f: symval, x }, (ctx.gas, false))) + }, + Clause::P(Primitive::Atom(atom)) => { + // take a step in expanding atom let AtomicReturn { clause, gas, inert } = atom.run(ctx.clone())?; Ok((Clause::Apply { f: clause.wrap(), x }, (gas, inert))) }, - Clause::Apply{ f: fun, x: arg } => { // take a step in resolving pre-function + Clause::Apply { f: fun, x: arg } => { + // take a step in resolving pre-function let ret = apply(fun.clone(), arg.clone(), ctx.clone())?; let Return { state, inert, gas } = ret; - Ok((Clause::Apply{ f: state, x }, (gas, inert))) + Ok((Clause::Apply { f: state, x }, (gas, inert))) }, - _ => Err(RuntimeError::NonFunctionApplication(f.clone())) + _ => Err(RuntimeError::NonFunctionApplication(f.clone())), })?; Ok(Return { state, gas, inert }) -} \ No newline at end of file +} diff --git a/src/interpreter/context.rs b/src/interpreter/context.rs index d8dc414..396b423 100644 --- a/src/interpreter/context.rs +++ b/src/interpreter/context.rs @@ -1,29 +1,27 @@ use hashbrown::HashMap; +use crate::interner::{Interner, Sym}; use crate::representations::interpreted::ExprInst; -use crate::interner::{Token, Interner}; +/// All the data associated with an interpreter run #[derive(Clone)] pub struct Context<'a> { - pub symbols: &'a HashMap>>, ExprInst>, + /// Table used to resolve constants + pub symbols: &'a HashMap, + /// The interner used for strings internally, so external functions can deduce + /// referenced constant names on the fly pub interner: &'a Interner, + /// The number of reduction steps the interpreter can take before returning pub gas: Option, } -impl Context<'_> { - pub fn is_stuck(&self, res: Option) -> bool { - match (res, self.gas) { - (Some(a), Some(b)) => a == b, - (None, None) => false, - (None, Some(_)) => panic!("gas not tracked despite limit"), - (Some(_), None) => panic!("gas tracked without request"), - } - } -} - +/// All the data produced by an interpreter run #[derive(Clone)] pub struct Return { + /// The new expression tree pub state: ExprInst, + /// Leftover [Context::gas] if counted pub gas: Option, + /// If true, the next run would not modify the expression pub inert: bool, } diff --git a/src/interpreter/error.rs b/src/interpreter/error.rs index 7055974..ebbeb8b 100644 --- a/src/interpreter/error.rs +++ b/src/interpreter/error.rs @@ -1,8 +1,8 @@ use std::fmt::Display; use std::rc::Rc; -use crate::representations::interpreted::ExprInst; use crate::foreign::ExternError; +use crate::representations::interpreted::ExprInst; /// Problems in the process of execution #[derive(Clone, Debug)] @@ -21,7 +21,8 @@ impl Display for RuntimeError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Extern(e) => write!(f, "Error in external function: {e}"), - Self::NonFunctionApplication(loc) => write!(f, "Primitive applied as function at {loc:?}") + Self::NonFunctionApplication(loc) => + write!(f, "Primitive applied as function at {loc:?}"), } } -} \ No newline at end of file +} diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index b902c7a..3de9f22 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -1,8 +1,8 @@ mod apply; -mod error; mod context; +mod error; mod run; pub use context::{Context, Return}; pub use error::RuntimeError; -pub use run::{run, run_handler, Handler, HandlerParm, HandlerRes}; \ No newline at end of file +pub use run::{run, run_handler, Handler, HandlerParm, HandlerRes}; diff --git a/src/interpreter/run.rs b/src/interpreter/run.rs index dd96e39..2f8e21f 100644 --- a/src/interpreter/run.rs +++ b/src/interpreter/run.rs @@ -1,106 +1,155 @@ use std::mem; use std::rc::Rc; -use crate::foreign::{AtomicReturn, Atomic, ExternError, Atom}; -use crate::representations::Primitive; -use crate::representations::interpreted::{Clause, ExprInst}; - use super::apply::apply; -use super::error::RuntimeError; use super::context::{Context, Return}; +use super::error::RuntimeError; +use crate::foreign::{Atom, Atomic, AtomicReturn, ExternError}; +use crate::representations::interpreted::{Clause, ExprInst}; +use crate::representations::Primitive; -pub fn run( - expr: ExprInst, - mut ctx: Context -) -> Result { - let (state, (gas, inert)) = expr.try_normalize(|cls| -> Result<(Clause, _), RuntimeError> { - let mut i = cls.clone(); - while ctx.gas.map(|g| g > 0).unwrap_or(true) { - match &i { - Clause::Apply { f, x } => { - let res = apply(f.clone(), x.clone(), ctx.clone())?; - if res.inert {return Ok((i, (res.gas, true)))} - ctx.gas = res.gas; - i = res.state.expr().clause.clone(); +/// Normalize an expression using beta reduction with memoization +pub fn run(expr: ExprInst, mut ctx: Context) -> Result { + let (state, (gas, inert)) = + expr.try_normalize(|cls| -> Result<(Clause, _), RuntimeError> { + let mut i = cls.clone(); + while ctx.gas.map(|g| g > 0).unwrap_or(true) { + match &i { + Clause::Apply { f, x } => { + let res = apply(f.clone(), x.clone(), ctx.clone())?; + if res.inert { + return Ok((i, (res.gas, true))); + } + ctx.gas = res.gas; + i = res.state.expr().clause.clone(); + }, + Clause::P(Primitive::Atom(data)) => { + let ret = data.run(ctx.clone())?; + let AtomicReturn { clause, gas, inert } = ret; + if inert { + return Ok((i, (gas, true))); + } + ctx.gas = gas; + i = clause.clone(); + }, + Clause::Constant(c) => { + let symval = ctx.symbols.get(c).expect("missing symbol for value"); + ctx.gas = ctx.gas.map(|g| g - 1); // cost of lookup + i = symval.expr().clause.clone(); + }, + // non-reducible + _ => return Ok((i, (ctx.gas, true))), } - Clause::P(Primitive::Atom(data)) => { - let ret = data.run(ctx.clone())?; - let AtomicReturn { clause, gas, inert } = ret; - if inert {return Ok((i, (gas, true)))} - ctx.gas = gas; - i = clause.clone(); - } - Clause::Constant(c) => { - let symval = ctx.symbols.get(c).expect("missing symbol for value"); - ctx.gas = ctx.gas.map(|g| g - 1); // cost of lookup - i = symval.expr().clause.clone(); - } - // non-reducible - _ => return Ok((i, (ctx.gas, true))) } - } - // out of gas - Ok((i, (ctx.gas, false))) - })?; + // out of gas + Ok((i, (ctx.gas, false))) + })?; Ok(Return { state, gas, inert }) } +/// Opaque inert data that may encode a command to a [Handler] pub type HandlerParm = Box; -pub type HandlerRes = Result< - Result>, - HandlerParm ->; -pub trait Handler { - fn resolve(&mut self, data: HandlerParm) -> HandlerRes; - - fn then(self, t: T) -> impl Handler where Self: Sized { - Pair(self, t) +/// Reasons why a [Handler] could not interpret a command. Convertible from +/// either variant +pub enum HandlerErr { + /// The command was addressed to us but its execution resulted in an error + Extern(Rc), + /// This handler is not applicable, either because the [HandlerParm] is not a + /// command or because it's meant for some other handler + NA(HandlerParm), +} +impl From> for HandlerErr { + fn from(value: Rc) -> Self { + Self::Extern(value) + } +} +impl From for HandlerErr +where + T: ExternError + 'static, +{ + fn from(value: T) -> Self { + Self::Extern(value.into_extern()) + } +} +impl From for HandlerErr { + fn from(value: HandlerParm) -> Self { + Self::NA(value) } } -impl Handler for F where F: FnMut(HandlerParm) -> HandlerRes { +/// Various possible outcomes of a [Handler] execution. +pub type HandlerRes = Result; + +/// A trait for things that may be able to handle commands returned by Orchid +/// code. This trait is implemented for [FnMut(HandlerParm) -> HandlerRes] and +/// [(Handler, Handler)], users are not supposed to implement it themselves. +/// +/// A handler receives an arbitrary inert [Atomic] and uses [Atomic::as_any] +/// then [std::any::Any::downcast_ref] to obtain a known type. If this fails, it +/// returns the box in [HandlerErr::NA] which will be passed to the next +/// handler. +pub trait Handler { + /// Attempt to resolve a command with this handler. + fn resolve(&mut self, data: HandlerParm) -> HandlerRes; + + /// If this handler isn't applicable, try the other one. + fn or(self, t: T) -> impl Handler + where + Self: Sized, + { + (self, t) + } +} + +impl Handler for F +where + F: FnMut(HandlerParm) -> HandlerRes, +{ fn resolve(&mut self, data: HandlerParm) -> HandlerRes { self(data) } } -pub struct Pair(T, U); - -impl Handler for Pair { +impl Handler for (T, U) { fn resolve(&mut self, data: HandlerParm) -> HandlerRes { match self.0.resolve(data) { - Ok(out) => Ok(out), - Err(data) => self.1.resolve(data) + Err(HandlerErr::NA(data)) => self.1.resolve(data), + x => x, } } } +/// [run] orchid code, executing any commands it returns using the specified +/// [Handler]s. pub fn run_handler( mut expr: ExprInst, mut handler: impl Handler, - mut ctx: Context + mut ctx: Context, ) -> Result { loop { let ret = run(expr.clone(), ctx.clone())?; if ret.gas == Some(0) { - return Ok(ret) + return Ok(ret); } let state_ex = ret.state.expr(); - let a = if let Clause::P(Primitive::Atom(a)) = &state_ex.clause {a} - else { + let a = if let Clause::P(Primitive::Atom(a)) = &state_ex.clause { + a + } else { mem::drop(state_ex); - return Ok(ret) + return Ok(ret); }; let boxed = a.clone().0; expr = match handler.resolve(boxed) { - Ok(r) => r.map_err(RuntimeError::Extern)?, - Err(e) => return Ok(Return{ - gas: ret.gas, - inert: ret.inert, - state: Clause::P(Primitive::Atom(Atom(e))).wrap() - }) + Ok(expr) => expr, + Err(HandlerErr::Extern(ext)) => Err(ext)?, + Err(HandlerErr::NA(atomic)) => + return Ok(Return { + gas: ret.gas, + inert: ret.inert, + state: Clause::P(Primitive::Atom(Atom(atomic))).wrap(), + }), }; ctx.gas = ret.gas; } -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index f12e5fe..5e37f65 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,19 +12,20 @@ #![feature(trait_alias)] #![feature(return_position_impl_trait_in_trait)] -mod parse; +mod cli; +mod external; +pub(crate) mod foreign; +mod foreign_macros; mod interner; mod interpreter; -mod utils; +mod parse; +mod pipeline; mod representations; mod rule; -pub(crate) mod foreign; -mod external; -mod foreign_macros; -mod pipeline; mod run_dir; -mod cli; -use std::{path::PathBuf, fs::File}; +mod utils; +use std::fs::File; +use std::path::PathBuf; use clap::Parser; use cli::prompt; @@ -37,7 +38,7 @@ use run_dir::run_dir; struct Args { /// Folder containing main.orc #[arg(short, long)] - pub project: Option + pub project: Option, } fn main() { @@ -48,7 +49,7 @@ fn main() { path.push("main.orc"); match File::open(&path) { Ok(_) => Ok(p), - Err(e) => Err(format!("{}: {e}", path.display())) + Err(e) => Err(format!("{}: {e}", path.display())), } }) }); diff --git a/src/parse/comment.rs b/src/parse/comment.rs index 6b15ca0..741b7af 100644 --- a/src/parse/comment.rs +++ b/src/parse/comment.rs @@ -1,13 +1,15 @@ -pub use chumsky::{self, prelude::*, Parser}; +pub use chumsky::prelude::*; +pub use chumsky::{self, Parser}; + +use super::decls::SimpleParser; /// Parses Lua-style comments -pub fn comment_parser() -> impl Parser> { +pub fn comment_parser() -> impl SimpleParser { choice(( - just("--[").ignore_then(take_until( - just("]--").ignored() - )), - just("--").ignore_then(take_until( - just("\n").rewind().ignored().or(end()) - )) - )).map(|(vc, ())| vc).collect().labelled("comment") + just("--[").ignore_then(take_until(just("]--").ignored())), + just("--").ignore_then(take_until(just("\n").rewind().ignored().or(end()))), + )) + .map(|(vc, ())| vc) + .collect() + .labelled("comment") } diff --git a/src/parse/context.rs b/src/parse/context.rs index 892480f..df0d40b 100644 --- a/src/parse/context.rs +++ b/src/parse/context.rs @@ -3,46 +3,53 @@ use std::rc::Rc; use crate::interner::Interner; /// Trait enclosing all context features -/// +/// /// Hiding type parameters in associated types allows for simpler /// parser definitions pub trait Context: Clone { type Op: AsRef; - fn ops<'a>(&'a self) -> &'a [Self::Op]; + fn ops(&self) -> &[Self::Op]; fn file(&self) -> Rc>; - fn interner<'a>(&'a self) -> &'a Interner; + fn interner(&self) -> &Interner; } /// Struct implementing context -/// +/// /// Hiding type parameters in associated types allows for simpler /// parser definitions pub struct ParsingContext<'a, Op> { pub ops: &'a [Op], pub interner: &'a Interner, - pub file: Rc> + pub file: Rc>, } impl<'a, Op> ParsingContext<'a, Op> { - pub fn new(ops: &'a [Op], interner: &'a Interner, file: Rc>) - -> Self { Self { ops, interner, file } } + pub fn new( + ops: &'a [Op], + interner: &'a Interner, + file: Rc>, + ) -> Self { + Self { ops, interner, file } + } } impl<'a, Op> Clone for ParsingContext<'a, Op> { fn clone(&self) -> Self { - Self { - ops: self.ops, - interner: self.interner, - file: self.file.clone() - } + Self { ops: self.ops, interner: self.interner, file: self.file.clone() } } } impl> Context for ParsingContext<'_, Op> { type Op = Op; - fn interner<'a>(&'a self) -> &'a Interner { self.interner } - fn file(&self) -> Rc> {self.file.clone()} - fn ops<'a>(&'a self) -> &'a [Self::Op] { self.ops } -} \ No newline at end of file + fn interner(&self) -> &Interner { + self.interner + } + fn file(&self) -> Rc> { + self.file.clone() + } + fn ops(&self) -> &[Self::Op] { + self.ops + } +} diff --git a/src/parse/decls.rs b/src/parse/decls.rs new file mode 100644 index 0000000..7a44d36 --- /dev/null +++ b/src/parse/decls.rs @@ -0,0 +1,14 @@ +use std::hash::Hash; + +use chumsky::prelude::Simple; +use chumsky::recursive::Recursive; +use chumsky::{BoxedParser, Parser}; + +/// Wrapper around [Parser] with [Simple] error to avoid repeating the input +pub trait SimpleParser = + Parser>; +/// Boxed version of [SimpleParser] +pub type BoxedSimpleParser<'a, I, O> = BoxedParser<'a, I, O, Simple>; +/// [Recursive] specialization of [SimpleParser] to parameterize calls to +/// [chumsky::recursive::recursive] +pub type SimpleRecursive<'a, I, O> = Recursive<'a, I, O, Simple>; diff --git a/src/parse/enum_filter.rs b/src/parse/enum_filter.rs index 2de48e9..5e63b03 100644 --- a/src/parse/enum_filter.rs +++ b/src/parse/enum_filter.rs @@ -1,8 +1,12 @@ /// Produces filter_mapping functions for enum types: /// ```rs -/// enum_parser!(Foo::Bar | "Some error!") // Accepts Foo::Bar(T) into T -/// enum_parser!(Foo::Bar) // same as above but with the default error "Expected Foo::Bar" -/// enum_parser!(Foo >> Quz; Bar, Baz) // Parses Foo::Bar(T) into Quz::Bar(T) and Foo::Baz(U) into Quz::Baz(U) +/// enum_parser!(Foo::Bar | "Some error!") +/// // Foo::Bar(T) into T +/// enum_parser!(Foo::Bar) +/// // same as above but with the default error "Expected Foo::Bar" +/// enum_parser!(Foo >> Quz; Bar, Baz) +/// // Foo::Bar(T) into Quz::Bar(T) +/// // Foo::Baz(U) into Quz::Baz(U) /// ``` #[macro_export] macro_rules! enum_filter { @@ -43,4 +47,4 @@ macro_rules! enum_filter { ($p:path) => { enum_filter!($p | {concat!("Expected ", stringify!($p))}) }; -} \ No newline at end of file +} diff --git a/src/parse/expression.rs b/src/parse/expression.rs index 8009536..02ce1e0 100644 --- a/src/parse/expression.rs +++ b/src/parse/expression.rs @@ -1,107 +1,110 @@ use std::ops::Range; use std::rc::Rc; -use chumsky::{self, prelude::*, Parser}; - -use crate::enum_filter; -use crate::representations::Primitive; -use crate::representations::ast::{Clause, Expr}; -use crate::representations::location::Location; -use crate::interner::Token; +use chumsky::prelude::*; +use chumsky::{self, Parser}; use super::context::Context; -use super::lexer::{Lexeme, Entry, filter_map_lex}; +use super::decls::SimpleParser; +use super::lexer::{filter_map_lex, Entry, Lexeme}; +use crate::enum_filter; +use crate::interner::Sym; +use crate::representations::ast::{Clause, Expr}; +use crate::representations::location::Location; +use crate::representations::Primitive; /// Parses any number of expr wrapped in (), [] or {} fn sexpr_parser( - expr: impl Parser> + Clone -) -> impl Parser), Error = Simple> + Clone { + expr: impl SimpleParser + Clone, +) -> impl SimpleParser)> + Clone { let body = expr.repeated(); choice(( - Lexeme::LP('(').parser().then(body.clone()) - .then(Lexeme::RP('(').parser()), - Lexeme::LP('[').parser().then(body.clone()) - .then(Lexeme::RP('[').parser()), - Lexeme::LP('{').parser().then(body.clone()) - .then(Lexeme::RP('{').parser()), - )).map(|((lp, body), rp)| { - let Entry{lexeme, range: Range{start, ..}} = lp; + Lexeme::LP('(').parser().then(body.clone()).then(Lexeme::RP('(').parser()), + Lexeme::LP('[').parser().then(body.clone()).then(Lexeme::RP('[').parser()), + Lexeme::LP('{').parser().then(body).then(Lexeme::RP('{').parser()), + )) + .map(|((lp, body), rp)| { + let Entry { lexeme, range: Range { start, .. } } = lp; let end = rp.range.end; - let char = if let Lexeme::LP(c) = lexeme {c} - else {unreachable!("The parser only matches Lexeme::LP")}; + let char = if let Lexeme::LP(c) = lexeme { + c + } else { + unreachable!("The parser only matches Lexeme::LP") + }; (Clause::S(char, Rc::new(body)), start..end) - }).labelled("S-expression") + }) + .labelled("S-expression") } /// Parses `\name.body` or `\name:type.body` where name is any valid name /// and type and body are both expressions. Comments are allowed /// and ignored everywhere in between the tokens fn lambda_parser<'a>( - expr: impl Parser> + Clone + 'a, - ctx: impl Context + 'a -) -> impl Parser), Error = Simple> + Clone + 'a { - Lexeme::BS.parser() - .ignore_then(expr.clone()) - .then_ignore(Lexeme::Name(ctx.interner().i(".")).parser()) - .then(expr.repeated().at_least(1)) - .map_with_span(move |(arg, body), span| { - (Clause::Lambda(Rc::new(arg), Rc::new(body)), span) - }).labelled("Lambda") + expr: impl SimpleParser + Clone + 'a, + ctx: impl Context + 'a, +) -> impl SimpleParser)> + Clone + 'a { + Lexeme::BS + .parser() + .ignore_then(expr.clone()) + .then_ignore(Lexeme::Name(ctx.interner().i(".")).parser()) + .then(expr.repeated().at_least(1)) + .map_with_span(move |(arg, body), span| { + (Clause::Lambda(Rc::new(arg), Rc::new(body)), span) + }) + .labelled("Lambda") } /// Parses a sequence of names separated by ::
/// Comments and line breaks are allowed and ignored in between -pub fn ns_name_parser<'a>(ctx: impl Context + 'a) --> impl Parser>>, Range), Error = Simple> + Clone + 'a -{ +pub fn ns_name_parser<'a>( + ctx: impl Context + 'a, +) -> impl SimpleParser)> + Clone + 'a { filter_map_lex(enum_filter!(Lexeme::Name)) - .separated_by(Lexeme::NS.parser()).at_least(1) + .separated_by(Lexeme::NS.parser()) + .at_least(1) .map(move |elements| { let start = elements.first().expect("can never be empty").1.start; let end = elements.last().expect("can never be empty").1.end; - let tokens = - /*ctx.prefix().iter().copied().chain*/( - elements.iter().map(|(t, _)| *t) - ).collect::>(); + let tokens = (elements.iter().map(|(t, _)| *t)).collect::>(); (ctx.interner().i(&tokens), start..end) - }).labelled("Namespaced name") + }) + .labelled("Namespaced name") } -pub fn namelike_parser<'a>(ctx: impl Context + 'a) --> impl Parser), Error = Simple> + Clone + 'a -{ +pub fn namelike_parser<'a>( + ctx: impl Context + 'a, +) -> impl SimpleParser)> + Clone + 'a { choice(( filter_map_lex(enum_filter!(Lexeme::PH)) .map(|(ph, range)| (Clause::Placeh(ph), range)), - ns_name_parser(ctx) - .map(|(token, range)| (Clause::Name(token), range)), + ns_name_parser(ctx).map(|(token, range)| (Clause::Name(token), range)), )) } pub fn clause_parser<'a>( - expr: impl Parser> + Clone + 'a, - ctx: impl Context + 'a -) -> impl Parser), Error = Simple> + Clone + 'a { + expr: impl SimpleParser + Clone + 'a, + ctx: impl Context + 'a, +) -> impl SimpleParser)> + Clone + 'a { choice(( filter_map_lex(enum_filter!(Lexeme >> Primitive; Literal)) - .map(|(p, s)| (Clause::P(p), s)).labelled("Literal"), + .map(|(p, s)| (Clause::P(p), s)) + .labelled("Literal"), sexpr_parser(expr.clone()), - lambda_parser(expr.clone(), ctx.clone()), + lambda_parser(expr, ctx.clone()), namelike_parser(ctx), - )).labelled("Clause") + )) + .labelled("Clause") } /// Parse an expression -pub fn xpr_parser<'a>(ctx: impl Context + 'a) --> impl Parser> + 'a -{ +pub fn xpr_parser<'a>( + ctx: impl Context + 'a, +) -> impl SimpleParser + 'a { recursive(move |expr| { - clause_parser(expr, ctx.clone()) - .map(move |(value, range)| { - Expr{ - value: value.clone(), - location: Location::Range { file: ctx.file(), range } - } + clause_parser(expr, ctx.clone()).map(move |(value, range)| Expr { + value, + location: Location::Range { file: ctx.file(), range }, }) - }).labelled("Expression") -} \ No newline at end of file + }) + .labelled("Expression") +} diff --git a/src/parse/parse.rs b/src/parse/facade.rs similarity index 54% rename from src/parse/parse.rs rename to src/parse/facade.rs index e21ddb8..b7b11ca 100644 --- a/src/parse/parse.rs +++ b/src/parse/facade.rs @@ -1,58 +1,59 @@ use std::fmt::Debug; -use chumsky::{prelude::*, Parser}; +use chumsky::prelude::*; +use chumsky::Parser; use thiserror::Error; -use crate::representations::sourcefile::{FileEntry}; -use crate::parse::sourcefile::split_lines; - use super::context::Context; use super::{lexer, line_parser, Entry}; - +use crate::parse::sourcefile::split_lines; +use crate::representations::sourcefile::FileEntry; #[derive(Error, Debug, Clone)] pub enum ParseError { #[error("Could not tokenize {0:?}")] Lex(Vec>), - #[error("Could not parse {:?} on line {}", .0.first().unwrap().1.span(), .0.first().unwrap().0)] - Ast(Vec<(usize, Simple)>) + #[error( + "Could not parse {:?} on line {}", + .0.first().unwrap().1.span(), + .0.first().unwrap().0 + )] + Ast(Vec<(usize, Simple)>), } -/// All the data required for parsing - - /// Parse a string of code into a collection of module elements; /// imports, exports, comments, declarations, etc. -/// +/// /// Notice that because the lexer splits operators based on the provided /// list, the output will only be correct if operator list already /// contains all operators defined or imported by this module. -pub fn parse<'a>(data: &str, ctx: impl Context) --> Result, ParseError> -{ +pub fn parse( + data: &str, + ctx: impl Context, +) -> Result, ParseError> { // TODO: wrap `i`, `ops` and `prefix` in a parsing context let lexie = lexer(ctx.clone()); let token_batchv = lexie.parse(data).map_err(ParseError::Lex)?; - // println!("Lexed:\n{}", LexedText(token_batchv.clone()).bundle(ctx.interner())); - // println!("Lexed:\n{:?}", token_batchv.clone()); let parsr = line_parser(ctx).then_ignore(end()); let (parsed_lines, errors_per_line) = split_lines(&token_batchv) .enumerate() - .map(|(i, entv)| (i, - entv.iter() - .filter(|e| !e.is_filler()) - .cloned() - .collect::>() - )) - .filter(|(_, l)| l.len() > 0) + .map(|(i, entv)| { + (i, entv.iter().filter(|e| !e.is_filler()).cloned().collect::>()) + }) + .filter(|(_, l)| !l.is_empty()) .map(|(i, l)| (i, parsr.parse(l))) .map(|(i, res)| match res { Ok(r) => (Some(r), (i, vec![])), - Err(e) => (None, (i, e)) - }).unzip::<_, _, Vec<_>, Vec<_>>(); - let total_err = errors_per_line.into_iter() + Err(e) => (None, (i, e)), + }) + .unzip::<_, _, Vec<_>, Vec<_>>(); + let total_err = errors_per_line + .into_iter() .flat_map(|(i, v)| v.into_iter().map(move |e| (i, e))) .collect::>(); - if !total_err.is_empty() { Err(ParseError::Ast(total_err)) } - else { Ok(parsed_lines.into_iter().map(Option::unwrap).collect()) } + if !total_err.is_empty() { + Err(ParseError::Ast(total_err)) + } else { + Ok(parsed_lines.into_iter().map(Option::unwrap).collect()) + } } diff --git a/src/parse/import.rs b/src/parse/import.rs index 38c2a88..4308d74 100644 --- a/src/parse/import.rs +++ b/src/parse/import.rs @@ -1,16 +1,20 @@ -use chumsky::{Parser, prelude::*}; +use chumsky::prelude::*; +use chumsky::Parser; use itertools::Itertools; + +use super::context::Context; +use super::decls::{SimpleParser, SimpleRecursive}; +use super::lexer::{filter_map_lex, Lexeme}; +use super::Entry; +use crate::interner::Tok; use crate::representations::sourcefile::Import; -use crate::utils::iter::{box_once, box_flatten, into_boxed_iter, BoxedIterIter}; -use crate::interner::Token; +use crate::utils::iter::{ + box_flatten, box_once, into_boxed_iter, BoxedIterIter, +}; use crate::{box_chain, enum_filter}; -use super::Entry; -use super::context::Context; -use super::lexer::{Lexeme, filter_map_lex}; - /// initialize a BoxedIter> with a single element. -fn init_table(name: Token) -> BoxedIterIter<'static, Token> { +fn init_table(name: Tok) -> BoxedIterIter<'static, Tok> { // I'm not at all confident that this is a good approach. box_once(box_once(name)) } @@ -21,56 +25,74 @@ fn init_table(name: Token) -> BoxedIterIter<'static, Token> { /// preferably contain crossplatform filename-legal characters but the /// symbols are explicitly allowed to go wild. /// There's a blacklist in [name] -pub fn import_parser<'a>(ctx: impl Context + 'a) --> impl Parser, Error = Simple> + 'a -{ +pub fn import_parser<'a>( + ctx: impl Context + 'a, +) -> impl SimpleParser> + 'a { // TODO: this algorithm isn't cache friendly and copies a lot recursive({ let ctx = ctx.clone(); - move |expr:Recursive>, Simple>| { - filter_map_lex(enum_filter!(Lexeme::Name)).map(|(t, _)| t) - .separated_by(Lexeme::NS.parser()) - .then( - Lexeme::NS.parser() - .ignore_then( - choice(( - expr.clone() - .separated_by(Lexeme::Name(ctx.interner().i(",")).parser()) - .delimited_by(Lexeme::LP('(').parser(), Lexeme::RP('(').parser()) - .map(|v| box_flatten(v.into_iter())) - .labelled("import group"), - // Each expr returns a list of imports, flatten into common list - Lexeme::Name(ctx.interner().i("*")).parser() - .map(move |_| init_table(ctx.interner().i("*"))) - .labelled("wildcard import"), // Just a *, wrapped - filter_map_lex(enum_filter!(Lexeme::Name)) - .map(|(t, _)| init_table(t)) - .labelled("import terminal") // Just a name, wrapped - )) - ).or_not() - ) - .map(|(name, opt_post): (Vec>, Option>>)| - -> BoxedIterIter> { - if let Some(post) = opt_post { - Box::new(post.map(move |el| { - box_chain!(name.clone().into_iter(), el) - })) - } else { - box_once(into_boxed_iter(name)) - } - }) + move |expr: SimpleRecursive>>| { + filter_map_lex(enum_filter!(Lexeme::Name)) + .map(|(t, _)| t) + .separated_by(Lexeme::NS.parser()) + .then( + Lexeme::NS + .parser() + .ignore_then(choice(( + expr + .clone() + .separated_by(Lexeme::Name(ctx.interner().i(",")).parser()) + .delimited_by( + Lexeme::LP('(').parser(), + Lexeme::RP('(').parser(), + ) + .map(|v| box_flatten(v.into_iter())) + .labelled("import group"), + // Each expr returns a list of imports, flatten into common list + Lexeme::Name(ctx.interner().i("*")) + .parser() + .map(move |_| init_table(ctx.interner().i("*"))) + .labelled("wildcard import"), // Just a *, wrapped + filter_map_lex(enum_filter!(Lexeme::Name)) + .map(|(t, _)| init_table(t)) + .labelled("import terminal"), // Just a name, wrapped + ))) + .or_not(), + ) + .map( + |(name, opt_post): ( + Vec>, + Option>>, + )| + -> BoxedIterIter> { + if let Some(post) = opt_post { + Box::new( + post.map(move |el| box_chain!(name.clone().into_iter(), el)), + ) + } else { + box_once(into_boxed_iter(name)) + } + }, + ) } - }).map(move |paths| { - paths.filter_map(|namespaces| { - let mut path = namespaces.collect_vec(); - let name = path.pop()?; - Some(Import { - path: ctx.interner().i(&path), - name: { - if name == ctx.interner().i("*") { None } - else { Some(name) } - } + }) + .map(move |paths| { + paths + .filter_map(|namespaces| { + let mut path = namespaces.collect_vec(); + let name = path.pop()?; + Some(Import { + path: ctx.interner().i(&path), + name: { + if name == ctx.interner().i("*") { + None + } else { + Some(name) + } + }, + }) }) - }).collect() - }).labelled("import") + .collect() + }) + .labelled("import") } diff --git a/src/parse/lexer.rs b/src/parse/lexer.rs index 07f56e1..56d4349 100644 --- a/src/parse/lexer.rs +++ b/src/parse/lexer.rs @@ -1,31 +1,36 @@ use std::fmt; use std::ops::Range; +use chumsky::prelude::*; +use chumsky::text::keyword; +use chumsky::{Parser, Span}; use ordered_float::NotNan; -use chumsky::{Parser, prelude::*, text::keyword, Span}; - -use crate::ast::{Placeholder, PHClass}; -use crate::representations::Literal; -use crate::interner::{Token, InternedDisplay, Interner}; use super::context::Context; -use super::placeholder; -use super::{number, string, name, comment}; +use super::decls::SimpleParser; +use super::{comment, name, number, placeholder, string}; +use crate::ast::{PHClass, Placeholder}; +use crate::interner::{InternedDisplay, Interner, Tok}; +use crate::representations::Literal; #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct Entry{ +pub struct Entry { pub lexeme: Lexeme, - pub range: Range + pub range: Range, } impl Entry { pub fn is_filler(&self) -> bool { matches!(self.lexeme, Lexeme::Comment(_)) - || matches!(self.lexeme, Lexeme::BR) + || matches!(self.lexeme, Lexeme::BR) } } impl InternedDisplay for Entry { - fn fmt_i(&self, f: &mut std::fmt::Formatter<'_>, i: &Interner) -> std::fmt::Result { + fn fmt_i( + &self, + f: &mut std::fmt::Formatter<'_>, + i: &Interner, + ) -> std::fmt::Result { self.lexeme.fmt_i(f, i) } } @@ -40,21 +45,24 @@ impl Span for Entry { type Context = Lexeme; type Offset = usize; - fn context(&self) -> Self::Context {self.lexeme.clone()} - fn start(&self) -> Self::Offset {self.range.start()} - fn end(&self) -> Self::Offset {self.range.end()} + fn context(&self) -> Self::Context { + self.lexeme.clone() + } + fn start(&self) -> Self::Offset { + self.range.start() + } + fn end(&self) -> Self::Offset { + self.range.end() + } fn new(context: Self::Context, range: Range) -> Self { - Self{ - lexeme: context, - range - } + Self { lexeme: context, range } } } #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum Lexeme { Literal(Literal), - Name(Token), + Name(Tok), Rule(NotNan), /// Walrus operator (formerly shorthand macro) Const, @@ -74,11 +82,15 @@ pub enum Lexeme { Export, Import, Namespace, - PH(Placeholder) + PH(Placeholder), } impl InternedDisplay for Lexeme { - fn fmt_i(&self, f: &mut std::fmt::Formatter<'_>, i: &Interner) -> std::fmt::Result { + fn fmt_i( + &self, + f: &mut std::fmt::Formatter<'_>, + i: &Interner, + ) -> std::fmt::Result { match self { Self::Literal(l) => write!(f, "{:?}", l), Self::Name(token) => write!(f, "{}", i.r(*token)), @@ -90,9 +102,9 @@ impl InternedDisplay for Lexeme { '(' => write!(f, ")"), '[' => write!(f, "]"), '{' => write!(f, "}}"), - _ => f.debug_tuple("RP").field(l).finish() + _ => f.debug_tuple("RP").field(l).finish(), }, - Self::BR => write!(f, "\n"), + Self::BR => writeln!(f), Self::BS => write!(f, "\\"), Self::At => write!(f, "@"), Self::Type => write!(f, ":"), @@ -103,27 +115,30 @@ impl InternedDisplay for Lexeme { Self::PH(Placeholder { name, class }) => match *class { PHClass::Scalar => write!(f, "${}", i.r(*name)), PHClass::Vec { nonzero, prio } => { - if nonzero {write!(f, "...")} - else {write!(f, "..")}?; + if nonzero { + write!(f, "...") + } else { + write!(f, "..") + }?; write!(f, "${}", i.r(*name))?; - if prio != 0 {write!(f, ":{}", prio)?;}; + if prio != 0 { + write!(f, ":{}", prio)?; + }; Ok(()) - } - } + }, + }, } } } impl Lexeme { pub fn rule(prio: impl Into) -> Self { - Lexeme::Rule( - NotNan::new(prio.into()) - .expect("Rule priority cannot be NaN") - ) + Lexeme::Rule(NotNan::new(prio.into()).expect("Rule priority cannot be NaN")) } - pub fn parser>(self) - -> impl Parser + Clone { + pub fn parser>( + self, + ) -> impl Parser + Clone { filter(move |ent: &Entry| ent.lexeme == self) } } @@ -141,16 +156,14 @@ impl InternedDisplay for LexedText { } } -fn paren_parser(lp: char, rp: char) --> impl Parser> -{ - just(lp).to(Lexeme::LP(lp)) - .or(just(rp).to(Lexeme::RP(lp))) +fn paren_parser(lp: char, rp: char) -> impl SimpleParser { + just(lp).to(Lexeme::LP(lp)).or(just(rp).to(Lexeme::RP(lp))) } -pub fn literal_parser() -> impl Parser> { +pub fn literal_parser() -> impl SimpleParser { choice(( - number::int_parser().map(Literal::Uint), // all ints are valid floats so it takes precedence + // all ints are valid floats so it takes precedence + number::int_parser().map(Literal::Uint), number::float_parser().map(Literal::Num), string::char_parser().map(Literal::Char), string::str_parser().map(Literal::Str), @@ -159,10 +172,12 @@ pub fn literal_parser() -> impl Parser> { pub static BASE_OPS: &[&str] = &[",", ".", "..", "..."]; -pub fn lexer<'a>(ctx: impl Context + 'a) --> impl Parser, Error=Simple> + 'a -{ - let all_ops = ctx.ops().iter() +pub fn lexer<'a>( + ctx: impl Context + 'a, +) -> impl SimpleParser> + 'a { + let all_ops = ctx + .ops() + .iter() .map(|op| op.as_ref()) .chain(BASE_OPS.iter().cloned()) .map(str::to_string) @@ -175,7 +190,10 @@ pub fn lexer<'a>(ctx: impl Context + 'a) paren_parser('[', ']'), paren_parser('{', '}'), just(":=").to(Lexeme::Const), - just("=").ignore_then(number::float_parser()).then_ignore(just("=>")).map(Lexeme::rule), + just("=") + .ignore_then(number::float_parser()) + .then_ignore(just("=>")) + .map(Lexeme::rule), comment::comment_parser().map(Lexeme::Comment), just("::").to(Lexeme::NS), just('\\').to(Lexeme::BS), @@ -184,20 +202,18 @@ pub fn lexer<'a>(ctx: impl Context + 'a) just('\n').to(Lexeme::BR), placeholder::placeholder_parser(ctx.clone()).map(Lexeme::PH), literal_parser().map(Lexeme::Literal), - name::name_parser(&all_ops).map(move |n| { - Lexeme::Name(ctx.interner().i(&n)) - }) + name::name_parser(&all_ops) + .map(move |n| Lexeme::Name(ctx.interner().i(&n))), )) - .map_with_span(|lexeme, range| Entry{ lexeme, range }) - .padded_by(one_of(" \t").repeated()) - .repeated() - .then_ignore(end()) + .map_with_span(|lexeme, range| Entry { lexeme, range }) + .padded_by(one_of(" \t").repeated()) + .repeated() + .then_ignore(end()) } - pub fn filter_map_lex<'a, O, M: ToString>( - f: impl Fn(Lexeme) -> Result + Clone + 'a -) -> impl Parser), Error = Simple> + Clone + 'a { + f: impl Fn(Lexeme) -> Result + Clone + 'a, +) -> impl SimpleParser)> + Clone + 'a { filter_map(move |s: Range, e: Entry| { let out = f(e.lexeme).map_err(|msg| Simple::custom(s.clone(), msg))?; Ok((out, s)) diff --git a/src/parse/mod.rs b/src/parse/mod.rs index fff5923..4c782e0 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -1,19 +1,20 @@ -mod string; -mod number; -mod name; -mod lexer; mod comment; -mod expression; -mod sourcefile; -mod import; -mod parse; -mod enum_filter; -mod placeholder; mod context; +mod decls; +mod enum_filter; +mod expression; +mod facade; +mod import; +mod lexer; +mod name; +mod number; +mod placeholder; +mod sourcefile; +mod string; -pub use sourcefile::line_parser; -pub use lexer::{lexer, Lexeme, Entry}; +pub use context::ParsingContext; +pub use facade::{parse, ParseError}; +pub use lexer::{lexer, Entry, Lexeme}; pub use name::is_op; -pub use parse::{parse, ParseError}; pub use number::{float_parser, int_parser}; -pub use context::ParsingContext; \ No newline at end of file +pub use sourcefile::line_parser; diff --git a/src/parse/name.rs b/src/parse/name.rs index deb30d8..de7cf77 100644 --- a/src/parse/name.rs +++ b/src/parse/name.rs @@ -1,22 +1,28 @@ -use chumsky::{self, prelude::*, Parser}; +use chumsky::prelude::*; +use chumsky::{self, Parser}; + +use super::decls::{BoxedSimpleParser, SimpleParser}; /// Matches any one of the passed operators, preferring longer ones -fn op_parser<'a>(ops: &[impl AsRef + Clone]) --> BoxedParser<'a, char, String, Simple> -{ - let mut sorted_ops: Vec = ops.iter() - .map(|t| t.as_ref().to_string()).collect(); +fn op_parser<'a>( + ops: &[impl AsRef + Clone], +) -> BoxedSimpleParser<'a, char, String> { + let mut sorted_ops: Vec = + ops.iter().map(|t| t.as_ref().to_string()).collect(); sorted_ops.sort_by_key(|op| -(op.len() as i64)); - sorted_ops.into_iter() + sorted_ops + .into_iter() .map(|op| just(op).boxed()) .reduce(|a, b| a.or(b).boxed()) .unwrap_or_else(|| { empty().map(|()| panic!("Empty isn't meant to match")).boxed() - }).labelled("operator").boxed() + }) + .labelled("operator") + .boxed() } /// Characters that cannot be parsed as part of an operator -/// +/// /// The initial operator list overrides this. static NOT_NAME_CHAR: &[char] = &[ ':', // used for namespacing and type annotations @@ -28,35 +34,34 @@ static NOT_NAME_CHAR: &[char] = &[ ]; /// Matches anything that's allowed as an operator -/// +/// /// FIXME: `@name` without a dot should be parsed correctly for overrides. /// Could be an operator but then parametrics should take precedence, /// which might break stuff. investigate. -/// +/// /// TODO: `'` could work as an operator whenever it isn't closed. /// It's common im maths so it's worth a try -/// +/// /// TODO: `.` could possibly be parsed as an operator in some contexts. /// This operator is very common in maths so it's worth a try. /// Investigate. -pub fn modname_parser<'a>() --> impl Parser> + 'a -{ +pub fn modname_parser<'a>() -> impl SimpleParser + 'a { filter(move |c| !NOT_NAME_CHAR.contains(c) && !c.is_whitespace()) - .repeated().at_least(1) + .repeated() + .at_least(1) .collect() .labelled("modname") } /// Parse an operator or name. Failing both, parse everything up to /// the next whitespace or blacklisted character as a new operator. -pub fn name_parser<'a>(ops: &[impl AsRef + Clone]) --> impl Parser> + 'a -{ +pub fn name_parser<'a>( + ops: &[impl AsRef + Clone], +) -> impl SimpleParser + 'a { choice(( op_parser(ops), // First try to parse a known operator text::ident().labelled("plain text"), // Failing that, parse plain text - modname_parser() // Finally parse everything until tne next forbidden char + modname_parser(), // Finally parse everything until tne next forbidden char )) .labelled("name") } @@ -65,7 +70,7 @@ pub fn name_parser<'a>(ops: &[impl AsRef + Clone]) /// and text, just not at the start. pub fn is_op(s: impl AsRef) -> bool { return match s.as_ref().chars().next() { - Some(x) => !x.is_alphanumeric(), - None => false - } + Some(x) => !x.is_alphanumeric(), + None => false, + }; } diff --git a/src/parse/number.rs b/src/parse/number.rs index 8510e18..05fe4ca 100644 --- a/src/parse/number.rs +++ b/src/parse/number.rs @@ -1,6 +1,9 @@ -use chumsky::{self, prelude::*, Parser}; +use chumsky::prelude::*; +use chumsky::{self, Parser}; use ordered_float::NotNan; +use super::decls::SimpleParser; + fn assert_not_digit(base: u32, c: char) { if base > (10 + (c as u32 - 'a' as u32)) { panic!("The character '{}' is a digit in base ({})", c, base) @@ -8,9 +11,9 @@ fn assert_not_digit(base: u32, c: char) { } /// Parse an arbitrarily grouped sequence of digits starting with an underscore. -/// +/// /// TODO: this should use separated_by and parse the leading group too -fn separated_digits_parser(base: u32) -> impl Parser> { +fn separated_digits_parser(base: u32) -> impl SimpleParser { just('_') .ignore_then(text::digits(base)) .repeated() @@ -18,57 +21,62 @@ fn separated_digits_parser(base: u32) -> impl Parser impl Parser> { - text::int(base) - .then(separated_digits_parser(base)) - .map(move |(s1, s2): (String, String)| { +fn uint_parser(base: u32) -> impl SimpleParser { + text::int(base).then(separated_digits_parser(base)).map( + move |(s1, s2): (String, String)| { u64::from_str_radix(&(s1 + &s2), base).unwrap() - }) + }, + ) } /// parse exponent notation, or return 0 as the default exponent. -/// The exponent is always in decimal. -fn pow_parser() -> impl Parser> { +/// The exponent is always in decimal. +fn pow_parser() -> impl SimpleParser { choice(( - just('p') - .ignore_then(text::int(10)) - .map(|s: String| s.parse().unwrap()), + just('p').ignore_then(text::int(10)).map(|s: String| s.parse().unwrap()), just("p-") .ignore_then(text::int(10)) .map(|s: String| -s.parse::().unwrap()), - )).or_else(|_| Ok(0)) + )) + .or_else(|_| Ok(0)) } /// returns a mapper that converts a mantissa and an exponent into an uint -/// +/// /// TODO it panics if it finds a negative exponent -fn nat2u(base: u64) -> impl Fn((u64, i32),) -> u64 { +fn nat2u(base: u64) -> impl Fn((u64, i32)) -> u64 { move |(val, exp)| { - if exp == 0 {val} - else {val * base.checked_pow(exp.try_into().unwrap()).unwrap()} + if exp == 0 { + val + } else { + val * base.checked_pow(exp.try_into().unwrap()).unwrap() + } } } /// returns a mapper that converts a mantissa and an exponent into a float -fn nat2f(base: u64) -> impl Fn((NotNan, i32),) -> NotNan { +fn nat2f(base: u64) -> impl Fn((NotNan, i32)) -> NotNan { move |(val, exp)| { - if exp == 0 {val} - else {val * (base as f64).powf(exp.try_into().unwrap())} + if exp == 0 { + val + } else { + val * (base as f64).powf(exp.try_into().unwrap()) + } } } /// parse an uint from exponential notation (panics if 'p' is a digit in base) -fn pow_uint_parser(base: u32) -> impl Parser> { +fn pow_uint_parser(base: u32) -> impl SimpleParser { assert_not_digit(base, 'p'); uint_parser(base).then(pow_parser()).map(nat2u(base.into())) } /// parse an uint from a base determined by its prefix or lack thereof -/// +/// /// Not to be confused with [uint_parser] which is a component of it. -pub fn int_parser() -> impl Parser> { +pub fn int_parser() -> impl SimpleParser { choice(( just("0b").ignore_then(pow_uint_parser(2)), just("0x").ignore_then(pow_uint_parser(16)), @@ -78,35 +86,40 @@ pub fn int_parser() -> impl Parser> { } /// parse a float from dot notation -fn dotted_parser(base: u32) -> impl Parser, Error = Simple> { +fn dotted_parser(base: u32) -> impl SimpleParser> { uint_parser(base) - .then( - just('.').ignore_then( - text::digits(base).then(separated_digits_parser(base)) - ).map(move |(frac1, frac2)| { - let frac = frac1 + &frac2; - let frac_num = u64::from_str_radix(&frac, base).unwrap() as f64; - let dexp = base.pow(frac.len().try_into().unwrap()); - frac_num / dexp as f64 - }).or_not().map(|o| o.unwrap_or_default()) - ).try_map(|(wh, f), s| { - NotNan::new(wh as f64 + f).map_err(|_| Simple::custom(s, "Float literal evaluates to NaN")) - }) + .then( + just('.') + .ignore_then(text::digits(base).then(separated_digits_parser(base))) + .map(move |(frac1, frac2)| { + let frac = frac1 + &frac2; + let frac_num = u64::from_str_radix(&frac, base).unwrap() as f64; + let dexp = base.pow(frac.len().try_into().unwrap()); + frac_num / dexp as f64 + }) + .or_not() + .map(|o| o.unwrap_or_default()), + ) + .try_map(|(wh, f), s| { + NotNan::new(wh as f64 + f) + .map_err(|_| Simple::custom(s, "Float literal evaluates to NaN")) + }) } /// parse a float from dotted and optionally also exponential notation -fn pow_float_parser(base: u32) -> impl Parser, Error = Simple> { +fn pow_float_parser(base: u32) -> impl SimpleParser> { assert_not_digit(base, 'p'); dotted_parser(base).then(pow_parser()).map(nat2f(base.into())) } -/// parse a float with dotted and optionally exponential notation from a base determined by its -/// prefix -pub fn float_parser() -> impl Parser, Error = Simple> { +/// parse a float with dotted and optionally exponential notation from a base +/// determined by its prefix +pub fn float_parser() -> impl SimpleParser> { choice(( just("0b").ignore_then(pow_float_parser(2)), just("0x").ignore_then(pow_float_parser(16)), just('0').ignore_then(pow_float_parser(8)), pow_float_parser(10), - )).labelled("float") + )) + .labelled("float") } diff --git a/src/parse/placeholder.rs b/src/parse/placeholder.rs index dab468c..c14da2f 100644 --- a/src/parse/placeholder.rs +++ b/src/parse/placeholder.rs @@ -1,16 +1,18 @@ -use chumsky::{Parser, prelude::*}; +use chumsky::prelude::*; +use chumsky::Parser; -use crate::ast::{Placeholder, PHClass}; +use super::context::Context; +use super::decls::SimpleParser; +use super::number::int_parser; +use crate::ast::{PHClass, Placeholder}; -use super::{number::int_parser, context::Context}; - -pub fn placeholder_parser<'a>(ctx: impl Context + 'a) --> impl Parser> + 'a -{ +pub fn placeholder_parser( + ctx: impl Context, +) -> impl SimpleParser { choice(( just("...").to(Some(true)), just("..").to(Some(false)), - empty().to(None) + empty().to(None), )) .then(just("$").ignore_then(text::ident())) .then(just(":").ignore_then(int_parser()).or_not()) @@ -19,12 +21,10 @@ pub fn placeholder_parser<'a>(ctx: impl Context + 'a) if let Some(nonzero) = vec_nonzero { let prio = vec_prio.unwrap_or_default(); Ok(Placeholder { name, class: PHClass::Vec { nonzero, prio } }) + } else if vec_prio.is_some() { + Err(Simple::custom(span, "Scalar placeholders have no priority")) } else { - if vec_prio.is_some() { - Err(Simple::custom(span, "Scalar placeholders have no priority")) - } else { - Ok(Placeholder { name, class: PHClass::Scalar }) - } + Ok(Placeholder { name, class: PHClass::Scalar }) } }) } diff --git a/src/parse/sourcefile.rs b/src/parse/sourcefile.rs index e2357cd..868c398 100644 --- a/src/parse/sourcefile.rs +++ b/src/parse/sourcefile.rs @@ -1,55 +1,67 @@ use std::iter; use std::rc::Rc; -use crate::representations::location::Location; -use crate::representations::sourcefile::{FileEntry, Member}; -use crate::enum_filter; -use crate::ast::{Rule, Constant, Expr, Clause}; -use crate::interner::Token; - -use super::Entry; -use super::context::Context; -use super::expression::xpr_parser; -use super::import::import_parser; -use super::lexer::{Lexeme, filter_map_lex}; - -use chumsky::{Parser, prelude::*}; +use chumsky::prelude::*; +use chumsky::Parser; use itertools::Itertools; -fn rule_parser<'a>(ctx: impl Context + 'a) --> impl Parser> + 'a -{ - xpr_parser(ctx.clone()).repeated().at_least(1) +use super::context::Context; +use super::decls::{SimpleParser, SimpleRecursive}; +use super::expression::xpr_parser; +use super::import::import_parser; +use super::lexer::{filter_map_lex, Lexeme}; +use super::Entry; +use crate::ast::{Clause, Constant, Expr, Rule}; +use crate::enum_filter; +use crate::representations::location::Location; +use crate::representations::sourcefile::{FileEntry, Member, Namespace}; + +fn rule_parser<'a>( + ctx: impl Context + 'a, +) -> impl SimpleParser + 'a { + xpr_parser(ctx.clone()) + .repeated() + .at_least(1) .then(filter_map_lex(enum_filter!(Lexeme::Rule))) .then(xpr_parser(ctx).repeated().at_least(1)) - .map(|((s, (prio, _)), t)| Rule{ + .map(|((s, (prio, _)), t)| Rule { source: Rc::new(s), prio, - target: Rc::new(t) - }).labelled("Rule") + target: Rc::new(t), + }) + .labelled("Rule") } -fn const_parser<'a>(ctx: impl Context + 'a) --> impl Parser> + 'a -{ +fn const_parser<'a>( + ctx: impl Context + 'a, +) -> impl SimpleParser + 'a { filter_map_lex(enum_filter!(Lexeme::Name)) .then_ignore(Lexeme::Const.parser()) .then(xpr_parser(ctx.clone()).repeated().at_least(1)) - .map(move |((name, _), value)| Constant{ + .map(move |((name, _), value)| Constant { name, - value: if let Ok(ex) = value.iter().exactly_one() { ex.clone() } - else { - let start = value.first().expect("value cannot be empty") - .location.range().expect("all locations in parsed source are known") + value: if let Ok(ex) = value.iter().exactly_one() { + ex.clone() + } else { + let start = value + .first() + .expect("value cannot be empty") + .location + .range() + .expect("all locations in parsed source are known") .start; - let end = value.last().expect("asserted right above") - .location.range().expect("all locations in parsed source are known") + let end = value + .last() + .expect("asserted right above") + .location + .range() + .expect("all locations in parsed source are known") .end; - Expr{ + Expr { location: Location::Range { file: ctx.file(), range: start..end }, - value: Clause::S('(', Rc::new(value)) + value: Clause::S('(', Rc::new(value)), } - } + }, }) } @@ -60,56 +72,61 @@ pub fn collect_errors>(e: Vec) -> E { } fn namespace_parser<'a>( - line: impl Parser> + 'a, -) -> impl Parser, Vec), Error = Simple> + 'a { - Lexeme::Namespace.parser() - .ignore_then(filter_map_lex(enum_filter!(Lexeme::Name))) - .then( - any().repeated().delimited_by( - Lexeme::LP('(').parser(), - Lexeme::RP('(').parser() - ).try_map(move |body, _| { - split_lines(&body) - .map(|l| line.parse(l)) - .collect::,_>>() - .map_err(collect_errors) - }) - ).map(move |((name, _), body)| { - (name, body) - }) + line: impl SimpleParser + 'a, +) -> impl SimpleParser + 'a { + Lexeme::Namespace + .parser() + .ignore_then(filter_map_lex(enum_filter!(Lexeme::Name))) + .then( + any() + .repeated() + .delimited_by(Lexeme::LP('(').parser(), Lexeme::RP('(').parser()) + .try_map(move |body, _| { + split_lines(&body) + .map(|l| line.parse(l)) + .collect::, _>>() + .map_err(collect_errors) + }), + ) + .map(move |((name, _), body)| Namespace { name, body }) } fn member_parser<'a>( - line: impl Parser> + 'a, - ctx: impl Context + 'a -) -> impl Parser> + 'a { + line: impl SimpleParser + 'a, + ctx: impl Context + 'a, +) -> impl SimpleParser + 'a { choice(( - namespace_parser(line) - .map(|(name, body)| Member::Namespace(name, body)), + namespace_parser(line).map(Member::Namespace), rule_parser(ctx.clone()).map(Member::Rule), const_parser(ctx).map(Member::Constant), )) } -pub fn line_parser<'a>(ctx: impl Context + 'a) --> impl Parser> + 'a -{ - recursive(|line: Recursive>| { +pub fn line_parser<'a>( + ctx: impl Context + 'a, +) -> impl SimpleParser + 'a { + recursive(|line: SimpleRecursive| { choice(( // In case the usercode wants to parse doc - filter_map_lex(enum_filter!(Lexeme >> FileEntry; Comment)).map(|(ent, _)| ent), + filter_map_lex(enum_filter!(Lexeme >> FileEntry; Comment)) + .map(|(ent, _)| ent), // plain old imports - Lexeme::Import.parser() + Lexeme::Import + .parser() .ignore_then(import_parser(ctx.clone()).map(FileEntry::Import)), Lexeme::Export.parser().ignore_then(choice(( // token collection - Lexeme::NS.parser().ignore_then( - filter_map_lex(enum_filter!(Lexeme::Name)).map(|(e, _)| e) - .separated_by(Lexeme::Name(ctx.interner().i(",")).parser()) - .delimited_by(Lexeme::LP('(').parser(), Lexeme::RP('(').parser()) - ).map(FileEntry::Export), + Lexeme::NS + .parser() + .ignore_then( + filter_map_lex(enum_filter!(Lexeme::Name)) + .map(|(e, _)| e) + .separated_by(Lexeme::Name(ctx.interner().i(",")).parser()) + .delimited_by(Lexeme::LP('(').parser(), Lexeme::RP('(').parser()), + ) + .map(FileEntry::Export), // public declaration - member_parser(line.clone(), ctx.clone()).map(FileEntry::Exported) + member_parser(line.clone(), ctx.clone()).map(FileEntry::Exported), ))), // This could match almost anything so it has to go last member_parser(line, ctx).map(FileEntry::Internal), @@ -123,13 +140,13 @@ pub fn split_lines(data: &[Entry]) -> impl Iterator { let mut finished = false; iter::from_fn(move || { let mut paren_count = 0; - while let Some((i, Entry{ lexeme, .. })) = source.next() { + for (i, Entry { lexeme, .. }) in source.by_ref() { match lexeme { Lexeme::LP(_) => paren_count += 1, Lexeme::RP(_) => paren_count -= 1, Lexeme::BR if paren_count == 0 => { let begin = last_slice; - last_slice = i+1; + last_slice = i + 1; return Some(&data[begin..i]); }, _ => (), @@ -138,8 +155,9 @@ pub fn split_lines(data: &[Entry]) -> impl Iterator { // Include last line even without trailing newline if !finished { finished = true; - return Some(&data[last_slice..]) + return Some(&data[last_slice..]); } None - }).filter(|s| s.len() > 0) + }) + .filter(|s| !s.is_empty()) } diff --git a/src/parse/string.rs b/src/parse/string.rs index 9d56f26..2f512c3 100644 --- a/src/parse/string.rs +++ b/src/parse/string.rs @@ -1,7 +1,10 @@ -use chumsky::{self, prelude::*, Parser}; +use chumsky::prelude::*; +use chumsky::{self, Parser}; + +use super::decls::SimpleParser; /// Parses a text character that is not the specified delimiter -fn text_parser(delim: char) -> impl Parser> { +fn text_parser(delim: char) -> impl SimpleParser { // Copied directly from Chumsky's JSON example. let escape = just('\\').ignore_then( just('\\') @@ -12,35 +15,39 @@ fn text_parser(delim: char) -> impl Parser> { .or(just('n').to('\n')) .or(just('r').to('\r')) .or(just('t').to('\t')) - .or(just('u').ignore_then( - filter(|c: &char| c.is_ascii_hexdigit()) - .repeated() - .exactly(4) - .collect::() - .validate(|digits, span, emit| { - char::from_u32(u32::from_str_radix(&digits, 16).unwrap()) - .unwrap_or_else(|| { - emit(Simple::custom(span, "invalid unicode character")); - '\u{FFFD}' // unicode replacement character - }) - }), - )), + .or( + just('u').ignore_then( + filter(|c: &char| c.is_ascii_hexdigit()) + .repeated() + .exactly(4) + .collect::() + .validate(|digits, span, emit| { + char::from_u32(u32::from_str_radix(&digits, 16).unwrap()) + .unwrap_or_else(|| { + emit(Simple::custom(span, "invalid unicode character")); + '\u{FFFD}' // unicode replacement character + }) + }), + ), + ), ); filter(move |&c| c != '\\' && c != delim).or(escape) } /// Parse a character literal between single quotes -pub fn char_parser() -> impl Parser> { +pub fn char_parser() -> impl SimpleParser { just('\'').ignore_then(text_parser('\'')).then_ignore(just('\'')) } /// Parse a string between double quotes -pub fn str_parser() -> impl Parser> { +pub fn str_parser() -> impl SimpleParser { just('"') - .ignore_then( - text_parser('"').map(Some) + .ignore_then( + text_parser('"').map(Some) .or(just("\\\n").map(|_| None)) // Newlines preceded by backslashes are ignored. - .repeated() - ).then_ignore(just('"')) - .flatten().collect() + .repeated(), + ) + .then_ignore(just('"')) + .flatten() + .collect() } diff --git a/src/pipeline/error/mod.rs b/src/pipeline/error/mod.rs index dfae3b4..6975c90 100644 --- a/src/pipeline/error/mod.rs +++ b/src/pipeline/error/mod.rs @@ -1,15 +1,15 @@ -mod project_error; -mod parse_error_with_path; -mod unexpected_directory; mod module_not_found; mod not_exported; +mod parse_error_with_path; +mod project_error; mod too_many_supers; +mod unexpected_directory; mod visibility_mismatch; -pub use project_error::{ErrorPosition, ProjectError}; -pub use parse_error_with_path::ParseErrorWithPath; -pub use unexpected_directory::UnexpectedDirectory; pub use module_not_found::ModuleNotFound; pub use not_exported::NotExported; +pub use parse_error_with_path::ParseErrorWithPath; +pub use project_error::{ErrorPosition, ProjectError}; pub use too_many_supers::TooManySupers; -pub use visibility_mismatch::VisibilityMismatch; \ No newline at end of file +pub use unexpected_directory::UnexpectedDirectory; +pub use visibility_mismatch::VisibilityMismatch; diff --git a/src/pipeline/error/module_not_found.rs b/src/pipeline/error/module_not_found.rs index c573a15..12cc7e5 100644 --- a/src/pipeline/error/module_not_found.rs +++ b/src/pipeline/error/module_not_found.rs @@ -1,16 +1,16 @@ -use crate::utils::{BoxedIter, iter::box_once}; - -use super::{ProjectError, ErrorPosition}; +use super::{ErrorPosition, ProjectError}; +use crate::utils::iter::box_once; +use crate::utils::BoxedIter; /// Error produced when an import refers to a nonexistent module #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ModuleNotFound { pub file: Vec, - pub subpath: Vec + pub subpath: Vec, } impl ProjectError for ModuleNotFound { fn description(&self) -> &str { - "an import refers to a nonexistent module" + "an import refers to a nonexistent module" } fn message(&self) -> String { format!( @@ -22,4 +22,4 @@ impl ProjectError for ModuleNotFound { fn positions(&self) -> BoxedIter { box_once(ErrorPosition::just_file(self.file.clone())) } -} \ No newline at end of file +} diff --git a/src/pipeline/error/not_exported.rs b/src/pipeline/error/not_exported.rs index b617c99..176ed59 100644 --- a/src/pipeline/error/not_exported.rs +++ b/src/pipeline/error/not_exported.rs @@ -1,8 +1,8 @@ use std::rc::Rc; -use crate::{utils::BoxedIter, representations::location::Location}; - -use super::{ProjectError, ErrorPosition}; +use super::{ErrorPosition, ProjectError}; +use crate::representations::location::Location; +use crate::utils::BoxedIter; #[derive(Debug)] pub struct NotExported { @@ -16,21 +16,21 @@ impl ProjectError for NotExported { "An import refers to a symbol that exists but isn't exported" } fn positions(&self) -> BoxedIter { - Box::new([ - ErrorPosition{ - location: Location::File(Rc::new(self.file.clone())), - message: Some(format!( - "{} isn't exported", - self.subpath.join("::") - )), - }, - ErrorPosition{ - location: Location::File(Rc::new(self.referrer_file.clone())), - message: Some(format!( - "{} cannot see this symbol", - self.referrer_subpath.join("::") - )), - } - ].into_iter()) + Box::new( + [ + ErrorPosition { + location: Location::File(Rc::new(self.file.clone())), + message: Some(format!("{} isn't exported", self.subpath.join("::"))), + }, + ErrorPosition { + location: Location::File(Rc::new(self.referrer_file.clone())), + message: Some(format!( + "{} cannot see this symbol", + self.referrer_subpath.join("::") + )), + }, + ] + .into_iter(), + ) } -} \ No newline at end of file +} diff --git a/src/pipeline/error/parse_error_with_path.rs b/src/pipeline/error/parse_error_with_path.rs index ff0c3ec..34a0855 100644 --- a/src/pipeline/error/parse_error_with_path.rs +++ b/src/pipeline/error/parse_error_with_path.rs @@ -1,21 +1,21 @@ use std::rc::Rc; +use super::{ErrorPosition, ProjectError}; +use crate::parse::ParseError; use crate::representations::location::Location; use crate::utils::BoxedIter; -use crate::parse::ParseError; - -use super::ErrorPosition; -use super::ProjectError; /// Produced by stages that parse text when it fails. #[derive(Debug)] pub struct ParseErrorWithPath { pub full_source: String, pub path: Vec, - pub error: ParseError + pub error: ParseError, } impl ProjectError for ParseErrorWithPath { - fn description(&self) -> &str {"Failed to parse code"} + fn description(&self) -> &str { + "Failed to parse code" + } fn positions(&self) -> BoxedIter { match &self.error { ParseError::Lex(lex) => Box::new(lex.iter().map(|s| ErrorPosition { @@ -23,15 +23,20 @@ impl ProjectError for ParseErrorWithPath { file: Rc::new(self.path.clone()), range: s.span(), }, - message: Some(s.to_string()) + message: Some(s.to_string()), })), - ParseError::Ast(ast) => Box::new(ast.iter().map(|(_i, s)| ErrorPosition { - location: s.found().map(|e| Location::Range { - file: Rc::new(self.path.clone()), - range: e.range.clone() - }).unwrap_or_else(|| Location::File(Rc::new(self.path.clone()))), - message: Some(s.label().unwrap_or("Parse error").to_string()) + ParseError::Ast(ast) => Box::new(ast.iter().map(|(_i, s)| { + ErrorPosition { + location: s + .found() + .map(|e| Location::Range { + file: Rc::new(self.path.clone()), + range: e.range.clone(), + }) + .unwrap_or_else(|| Location::File(Rc::new(self.path.clone()))), + message: Some(s.label().unwrap_or("Parse error").to_string()), + } })), } } -} \ No newline at end of file +} diff --git a/src/pipeline/error/project_error.rs b/src/pipeline/error/project_error.rs index 2d18de0..8f09de0 100644 --- a/src/pipeline/error/project_error.rs +++ b/src/pipeline/error/project_error.rs @@ -8,7 +8,7 @@ use crate::utils::BoxedIter; /// processing got stuck, a command that is likely to be incorrect pub struct ErrorPosition { pub location: Location, - pub message: Option + pub message: Option, } impl ErrorPosition { @@ -24,12 +24,17 @@ pub trait ProjectError: Debug { /// A general description of this type of error fn description(&self) -> &str; /// A formatted message that includes specific parameters - fn message(&self) -> String {String::new()} + fn message(&self) -> String { + String::new() + } /// Code positions relevant to this error fn positions(&self) -> BoxedIter; /// Convert the error into an [Rc] to be able to /// handle various errors together - fn rc(self) -> Rc where Self: Sized + 'static { + fn rc(self) -> Rc + where + Self: Sized + 'static, + { Rc::new(self) } } @@ -41,10 +46,12 @@ impl Display for dyn ProjectError { let positions = self.positions(); write!(f, "Problem with the project: {description}; {message}")?; for ErrorPosition { location, message } in positions { - write!(f, "@{location}: {}", + write!( + f, + "@{location}: {}", message.unwrap_or("location of interest".to_string()) )? } Ok(()) } -} \ No newline at end of file +} diff --git a/src/pipeline/error/too_many_supers.rs b/src/pipeline/error/too_many_supers.rs index 0fd7e40..a44340d 100644 --- a/src/pipeline/error/too_many_supers.rs +++ b/src/pipeline/error/too_many_supers.rs @@ -1,8 +1,9 @@ use std::rc::Rc; -use crate::{utils::{BoxedIter, iter::box_once}, representations::location::Location}; - -use super::{ProjectError, ErrorPosition}; +use super::{ErrorPosition, ProjectError}; +use crate::representations::location::Location; +use crate::utils::iter::box_once; +use crate::utils::BoxedIter; /// Error produced when an import path starts with more `super` segments /// than the current module's absolute path @@ -10,12 +11,12 @@ use super::{ProjectError, ErrorPosition}; pub struct TooManySupers { pub path: Vec, pub offender_file: Vec, - pub offender_mod: Vec + pub offender_mod: Vec, } impl ProjectError for TooManySupers { fn description(&self) -> &str { - "an import path starts with more `super` segments than \ - the current module's absolute path" + "an import path starts with more `super` segments than the current \ + module's absolute path" } fn message(&self) -> String { format!( @@ -32,7 +33,7 @@ impl ProjectError for TooManySupers { "path {} in {} contains too many `super` steps.", self.path.join("::"), self.offender_mod.join("::") - )) + )), }) } -} \ No newline at end of file +} diff --git a/src/pipeline/error/unexpected_directory.rs b/src/pipeline/error/unexpected_directory.rs index 7968f44..9528ded 100644 --- a/src/pipeline/error/unexpected_directory.rs +++ b/src/pipeline/error/unexpected_directory.rs @@ -1,18 +1,17 @@ -use crate::utils::{BoxedIter, iter::box_once}; - -use super::ErrorPosition; -use super::ProjectError; +use super::{ErrorPosition, ProjectError}; +use crate::utils::iter::box_once; +use crate::utils::BoxedIter; /// Produced when a stage that deals specifically with code encounters /// a path that refers to a directory #[derive(Debug)] pub struct UnexpectedDirectory { - pub path: Vec + pub path: Vec, } impl ProjectError for UnexpectedDirectory { fn description(&self) -> &str { - "A stage that deals specifically with code encountered a path \ - that refers to a directory" + "A stage that deals specifically with code encountered a path that refers \ + to a directory" } fn positions(&self) -> BoxedIter { box_once(ErrorPosition::just_file(self.path.clone())) @@ -23,4 +22,4 @@ impl ProjectError for UnexpectedDirectory { self.path.join("/") ) } -} \ No newline at end of file +} diff --git a/src/pipeline/error/visibility_mismatch.rs b/src/pipeline/error/visibility_mismatch.rs index abf2956..9cc93e0 100644 --- a/src/pipeline/error/visibility_mismatch.rs +++ b/src/pipeline/error/visibility_mismatch.rs @@ -1,13 +1,14 @@ use std::rc::Rc; -use crate::representations::location::Location; -use crate::utils::{BoxedIter, iter::box_once}; -use super::project_error::{ProjectError, ErrorPosition}; +use super::project_error::{ErrorPosition, ProjectError}; +use crate::representations::location::Location; +use crate::utils::iter::box_once; +use crate::utils::BoxedIter; #[derive(Debug)] -pub struct VisibilityMismatch{ +pub struct VisibilityMismatch { pub namespace: Vec, - pub file: Rc> + pub file: Rc>, } impl ProjectError for VisibilityMismatch { fn description(&self) -> &str { @@ -19,7 +20,7 @@ impl ProjectError for VisibilityMismatch { message: Some(format!( "{} is opened multiple times with different visibilities", self.namespace.join("::") - )) + )), }) } } diff --git a/src/pipeline/file_loader.rs b/src/pipeline/file_loader.rs index afa0558..51ee1ac 100644 --- a/src/pipeline/file_loader.rs +++ b/src/pipeline/file_loader.rs @@ -1,25 +1,23 @@ -use std::path::Path; +use std::path::{Path, PathBuf}; use std::rc::Rc; -use std::path::PathBuf; -use std::io; -use std::fs; +use std::{fs, io}; +use crate::interner::{Interner, Sym}; +use crate::pipeline::error::{ + ErrorPosition, ProjectError, UnexpectedDirectory, +}; use crate::utils::iter::box_once; -use crate::utils::{Cache, BoxedIter}; -use crate::interner::{Interner, Token}; -use crate::pipeline::error::UnexpectedDirectory; -use crate::pipeline::error::{ProjectError, ErrorPosition}; +use crate::utils::{BoxedIter, Cache}; #[derive(Debug)] -pub struct FileLoadingError{ +pub struct FileLoadingError { file: io::Error, dir: io::Error, - path: Vec + path: Vec, } impl ProjectError for FileLoadingError { fn description(&self) -> &str { - "Neither a file nor a directory could be read from \ - the requested path" + "Neither a file nor a directory could be read from the requested path" } fn positions(&self) -> BoxedIter { box_once(ErrorPosition::just_file(self.path.clone())) @@ -37,57 +35,55 @@ pub enum Loaded { Collection(Rc>), } impl Loaded { - pub fn is_code(&self) -> bool {matches!(self, Loaded::Code(_))} + pub fn is_code(&self) -> bool { + matches!(self, Loaded::Code(_)) + } } pub type IOResult = Result>; -pub type FileCache<'a> = Cache<'a, Token>>, IOResult>; +pub type FileCache<'a> = Cache<'a, Sym, IOResult>; /// Load a file from a path expressed in Rust strings, but relative to /// a root expressed as an OS Path. pub fn load_file(root: &Path, path: &[impl AsRef]) -> IOResult { - // let os_path = path.into_iter() - // .map_into::() - // .collect::>(); - let full_path = path.iter().fold( - root.to_owned(), - |p, s| p.join(s.as_ref()) - ); + let full_path = path.iter().fold(root.to_owned(), |p, s| p.join(s.as_ref())); let file_path = full_path.with_extension("orc"); - let file_error = match fs::read_to_string(&file_path) { + let file_error = match fs::read_to_string(file_path) { Ok(string) => return Ok(Loaded::Code(Rc::new(string))), - Err(err) => err + Err(err) => err, }; let dir = match fs::read_dir(&full_path) { Ok(dir) => dir, - Err(dir_error) => { - return Err(FileLoadingError { - file: file_error, - dir: dir_error, - path: path.iter() - .map(|s| s.as_ref().to_string()) - .collect(), - }.rc()) - } + Err(dir_error) => + return Err( + FileLoadingError { + file: file_error, + dir: dir_error, + path: path.iter().map(|s| s.as_ref().to_string()).collect(), + } + .rc(), + ), }; - let names = dir.filter_map(Result::ok) + let names = dir + .filter_map(Result::ok) .filter_map(|ent| { let fname = ent.file_name().into_string().ok()?; let ftyp = ent.metadata().ok()?.file_type(); - Some(if ftyp.is_dir() {fname} else { + Some(if ftyp.is_dir() { + fname + } else { fname.strip_suffix(".or")?.to_string() }) - }).collect(); + }) + .collect(); Ok(Loaded::Collection(Rc::new(names))) } /// Generates a cached file loader for a directory pub fn mk_cache(root: PathBuf, i: &Interner) -> FileCache { - Cache::new(move |token: Token>>, _this| -> IOResult { - let path = i.r(token).iter() - .map(|t| i.r(*t).as_str()) - .collect::>(); + Cache::new(move |token: Sym, _this| -> IOResult { + let path = i.r(token).iter().map(|t| i.r(*t).as_str()).collect::>(); load_file(&root, &path) }) } @@ -95,12 +91,18 @@ pub fn mk_cache(root: PathBuf, i: &Interner) -> FileCache { /// Loads the string contents of a file at the given location. /// If the path points to a directory, raises an error. pub fn load_text( - path: Token>>, - load_file: &impl Fn(Token>>) -> IOResult, - i: &Interner + path: Sym, + load_file: &impl Fn(Sym) -> IOResult, + i: &Interner, ) -> Result, Rc> { - if let Loaded::Code(s) = load_file(path)? {Ok(s)} - else {Err(UnexpectedDirectory{ - path: i.r(path).iter().map(|t| i.r(*t)).cloned().collect() - }.rc())} -} \ No newline at end of file + if let Loaded::Code(s) = load_file(path)? { + Ok(s) + } else { + Err( + UnexpectedDirectory { + path: i.r(path).iter().map(|t| i.r(*t)).cloned().collect(), + } + .rc(), + ) + } +} diff --git a/src/pipeline/import_abs_path.rs b/src/pipeline/import_abs_path.rs index 27ef529..a732228 100644 --- a/src/pipeline/import_abs_path.rs +++ b/src/pipeline/import_abs_path.rs @@ -1,32 +1,32 @@ use std::rc::Rc; -use crate::representations::tree::Module; -use crate::representations::sourcefile::absolute_path; -use crate::utils::{Substack}; -use crate::interner::{Token, Interner}; - use super::error::{ProjectError, TooManySupers}; +use crate::interner::{Interner, Tok}; +use crate::representations::sourcefile::absolute_path; +use crate::utils::Substack; pub fn import_abs_path( - src_path: &[Token], - mod_stack: Substack>, - module: &Module, - import_path: &[Token], + src_path: &[Tok], + mod_stack: Substack>, + import_path: &[Tok], i: &Interner, -) -> Result>, Rc> { +) -> Result>, Rc> { // path of module within file let mod_pathv = mod_stack.iter().rev_vec_clone(); // path of module within compilation - let abs_pathv = src_path.iter().copied() + let abs_pathv = src_path + .iter() + .copied() .chain(mod_pathv.iter().copied()) .collect::>(); // preload-target path relative to module // preload-target path within compilation - absolute_path(&abs_pathv, import_path, i, &|n| { - module.items.contains_key(&n) - }).map_err(|_| TooManySupers{ + absolute_path(&abs_pathv, import_path, i).map_err(|_| { + TooManySupers { path: import_path.iter().map(|t| i.r(*t)).cloned().collect(), offender_file: src_path.iter().map(|t| i.r(*t)).cloned().collect(), offender_mod: mod_pathv.iter().map(|t| i.r(*t)).cloned().collect(), - }.rc()) -} \ No newline at end of file + } + .rc() + }) +} diff --git a/src/pipeline/import_resolution/alias_map.rs b/src/pipeline/import_resolution/alias_map.rs index 3880600..63ab3fc 100644 --- a/src/pipeline/import_resolution/alias_map.rs +++ b/src/pipeline/import_resolution/alias_map.rs @@ -1,18 +1,20 @@ -use hashbrown::{HashMap, HashSet}; - use std::hash::Hash; -use crate::interner::Token; +use hashbrown::{HashMap, HashSet}; + +use crate::interner::Sym; #[derive(Clone, Debug, Default)] -pub struct AliasMap{ - pub targets: HashMap>>, Token>>>, - pub aliases: HashMap>>, HashSet>>>>, +pub struct AliasMap { + pub targets: HashMap, + pub aliases: HashMap>, } impl AliasMap { - pub fn new() -> Self {Self::default()} + pub fn new() -> Self { + Self::default() + } - pub fn link(&mut self, alias: Token>>, target: Token>>) { + pub fn link(&mut self, alias: Sym, target: Sym) { let prev = self.targets.insert(alias, target); debug_assert!(prev.is_none(), "Alias already has a target"); multimap_entry(&mut self.aliases, &target).insert(alias); @@ -21,9 +23,7 @@ impl AliasMap { for alt in alts { // Assert that this step has always been done in the past debug_assert!( - self.aliases.get(&alt) - .map(HashSet::is_empty) - .unwrap_or(true), + self.aliases.get(&alt).map(HashSet::is_empty).unwrap_or(true), "Alias set of alias not empty" ); debug_assert!( @@ -35,7 +35,7 @@ impl AliasMap { } } - pub fn resolve(&self, alias: Token>>) -> Option>>> { + pub fn resolve(&self, alias: Sym) -> Option { self.targets.get(&alias).copied() } } @@ -44,10 +44,11 @@ impl AliasMap { /// map-to-set (aka. multimap) fn multimap_entry<'a, K: Eq + Hash + Clone, V>( map: &'a mut HashMap>, - key: &'_ K + key: &'_ K, ) -> &'a mut HashSet { - map.raw_entry_mut() + map + .raw_entry_mut() .from_key(key) .or_insert_with(|| (key.clone(), HashSet::new())) .1 -} \ No newline at end of file +} diff --git a/src/pipeline/import_resolution/apply_aliases.rs b/src/pipeline/import_resolution/apply_aliases.rs index fc38c92..de65d63 100644 --- a/src/pipeline/import_resolution/apply_aliases.rs +++ b/src/pipeline/import_resolution/apply_aliases.rs @@ -2,22 +2,28 @@ use std::rc::Rc; use hashbrown::HashMap; -use crate::{utils::Substack, interner::{Token, Interner}, pipeline::{ProjectModule, ProjectExt}, representations::tree::{ModEntry, ModMember}, ast::{Rule, Expr}}; - -use super::{alias_map::AliasMap, decls::InjectedAsFn}; +use super::alias_map::AliasMap; +use super::decls::InjectedAsFn; +use crate::ast::{Expr, Rule}; +use crate::interner::{Interner, Sym, Tok}; +use crate::pipeline::{ProjectExt, ProjectModule}; +use crate::representations::tree::{ModEntry, ModMember}; +use crate::utils::Substack; fn resolve( - token: Token>>, + token: Sym, alias_map: &AliasMap, i: &Interner, -) -> Option>> { +) -> Option>> { if let Some(alias) = alias_map.resolve(token) { Some(i.r(alias).clone()) } else if let Some((foot, body)) = i.r(token).split_last() { let mut new_beginning = resolve(i.i(body), alias_map, i)?; new_beginning.push(*foot); Some(new_beginning) - } else {None} + } else { + None + } } fn process_expr( @@ -26,74 +32,91 @@ fn process_expr( injected_as: &impl InjectedAsFn, i: &Interner, ) -> Expr { - expr.map_names(&|n| { - injected_as(&i.r(n)[..]).or_else(|| { - let next_v = resolve(n, alias_map, i)?; - // println!("Resolved alias {} to {}", - // i.extern_vec(n).join("::"), - // i.extern_all(&next_v).join("::") - // ); - Some( - injected_as(&next_v) - .unwrap_or_else(|| i.i(&next_v)) - ) + expr + .map_names(&|n| { + injected_as(&i.r(n)[..]).or_else(|| { + let next_v = resolve(n, alias_map, i)?; + // println!("Resolved alias {} to {}", + // i.extern_vec(n).join("::"), + // i.extern_all(&next_v).join("::") + // ); + Some(injected_as(&next_v).unwrap_or_else(|| i.i(&next_v))) + }) }) - }).unwrap_or_else(|| expr.clone()) + .unwrap_or_else(|| expr.clone()) } // TODO: replace is_injected with injected_as /// Replace all aliases with the name they're originally defined as fn apply_aliases_rec( - path: Substack>, + path: Substack>, module: &ProjectModule, alias_map: &AliasMap, i: &Interner, injected_as: &impl InjectedAsFn, ) -> ProjectModule { - let items = module.items.iter().map(|(name, ent)| { - let ModEntry{ exported, member } = ent; - let member = match member { - ModMember::Item(expr) => ModMember::Item( - process_expr(expr, alias_map, injected_as, i) - ), - ModMember::Sub(module) => { - let subpath = path.push(*name); - let is_ignored = injected_as(&subpath.iter().rev_vec_clone()).is_some(); - let new_mod = if is_ignored {module.clone()} else { - let module = module.as_ref(); - Rc::new(apply_aliases_rec( - subpath, module, - alias_map, i, injected_as - )) - }; - ModMember::Sub(new_mod) + let items = module + .items + .iter() + .map(|(name, ent)| { + let ModEntry { exported, member } = ent; + let member = match member { + ModMember::Item(expr) => + ModMember::Item(process_expr(expr, alias_map, injected_as, i)), + ModMember::Sub(module) => { + let subpath = path.push(*name); + let is_ignored = + injected_as(&subpath.iter().rev_vec_clone()).is_some(); + let new_mod = if is_ignored { + module.clone() + } else { + let module = module.as_ref(); + Rc::new(apply_aliases_rec( + subpath, + module, + alias_map, + i, + injected_as, + )) + }; + ModMember::Sub(new_mod) + }, + }; + (*name, ModEntry { exported: *exported, member }) + }) + .collect::>(); + let rules = module + .extra + .rules + .iter() + .map(|rule| { + let Rule { source, prio, target } = rule; + Rule { + prio: *prio, + source: Rc::new( + source + .iter() + .map(|expr| process_expr(expr, alias_map, injected_as, i)) + .collect::>(), + ), + target: Rc::new( + target + .iter() + .map(|expr| process_expr(expr, alias_map, injected_as, i)) + .collect::>(), + ), } - }; - (*name, ModEntry{ exported: *exported, member }) - }).collect::>(); - let rules = module.extra.rules.iter().map(|rule| { - let Rule{ source, prio, target } = rule; - Rule{ - prio: *prio, - source: Rc::new(source.iter() - .map(|expr| process_expr(expr, alias_map, injected_as, i)) - .collect::>() - ), - target: Rc::new(target.iter() - .map(|expr| process_expr(expr, alias_map, injected_as, i)) - .collect::>() - ), - } - }).collect::>(); - ProjectModule{ + }) + .collect::>(); + ProjectModule { items, imports: module.imports.clone(), - extra: ProjectExt{ + extra: ProjectExt { rules, exports: module.extra.exports.clone(), file: module.extra.file.clone(), imports_from: module.extra.imports_from.clone(), - } + }, } } @@ -104,4 +127,4 @@ pub fn apply_aliases( injected_as: &impl InjectedAsFn, ) -> ProjectModule { apply_aliases_rec(Substack::Bottom, module, alias_map, i, injected_as) -} \ No newline at end of file +} diff --git a/src/pipeline/import_resolution/collect_aliases.rs b/src/pipeline/import_resolution/collect_aliases.rs index 24c3c71..4e9ca1b 100644 --- a/src/pipeline/import_resolution/collect_aliases.rs +++ b/src/pipeline/import_resolution/collect_aliases.rs @@ -1,62 +1,70 @@ use std::rc::Rc; -use crate::representations::tree::{WalkErrorKind, ModMember}; -use crate::pipeline::error::{ProjectError, NotExported}; -use crate::pipeline::project_tree::{ProjectTree, split_path, ProjectModule}; -use crate::interner::{Token, Interner}; -use crate::utils::{Substack, pushed}; - use super::alias_map::AliasMap; use super::decls::InjectedAsFn; +use crate::interner::{Interner, Tok}; +use crate::pipeline::error::{NotExported, ProjectError}; +use crate::pipeline::project_tree::{split_path, ProjectModule, ProjectTree}; +use crate::representations::tree::{ModMember, WalkErrorKind}; +use crate::utils::{pushed, Substack}; /// Assert that a module identified by a path can see a given symbol fn assert_visible( - source: &[Token], // must point to a file or submodule - target: &[Token], // may point to a symbol or module of any kind + source: &[Tok], // must point to a file or submodule + target: &[Tok], // may point to a symbol or module of any kind project: &ProjectTree, - i: &Interner + i: &Interner, ) -> Result<(), Rc> { - let (tgt_item, tgt_path) = if let Some(s) = target.split_last() {s} - else {return Ok(())}; - let shared_len = source.iter() - .zip(tgt_path.iter()) - .take_while(|(a, b)| a == b) - .count(); - let shared_root = project.0.walk(&tgt_path[..shared_len], false) - .expect("checked in parsing"); - let direct_parent = shared_root.walk(&tgt_path[shared_len..], true) - .map_err(|e| match e.kind { - WalkErrorKind::Missing => panic!("checked in parsing"), - WalkErrorKind::Private => { - let full_path = &tgt_path[..shared_len + e.pos]; - let (file, sub) = split_path(full_path, &project); - let (ref_file, ref_sub) = split_path(source, &project); - NotExported{ - file: i.extern_all(file), - subpath: i.extern_all(sub), - referrer_file: i.extern_all(ref_file), - referrer_subpath: i.extern_all(ref_sub), - }.rc() + let (tgt_item, tgt_path) = if let Some(s) = target.split_last() { + s + } else { + return Ok(()); + }; + let shared_len = + source.iter().zip(tgt_path.iter()).take_while(|(a, b)| a == b).count(); + let shared_root = + project.0.walk(&tgt_path[..shared_len], false).expect("checked in parsing"); + let direct_parent = + shared_root.walk(&tgt_path[shared_len..], true).map_err(|e| { + match e.kind { + WalkErrorKind::Missing => panic!("checked in parsing"), + WalkErrorKind::Private => { + let full_path = &tgt_path[..shared_len + e.pos]; + let (file, sub) = split_path(full_path, project); + let (ref_file, ref_sub) = split_path(source, project); + NotExported { + file: i.extern_all(file), + subpath: i.extern_all(sub), + referrer_file: i.extern_all(ref_file), + referrer_subpath: i.extern_all(ref_sub), + } + .rc() + }, } })?; let tgt_item_exported = direct_parent.extra.exports.contains_key(tgt_item); - let target_prefixes_source = shared_len == tgt_path.len() - && source.get(shared_len) == Some(tgt_item); + let target_prefixes_source = + shared_len == tgt_path.len() && source.get(shared_len) == Some(tgt_item); if !tgt_item_exported && !target_prefixes_source { - let (file, sub) = split_path(target, &project); - let (ref_file, ref_sub) = split_path(source, &project); - Err(NotExported{ - file: i.extern_all(file), - subpath: i.extern_all(sub), - referrer_file: i.extern_all(ref_file), - referrer_subpath: i.extern_all(ref_sub), - }.rc()) - } else {Ok(())} + let (file, sub) = split_path(target, project); + let (ref_file, ref_sub) = split_path(source, project); + Err( + NotExported { + file: i.extern_all(file), + subpath: i.extern_all(sub), + referrer_file: i.extern_all(ref_file), + referrer_subpath: i.extern_all(ref_sub), + } + .rc(), + ) + } else { + Ok(()) + } } /// Populate target and alias maps from the module tree recursively fn collect_aliases_rec( - path: Substack>, + path: Substack>, module: &ProjectModule, project: &ProjectTree, alias_map: &mut AliasMap, @@ -65,7 +73,9 @@ fn collect_aliases_rec( ) -> Result<(), Rc> { // Assume injected module has been alias-resolved let mod_path_v = path.iter().rev_vec_clone(); - if injected_as(&mod_path_v).is_some() {return Ok(())}; + if injected_as(&mod_path_v).is_some() { + return Ok(()); + }; for (&name, &target_mod) in module.extra.imports_from.iter() { let target_mod_v = i.r(target_mod); let target_sym_v = pushed(target_mod_v, name); @@ -78,11 +88,16 @@ fn collect_aliases_rec( for (&name, entry) in module.items.iter() { let submodule = if let ModMember::Sub(s) = &entry.member { s.as_ref() - } else {continue}; + } else { + continue; + }; collect_aliases_rec( path.push(name), - submodule, project, alias_map, - i, injected_as, + submodule, + project, + alias_map, + i, + injected_as, )? } Ok(()) @@ -97,7 +112,11 @@ pub fn collect_aliases( injected_as: &impl InjectedAsFn, ) -> Result<(), Rc> { collect_aliases_rec( - Substack::Bottom, module, project, alias_map, - i, injected_as + Substack::Bottom, + module, + project, + alias_map, + i, + injected_as, ) -} \ No newline at end of file +} diff --git a/src/pipeline/import_resolution/decls.rs b/src/pipeline/import_resolution/decls.rs index b69d01e..00d21be 100644 --- a/src/pipeline/import_resolution/decls.rs +++ b/src/pipeline/import_resolution/decls.rs @@ -1,5 +1,3 @@ -use crate::interner::Token; +use crate::interner::{Sym, Tok}; -pub trait InjectedAsFn = Fn( - &[Token] -) -> Option>>>; \ No newline at end of file +pub trait InjectedAsFn = Fn(&[Tok]) -> Option; diff --git a/src/pipeline/import_resolution/mod.rs b/src/pipeline/import_resolution/mod.rs index d498bcd..ddc665e 100644 --- a/src/pipeline/import_resolution/mod.rs +++ b/src/pipeline/import_resolution/mod.rs @@ -1,7 +1,7 @@ mod alias_map; -mod collect_aliases; mod apply_aliases; -mod resolve_imports; +mod collect_aliases; mod decls; +mod resolve_imports; pub use resolve_imports::resolve_imports; diff --git a/src/pipeline/import_resolution/resolve_imports.rs b/src/pipeline/import_resolution/resolve_imports.rs index b8daf40..7f93f05 100644 --- a/src/pipeline/import_resolution/resolve_imports.rs +++ b/src/pipeline/import_resolution/resolve_imports.rs @@ -2,16 +2,14 @@ use std::rc::Rc; use itertools::Itertools; +use super::alias_map::AliasMap; +use super::apply_aliases::apply_aliases; +use super::collect_aliases::collect_aliases; +use super::decls::InjectedAsFn; use crate::interner::Interner; use crate::pipeline::error::ProjectError; use crate::pipeline::project_tree::ProjectTree; - -use super::alias_map::AliasMap; -use super::collect_aliases::collect_aliases; -use super::apply_aliases::apply_aliases; -use super::decls::InjectedAsFn; - /// Follow import chains to locate the original name of all tokens, then /// replace these aliases with the original names throughout the tree pub fn resolve_imports( @@ -20,14 +18,14 @@ pub fn resolve_imports( injected_as: &impl InjectedAsFn, ) -> Result> { let mut map = AliasMap::new(); - collect_aliases( - project.0.as_ref(), - &project, &mut map, - i, injected_as - )?; - println!("Aliases: {{{:?}}}", - map.targets.iter() - .map(|(kt, vt)| format!("{} => {}", + collect_aliases(project.0.as_ref(), &project, &mut map, i, injected_as)?; + println!( + "Aliases: {{{:?}}}", + map + .targets + .iter() + .map(|(kt, vt)| format!( + "{} => {}", i.extern_vec(*kt).join("::"), i.extern_vec(*vt).join("::") )) @@ -35,4 +33,4 @@ pub fn resolve_imports( ); let new_mod = apply_aliases(project.0.as_ref(), &map, i, injected_as); Ok(ProjectTree(Rc::new(new_mod))) -} \ No newline at end of file +} diff --git a/src/pipeline/mod.rs b/src/pipeline/mod.rs index 48e9f4a..e792773 100644 --- a/src/pipeline/mod.rs +++ b/src/pipeline/mod.rs @@ -1,19 +1,14 @@ pub mod error; +pub mod file_loader; +mod import_abs_path; +mod import_resolution; +mod parse_layer; mod project_tree; mod source_loader; -mod import_abs_path; mod split_name; -mod import_resolution; -pub mod file_loader; -mod parse_layer; pub use parse_layer::parse_layer; pub use project_tree::{ - ConstTree, ProjectExt, ProjectModule, ProjectTree, from_const_tree, - collect_consts, collect_rules, + collect_consts, collect_rules, from_const_tree, ConstTree, ProjectExt, + ProjectModule, ProjectTree, }; -// pub use file_loader::{Loaded, FileLoadingError, IOResult}; -// pub use error::{ -// ErrorPosition, ModuleNotFound, NotExported, ParseErrorWithPath, -// ProjectError, TooManySupers, UnexpectedDirectory -// }; \ No newline at end of file diff --git a/src/pipeline/parse_layer.rs b/src/pipeline/parse_layer.rs index d50d431..44b9608 100644 --- a/src/pipeline/parse_layer.rs +++ b/src/pipeline/parse_layer.rs @@ -1,52 +1,46 @@ use std::rc::Rc; -use crate::representations::sourcefile::FileEntry; -use crate::interner::{Token, Interner}; - -use super::{project_tree, import_resolution}; -use super::source_loader; -use super::file_loader::IOResult; use super::error::ProjectError; -use super::ProjectTree; +use super::file_loader::IOResult; +use super::{import_resolution, project_tree, source_loader, ProjectTree}; +use crate::interner::{Interner, Sym, Tok}; +use crate::representations::sourcefile::FileEntry; /// Using an IO callback, produce a project tree that includes the given /// target symbols or files if they're defined. -/// +/// /// The environment accessible to the loaded source can be specified with /// a pre-existing tree which will be merged with the loaded data, and a /// prelude which will be prepended to each individual file. Since the /// prelude gets compiled with each file, normally it should be a glob /// import pointing to a module in the environment. -pub fn parse_layer<'a>( - targets: &[Token>>], - loader: &impl Fn(Token>>) -> IOResult, - environment: &'a ProjectTree, +pub fn parse_layer( + targets: &[Sym], + loader: &impl Fn(Sym) -> IOResult, + environment: &ProjectTree, prelude: &[FileEntry], i: &Interner, ) -> Result> { // A path is injected if it is walkable in the injected tree - let injected_as = |path: &[Token]| { + let injected_as = |path: &[Tok]| { let (item, modpath) = path.split_last()?; let module = environment.0.walk(modpath, false).ok()?; let inj = module.extra.exports.get(item).copied()?; Some(inj) }; - let injected_names = |path: Token>>| { - let pathv = &i.r(path)[..]; - let module = environment.0.walk(&pathv, false).ok()?; - Some(Rc::new( - module.extra.exports.keys().copied().collect() - )) + let injected_names = |path: Tok>>| { + let module = environment.0.walk(&i.r(path)[..], false).ok()?; + Some(Rc::new(module.extra.exports.keys().copied().collect())) }; - let source = source_loader::load_source( - targets, prelude, i, loader, &|path| injected_as(path).is_some() - )?; + let source = + source_loader::load_source(targets, prelude, i, loader, &|path| { + injected_as(path).is_some() + })?; let tree = project_tree::build_tree(source, i, prelude, &injected_names)?; let sum = ProjectTree(Rc::new( - environment.0.as_ref().clone() - + tree.0.as_ref().clone() + environment.0.as_ref().clone() + tree.0.as_ref().clone(), )); let resolvd = import_resolution::resolve_imports(sum, i, &injected_as)?; // Addition among modules favours the left hand side. Ok(resolvd) -} \ No newline at end of file +} diff --git a/src/pipeline/project_tree/add_prelude.rs b/src/pipeline/project_tree/add_prelude.rs index 5784524..7a10051 100644 --- a/src/pipeline/project_tree/add_prelude.rs +++ b/src/pipeline/project_tree/add_prelude.rs @@ -1,23 +1,19 @@ -use crate::representations::sourcefile::{Member, FileEntry}; -use crate::interner::Token; +use crate::interner::Tok; +use crate::representations::sourcefile::{FileEntry, Member, Namespace}; fn member_rec( // object member: Member, // context - path: &[Token], + path: &[Tok], prelude: &[FileEntry], ) -> Member { match member { - Member::Namespace(name, body) => { - let new_body = entv_rec( - body, - path, - prelude - ); - Member::Namespace(name, new_body) + Member::Namespace(Namespace { name, body }) => { + let new_body = entv_rec(body, path, prelude); + Member::Namespace(Namespace { name, body: new_body }) }, - any => any + any => any, } } @@ -25,28 +21,26 @@ fn entv_rec( // object data: Vec, // context - mod_path: &[Token], + mod_path: &[Tok], prelude: &[FileEntry], ) -> Vec { - prelude.iter().cloned() - .chain(data.into_iter() - .map(|ent| match ent { - FileEntry::Exported(mem) => FileEntry::Exported(member_rec( - mem, mod_path, prelude - )), - FileEntry::Internal(mem) => FileEntry::Internal(member_rec( - mem, mod_path, prelude - )), - any => any - }) - ) + prelude + .iter() + .cloned() + .chain(data.into_iter().map(|ent| match ent { + FileEntry::Exported(mem) => + FileEntry::Exported(member_rec(mem, mod_path, prelude)), + FileEntry::Internal(mem) => + FileEntry::Internal(member_rec(mem, mod_path, prelude)), + any => any, + })) .collect() } pub fn add_prelude( data: Vec, - path: &[Token], + path: &[Tok], prelude: &[FileEntry], ) -> Vec { entv_rec(data, path, prelude) -} \ No newline at end of file +} diff --git a/src/pipeline/project_tree/build_tree.rs b/src/pipeline/project_tree/build_tree.rs index 38e9493..6abf964 100644 --- a/src/pipeline/project_tree/build_tree.rs +++ b/src/pipeline/project_tree/build_tree.rs @@ -2,38 +2,41 @@ use std::rc::Rc; use hashbrown::HashMap; -use crate::pipeline::error::ProjectError; -use crate::interner::{Token, Interner}; -use crate::utils::iter::{box_once, box_empty}; -use crate::utils::{Substack, pushed}; -use crate::ast::{Expr, Constant}; -use crate::pipeline::source_loader::{LoadedSourceTable, LoadedSource}; -use crate::representations::tree::{Module, ModMember, ModEntry}; -use crate::representations::sourcefile::{FileEntry, Member, absolute_path}; - use super::collect_ops::InjectedOperatorsFn; -use super::{collect_ops, ProjectTree, ProjectExt}; use super::parse_file::parse_file; +use super::{collect_ops, ProjectExt, ProjectTree}; +use crate::ast::{Constant, Expr}; +use crate::interner::{Interner, Tok}; +use crate::pipeline::error::ProjectError; +use crate::pipeline::source_loader::{LoadedSource, LoadedSourceTable}; +use crate::representations::sourcefile::{absolute_path, FileEntry, Member}; +use crate::representations::tree::{ModEntry, ModMember, Module}; +use crate::utils::iter::{box_empty, box_once}; +use crate::utils::{pushed, Substack}; #[derive(Debug)] struct ParsedSource<'a> { - path: Vec>, + path: Vec>, loaded: &'a LoadedSource, - parsed: Vec + parsed: Vec, } -pub fn split_path<'a>(path: &'a [Token], proj: &'a ProjectTree) --> (&'a [Token], &'a [Token]) -{ - let (end, body) = if let Some(s) = path.split_last() {s} - else {return (&[], &[])}; - let mut module = proj.0.walk(body, false).expect("invalid path cannot be split"); +pub fn split_path<'a>( + path: &'a [Tok], + proj: &'a ProjectTree, +) -> (&'a [Tok], &'a [Tok]) { + let (end, body) = if let Some(s) = path.split_last() { + s + } else { + return (&[], &[]); + }; + let mut module = + proj.0.walk(body, false).expect("invalid path cannot be split"); if let ModMember::Sub(m) = &module.items[end].member { module = m.clone(); } - let file = module.extra.file.as_ref() - .map(|s| &path[..s.len()]) - .unwrap_or(&path[..]); + let file = + module.extra.file.as_ref().map(|s| &path[..s.len()]).unwrap_or(path); let subpath = &path[file.len()..]; (file, subpath) } @@ -41,7 +44,7 @@ pub fn split_path<'a>(path: &'a [Token], proj: &'a ProjectTree) /// Convert normalized, prefixed source into a module fn source_to_module( // level - path: Substack>, + path: Substack>, preparsed: &Module, // data data: Vec, @@ -50,35 +53,38 @@ fn source_to_module( filepath_len: usize, ) -> Rc> { let path_v = path.iter().rev_vec_clone(); - let imports = data.iter() - .filter_map(|ent| if let FileEntry::Import(impv) = ent { - Some(impv.iter()) - } else {None}) + let imports = data + .iter() + .filter_map(|ent| { + if let FileEntry::Import(impv) = ent { + Some(impv.iter()) + } else { + None + } + }) .flatten() .cloned() .collect::>(); - let imports_from = imports.iter() + let imports_from = imports + .iter() .map(|imp| { let mut imp_path_v = i.r(imp.path).clone(); imp_path_v.push(imp.name.expect("imports normalized")); - let mut abs_path = absolute_path( - &path_v, - &imp_path_v, - i, &|n| preparsed.items.contains_key(&n) - ).expect("tested in preparsing"); + let mut abs_path = + absolute_path(&path_v, &imp_path_v, i).expect("tested in preparsing"); let name = abs_path.pop().expect("importing the global context"); (name, i.i(&abs_path)) }) .collect::>(); - let exports = data.iter() + let exports = data + .iter() .flat_map(|ent| { let mk_ent = |name| (name, i.i(&pushed(&path_v, name))); match ent { - FileEntry::Export(names) - => Box::new(names.iter().copied().map(mk_ent)), + FileEntry::Export(names) => Box::new(names.iter().copied().map(mk_ent)), FileEntry::Exported(mem) => match mem { Member::Constant(constant) => box_once(mk_ent(constant.name)), - Member::Namespace(name, _) => box_once(mk_ent(*name)), + Member::Namespace(ns) => box_once(mk_ent(ns.name)), Member::Rule(rule) => { let mut names = Vec::new(); for e in rule.source.iter() { @@ -89,13 +95,14 @@ fn source_to_module( }) } Box::new(names.into_iter()) - } - } - _ => box_empty() + }, + }, + _ => box_empty(), } }) .collect::>(); - let rules = data.iter() + let rules = data + .iter() .filter_map(|ent| match ent { FileEntry::Exported(Member::Rule(rule)) => Some(rule), FileEntry::Internal(Member::Rule(rule)) => Some(rule), @@ -103,38 +110,51 @@ fn source_to_module( }) .cloned() .collect::>(); - let items = data.into_iter() + let items = data + .into_iter() .filter_map(|ent| match ent { - FileEntry::Exported(Member::Namespace(name, body)) => { - let prep_member = &preparsed.items[&name].member; - let new_prep = if let ModMember::Sub(s) = prep_member {s.as_ref()} - else { panic!("preparsed missing a submodule") }; + FileEntry::Exported(Member::Namespace(ns)) => { + let prep_member = &preparsed.items[&ns.name].member; + let new_prep = if let ModMember::Sub(s) = prep_member { + s.as_ref() + } else { + panic!("preparsed missing a submodule") + }; let module = source_to_module( - path.push(name), - new_prep, body, i, filepath_len + path.push(ns.name), + new_prep, + ns.body, + i, + filepath_len, ); let member = ModMember::Sub(module); - Some((name, ModEntry{ exported: true, member })) - } - FileEntry::Internal(Member::Namespace(name, body)) => { - let prep_member = &preparsed.items[&name].member; - let new_prep = if let ModMember::Sub(s) = prep_member {s.as_ref()} - else { panic!("preparsed missing a submodule") }; + Some((ns.name, ModEntry { exported: true, member })) + }, + FileEntry::Internal(Member::Namespace(ns)) => { + let prep_member = &preparsed.items[&ns.name].member; + let new_prep = if let ModMember::Sub(s) = prep_member { + s.as_ref() + } else { + panic!("preparsed missing a submodule") + }; let module = source_to_module( - path.push(name), - new_prep, body, i, filepath_len + path.push(ns.name), + new_prep, + ns.body, + i, + filepath_len, ); let member = ModMember::Sub(module); - Some((name, ModEntry{ exported: false, member })) - } - FileEntry::Exported(Member::Constant(Constant{ name, value })) => { + Some((ns.name, ModEntry { exported: false, member })) + }, + FileEntry::Exported(Member::Constant(Constant { name, value })) => { let member = ModMember::Item(value); - Some((name, ModEntry{ exported: true, member })) - } - FileEntry::Internal(Member::Constant(Constant{ name, value })) => { + Some((name, ModEntry { exported: true, member })) + }, + FileEntry::Internal(Member::Constant(Constant { name, value })) => { let member = ModMember::Item(value); - Some((name, ModEntry{ exported: false, member })) - } + Some((name, ModEntry { exported: false, member })) + }, _ => None, }) .collect::>(); @@ -150,15 +170,15 @@ fn source_to_module( imports_from, exports, rules, - file: Some(path_v[..filepath_len].to_vec()) - } + file: Some(path_v[..filepath_len].to_vec()), + }, }) } fn files_to_module( - path: Substack>, + path: Substack>, files: &[ParsedSource], - i: &Interner + i: &Interner, ) -> Rc> { let lvl = path.len(); let path_v = path.iter().rev_vec_clone(); @@ -167,19 +187,22 @@ fn files_to_module( path, files[0].loaded.preparsed.0.as_ref(), files[0].parsed.clone(), - i, path.len() - ) + i, + path.len(), + ); } - let items = files.group_by(|a, b| a.path[lvl] == b.path[lvl]).into_iter() + let items = files + .group_by(|a, b| a.path[lvl] == b.path[lvl]) .map(|files| { let namespace = files[0].path[lvl]; let subpath = path.push(namespace); let module = files_to_module(subpath, files, i); let member = ModMember::Sub(module); - (namespace, ModEntry{ exported: true, member }) + (namespace, ModEntry { exported: true, member }) }) .collect::>(); - let exports: HashMap<_, _> = items.keys() + let exports: HashMap<_, _> = items + .keys() .copied() .map(|name| (name, i.i(&pushed(&path_v, name)))) .collect(); @@ -188,38 +211,44 @@ fn files_to_module( // i.extern_all(&path_v[..]).join("::"), // exports.keys().map(|t| i.r(*t)).join(", ") // ); - Rc::new(Module{ + Rc::new(Module { items, imports: vec![], extra: ProjectExt { exports, imports_from: HashMap::new(), - rules: vec![], file: None, - } + rules: vec![], + file: None, + }, }) } -pub fn build_tree<'a>( +pub fn build_tree( files: LoadedSourceTable, i: &Interner, prelude: &[FileEntry], injected: &impl InjectedOperatorsFn, ) -> Result> { let ops_cache = collect_ops::mk_cache(&files, i, injected); - let mut entries = files.iter() - .map(|(path, loaded)| Ok(( - i.r(*path), - loaded, - parse_file(*path, &files, &ops_cache, i, prelude)? - ))) + let mut entries = files + .iter() + .map(|(path, loaded)| { + Ok(( + i.r(*path), + loaded, + parse_file(*path, &files, &ops_cache, i, prelude)?, + )) + }) .collect::, Rc>>()?; // sort by similarity, then longest-first - entries.sort_unstable_by(|a, b| a.0.cmp(&b.0).reverse()); - let files = entries.into_iter() - .map(|(path, loaded, parsed)| ParsedSource{ - loaded, parsed, - path: path.clone() + entries.sort_unstable_by(|a, b| a.0.cmp(b.0).reverse()); + let files = entries + .into_iter() + .map(|(path, loaded, parsed)| ParsedSource { + loaded, + parsed, + path: path.clone(), }) .collect::>(); Ok(ProjectTree(files_to_module(Substack::Bottom, &files, i))) -} \ No newline at end of file +} diff --git a/src/pipeline/project_tree/collect_ops/exported_ops.rs b/src/pipeline/project_tree/collect_ops/exported_ops.rs index 0d408b4..fc6ddec 100644 --- a/src/pipeline/project_tree/collect_ops/exported_ops.rs +++ b/src/pipeline/project_tree/collect_ops/exported_ops.rs @@ -4,73 +4,80 @@ use std::rc::Rc; use hashbrown::HashSet; use itertools::Itertools; -use crate::representations::tree::WalkErrorKind; +use crate::interner::{Interner, Sym, Tok}; +use crate::pipeline::error::{ModuleNotFound, ProjectError}; use crate::pipeline::source_loader::LoadedSourceTable; -use crate::pipeline::error::{ProjectError, ModuleNotFound}; -use crate::interner::{Token, Interner}; -use crate::utils::Cache; use crate::pipeline::split_name::split_name; +use crate::representations::tree::WalkErrorKind; +use crate::utils::Cache; -pub type OpsResult = Result>>, Rc>; -pub type ExportedOpsCache<'a> = Cache<'a, Token>>, OpsResult>; +pub type OpsResult = Result>>, Rc>; +pub type ExportedOpsCache<'a> = Cache<'a, Sym, OpsResult>; -pub trait InjectedOperatorsFn = Fn( - Token>> -) -> Option>>>; +pub trait InjectedOperatorsFn = Fn(Sym) -> Option>>>; fn coprefix( l: impl Iterator, - r: impl Iterator + r: impl Iterator, ) -> usize { l.zip(r).take_while(|(a, b)| a == b).count() } /// Collect all names exported by the module at the specified path pub fn collect_exported_ops( - path: Token>>, + path: Sym, loaded: &LoadedSourceTable, i: &Interner, - injected: &impl InjectedOperatorsFn + injected: &impl InjectedOperatorsFn, ) -> OpsResult { if let Some(ops) = injected(path) { if path == i.i(&[i.i("prelude")][..]) { println!("%%% Prelude exported ops %%%"); println!("{}", ops.iter().map(|t| i.r(*t)).join(", ")); } - return Ok(ops) + return Ok(ops); } - let is_file = |n: &[Token]| loaded.contains_key(&i.i(n)); + let is_file = |n: &[Tok]| loaded.contains_key(&i.i(n)); let path_s = &i.r(path)[..]; let name_split = split_name(path_s, &is_file); - let (fpath_v, subpath_v) = if let Some(f) = name_split {f} else { - return Ok(Rc::new(loaded.keys().copied() - .filter_map(|modname| { - let modname_s = i.r(modname); - if path_s.len() == coprefix(path_s.iter(), modname_s.iter()) { - Some(modname_s[path_s.len()]) - } else {None} - }) - .collect::>() - )) + let (fpath_v, subpath_v) = if let Some(f) = name_split { + f + } else { + return Ok(Rc::new( + loaded + .keys() + .copied() + .filter_map(|modname| { + let modname_s = i.r(modname); + if path_s.len() == coprefix(path_s.iter(), modname_s.iter()) { + Some(modname_s[path_s.len()]) + } else { + None + } + }) + .collect::>(), + )); }; let fpath = i.i(fpath_v); let preparsed = &loaded[&fpath].preparsed; - let module = preparsed.0.walk(&subpath_v, false) - .map_err(|walk_err| match walk_err.kind { - WalkErrorKind::Private => unreachable!("visibility is not being checked here"), - WalkErrorKind::Missing => ModuleNotFound{ + let module = preparsed.0.walk(subpath_v, false).map_err(|walk_err| { + match walk_err.kind { + WalkErrorKind::Private => + unreachable!("visibility is not being checked here"), + WalkErrorKind::Missing => ModuleNotFound { file: i.extern_vec(fpath), - subpath: subpath_v.into_iter() + subpath: subpath_v + .iter() .take(walk_err.pos) .map(|t| i.r(*t)) .cloned() - .collect() - }.rc(), - })?; - let out: HashSet<_> = module.items.iter() - .filter(|(_, v)| v.exported) - .map(|(k, _)| *k) - .collect(); + .collect(), + } + .rc(), + } + })?; + let out: HashSet<_> = + module.items.iter().filter(|(_, v)| v.exported).map(|(k, _)| *k).collect(); if path == i.i(&[i.i("prelude")][..]) { println!("%%% Prelude exported ops %%%"); println!("{}", out.iter().map(|t| i.r(*t)).join(", ")); @@ -83,7 +90,5 @@ pub fn mk_cache<'a>( i: &'a Interner, injected: &'a impl InjectedOperatorsFn, ) -> ExportedOpsCache<'a> { - Cache::new(|path, _this| { - collect_exported_ops(path, loaded, i, injected) - }) -} \ No newline at end of file + Cache::new(|path, _this| collect_exported_ops(path, loaded, i, injected)) +} diff --git a/src/pipeline/project_tree/collect_ops/mod.rs b/src/pipeline/project_tree/collect_ops/mod.rs index 36e90b3..340860b 100644 --- a/src/pipeline/project_tree/collect_ops/mod.rs +++ b/src/pipeline/project_tree/collect_ops/mod.rs @@ -2,7 +2,7 @@ mod exported_ops; mod ops_for; pub use exported_ops::{ - ExportedOpsCache, OpsResult, InjectedOperatorsFn, - collect_exported_ops, mk_cache + collect_exported_ops, mk_cache, ExportedOpsCache, InjectedOperatorsFn, + OpsResult, }; -pub use ops_for::collect_ops_for; \ No newline at end of file +pub use ops_for::collect_ops_for; diff --git a/src/pipeline/project_tree/collect_ops/ops_for.rs b/src/pipeline/project_tree/collect_ops/ops_for.rs index 2285781..f2ae1be 100644 --- a/src/pipeline/project_tree/collect_ops/ops_for.rs +++ b/src/pipeline/project_tree/collect_ops/ops_for.rs @@ -3,20 +3,19 @@ use std::rc::Rc; use hashbrown::HashSet; use itertools::Itertools; +use super::exported_ops::{ExportedOpsCache, OpsResult}; +use crate::interner::{Interner, Tok}; use crate::parse::is_op; use crate::pipeline::error::ProjectError; -use crate::pipeline::source_loader::LoadedSourceTable; -use crate::interner::{Token, Interner}; -use crate::representations::tree::{Module, ModMember}; use crate::pipeline::import_abs_path::import_abs_path; - -use super::exported_ops::{ExportedOpsCache, OpsResult}; +use crate::pipeline::source_loader::LoadedSourceTable; +use crate::representations::tree::{ModMember, Module}; /// Collect all operators and names, exported or local, defined in this /// tree. fn tree_all_ops( module: &Module, - ops: &mut HashSet> + ops: &mut HashSet>, ) { ops.extend(module.items.keys().copied()); for ent in module.items.values() { @@ -28,21 +27,22 @@ fn tree_all_ops( /// Collect all names imported in this file pub fn collect_ops_for( - file: &[Token], + file: &[Tok], loaded: &LoadedSourceTable, ops_cache: &ExportedOpsCache, - i: &Interner + i: &Interner, ) -> OpsResult { let tree = &loaded[&i.i(file)].preparsed.0; let mut ret = HashSet::new(); println!("collecting ops for {}", i.extern_all(file).join("::")); tree_all_ops(tree.as_ref(), &mut ret); - tree.visit_all_imports(&mut |modpath, module, import| { - if let Some(n) = import.name { ret.insert(n); } else { + tree.visit_all_imports(&mut |modpath, _module, import| { + if let Some(n) = import.name { + ret.insert(n); + } else { println!("\tglob import from {}", i.extern_vec(import.path).join("::")); - let path = import_abs_path( - &file, modpath, module, &i.r(import.path)[..], i - ).expect("This error should have been caught during loading"); + let path = import_abs_path(file, modpath, &i.r(import.path)[..], i) + .expect("This error should have been caught during loading"); ret.extend(ops_cache.find(&i.i(&path))?.iter().copied()); } Ok::<_, Rc>(()) @@ -53,4 +53,4 @@ pub fn collect_ops_for( println!("{}", ret.iter().map(|t| i.r(*t)).join(", ")) } Ok(Rc::new(ret)) -} \ No newline at end of file +} diff --git a/src/pipeline/project_tree/const_tree.rs b/src/pipeline/project_tree/const_tree.rs index 25fff07..667ffdc 100644 --- a/src/pipeline/project_tree/const_tree.rs +++ b/src/pipeline/project_tree/const_tree.rs @@ -1,26 +1,26 @@ -use std::{ops::Add, rc::Rc}; +use std::ops::Add; +use std::rc::Rc; use hashbrown::HashMap; +use super::{ProjectExt, ProjectModule, ProjectTree}; +use crate::ast::{Clause, Expr}; +use crate::foreign::{Atom, Atomic, ExternFn}; +use crate::interner::{Interner, Tok}; +use crate::representations::location::Location; use crate::representations::tree::{ModEntry, ModMember, Module}; use crate::representations::Primitive; -use crate::representations::location::Location; -use crate::foreign::{ExternFn, Atomic, Atom}; -use crate::interner::{Token, Interner}; -use crate::ast::{Expr, Clause}; -use crate::utils::{Substack, pushed}; - -use super::{ProjectModule, ProjectExt, ProjectTree}; +use crate::utils::{pushed, Substack}; pub enum ConstTree { Const(Expr), - Tree(HashMap, ConstTree>) + Tree(HashMap, ConstTree>), } impl ConstTree { pub fn primitive(primitive: Primitive) -> Self { - Self::Const(Expr{ + Self::Const(Expr { location: Location::Unknown, - value: Clause::P(primitive) + value: Clause::P(primitive), }) } pub fn xfn(xfn: impl ExternFn + 'static) -> Self { @@ -29,9 +29,7 @@ impl ConstTree { pub fn atom(atom: impl Atomic + 'static) -> Self { Self::primitive(Primitive::Atom(Atom(Box::new(atom)))) } - pub fn tree( - arr: impl IntoIterator, Self)> - ) -> Self { + pub fn tree(arr: impl IntoIterator, Self)>) -> Self { Self::Tree(arr.into_iter().collect()) } } @@ -57,27 +55,29 @@ impl Add for ConstTree { } fn from_const_tree_rec( - path: Substack>, - consts: HashMap, ConstTree>, - file: &[Token], + path: Substack>, + consts: HashMap, ConstTree>, + file: &[Tok], i: &Interner, ) -> ProjectModule { let mut items = HashMap::new(); let path_v = path.iter().rev_vec_clone(); for (name, item) in consts { - items.insert(name, ModEntry{ + items.insert(name, ModEntry { exported: true, member: match item { ConstTree::Const(c) => ModMember::Item(c), - ConstTree::Tree(t) => ModMember::Sub(Rc::new( - from_const_tree_rec(path.push(name), t, file, i) - )), - } + ConstTree::Tree(t) => ModMember::Sub(Rc::new(from_const_tree_rec( + path.push(name), + t, + file, + i, + ))), + }, }); } - let exports = items.keys() - .map(|name| (*name, i.i(&pushed(&path_v, *name)))) - .collect(); + let exports = + items.keys().map(|name| (*name, i.i(&pushed(&path_v, *name)))).collect(); Module { items, imports: vec![], @@ -85,15 +85,15 @@ fn from_const_tree_rec( exports, file: Some(file.to_vec()), ..Default::default() - } + }, } } pub fn from_const_tree( - consts: HashMap, ConstTree>, - file: &[Token], + consts: HashMap, ConstTree>, + file: &[Tok], i: &Interner, ) -> ProjectTree { let module = from_const_tree_rec(Substack::Bottom, consts, file, i); ProjectTree(Rc::new(module)) -} \ No newline at end of file +} diff --git a/src/pipeline/project_tree/mod.rs b/src/pipeline/project_tree/mod.rs index 2e83906..cc70436 100644 --- a/src/pipeline/project_tree/mod.rs +++ b/src/pipeline/project_tree/mod.rs @@ -1,38 +1,30 @@ -/* FILE SEPARATION BOUNDARY +// FILE SEPARATION BOUNDARY +// +// Collect all operators accessible in each file, parse the files with +// correct tokenization, resolve glob imports, convert expressions to +// refer to tokens with (local) absolute path, and connect them into a +// single tree. +// +// The module checks for imports from missing modules (including +// submodules). All other errors must be checked later. +// +// Injection strategy: +// Return all items of the given module in the injected tree for +// `injected` The output of this stage is a tree, which can simply be +// overlaid with the injected tree -Collect all operators accessible in each file, parse the files with -correct tokenization, resolve glob imports, convert expressions to -refer to tokens with (local) absolute path, and connect them into a -single tree. - -The module checks for imports from missing modules (including submodules). -All other errors must be checked later. - -Injection strategy: -Return all items of the given module in the injected tree for `injected` -The output of this stage is a tree, which can simply be overlaid with -the injected tree -*/ - -mod collect_ops; -mod parse_file; +mod add_prelude; mod build_tree; +mod collect_ops; +mod const_tree; mod normalize_imports; +mod parse_file; mod prefix; mod tree; -mod const_tree; -mod add_prelude; +pub use build_tree::{build_tree, split_path}; pub use collect_ops::InjectedOperatorsFn; - -pub use const_tree::{ - ConstTree, from_const_tree, -}; - +pub use const_tree::{from_const_tree, ConstTree}; pub use tree::{ - ProjectExt, ProjectModule, ProjectTree, collect_consts, collect_rules + collect_consts, collect_rules, ProjectExt, ProjectModule, ProjectTree, }; - -pub use build_tree::{ - build_tree, split_path -}; \ No newline at end of file diff --git a/src/pipeline/project_tree/normalize_imports.rs b/src/pipeline/project_tree/normalize_imports.rs index 12d6a0c..e7465cf 100644 --- a/src/pipeline/project_tree/normalize_imports.rs +++ b/src/pipeline/project_tree/normalize_imports.rs @@ -1,74 +1,88 @@ -use crate::representations::tree::{Module, ModMember}; -use crate::representations::sourcefile::{Member, FileEntry, Import}; -use crate::utils::BoxedIter; -use crate::utils::{Substack, iter::box_once}; -use crate::interner::{Interner, Token}; -use crate::pipeline::import_abs_path::import_abs_path; - use super::collect_ops::ExportedOpsCache; +use crate::interner::{Interner, Tok}; +use crate::pipeline::import_abs_path::import_abs_path; +use crate::representations::sourcefile::{ + FileEntry, Import, Member, Namespace, +}; +use crate::representations::tree::{ModMember, Module}; +use crate::utils::iter::box_once; +use crate::utils::{BoxedIter, Substack}; fn member_rec( // level - mod_stack: Substack>, + mod_stack: Substack>, preparsed: &Module, // object member: Member, // context - path: &[Token], + path: &[Tok], ops_cache: &ExportedOpsCache, - i: &Interner + i: &Interner, ) -> Member { match member { - Member::Namespace(name, body) => { + Member::Namespace(Namespace { name, body }) => { let prepmember = &preparsed.items[&name].member; - let subprep = if let ModMember::Sub(m) = prepmember {m.clone()} - else {unreachable!("This name must point to a namespace")}; + let subprep = if let ModMember::Sub(m) = prepmember { + m.clone() + } else { + unreachable!("This name must point to a namespace") + }; let new_body = entv_rec( mod_stack.push(name), subprep.as_ref(), body, - path, ops_cache, i + path, + ops_cache, + i, ); - Member::Namespace(name, new_body) + Member::Namespace(Namespace { name, body: new_body }) }, - any => any + any => any, } } fn entv_rec( // level - mod_stack: Substack>, + mod_stack: Substack>, preparsed: &Module, // object data: Vec, // context - mod_path: &[Token], + mod_path: &[Tok], ops_cache: &ExportedOpsCache, - i: &Interner + i: &Interner, ) -> Vec { - data.into_iter() + data + .into_iter() .map(|ent| match ent { - FileEntry::Import(imps) => FileEntry::Import(imps.into_iter() - .flat_map(|import| if let Import{ name: None, path } = import { - let p = import_abs_path( - mod_path, mod_stack, preparsed, &i.r(path)[..], i - ).expect("Should have emerged in preparsing"); - let names = ops_cache.find(&i.i(&p)) - .expect("Should have emerged in second parsing"); - let imports = names.iter() - .map(move |&n| Import{ name: Some(n), path }) - .collect::>(); - Box::new(imports.into_iter()) as BoxedIter - } else {box_once(import)}) - .collect() + FileEntry::Import(imps) => FileEntry::Import( + imps + .into_iter() + .flat_map(|import| { + if let Import { name: None, path } = import { + let p = import_abs_path(mod_path, mod_stack, &i.r(path)[..], i) + .expect("Should have emerged in preparsing"); + let names = ops_cache + .find(&i.i(&p)) + .expect("Should have emerged in second parsing"); + let imports = names + .iter() + .map(move |&n| Import { name: Some(n), path }) + .collect::>(); + Box::new(imports.into_iter()) as BoxedIter + } else { + box_once(import) + } + }) + .collect(), ), FileEntry::Exported(mem) => FileEntry::Exported(member_rec( - mod_stack, preparsed, mem, mod_path, ops_cache, i + mod_stack, preparsed, mem, mod_path, ops_cache, i, )), FileEntry::Internal(mem) => FileEntry::Internal(member_rec( - mod_stack, preparsed, mem, mod_path, ops_cache, i + mod_stack, preparsed, mem, mod_path, ops_cache, i, )), - any => any + any => any, }) .collect() } @@ -76,9 +90,9 @@ fn entv_rec( pub fn normalize_imports( preparsed: &Module, data: Vec, - path: &[Token], + path: &[Tok], ops_cache: &ExportedOpsCache, - i: &Interner + i: &Interner, ) -> Vec { entv_rec(Substack::Bottom, preparsed, data, path, ops_cache, i) -} \ No newline at end of file +} diff --git a/src/pipeline/project_tree/parse_file.rs b/src/pipeline/project_tree/parse_file.rs index d9b2d60..f483891 100644 --- a/src/pipeline/project_tree/parse_file.rs +++ b/src/pipeline/project_tree/parse_file.rs @@ -1,18 +1,17 @@ use std::rc::Rc; -use crate::parse; -use crate::pipeline::error::ProjectError; -use crate::representations::sourcefile::{FileEntry, normalize_namespaces}; -use crate::pipeline::source_loader::LoadedSourceTable; -use crate::interner::{Token, Interner}; - use super::add_prelude::add_prelude; -use super::collect_ops::{ExportedOpsCache, collect_ops_for}; +use super::collect_ops::{collect_ops_for, ExportedOpsCache}; use super::normalize_imports::normalize_imports; use super::prefix::prefix; +use crate::interner::{Interner, Sym}; +use crate::parse; +use crate::pipeline::error::ProjectError; +use crate::pipeline::source_loader::LoadedSourceTable; +use crate::representations::sourcefile::{normalize_namespaces, FileEntry}; pub fn parse_file( - path: Token>>, + path: Sym, loaded: &LoadedSourceTable, ops_cache: &ExportedOpsCache, i: &Interner, @@ -21,24 +20,24 @@ pub fn parse_file( let ld = &loaded[&path]; // let ops_cache = collect_ops::mk_cache(loaded, i); let ops = collect_ops_for(&i.r(path)[..], loaded, ops_cache, i)?; - let ops_vec = ops.iter() - .map(|t| i.r(*t)) - .cloned() - .collect::>(); - let ctx = parse::ParsingContext{ + let ops_vec = ops.iter().map(|t| i.r(*t)).cloned().collect::>(); + let ctx = parse::ParsingContext { interner: i, ops: &ops_vec, - file: Rc::new(i.extern_vec(path)) + file: Rc::new(i.extern_vec(path)), }; let entries = parse::parse(ld.text.as_str(), ctx) .expect("This error should have been caught during loading"); let with_prelude = add_prelude(entries, &i.r(path)[..], prelude); let impnormalized = normalize_imports( - &ld.preparsed.0, with_prelude, &i.r(path)[..], ops_cache, i + &ld.preparsed.0, + with_prelude, + &i.r(path)[..], + ops_cache, + i, ); - let nsnormalized = normalize_namespaces( - Box::new(impnormalized.into_iter()), i - ).expect("This error should have been caught during preparsing"); + let nsnormalized = normalize_namespaces(Box::new(impnormalized.into_iter())) + .expect("This error should have been caught during preparsing"); let prefixed = prefix(nsnormalized, &i.r(path)[..], ops_cache, i); Ok(prefixed) -} \ No newline at end of file +} diff --git a/src/pipeline/project_tree/prefix.rs b/src/pipeline/project_tree/prefix.rs index fb9705a..d292dfe 100644 --- a/src/pipeline/project_tree/prefix.rs +++ b/src/pipeline/project_tree/prefix.rs @@ -1,82 +1,78 @@ use std::rc::Rc; -use crate::ast::{Constant, Rule}; -use crate::interner::{Token, Interner}; -use crate::utils::Substack; -use crate::representations::sourcefile::{Member, FileEntry}; - use super::collect_ops::ExportedOpsCache; +use crate::ast::{Constant, Rule}; +use crate::interner::{Interner, Tok}; +use crate::representations::sourcefile::{FileEntry, Member, Namespace}; +use crate::utils::Substack; fn member_rec( // level - mod_stack: Substack>, + mod_stack: Substack>, // object data: Member, // context - path: &[Token], + path: &[Tok], ops_cache: &ExportedOpsCache, - i: &Interner + i: &Interner, ) -> Member { // let except = |op| imported.contains(&op); let except = |_| false; - let prefix_v = path.iter().copied() + let prefix_v = path + .iter() + .copied() .chain(mod_stack.iter().rev_vec_clone().into_iter()) .collect::>(); let prefix = i.i(&prefix_v); match data { - Member::Namespace(name, body) => { - let new_body = entv_rec( - mod_stack.push(name), - body, - path, ops_cache, i - ); - Member::Namespace(name, new_body) - } - Member::Constant(constant) => Member::Constant(Constant{ + Member::Namespace(Namespace { name, body }) => { + let new_body = entv_rec(mod_stack.push(name), body, path, ops_cache, i); + Member::Namespace(Namespace { name, body: new_body }) + }, + Member::Constant(constant) => Member::Constant(Constant { name: constant.name, - value: constant.value.prefix(prefix, i, &except) + value: constant.value.prefix(prefix, i, &except), }), - Member::Rule(rule) => Member::Rule(Rule{ + Member::Rule(rule) => Member::Rule(Rule { prio: rule.prio, - source: Rc::new(rule.source.iter() - .map(|e| e.prefix(prefix, i, &except)) - .collect() + source: Rc::new( + rule.source.iter().map(|e| e.prefix(prefix, i, &except)).collect(), ), - target: Rc::new(rule.target.iter() - .map(|e| e.prefix(prefix, i, &except)) - .collect() + target: Rc::new( + rule.target.iter().map(|e| e.prefix(prefix, i, &except)).collect(), ), - }) + }), } } fn entv_rec( // level - mod_stack: Substack>, + mod_stack: Substack>, // object data: Vec, // context - path: &[Token], + path: &[Tok], ops_cache: &ExportedOpsCache, - i: &Interner + i: &Interner, ) -> Vec { - data.into_iter().map(|fe| match fe { - FileEntry::Exported(mem) => FileEntry::Exported(member_rec( - mod_stack, mem, path, ops_cache, i - )), - FileEntry::Internal(mem) => FileEntry::Internal(member_rec( - mod_stack, mem, path, ops_cache, i - )), - // XXX should [FileEntry::Export] be prefixed? - any => any - }).collect() + data + .into_iter() + .map(|fe| match fe { + FileEntry::Exported(mem) => + FileEntry::Exported(member_rec(mod_stack, mem, path, ops_cache, i)), + FileEntry::Internal(mem) => + FileEntry::Internal(member_rec(mod_stack, mem, path, ops_cache, i)), + // XXX should [FileEntry::Export] be prefixed? + any => any, + }) + .collect() } pub fn prefix( data: Vec, - path: &[Token], + path: &[Tok], ops_cache: &ExportedOpsCache, - i: &Interner + i: &Interner, ) -> Vec { entv_rec(Substack::Bottom, data, path, ops_cache, i) -} \ No newline at end of file +} diff --git a/src/pipeline/project_tree/tree.rs b/src/pipeline/project_tree/tree.rs index 53774fa..0384030 100644 --- a/src/pipeline/project_tree/tree.rs +++ b/src/pipeline/project_tree/tree.rs @@ -1,33 +1,36 @@ -use std::{ops::Add, rc::Rc}; +use std::ops::Add; +use std::rc::Rc; use hashbrown::HashMap; -use crate::representations::tree::{Module, ModMember}; -use crate::ast::{Rule, Expr}; -use crate::interner::{Token, Interner}; +use crate::ast::{Expr, Rule}; +use crate::interner::{Interner, Sym, Tok}; +use crate::representations::tree::{ModMember, Module}; use crate::utils::Substack; #[derive(Clone, Debug, Default)] -pub struct ProjectExt{ +pub struct ProjectExt { /// Pairs each foreign token to the module it was imported from - pub imports_from: HashMap, Token>>>, + pub imports_from: HashMap, Sym>, /// Pairs each exported token to its original full name. - pub exports: HashMap, Token>>>, + pub exports: HashMap, Sym>, /// All rules defined in this module, exported or not pub rules: Vec, /// Filename, if known, for error reporting - pub file: Option>> + pub file: Option>>, } impl Add for ProjectExt { type Output = Self; fn add(mut self, rhs: Self) -> Self::Output { - let ProjectExt{ imports_from, exports, rules, file } = rhs; + let ProjectExt { imports_from, exports, rules, file } = rhs; self.imports_from.extend(imports_from.into_iter()); self.exports.extend(exports.into_iter()); self.rules.extend(rules.into_iter()); - if file.is_some() { self.file = file } + if file.is_some() { + self.file = file + } self } } @@ -51,10 +54,10 @@ pub fn collect_rules(project: &ProjectTree) -> Vec { } fn collect_consts_rec( - path: Substack>, - bag: &mut HashMap>>, Expr>, + path: Substack>, + bag: &mut HashMap, module: &ProjectModule, - i: &Interner + i: &Interner, ) { for (key, entry) in module.items.iter() { match &entry.member { @@ -62,26 +65,18 @@ fn collect_consts_rec( let mut name = path.iter().rev_vec_clone(); name.push(*key); bag.insert(i.i(&name), expr.clone()); - } - ModMember::Sub(module) => { - collect_consts_rec( - path.push(*key), - bag, module, i - ) - } + }, + ModMember::Sub(module) => + collect_consts_rec(path.push(*key), bag, module, i), } } } -pub fn collect_consts(project: &ProjectTree, i: &Interner) --> HashMap>>, Expr> -{ +pub fn collect_consts( + project: &ProjectTree, + i: &Interner, +) -> HashMap { let mut consts = HashMap::new(); - collect_consts_rec( - Substack::Bottom, - &mut consts, - project.0.as_ref(), - i - ); + collect_consts_rec(Substack::Bottom, &mut consts, project.0.as_ref(), i); consts -} \ No newline at end of file +} diff --git a/src/pipeline/source_loader/load_source.rs b/src/pipeline/source_loader/load_source.rs index 1b10976..e0d4193 100644 --- a/src/pipeline/source_loader/load_source.rs +++ b/src/pipeline/source_loader/load_source.rs @@ -1,47 +1,58 @@ use std::iter; use std::rc::Rc; +use super::loaded_source::{LoadedSource, LoadedSourceTable}; +use super::preparse::preparse; +use crate::interner::{Interner, Sym, Tok}; use crate::pipeline::error::ProjectError; +use crate::pipeline::file_loader::{load_text, IOResult, Loaded}; use crate::pipeline::import_abs_path::import_abs_path; use crate::pipeline::split_name::split_name; -use crate::interner::{Token, Interner}; - -use crate::pipeline::file_loader::{Loaded, load_text, IOResult}; use crate::representations::sourcefile::FileEntry; -use super::loaded_source::{LoadedSourceTable, LoadedSource}; -use super::preparse::preparse; /// Load the source at the given path or all within if it's a collection, /// and all sources imported from these. fn load_abs_path_rec( - abs_path: Token>>, + abs_path: Sym, table: &mut LoadedSourceTable, prelude: &[FileEntry], i: &Interner, - get_source: &impl Fn(Token>>) -> IOResult, - is_injected: &impl Fn(&[Token]) -> bool + get_source: &impl Fn(Sym) -> IOResult, + is_injected: &impl Fn(&[Tok]) -> bool, ) -> Result<(), Rc> { let abs_pathv = i.r(abs_path); // short-circuit if this import is defined externally or already known - if is_injected(&abs_pathv) | table.contains_key(&abs_path) { - return Ok(()) + if is_injected(abs_pathv) | table.contains_key(&abs_path) { + return Ok(()); } // try splitting the path to file, swallowing any IO errors let is_file = |p| (get_source)(p).map(|l| l.is_code()).unwrap_or(false); - let name_split = split_name(&abs_pathv, &|p| is_file(i.i(p))); - let filename = if let Some((f, _)) = name_split {f} else { + let name_split = split_name(abs_pathv, &|p| is_file(i.i(p))); + let filename = if let Some((f, _)) = name_split { + f + } else { // If the path could not be split to file, load it as directory - let coll = if let Loaded::Collection(c) = (get_source)(abs_path)? {c} - // ^^ raise any IO error that was previously swallowed - else {panic!("split_name returned None but the path is a file")}; + let coll = if let Loaded::Collection(c) = (get_source)(abs_path)? { + c + } + // ^^ raise any IO error that was previously swallowed + else { + panic!("split_name returned None but the path is a file") + }; // recurse on all files and folders within for item in coll.iter() { - let abs_subpath = abs_pathv.iter() + let abs_subpath = abs_pathv + .iter() .copied() .chain(iter::once(i.i(item))) .collect::>(); load_abs_path_rec( - i.i(&abs_subpath), table, prelude, i, get_source, is_injected + i.i(&abs_subpath), + table, + prelude, + i, + get_source, + is_injected, )? } return Ok(()); @@ -50,18 +61,23 @@ fn load_abs_path_rec( let text = load_text(i.i(filename), &get_source, i)?; let preparsed = preparse( filename.iter().map(|t| i.r(*t)).cloned().collect(), - text.as_str(), prelude, i + text.as_str(), + prelude, + i, )?; - table.insert(abs_path, LoadedSource{ text, preparsed: preparsed.clone() }); + table.insert(abs_path, LoadedSource { text, preparsed: preparsed.clone() }); // recurse on all imported modules - preparsed.0.visit_all_imports(&mut |modpath, module, import| { - let abs_pathv = import_abs_path( - &filename, modpath, - module, &import.nonglob_path(i), i - )?; + preparsed.0.visit_all_imports(&mut |modpath, _module, import| { + let abs_pathv = + import_abs_path(filename, modpath, &import.nonglob_path(i), i)?; // recurse on imported module load_abs_path_rec( - i.i(&abs_pathv), table, prelude, i, get_source, is_injected + i.i(&abs_pathv), + table, + prelude, + i, + get_source, + is_injected, ) }) } @@ -69,20 +85,15 @@ fn load_abs_path_rec( /// Load and preparse all files reachable from the load targets via /// imports that aren't injected. pub fn load_source( - targets: &[Token>>], + targets: &[Sym], prelude: &[FileEntry], i: &Interner, - get_source: &impl Fn(Token>>) -> IOResult, - is_injected: &impl Fn(&[Token]) -> bool, + get_source: &impl Fn(Sym) -> IOResult, + is_injected: &impl Fn(&[Tok]) -> bool, ) -> Result> { let mut table = LoadedSourceTable::new(); for target in targets { - load_abs_path_rec( - *target, - &mut table, - prelude, - i, get_source, is_injected - )? + load_abs_path_rec(*target, &mut table, prelude, i, get_source, is_injected)? } Ok(table) -} \ No newline at end of file +} diff --git a/src/pipeline/source_loader/loaded_source.rs b/src/pipeline/source_loader/loaded_source.rs index e33a1b0..ae9fd43 100644 --- a/src/pipeline/source_loader/loaded_source.rs +++ b/src/pipeline/source_loader/loaded_source.rs @@ -1,8 +1,8 @@ -use std::{rc::Rc, collections::HashMap}; - -use crate::interner::Token; +use std::collections::HashMap; +use std::rc::Rc; use super::preparse::Preparsed; +use crate::interner::Sym; #[derive(Debug)] pub struct LoadedSource { @@ -10,4 +10,4 @@ pub struct LoadedSource { pub preparsed: Preparsed, } -pub type LoadedSourceTable = HashMap>>, LoadedSource>; \ No newline at end of file +pub type LoadedSourceTable = HashMap; diff --git a/src/pipeline/source_loader/mod.rs b/src/pipeline/source_loader/mod.rs index db32c49..68ec4c1 100644 --- a/src/pipeline/source_loader/mod.rs +++ b/src/pipeline/source_loader/mod.rs @@ -1,25 +1,24 @@ -/* PULL LOGISTICS BOUNDARY - -Specifying exactly what this module should be doing was an unexpectedly -hard challenge. It is intended to encapsulate all pull logistics, but -this definition is apparently prone to scope creep. - -Load files, preparse them to obtain a list of imports, follow these. -Preparsing also returns the module tree and list of exported synbols -for free, which is needed later so the output of preparsing is also -attached to the module output. - -The module checks for IO errors, syntax errors, malformed imports and -imports from missing files. All other errors must be checked later. - -Injection strategy: -see whether names are valid in the injected tree for is_injected -*/ +// PULL LOGISTICS BOUNDARY +// +// Specifying exactly what this module should be doing was an unexpectedly +// hard challenge. It is intended to encapsulate all pull logistics, but +// this definition is apparently prone to scope creep. +// +// Load files, preparse them to obtain a list of imports, follow these. +// Preparsing also returns the module tree and list of exported synbols +// for free, which is needed later so the output of preparsing is also +// attached to the module output. +// +// The module checks for IO errors, syntax errors, malformed imports and +// imports from missing files. All other errors must be checked later. +// +// Injection strategy: +// see whether names are valid in the injected tree for is_injected mod load_source; mod loaded_source; mod preparse; -pub use loaded_source::{LoadedSource, LoadedSourceTable}; pub use load_source::load_source; -pub use preparse::Preparsed; \ No newline at end of file +pub use loaded_source::{LoadedSource, LoadedSourceTable}; +pub use preparse::Preparsed; diff --git a/src/pipeline/source_loader/preparse.rs b/src/pipeline/source_loader/preparse.rs index 904b66e..a622483 100644 --- a/src/pipeline/source_loader/preparse.rs +++ b/src/pipeline/source_loader/preparse.rs @@ -1,39 +1,34 @@ -use hashbrown::HashMap; use std::hash::Hash; use std::rc::Rc; +use hashbrown::HashMap; + use crate::ast::Constant; -use crate::pipeline::error::{ProjectError, ParseErrorWithPath, VisibilityMismatch}; -use crate::representations::sourcefile::{normalize_namespaces, Member}; -use crate::representations::tree::{ModEntry, ModMember}; use crate::interner::Interner; use crate::parse::{self, ParsingContext}; -use crate::representations::{sourcefile::{FileEntry, imports}, tree::Module}; +use crate::pipeline::error::{ + ParseErrorWithPath, ProjectError, VisibilityMismatch, +}; +use crate::representations::sourcefile::{ + imports, normalize_namespaces, FileEntry, Member, +}; +use crate::representations::tree::{ModEntry, ModMember, Module}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Preparsed(pub Rc>); /// Add an internal flat name if it does not exist yet -fn add_intern( - map: &mut HashMap>, k: K -) { - let _ = map.try_insert(k, ModEntry { - exported: false, - member: ModMember::Item(()), - }); +fn add_intern(map: &mut HashMap>, k: K) { + let _ = map + .try_insert(k, ModEntry { exported: false, member: ModMember::Item(()) }); } /// Add an exported flat name or export any existing entry -fn add_export( - map: &mut HashMap>, k: K -) { +fn add_export(map: &mut HashMap>, k: K) { if let Some(entry) = map.get_mut(&k) { entry.exported = true } else { - map.insert(k, ModEntry { - exported: true, - member: ModMember::Item(()), - }); + map.insert(k, ModEntry { exported: true, member: ModMember::Item(()) }); } } @@ -41,47 +36,53 @@ fn add_export( fn to_module( src: &[FileEntry], prelude: &[FileEntry], - i: &Interner + i: &Interner, ) -> Rc> { let all_src = || src.iter().chain(prelude.iter()); let imports = imports(all_src()).cloned().collect::>(); - let mut items = all_src().filter_map(|ent| match ent { - FileEntry::Internal(Member::Namespace(name, data)) => { - let member = ModMember::Sub(to_module(data, prelude, i)); - let entry = ModEntry{ exported: false, member }; - Some((*name, entry)) + let mut items = all_src() + .filter_map(|ent| match ent { + FileEntry::Internal(Member::Namespace(ns)) => { + let member = ModMember::Sub(to_module(&ns.body, prelude, i)); + let entry = ModEntry { exported: false, member }; + Some((ns.name, entry)) + }, + FileEntry::Exported(Member::Namespace(ns)) => { + let member = ModMember::Sub(to_module(&ns.body, prelude, i)); + let entry = ModEntry { exported: true, member }; + Some((ns.name, entry)) + }, + _ => None, + }) + .collect::>(); + for file_entry in all_src() { + match file_entry { + FileEntry::Comment(_) + | FileEntry::Import(_) + | FileEntry::Internal(Member::Namespace(_)) + | FileEntry::Exported(Member::Namespace(_)) => (), + FileEntry::Export(tokv) => + for tok in tokv { + add_export(&mut items, *tok) + }, + FileEntry::Internal(Member::Constant(Constant { name, .. })) => + add_intern(&mut items, *name), + FileEntry::Exported(Member::Constant(Constant { name, .. })) => + add_export(&mut items, *name), + FileEntry::Internal(Member::Rule(rule)) => { + let names = rule.collect_single_names(i); + for name in names { + add_intern(&mut items, name) + } + }, + FileEntry::Exported(Member::Rule(rule)) => { + let names = rule.collect_single_names(i); + for name in names { + add_export(&mut items, name) + } + }, } - FileEntry::Exported(Member::Namespace(name, data)) => { - let member = ModMember::Sub(to_module(data, prelude, i)); - let entry = ModEntry{ exported: true, member }; - Some((*name, entry)) - } - _ => None - }).collect::>(); - for file_entry in all_src() { match file_entry { - FileEntry::Comment(_) | FileEntry::Import(_) - | FileEntry::Internal(Member::Namespace(..)) - | FileEntry::Exported(Member::Namespace(..)) => (), - FileEntry::Export(tokv) => for tok in tokv { - add_export(&mut items, *tok) - } - FileEntry::Internal(Member::Constant(Constant{ name, .. })) - => add_intern(&mut items, *name), - FileEntry::Exported(Member::Constant(Constant{ name, .. })) - => add_export(&mut items, *name), - FileEntry::Internal(Member::Rule(rule)) => { - let names = rule.collect_single_names(i); - for name in names { - add_intern(&mut items, name) - } - } - FileEntry::Exported(Member::Rule(rule)) => { - let names = rule.collect_single_names(i); - for name in names { - add_export(&mut items, name) - } - } - }} + } Rc::new(Module { imports, items, extra: () }) } @@ -95,16 +96,21 @@ pub fn preparse( ) -> Result> { // Parse with no operators let ctx = ParsingContext::<&str>::new(&[], i, Rc::new(file.clone())); - let entries = parse::parse(source, ctx) - .map_err(|error| ParseErrorWithPath{ + let entries = parse::parse(source, ctx).map_err(|error| { + ParseErrorWithPath { full_source: source.to_string(), error, - path: file.clone() - }.rc())?; - let normalized = normalize_namespaces(Box::new(entries.into_iter()), i) - .map_err(|ns| VisibilityMismatch{ - namespace: ns.into_iter().map(|t| i.r(t)).cloned().collect(), - file: Rc::new(file.clone()) - }.rc())?; + path: file.clone(), + } + .rc() + })?; + let normalized = normalize_namespaces(Box::new(entries.into_iter())) + .map_err(|ns| { + VisibilityMismatch { + namespace: ns.into_iter().map(|t| i.r(t)).cloned().collect(), + file: Rc::new(file.clone()), + } + .rc() + })?; Ok(Preparsed(to_module(&normalized, prelude, i))) -} \ No newline at end of file +} diff --git a/src/pipeline/split_name.rs b/src/pipeline/split_name.rs index acf786a..d22ba1d 100644 --- a/src/pipeline/split_name.rs +++ b/src/pipeline/split_name.rs @@ -1,14 +1,16 @@ -use crate::interner::Token; +use crate::interner::Tok; +#[allow(clippy::type_complexity)] +// FIXME couldn't find a good factoring pub fn split_name<'a>( - path: &'a [Token], - is_valid: &impl Fn(&[Token]) -> bool -) -> Option<(&'a [Token], &'a [Token])> { + path: &'a [Tok], + is_valid: &impl Fn(&[Tok]) -> bool, +) -> Option<(&'a [Tok], &'a [Tok])> { for split in (0..=path.len()).rev() { let (filename, subpath) = path.split_at(split); if is_valid(filename) { - return Some((filename, subpath)) + return Some((filename, subpath)); } } None -} \ No newline at end of file +} diff --git a/src/representations/ast.rs b/src/representations/ast.rs index f43403a..10dee87 100644 --- a/src/representations/ast.rs +++ b/src/representations/ast.rs @@ -1,19 +1,19 @@ -use itertools::Itertools; -use ordered_float::NotNan; use std::hash::Hash; use std::rc::Rc; -use crate::interner::{Interner, InternedDisplay}; -use crate::utils::Substack; -use crate::interner::Token; + +use itertools::Itertools; +use ordered_float::NotNan; use super::location::Location; use super::primitive::Primitive; +use crate::interner::{InternedDisplay, Interner, Sym, Tok}; +use crate::utils::Substack; /// An S-expression with a type #[derive(Clone, Debug, PartialEq)] -pub struct Expr{ +pub struct Expr { pub value: Clause, - pub location: Location + pub location: Location, } impl Expr { @@ -21,19 +21,14 @@ impl Expr { self.value } - pub fn visit_names(&self, - binds: Substack>>>, - cb: &mut impl FnMut(Token>>) - ) { - let Expr{value, ..} = self; - value.visit_names(binds.clone(), cb); + pub fn visit_names(&self, binds: Substack, cb: &mut impl FnMut(Sym)) { + let Expr { value, .. } = self; + value.visit_names(binds, cb); } /// Process all names with the given mapper. /// Return a new object if anything was processed - pub fn map_names(&self, - pred: &impl Fn(Token>>) -> Option>>> - ) -> Option { + pub fn map_names(&self, pred: &impl Fn(Sym) -> Option) -> Option { Some(Self { value: self.value.map_names(pred)?, location: self.location.clone(), @@ -41,12 +36,13 @@ impl Expr { } /// Add the specified prefix to every Name - pub fn prefix(&self, - prefix: Token>>, + pub fn prefix( + &self, + prefix: Sym, i: &Interner, - except: &impl Fn(Token) -> bool, + except: &impl Fn(Tok) -> bool, ) -> Self { - Self{ + Self { value: self.value.prefix(prefix, i, except), location: self.location.clone(), } @@ -54,8 +50,12 @@ impl Expr { } impl InternedDisplay for Expr { - fn fmt_i(&self, f: &mut std::fmt::Formatter<'_>, i: &Interner) -> std::fmt::Result { - let Expr{value, ..} = self; + fn fmt_i( + &self, + f: &mut std::fmt::Formatter<'_>, + i: &Interner, + ) -> std::fmt::Result { + let Expr { value, .. } = self; value.fmt_i(f, i)?; Ok(()) } @@ -63,28 +63,31 @@ impl InternedDisplay for Expr { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum PHClass { - Vec{ - nonzero: bool, - prio: u64 - }, + Vec { nonzero: bool, prio: u64 }, Scalar, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct Placeholder { - pub name: Token, - pub class: PHClass + pub name: Tok, + pub class: PHClass, } impl InternedDisplay for Placeholder { - fn fmt_i(&self, f: &mut std::fmt::Formatter<'_>, i: &Interner) -> std::fmt::Result { + fn fmt_i( + &self, + f: &mut std::fmt::Formatter<'_>, + i: &Interner, + ) -> std::fmt::Result { let name = i.r(self.name); match self.class { PHClass::Scalar => write!(f, "${name}"), - PHClass::Vec { nonzero, prio } => { - if nonzero {write!(f, "...${name}:{prio}")} - else {write!(f, "..${name}:{prio}")} - } + PHClass::Vec { nonzero, prio } => + if nonzero { + write!(f, "...${name}:{prio}") + } else { + write!(f, "..${name}:{prio}") + }, } } } @@ -94,130 +97,156 @@ impl InternedDisplay for Placeholder { pub enum Clause { P(Primitive), /// A c-style name or an operator, eg. `+`, `i`, `foo::bar` - Name(Token>>), + Name(Sym), /// A parenthesized exmrc_empty_slice()pression /// eg. `(print out "hello")`, `[1, 2, 3]`, `{Some(t) => t}` S(char, Rc>), /// A function expression, eg. `\x. x + 1` Lambda(Rc, Rc>), - /// A placeholder for macros, eg. `$name`, `...$body`, `...$lhs:1` - Placeh(Placeholder) + /// A placeholder for macros, eg. `$name`, `...$body`, `...$lhs:1` + Placeh(Placeholder), } impl Clause { - /// Extract the expressions from an auto, lambda or S + /// Extract the expressions from an auto, lambda or S pub fn body(&self) -> Option>> { match self { - Self::Lambda(_, body) | - Self::S(_, body) => Some(body.clone()), - _ => None + Self::Lambda(_, body) | Self::S(_, body) => Some(body.clone()), + _ => None, } } - + /// Convert with identical meaning pub fn into_expr(self) -> Expr { if let Self::S('(', body) = &self { - if body.len() == 1 { body[0].clone() } - else { Expr{ value: self, location: Location::Unknown } } - } else { Expr{ value: self, location: Location::Unknown } } + if body.len() == 1 { + body[0].clone() + } else { + Expr { value: self, location: Location::Unknown } + } + } else { + Expr { value: self, location: Location::Unknown } + } } /// Convert with identical meaning pub fn from_exprs(exprs: &[Expr]) -> Option { - if exprs.len() == 0 { None } - else if exprs.len() == 1 { Some(exprs[0].clone().into_clause()) } - else { Some(Self::S('(', Rc::new(exprs.to_vec()))) } + if exprs.is_empty() { + None + } else if exprs.len() == 1 { + Some(exprs[0].clone().into_clause()) + } else { + Some(Self::S('(', Rc::new(exprs.to_vec()))) + } } /// Convert with identical meaning pub fn from_exprv(exprv: &Rc>) -> Option { - if exprv.len() < 2 { Self::from_exprs(exprv) } - else { Some(Self::S('(', exprv.clone())) } + if exprv.len() < 2 { + Self::from_exprs(exprv) + } else { + Some(Self::S('(', exprv.clone())) + } } /// Recursively iterate through all "names" in an expression. /// It also finds a lot of things that aren't names, such as all /// bound parameters. Generally speaking, this is not a very /// sophisticated search. - pub fn visit_names(&self, - binds: Substack>>>, - cb: &mut impl FnMut(Token>>) - ) { + pub fn visit_names(&self, binds: Substack, cb: &mut impl FnMut(Sym)) { match self { Clause::Lambda(arg, body) => { arg.visit_names(binds, cb); let new_binds = if let Clause::Name(n) = arg.value { binds.push(n) - } else {binds}; + } else { + binds + }; for x in body.iter() { x.visit_names(new_binds, cb) } }, - Clause::S(_, body) => for x in body.iter() { - x.visit_names(binds.clone(), cb) - }, - Clause::Name(name) => { + Clause::S(_, body) => + for x in body.iter() { + x.visit_names(binds, cb) + }, + Clause::Name(name) => if binds.iter().all(|x| x != name) { cb(*name) - } - } + }, _ => (), } } /// Process all names with the given mapper. /// Return a new object if anything was processed - pub fn map_names(&self, - pred: &impl Fn(Token>>) -> Option>>> - ) -> Option { + pub fn map_names(&self, pred: &impl Fn(Sym) -> Option) -> Option { match self { Clause::P(_) | Clause::Placeh(_) => None, Clause::Name(name) => pred(*name).map(Clause::Name), Clause::S(c, body) => { let mut any_some = false; - let new_body = body.iter().map(|e| { - let val = e.map_names(pred); - any_some |= val.is_some(); - val.unwrap_or_else(|| e.clone()) - }).collect(); - if any_some {Some(Clause::S(*c, Rc::new(new_body)))} else {None} - } + let new_body = body + .iter() + .map(|e| { + let val = e.map_names(pred); + any_some |= val.is_some(); + val.unwrap_or_else(|| e.clone()) + }) + .collect(); + if any_some { + Some(Clause::S(*c, Rc::new(new_body))) + } else { + None + } + }, Clause::Lambda(arg, body) => { let new_arg = arg.map_names(pred); let mut any_some = new_arg.is_some(); - let new_body = body.iter().map(|e| { - let val = e.map_names(pred); - any_some |= val.is_some(); - val.unwrap_or_else(|| e.clone()) - }).collect(); - if any_some {Some(Clause::Lambda( - new_arg.map(Rc::new) - .unwrap_or_else(|| arg.clone()), - Rc::new(new_body) - ))} else {None} - } + let new_body = body + .iter() + .map(|e| { + let val = e.map_names(pred); + any_some |= val.is_some(); + val.unwrap_or_else(|| e.clone()) + }) + .collect(); + if any_some { + Some(Clause::Lambda( + new_arg.map(Rc::new).unwrap_or_else(|| arg.clone()), + Rc::new(new_body), + )) + } else { + None + } + }, } } /// Add the specified prefix to every Name - pub fn prefix(&self, - prefix: Token>>, + pub fn prefix( + &self, + prefix: Sym, i: &Interner, - except: &impl Fn(Token) -> bool, + except: &impl Fn(Tok) -> bool, ) -> Self { - self.map_names(&|name| { - let old = i.r(name); - if except(old[0]) {return None} - let mut new = i.r(prefix).clone(); - new.extend_from_slice(&old); - Some(i.i(&new)) - }).unwrap_or_else(|| self.clone()) + self + .map_names(&|name| { + let old = i.r(name); + if except(old[0]) { + return None; + } + let mut new = i.r(prefix).clone(); + new.extend_from_slice(old); + Some(i.i(&new)) + }) + .unwrap_or_else(|| self.clone()) } } fn fmt_expr_seq<'a>( it: &mut impl Iterator, f: &mut std::fmt::Formatter<'_>, - i: &Interner + i: &Interner, ) -> std::fmt::Result { for item in Itertools::intersperse(it.map(Some), None) { match item { @@ -229,12 +258,11 @@ fn fmt_expr_seq<'a>( } pub fn fmt_name( - name: Token>>, + name: Sym, f: &mut std::fmt::Formatter, - i: &Interner + i: &Interner, ) -> std::fmt::Result { - let strings = i.r(name).iter() - .map(|t| i.r(*t).as_str()); + let strings = i.r(name).iter().map(|t| i.r(*t).as_str()); for el in itertools::intersperse(strings, "::") { write!(f, "{}", el)? } @@ -242,7 +270,11 @@ pub fn fmt_name( } impl InternedDisplay for Clause { - fn fmt_i(&self, f: &mut std::fmt::Formatter<'_>, i: &Interner) -> std::fmt::Result { + fn fmt_i( + &self, + f: &mut std::fmt::Formatter<'_>, + i: &Interner, + ) -> std::fmt::Result { match self { Self::P(p) => write!(f, "{:?}", p), Self::Name(name) => fmt_name(*name, f, i), @@ -250,8 +282,10 @@ impl InternedDisplay for Clause { f.write_str(&del.to_string())?; fmt_expr_seq(&mut items.iter(), f, i)?; f.write_str(match del { - '(' => ")", '[' => "]", '{' => "}", - _ => "CLOSING_DELIM" + '(' => ")", + '[' => "]", + '{' => "}", + _ => "CLOSING_DELIM", }) }, Self::Lambda(arg, body) => { @@ -270,48 +304,59 @@ impl InternedDisplay for Clause { pub struct Rule { pub source: Rc>, pub prio: NotNan, - pub target: Rc> + pub target: Rc>, } impl Rule { - pub fn collect_single_names(&self, i: &Interner) -> Vec> { + pub fn collect_single_names(&self, i: &Interner) -> Vec> { let mut names = Vec::new(); for e in self.source.iter() { e.visit_names(Substack::Bottom, &mut |tok| { let ns_name = i.r(tok); - let (name, excess) = ns_name.split_first() - .expect("Namespaced name must not be empty"); - if excess.len() > 0 {return} + let (name, excess) = + ns_name.split_first().expect("Namespaced name must not be empty"); + if !excess.is_empty() { + return; + } names.push(*name) }); } names } - pub fn prefix(&self, - prefix: Token>>, + pub fn prefix( + &self, + prefix: Sym, i: &Interner, - except: &impl Fn(Token) -> bool + except: &impl Fn(Tok) -> bool, ) -> Self { Self { prio: self.prio, - source: Rc::new(self.source.iter() - .map(|e| e.prefix(prefix, i, except)) - .collect() + source: Rc::new( + self.source.iter().map(|e| e.prefix(prefix, i, except)).collect(), ), - target: Rc::new(self.target.iter() - .map(|e| e.prefix(prefix, i, except)) - .collect() + target: Rc::new( + self.target.iter().map(|e| e.prefix(prefix, i, except)).collect(), ), } } } impl InternedDisplay for Rule { - fn fmt_i(&self, f: &mut std::fmt::Formatter<'_>, i: &Interner) -> std::fmt::Result { - for e in self.source.iter() { e.fmt_i(f, i)?; write!(f, " ")?; } + fn fmt_i( + &self, + f: &mut std::fmt::Formatter<'_>, + i: &Interner, + ) -> std::fmt::Result { + for e in self.source.iter() { + e.fmt_i(f, i)?; + write!(f, " ")?; + } write!(f, "={}=>", self.prio)?; - for e in self.target.iter() { write!(f, " ")?; e.fmt_i(f, i)?; } + for e in self.target.iter() { + write!(f, " ")?; + e.fmt_i(f, i)?; + } Ok(()) } } @@ -319,13 +364,17 @@ impl InternedDisplay for Rule { /// A named constant #[derive(Debug, Clone, PartialEq)] pub struct Constant { - pub name: Token, - pub value: Expr + pub name: Tok, + pub value: Expr, } impl InternedDisplay for Constant { - fn fmt_i(&self, f: &mut std::fmt::Formatter<'_>, i: &Interner) -> std::fmt::Result { + fn fmt_i( + &self, + f: &mut std::fmt::Formatter<'_>, + i: &Interner, + ) -> std::fmt::Result { write!(f, "{} := ", i.r(self.name))?; self.value.fmt_i(f, i) } -} \ No newline at end of file +} diff --git a/src/representations/ast_to_postmacro.rs b/src/representations/ast_to_postmacro.rs index 46cc9f3..4fe8ccd 100644 --- a/src/representations/ast_to_postmacro.rs +++ b/src/representations/ast_to_postmacro.rs @@ -1,10 +1,11 @@ -use std::{rc::Rc, fmt::Display}; +use std::fmt::Display; +use std::rc::Rc; -use crate::interner::Token; +use super::location::Location; +use super::{ast, postmacro}; +use crate::interner::Sym; use crate::utils::Substack; -use super::{ast, postmacro, location::Location}; - #[derive(Clone)] pub enum Error { /// `()` as a clause is meaningless in lambda calculus @@ -12,20 +13,29 @@ pub enum Error { /// Only `(...)` may be converted to typed lambdas. `[...]` and `{...}` /// left in the code are signs of incomplete macro execution BadGroup(char), - /// Placeholders shouldn't even occur in the code during macro execution. - /// Something is clearly terribly wrong + /// Placeholders shouldn't even occur in the code during macro + /// execution. Something is clearly terribly wrong Placeholder, /// Arguments can only be [ast::Clause::Name] - InvalidArg + InvalidArg, } impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Error::EmptyS => write!(f, "`()` as a clause is meaningless in lambda calculus"), - Error::BadGroup(_) => write!(f, "Only `(...)` may be converted to typed lambdas. `[...]` and `{{...}}` left in the code are signs of incomplete macro execution"), - Error::Placeholder => write!(f, "Placeholders shouldn't even appear in the code during macro execution, this is likely a compiler bug"), - Error::InvalidArg => write!(f, "Arguments can only be Name nodes") + Error::EmptyS => + write!(f, "`()` as a clause is meaningless in lambda calculus"), + Error::BadGroup(_) => write!( + f, + "Only `(...)` may be converted to typed lambdas. `[...]` and \ + `{{...}}` left in the code are signs of incomplete macro execution" + ), + Error::Placeholder => write!( + f, + "Placeholders shouldn't even appear in the code during macro \ + execution, this is likely a compiler bug" + ), + Error::InvalidArg => write!(f, "Arguments can only be Name nodes"), } } } @@ -35,28 +45,16 @@ pub fn expr(expr: &ast::Expr) -> Result { expr_rec(expr, Context::new()) } -/// Try and convert a single clause from AST format to typed lambda -pub fn _clause(clause: &ast::Clause) --> Result -{ - clause_rec(clause, Context::new()) -} - -/// Try and convert a sequence of expressions from AST format to -/// typed lambda -pub fn _exprv(exprv: &[ast::Expr]) --> Result -{ - exprv_rec(exprv, Context::new()) -} - #[derive(Clone, Copy)] -struct Context<'a> { names: Substack<'a, Token>>> } +struct Context<'a> { + names: Substack<'a, Sym>, +} impl<'a> Context<'a> { - fn w_name<'b>(&'b self, - name: Token>> - ) -> Context<'b> where 'a: 'b { + fn w_name<'b>(&'b self, name: Sym) -> Context<'b> + where + 'a: 'b, + { Context { names: self.names.push(name) } } @@ -66,8 +64,10 @@ impl<'a> Context<'a> { } /// Recursive state of [exprv] -fn exprv_rec<'a>(v: &'a [ast::Expr], ctx: Context<'a>) --> Result { +fn exprv_rec<'a>( + v: &'a [ast::Expr], + ctx: Context<'a>, +) -> Result { let (last, rest) = v.split_last().ok_or(Error::EmptyS)?; if rest.is_empty() { return expr_rec(&v[0], ctx); @@ -75,34 +75,34 @@ fn exprv_rec<'a>(v: &'a [ast::Expr], ctx: Context<'a>) let f = exprv_rec(rest, ctx)?; let x = expr_rec(last, ctx)?; let value = postmacro::Clause::Apply(Rc::new(f), Rc::new(x)); - Ok(postmacro::Expr{ value, location: Location::Unknown }) + Ok(postmacro::Expr { value, location: Location::Unknown }) } /// Recursive state of [expr] fn expr_rec<'a>( - ast::Expr{ value, location }: &'a ast::Expr, - ctx: Context<'a> + ast::Expr { value, location }: &'a ast::Expr, + ctx: Context<'a>, ) -> Result { if let ast::Clause::S(paren, body) = value { - if *paren != '(' {return Err(Error::BadGroup(*paren))} + if *paren != '(' { + return Err(Error::BadGroup(*paren)); + } let expr = exprv_rec(body.as_ref(), ctx)?; - Ok(postmacro::Expr{ - value: expr.value, - location: location.clone() - }) + Ok(postmacro::Expr { value: expr.value, location: location.clone() }) } else { - let value = clause_rec(&value, ctx)?; - Ok(postmacro::Expr{ value, location: location.clone() }) + let value = clause_rec(value, ctx)?; + Ok(postmacro::Expr { value, location: location.clone() }) } } // (\t:(@T. Pair T T). t \left.\right. left) @number -- this will fail -// (@T. \t:Pair T T. t \left.\right. left) @number -- this is the correct phrasing +// (@T. \t:Pair T T. t \left.\right. left) @number -- this is the correct +// phrasing /// Recursive state of [clause] fn clause_rec<'a>( cls: &'a ast::Clause, - ctx: Context<'a> + ctx: Context<'a>, ) -> Result { match cls { ast::Clause::P(p) => Ok(postmacro::Clause::P(p.clone())), @@ -110,26 +110,31 @@ fn clause_rec<'a>( let name = match expr.value { ast::Clause::Name(name) => name, ast::Clause::Placeh { .. } => return Err(Error::Placeholder), - _ => return Err(Error::InvalidArg) + _ => return Err(Error::InvalidArg), }; let body_ctx = ctx.w_name(name); let body = exprv_rec(b.as_ref(), body_ctx)?; Ok(postmacro::Clause::Lambda(Rc::new(body))) - } + }, ast::Clause::Name(name) => { - let lvl_opt = ctx.names.iter().enumerate() + let lvl_opt = ctx + .names + .iter() + .enumerate() .find(|(_, n)| *n == name) .map(|(lvl, _)| lvl); Ok(match lvl_opt { Some(lvl) => postmacro::Clause::LambdaArg(lvl), - None => postmacro::Clause::Constant(*name) + None => postmacro::Clause::Constant(*name), }) - } + }, ast::Clause::S(paren, entries) => { - if *paren != '(' {return Err(Error::BadGroup(*paren))} + if *paren != '(' { + return Err(Error::BadGroup(*paren)); + } let expr = exprv_rec(entries.as_ref(), ctx)?; Ok(expr.value) }, - ast::Clause::Placeh { .. } => Err(Error::Placeholder) + ast::Clause::Placeh { .. } => Err(Error::Placeholder), } -} \ No newline at end of file +} diff --git a/src/representations/interpreted.rs b/src/representations/interpreted.rs index d9c2399..798da76 100644 --- a/src/representations/interpreted.rs +++ b/src/representations/interpreted.rs @@ -3,13 +3,12 @@ use std::fmt::Debug; use std::ops::{Deref, DerefMut}; use std::rc::Rc; -use crate::interner::{Token, InternedDisplay}; -use crate::utils::print_nname; - -use super::Literal; use super::location::Location; use super::path_set::PathSet; use super::primitive::Primitive; +use super::Literal; +use crate::interner::{InternedDisplay, Sym}; +use crate::utils::sym2string; // TODO: implement Debug, Eq and Hash with cycle detection @@ -22,20 +21,24 @@ impl Debug for Expr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.location { Location::Unknown => write!(f, "{:?}", self.clause), - loc => write!(f, "{:?}@{}", self.clause, loc) + loc => write!(f, "{:?}@{}", self.clause, loc), } } } impl InternedDisplay for Expr { - fn fmt_i(&self, f: &mut std::fmt::Formatter<'_>, i: &crate::interner::Interner) -> std::fmt::Result { + fn fmt_i( + &self, + f: &mut std::fmt::Formatter<'_>, + i: &crate::interner::Interner, + ) -> std::fmt::Result { match &self.location { Location::Unknown => self.clause.fmt_i(f, i), loc => { write!(f, "{}:(", loc)?; self.clause.fmt_i(f, i)?; write!(f, ")") - } + }, } } } @@ -45,19 +48,20 @@ impl InternedDisplay for Expr { #[derive(Clone)] pub struct ExprInst(pub Rc>); impl ExprInst { - pub fn expr<'a>(&'a self) -> impl Deref + 'a { + pub fn expr(&self) -> impl Deref + '_ { self.0.as_ref().borrow() } - pub fn expr_mut<'a>(&'a self) -> impl DerefMut + 'a { + pub fn expr_mut(&self) -> impl DerefMut + '_ { self.0.as_ref().borrow_mut() } /// Call a normalization function on the expression. The expr is /// updated with the new clause which affects all copies of it /// across the tree. - pub fn try_normalize(&self, - mapper: impl FnOnce(&Clause) -> Result<(Clause, T), E> + pub fn try_normalize( + &self, + mapper: impl FnOnce(&Clause) -> Result<(Clause, T), E>, ) -> Result<(Self, T), E> { let (new_clause, extra) = mapper(&self.expr().clause)?; self.expr_mut().clause = new_clause; @@ -67,15 +71,13 @@ impl ExprInst { /// Run a mutation function on the expression, producing a new, /// distinct expression. The new expression shares location info with /// the original but is normalized independently. - pub fn try_update(&self, - mapper: impl FnOnce(&Clause) -> Result<(Clause, T), E> + pub fn try_update( + &self, + mapper: impl FnOnce(&Clause) -> Result<(Clause, T), E>, ) -> Result<(Self, T), E> { let expr = self.expr(); let (clause, extra) = mapper(&expr.clause)?; - let new_expr = Expr{ - clause, - location: expr.location.clone(), - }; + let new_expr = Expr { clause, location: expr.location.clone() }; Ok((Self(Rc::new(RefCell::new(new_expr))), extra)) } @@ -86,13 +88,16 @@ impl ExprInst { predicate(&self.expr().clause) } - pub fn with_literal(&self, - predicate: impl FnOnce(&Literal) -> T + pub fn with_literal( + &self, + predicate: impl FnOnce(&Literal) -> T, ) -> Result { let expr = self.expr(); if let Clause::P(Primitive::Literal(l)) = &expr.clause { Ok(predicate(l)) - } else {Err(())} + } else { + Err(()) + } } } @@ -106,10 +111,14 @@ impl Debug for ExprInst { } impl InternedDisplay for ExprInst { - fn fmt_i(&self, f: &mut std::fmt::Formatter<'_>, i: &crate::interner::Interner) -> std::fmt::Result { + fn fmt_i( + &self, + f: &mut std::fmt::Formatter<'_>, + i: &crate::interner::Interner, + ) -> std::fmt::Result { match self.0.try_borrow() { Ok(expr) => expr.fmt_i(f, i), - Err(_) => write!(f, "") + Err(_) => write!(f, ""), } } } @@ -117,15 +126,9 @@ impl InternedDisplay for ExprInst { #[derive(Debug, Clone)] pub enum Clause { P(Primitive), - Apply{ - f: ExprInst, - x: ExprInst - }, - Constant(Token>>), - Lambda{ - args: Option, - body: ExprInst - }, + Apply { f: ExprInst, x: ExprInst }, + Constant(Sym), + Lambda { args: Option, body: ExprInst }, LambdaArg, } impl Clause { @@ -133,15 +136,19 @@ impl Clause { /// copied or moved clauses as it does not have debug information and /// does not share a normalization cache list with them. pub fn wrap(self) -> ExprInst { - ExprInst(Rc::new(RefCell::new(Expr{ + ExprInst(Rc::new(RefCell::new(Expr { location: Location::Unknown, - clause: self + clause: self, }))) } } impl InternedDisplay for Clause { - fn fmt_i(&self, f: &mut std::fmt::Formatter<'_>, i: &crate::interner::Interner) -> std::fmt::Result { + fn fmt_i( + &self, + f: &mut std::fmt::Formatter<'_>, + i: &crate::interner::Interner, + ) -> std::fmt::Result { match self { Clause::P(p) => write!(f, "{p:?}"), Clause::LambdaArg => write!(f, "arg"), @@ -151,7 +158,7 @@ impl InternedDisplay for Clause { write!(f, " ")?; x.fmt_i(f, i)?; write!(f, ")") - } + }, Clause::Lambda { args, body } => { write!(f, "\\")?; match args { @@ -161,7 +168,7 @@ impl InternedDisplay for Clause { write!(f, ".")?; body.fmt_i(f, i) }, - Clause::Constant(t) => write!(f, "{}", print_nname(*t, i)) + Clause::Constant(t) => write!(f, "{}", sym2string(*t, i)), } } } @@ -170,4 +177,4 @@ impl> From for Clause { fn from(value: T) -> Self { Self::P(Primitive::Literal(value.into())) } -} \ No newline at end of file +} diff --git a/src/representations/literal.rs b/src/representations/literal.rs index 686b418..e630438 100644 --- a/src/representations/literal.rs +++ b/src/representations/literal.rs @@ -1,7 +1,9 @@ -use ordered_float::NotNan; use std::fmt::Debug; -/// An exact value, read from the AST and unmodified in shape until compilation +use ordered_float::NotNan; + +/// An exact value, read from the AST and unmodified in shape until +/// compilation #[derive(Clone, PartialEq, Eq, Hash)] pub enum Literal { Num(NotNan), @@ -22,14 +24,22 @@ impl Debug for Literal { } impl From> for Literal { - fn from(value: NotNan) -> Self { Self::Num(value) } + fn from(value: NotNan) -> Self { + Self::Num(value) + } } impl From for Literal { - fn from(value: u64) -> Self { Self::Uint(value) } + fn from(value: u64) -> Self { + Self::Uint(value) + } } impl From for Literal { - fn from(value: char) -> Self { Self::Char(value) } + fn from(value: char) -> Self { + Self::Char(value) + } } impl From for Literal { - fn from(value: String) -> Self { Self::Str(value) } -} \ No newline at end of file + fn from(value: String) -> Self { + Self::Str(value) + } +} diff --git a/src/representations/location.rs b/src/representations/location.rs index d7f4ed4..d9e7b25 100644 --- a/src/representations/location.rs +++ b/src/representations/location.rs @@ -1,4 +1,6 @@ -use std::{ops::Range, rc::Rc, fmt::Display}; +use std::fmt::Display; +use std::ops::Range; +use std::rc::Rc; use itertools::Itertools; @@ -6,23 +8,24 @@ use itertools::Itertools; pub enum Location { Unknown, File(Rc>), - Range{ - file: Rc>, - range: Range, - } + Range { file: Rc>, range: Range }, } impl Location { pub fn range(&self) -> Option> { - if let Self::Range{ range, .. } = self { + if let Self::Range { range, .. } = self { Some(range.clone()) - } else { None } + } else { + None + } } pub fn file(&self) -> Option>> { if let Self::File(file) | Self::Range { file, .. } = self { Some(file.clone()) - } else { None } + } else { + None + } } } @@ -31,10 +34,13 @@ impl Display for Location { match self { Self::Unknown => write!(f, "unknown"), Self::File(file) => write!(f, "{}.orc", file.iter().join("/")), - Self::Range{ file, range } => write!(f, + Self::Range { file, range } => write!( + f, "{}.orc:{}..{}", - file.iter().join("/"), range.start, range.end - ) + file.iter().join("/"), + range.start, + range.end + ), } } } diff --git a/src/representations/mod.rs b/src/representations/mod.rs index 289c140..852dce7 100644 --- a/src/representations/mod.rs +++ b/src/representations/mod.rs @@ -1,15 +1,14 @@ pub mod ast; -// pub mod typed; -pub mod literal; pub mod ast_to_postmacro; -pub(crate) mod interpreted; +pub mod interpreted; +pub mod literal; +pub mod location; +pub mod path_set; pub mod postmacro; pub mod primitive; -pub mod path_set; pub mod sourcefile; pub mod tree; -pub mod location; pub use path_set::PathSet; pub use primitive::Primitive; pub mod postmacro_to_interpreted; -pub use literal::Literal; \ No newline at end of file +pub use literal::Literal; diff --git a/src/representations/path_set.rs b/src/representations/path_set.rs index 70de485..afab1bc 100644 --- a/src/representations/path_set.rs +++ b/src/representations/path_set.rs @@ -1,17 +1,16 @@ -use std::rc::Rc; -use std::ops::Add; use std::fmt::Debug; +use std::ops::Add; +use std::rc::Rc; use crate::utils::Side; - /// A set of paths into a Lambda expression #[derive(Clone, PartialEq, Eq, Hash)] pub struct PathSet { /// The definite steps pub steps: Rc>, /// if Some, it splits. If None, it ends. - pub next: Option<(Rc, Rc)> + pub next: Option<(Rc, Rc)>, } impl Debug for PathSet { @@ -33,23 +32,17 @@ impl Debug for PathSet { impl Add for PathSet { type Output = Self; fn add(self, rhs: Self) -> Self::Output { - Self { - steps: Rc::new(vec![]), - next: Some((Rc::new(self), Rc::new(rhs))) - } + Self { steps: Rc::new(vec![]), next: Some((Rc::new(self), Rc::new(rhs))) } } } impl Add for PathSet { type Output = Self; fn add(self, rhs: Side) -> Self::Output { - let PathSet{ steps, next } = self; + let PathSet { steps, next } = self; let mut new_steps = Rc::unwrap_or_clone(steps); new_steps.insert(0, rhs); - Self { - steps: Rc::new(new_steps), - next, - } + Self { steps: Rc::new(new_steps), next } } } @@ -59,8 +52,10 @@ mod tests { #[test] fn test_combine() -> Result<(), &'static str> { - let ps1 = PathSet { next: None, steps: Rc::new(vec![Side::Left, Side::Left]) }; - let ps2 = PathSet { next: None, steps: Rc::new(vec![Side::Left, Side::Right]) }; + let ps1 = + PathSet { next: None, steps: Rc::new(vec![Side::Left, Side::Left]) }; + let ps2 = + PathSet { next: None, steps: Rc::new(vec![Side::Left, Side::Right]) }; let sum = ps1.clone() + ps2.clone(); assert_eq!(sum.steps.as_ref(), &[]); let nexts = sum.next.ok_or("nexts not set")?; @@ -72,10 +67,16 @@ mod tests { fn extend_scaffold() -> PathSet { PathSet { next: Some(( - Rc::new(PathSet { next: None, steps: Rc::new(vec![Side::Left, Side::Left]) }), - Rc::new(PathSet { next: None, steps: Rc::new(vec![Side::Left, Side::Right]) }) + Rc::new(PathSet { + next: None, + steps: Rc::new(vec![Side::Left, Side::Left]), + }), + Rc::new(PathSet { + next: None, + steps: Rc::new(vec![Side::Left, Side::Right]), + }), )), - steps: Rc::new(vec![Side::Left, Side::Right, Side::Left]) + steps: Rc::new(vec![Side::Left, Side::Right, Side::Left]), } } @@ -83,7 +84,12 @@ mod tests { fn test_extend_noclone() { let ps = extend_scaffold(); let new = ps + Side::Left; - assert_eq!(new.steps.as_ref().as_slice(), &[Side::Left, Side::Left, Side::Right, Side::Left]) + assert_eq!(new.steps.as_ref().as_slice(), &[ + Side::Left, + Side::Left, + Side::Right, + Side::Left + ]) } #[test] @@ -93,4 +99,4 @@ mod tests { let new = ps + Side::Left; assert_eq!(new.steps.len(), 4); } -} \ No newline at end of file +} diff --git a/src/representations/postmacro.rs b/src/representations/postmacro.rs index 3c6b88c..5329360 100644 --- a/src/representations/postmacro.rs +++ b/src/representations/postmacro.rs @@ -1,26 +1,30 @@ -use crate::utils::string_from_charset; -use crate::interner::Token; - -use super::location::Location; -use super::primitive::Primitive; - use std::fmt::{Debug, Write}; use std::rc::Rc; -/// Indicates whether either side needs to be wrapped. Syntax whose end is ambiguous on that side -/// must use parentheses, or forward the flag +use super::location::Location; +use super::primitive::Primitive; +use crate::interner::Sym; +use crate::utils::string_from_charset; + +/// Indicates whether either side needs to be wrapped. Syntax whose end is +/// ambiguous on that side must use parentheses, or forward the flag #[derive(PartialEq, Eq, Clone, Copy)] struct Wrap(bool, bool); #[derive(Clone)] -pub struct Expr{ +pub struct Expr { pub value: Clause, pub location: Location, } impl Expr { - fn deep_fmt(&self, f: &mut std::fmt::Formatter<'_>, depth: usize, tr: Wrap) -> std::fmt::Result { - let Expr{ value, .. } = self; + fn deep_fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + depth: usize, + tr: Wrap, + ) -> std::fmt::Result { + let Expr { value, .. } = self; value.deep_fmt(f, depth, tr)?; Ok(()) } @@ -36,7 +40,7 @@ impl Debug for Expr { pub enum Clause { Apply(Rc, Rc), Lambda(Rc), - Constant(Token>>), + Constant(Sym), LambdaArg(usize), P(Primitive), } @@ -44,21 +48,32 @@ pub enum Clause { const ARGNAME_CHARSET: &str = "abcdefghijklmnopqrstuvwxyz"; fn parametric_fmt( - f: &mut std::fmt::Formatter<'_>, depth: usize, - prefix: &str, body: &Expr, wrap_right: bool + f: &mut std::fmt::Formatter<'_>, + depth: usize, + prefix: &str, + body: &Expr, + wrap_right: bool, ) -> std::fmt::Result { - if wrap_right { f.write_char('(')?; } + if wrap_right { + f.write_char('(')?; + } f.write_str(prefix)?; f.write_str(&string_from_charset(depth as u64, ARGNAME_CHARSET))?; f.write_str(".")?; body.deep_fmt(f, depth + 1, Wrap(false, false))?; - if wrap_right { f.write_char(')')?; } + if wrap_right { + f.write_char(')')?; + } Ok(()) } impl Clause { - fn deep_fmt(&self, f: &mut std::fmt::Formatter<'_>, depth: usize, Wrap(wl, wr): Wrap) - -> std::fmt::Result { + fn deep_fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + depth: usize, + Wrap(wl, wr): Wrap, + ) -> std::fmt::Result { match self { Self::P(p) => write!(f, "{p:?}"), Self::Lambda(body) => parametric_fmt(f, depth, "\\", body, wr), @@ -67,24 +82,24 @@ impl Clause { f.write_str(&string_from_charset(lambda_depth, ARGNAME_CHARSET)) }, Self::Apply(func, x) => { - if wl { f.write_char('(')?; } + if wl { + f.write_char('(')?; + } func.deep_fmt(f, depth, Wrap(false, true))?; f.write_char(' ')?; x.deep_fmt(f, depth, Wrap(true, wr && !wl))?; - if wl { f.write_char(')')?; } + if wl { + f.write_char(')')?; + } Ok(()) - } - Self::Constant(token) => write!(f, "{:?}", token) + }, + Self::Constant(token) => write!(f, "{:?}", token), } } - #[allow(unused)] - pub fn wrap(self) -> Box { - Box::new(Expr{ value: self, location: Location::Unknown }) - } } impl Debug for Clause { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.deep_fmt(f, 0, Wrap(false, false)) } -} \ No newline at end of file +} diff --git a/src/representations/postmacro_to_interpreted.rs b/src/representations/postmacro_to_interpreted.rs index bc8a094..0dc8b9d 100644 --- a/src/representations/postmacro_to_interpreted.rs +++ b/src/representations/postmacro_to_interpreted.rs @@ -1,19 +1,29 @@ -use std::{rc::Rc, cell::RefCell}; +use std::cell::RefCell; +use std::rc::Rc; +use super::path_set::PathSet; +use super::{interpreted, postmacro}; use crate::utils::Side; -use super::{postmacro, interpreted, path_set::PathSet}; - -fn collect_paths_expr_rec(expr: &postmacro::Expr, depth: usize) -> Option { +fn collect_paths_expr_rec( + expr: &postmacro::Expr, + depth: usize, +) -> Option { collect_paths_cls_rec(&expr.value, depth) } -fn collect_paths_cls_rec(cls: &postmacro::Clause, depth: usize) -> Option { +fn collect_paths_cls_rec( + cls: &postmacro::Clause, + depth: usize, +) -> Option { match cls { postmacro::Clause::P(_) | postmacro::Clause::Constant(_) => None, - postmacro::Clause::LambdaArg(h) => if *h != depth {None} else { - Some(PathSet{ next: None, steps: Rc::new(vec![]) }) - } + postmacro::Clause::LambdaArg(h) => + if *h != depth { + None + } else { + Some(PathSet { next: None, steps: Rc::new(vec![]) }) + }, postmacro::Clause::Lambda(b) => collect_paths_expr_rec(b, depth + 1), postmacro::Clause::Apply(f, x) => { let f_opt = collect_paths_expr_rec(f, depth); @@ -22,32 +32,29 @@ fn collect_paths_cls_rec(cls: &postmacro::Clause, depth: usize) -> Option Some(f_refs + x_refs), (Some(f_refs), None) => Some(f_refs + Side::Left), (None, Some(x_refs)) => Some(x_refs + Side::Right), - (None, None) => None + (None, None) => None, } - } + }, } } pub fn clause(cls: &postmacro::Clause) -> interpreted::Clause { match cls { - postmacro::Clause::Constant(name) - => interpreted::Clause::Constant(*name), + postmacro::Clause::Constant(name) => interpreted::Clause::Constant(*name), postmacro::Clause::P(p) => interpreted::Clause::P(p.clone()), - postmacro::Clause::Apply(f, x) => interpreted::Clause::Apply { - f: expr(f.as_ref()), - x: expr(x.as_ref()), - }, + postmacro::Clause::Apply(f, x) => + interpreted::Clause::Apply { f: expr(f.as_ref()), x: expr(x.as_ref()) }, postmacro::Clause::Lambda(body) => interpreted::Clause::Lambda { args: collect_paths_expr_rec(body, 0), - body: expr(body) + body: expr(body), }, - postmacro::Clause::LambdaArg(_) => interpreted::Clause::LambdaArg + postmacro::Clause::LambdaArg(_) => interpreted::Clause::LambdaArg, } } pub fn expr(expr: &postmacro::Expr) -> interpreted::ExprInst { - interpreted::ExprInst(Rc::new(RefCell::new(interpreted::Expr{ + interpreted::ExprInst(Rc::new(RefCell::new(interpreted::Expr { location: expr.location.clone(), clause: clause(&expr.value), }))) -} \ No newline at end of file +} diff --git a/src/representations/primitive.rs b/src/representations/primitive.rs index 00df325..59033a3 100644 --- a/src/representations/primitive.rs +++ b/src/representations/primitive.rs @@ -1,8 +1,7 @@ use std::fmt::Debug; -use crate::foreign::{ExternFn, Atom}; - use super::Literal; +use crate::foreign::{Atom, ExternFn}; pub enum Primitive { /// A literal value, eg. `1`, `"hello"` @@ -10,14 +9,16 @@ pub enum Primitive { /// An opaque function, eg. an effectful function employing CPS. ExternFn(Box), /// An opaque non-callable value, eg. a file handle. - Atom(Atom) + Atom(Atom), } impl PartialEq for Primitive { fn eq(&self, other: &Self) -> bool { if let (Self::Literal(l1), Self::Literal(l2)) = (self, other) { l1 == l2 - } else {false} + } else { + false + } } } @@ -26,9 +27,8 @@ impl Clone for Primitive { match self { Primitive::Literal(l) => Primitive::Literal(l.clone()), Primitive::Atom(a) => Primitive::Atom(a.clone()), - Primitive::ExternFn(ef) => Primitive::ExternFn( - dyn_clone::clone_box(ef.as_ref()) - ) + Primitive::ExternFn(ef) => + Primitive::ExternFn(dyn_clone::clone_box(ef.as_ref())), } } } @@ -42,4 +42,3 @@ impl Debug for Primitive { } } } - diff --git a/src/representations/sourcefile.rs b/src/representations/sourcefile.rs index 83f6dec..e7ce41e 100644 --- a/src/representations/sourcefile.rs +++ b/src/representations/sourcefile.rs @@ -1,23 +1,23 @@ -use itertools::{Itertools, Either}; +use itertools::{Either, Itertools}; -use crate::interner::{Token, Interner}; +use crate::ast::{Constant, Rule}; +use crate::interner::{Interner, Sym, Tok}; +use crate::unwrap_or; use crate::utils::BoxedIter; -use crate::ast::{Rule, Constant}; - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Import { - pub path: Token>>, + pub path: Sym, /// If name is None, this is a wildcard import - pub name: Option> + pub name: Option>, } impl Import { /// Get the preload target space for this import - the prefix below /// which all files should be included in the compilation - /// + /// /// Returns the path if this is a glob import, or the path plus the /// name if this is a specific import - pub fn nonglob_path(&self, i: &Interner) -> Vec> { + pub fn nonglob_path(&self, i: &Interner) -> Vec> { let mut path_vec = i.r(self.path).clone(); if let Some(n) = self.name { path_vec.push(n) @@ -26,12 +26,19 @@ impl Import { } } +/// A namespace block +#[derive(Debug, Clone)] +pub struct Namespace { + pub name: Tok, + pub body: Vec, +} + /// Things that may be prefixed with an export #[derive(Debug, Clone)] pub enum Member { Rule(Rule), Constant(Constant), - Namespace(Token, Vec) + Namespace(Namespace), } /// Anything we might encounter in a file @@ -41,55 +48,63 @@ pub enum FileEntry { Comment(String), Exported(Member), Internal(Member), - Export(Vec>), + Export(Vec>), } -/// Summarize all imports from a file in a single list of qualified names +/// Summarize all imports from a file in a single list of qualified names pub fn imports<'a>( - src: impl Iterator + 'a + src: impl Iterator + 'a, ) -> impl Iterator + 'a { - src.filter_map(|ent| match ent { - FileEntry::Import(impv) => Some(impv.iter()), - _ => None - }).flatten() + src + .filter_map(|ent| match ent { + FileEntry::Import(impv) => Some(impv.iter()), + _ => None, + }) + .flatten() } /// Join the various redeclarations of namespaces. /// Error if they're inconsistently exported pub fn normalize_namespaces( - src: BoxedIter, i: &Interner -) -> Result, Vec>> { + src: BoxedIter, +) -> Result, Vec>> { let (mut namespaces, mut rest) = src .partition_map::, Vec<_>, _, _, _>(|ent| match ent { - FileEntry::Exported(Member::Namespace(name, body)) - => Either::Left((true, name, body)), - FileEntry::Internal(Member::Namespace(name, body)) - => Either::Left((false, name, body)), - other => Either::Right(other) + FileEntry::Exported(Member::Namespace(ns)) => Either::Left((true, ns)), + FileEntry::Internal(Member::Namespace(ns)) => Either::Left((false, ns)), + other => Either::Right(other), }); // Combine namespace blocks with the same name - namespaces.sort_unstable_by_key(|(_, name, _)| *name); - let mut lumped = namespaces.into_iter() - .group_by(|(_, name, _)| *name).into_iter() + namespaces.sort_unstable_by_key(|(_, ns)| ns.name); + let mut lumped = namespaces + .into_iter() + .group_by(|(_, ns)| ns.name) + .into_iter() .map(|(name, grp)| { let mut any_exported = false; let mut any_internal = false; - let grp_src = grp.into_iter() - .map(|(exported, name, body)| { - if exported {any_exported = true} - else {any_internal = true}; - (name, body) // Impure map is less than ideal but works + let grp_src = grp + .into_iter() + .map(|(exported, ns)| { + if exported { + any_exported = true + } else { + any_internal = true + }; + ns // Impure map is less than ideal but works }) - .flat_map(|(_, entv)| entv.into_iter()); + .flat_map(|ns| ns.body.into_iter()); // Apply the function to the contents of these blocks too - let data = normalize_namespaces(Box::new(grp_src), i) - .map_err(|mut e| { e.push(name); e })?; - let member = Member::Namespace(name, data); + let body = normalize_namespaces(Box::new(grp_src)).map_err(|mut e| { + e.push(name); + e + })?; + let member = Member::Namespace(Namespace { name, body }); match (any_exported, any_internal) { (true, true) => Err(vec![name]), (true, false) => Ok(FileEntry::Exported(member)), (false, true) => Ok(FileEntry::Internal(member)), - (false, false) => unreachable!("The group cannot be empty") + (false, false) => unreachable!("The group cannot be empty"), } }) .collect::, _>>()?; @@ -99,30 +114,29 @@ pub fn normalize_namespaces( /// Turn a relative (import) path into an absolute path. /// If the import path is empty, the return value is also empty. -/// +/// /// # Errors -/// +/// /// if the relative path contains more `super` segments than the length /// of the absolute path. pub fn absolute_path( - abs_location: &[Token], - rel_path: &[Token], + abs_location: &[Tok], + rel_path: &[Tok], i: &Interner, - is_child: &impl Fn(Token) -> bool, -) -> Result>, ()> { - let (head, tail) = if let Some(p) = rel_path.split_first() {p} - else {return Ok(vec![])}; +) -> Result>, ()> { + let (head, tail) = unwrap_or!(rel_path.split_first(); + return Ok(vec![]) + ); if *head == i.i("super") { let (_, new_abs) = abs_location.split_last().ok_or(())?; - if tail.len() == 0 {Ok(new_abs.to_vec())} - else {absolute_path(new_abs, tail, i, is_child)} + if tail.is_empty() { + Ok(new_abs.to_vec()) + } else { + absolute_path(new_abs, tail, i) + } } else if *head == i.i("self") { - Ok(abs_location.iter() - .chain(tail.iter()) - .copied() - .collect() - ) + Ok(abs_location.iter().chain(tail.iter()).copied().collect()) } else { Ok(rel_path.to_vec()) } -} \ No newline at end of file +} diff --git a/src/representations/tree.rs b/src/representations/tree.rs index 4c4da5d..0ebe260 100644 --- a/src/representations/tree.rs +++ b/src/representations/tree.rs @@ -1,113 +1,98 @@ use std::ops::Add; use std::rc::Rc; + use hashbrown::HashMap; -use crate::interner::Token; +use super::sourcefile::Import; +use crate::interner::Tok; use crate::utils::Substack; -use super::sourcefile::Import; - #[derive(Debug, Clone, PartialEq, Eq)] -pub enum ModMember{ +pub enum ModMember { Item(TItem), - Sub(Rc>) -} -impl ModMember { - #[allow(unused)] - pub fn item(&self) -> &TItem { - if let Self::Item(it) = self {it} else { - panic!("Expected item, found submodule") - } - } - - #[allow(unused)] - pub fn sub(&self) -> &Rc> { - if let Self::Sub(sub) = self {sub} else { - panic!("Expected submodule, found item") - } - } + Sub(Rc>), } #[derive(Debug, Clone, PartialEq, Eq)] -pub struct ModEntry{ +pub struct ModEntry { pub member: ModMember, - pub exported: bool + pub exported: bool, } -/// A module, containing imports, +/// A module, containing imports, #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Module{ +pub struct Module { pub imports: Vec, - pub items: HashMap, ModEntry>, - pub extra: TExt + pub items: HashMap, ModEntry>, + pub extra: TExt, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum WalkErrorKind { Private, - Missing + Missing, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct WalkError { pub pos: usize, - pub kind: WalkErrorKind + pub kind: WalkErrorKind, } -pub type ModPath<'a> = Substack<'a, Token>; +pub type ModPath<'a> = Substack<'a, Tok>; impl Module { - pub fn walk(self: &Rc, - path: &[Token], require_exported: bool + pub fn walk( + self: &Rc, + path: &[Tok], + require_exported: bool, ) -> Result, WalkError> { let mut cur = self; for (pos, step) in path.iter().enumerate() { - if let Some(ModEntry{ - member: ModMember::Sub(next), - exported, - }) = cur.items.get(step) { + if let Some(ModEntry { member: ModMember::Sub(next), exported }) = + cur.items.get(step) + { if require_exported && !exported { - return Err(WalkError{ pos, kind: WalkErrorKind::Private }) + return Err(WalkError { pos, kind: WalkErrorKind::Private }); } cur = next } else { - return Err(WalkError{ pos, kind: WalkErrorKind::Missing }) + return Err(WalkError { pos, kind: WalkErrorKind::Missing }); } } Ok(cur.clone()) } - fn visit_all_imports_rec(&self, + fn visit_all_imports_rec( + &self, path: ModPath, - callback: &mut impl FnMut(ModPath, &Self, &Import) -> Result<(), E> + callback: &mut impl FnMut(ModPath, &Self, &Import) -> Result<(), E>, ) -> Result<(), E> { for import in self.imports.iter() { callback(path, self, import)? } for (name, entry) in self.items.iter() { if let ModMember::Sub(module) = &entry.member { - module.visit_all_imports_rec( - path.push(*name), - callback - )? + module.visit_all_imports_rec(path.push(*name), callback)? } } Ok(()) } - pub fn visit_all_imports(&self, - callback: &mut impl FnMut(ModPath, &Self, &Import) -> Result<(), E> + pub fn visit_all_imports( + &self, + callback: &mut impl FnMut(ModPath, &Self, &Import) -> Result<(), E>, ) -> Result<(), E> { self.visit_all_imports_rec(Substack::Bottom, callback) } } impl> Add -for Module + for Module { type Output = Self; fn add(mut self, rhs: Self) -> Self::Output { - let Module{ extra, imports, items } = rhs; + let Module { extra, imports, items } = rhs; for (key, right) in items { // if both contain a submodule if let Some(left) = self.items.remove(&key) { @@ -115,9 +100,9 @@ for Module if let ModMember::Sub(lsub) = &left.member { // merge them with rhs exportedness let new_mod = lsub.as_ref().clone() + rsub.as_ref().clone(); - self.items.insert(key, ModEntry{ + self.items.insert(key, ModEntry { exported: right.exported, - member: ModMember::Sub(Rc::new(new_mod)) + member: ModMember::Sub(Rc::new(new_mod)), }); continue; } @@ -130,4 +115,4 @@ for Module self.extra = self.extra + extra; self } -} \ No newline at end of file +} diff --git a/src/representations/typed.rs b/src/representations/typed.rs index 463c8b1..7a338a1 100644 --- a/src/representations/typed.rs +++ b/src/representations/typed.rs @@ -1,26 +1,29 @@ -use mappable_rc::Mrc; -use crate::executor::apply_lambda; -use crate::foreign::{Atom, ExternFn}; -use crate::utils::{to_mrc_slice, one_mrc_slice}; -use crate::utils::string_from_charset; - -use super::get_name::get_name; -use super::primitive::Primitive; -use super::{Literal, ast_to_postmacro, get_name}; -use super::ast; - use std::fmt::{Debug, Write}; use std::rc::Rc; -/// Indicates whether either side needs to be wrapped. Syntax whose end is ambiguous on that side -/// must use parentheses, or forward the flag +use mappable_rc::Mrc; + +use super::get_name::get_name; +use super::primitive::Primitive; +use super::{ast, ast_to_postmacro, get_name, Literal}; +use crate::executor::apply_lambda; +use crate::foreign::{Atom, ExternFn}; +use crate::utils::{one_mrc_slice, string_from_charset, to_mrc_slice}; + +/// Indicates whether either side needs to be wrapped. Syntax whose end is +/// ambiguous on that side must use parentheses, or forward the flag #[derive(PartialEq, Eq, Clone, Copy)] struct Wrap(bool, bool); #[derive(PartialEq, Eq, Hash, Clone)] pub struct Expr(pub Clause, pub Vec); impl Expr { - fn deep_fmt(&self, f: &mut std::fmt::Formatter<'_>, depth: usize, tr: Wrap) -> std::fmt::Result { + fn deep_fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + depth: usize, + tr: Wrap, + ) -> std::fmt::Result { let Expr(val, typ) = self; if typ.len() > 0 { val.deep_fmt(f, depth, Wrap(true, true))?; @@ -47,16 +50,23 @@ pub enum Clause { Apply(Rc, Rc), Lambda(Rc<[Clause]>, Rc), Auto(Rc<[Clause]>, Rc), - LambdaArg(usize), AutoArg(usize), + LambdaArg(usize), + AutoArg(usize), } const ARGNAME_CHARSET: &str = "abcdefghijklmnopqrstuvwxyz"; fn parametric_fmt( - f: &mut std::fmt::Formatter<'_>, depth: usize, - prefix: &str, argtyp: &[Clause], body: &Expr, wrap_right: bool + f: &mut std::fmt::Formatter<'_>, + depth: usize, + prefix: &str, + argtyp: &[Clause], + body: &Expr, + wrap_right: bool, ) -> std::fmt::Result { - if wrap_right { f.write_char('(')?; } + if wrap_right { + f.write_char('(')?; + } f.write_str(prefix)?; f.write_str(&string_from_charset(depth as u64, ARGNAME_CHARSET))?; for typ in argtyp.iter() { @@ -65,33 +75,49 @@ fn parametric_fmt( } f.write_str(".")?; body.deep_fmt(f, depth + 1, Wrap(false, false))?; - if wrap_right { f.write_char(')')?; } + if wrap_right { + f.write_char(')')?; + } Ok(()) } impl Clause { - fn deep_fmt(&self, f: &mut std::fmt::Formatter<'_>, depth: usize, Wrap(wl, wr): Wrap) - -> std::fmt::Result { + fn deep_fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + depth: usize, + Wrap(wl, wr): Wrap, + ) -> std::fmt::Result { match self { Self::P(p) => write!(f, "{p:?}"), - Self::Lambda(argtyp, body) => parametric_fmt(f, depth, "\\", argtyp, body, wr), - Self::Auto(argtyp, body) => parametric_fmt(f, depth, "@", argtyp, body, wr), + Self::Lambda(argtyp, body) => + parametric_fmt(f, depth, "\\", argtyp, body, wr), + Self::Auto(argtyp, body) => + parametric_fmt(f, depth, "@", argtyp, body, wr), Self::LambdaArg(skip) | Self::AutoArg(skip) => { let lambda_depth = (depth - skip - 1).try_into().unwrap(); f.write_str(&string_from_charset(lambda_depth, ARGNAME_CHARSET)) }, Self::Apply(func, x) => { - if wl { f.write_char('(')?; } - func.deep_fmt(f, depth, Wrap(false, true) )?; + if wl { + f.write_char('(')?; + } + func.deep_fmt(f, depth, Wrap(false, true))?; f.write_char(' ')?; - x.deep_fmt(f, depth, Wrap(true, wr && !wl) )?; - if wl { f.write_char(')')?; } + x.deep_fmt(f, depth, Wrap(true, wr && !wl))?; + if wl { + f.write_char(')')?; + } Ok(()) - } + }, } } - pub fn wrap(self) -> Box { Box::new(Expr(self, vec![])) } - pub fn wrap_t(self, t: Clause) -> Box { Box::new(Expr(self, vec![t])) } + pub fn wrap(self) -> Box { + Box::new(Expr(self, vec![])) + } + pub fn wrap_t(self, t: Clause) -> Box { + Box::new(Expr(self, vec![t])) + } } impl Clone for Clause { @@ -99,12 +125,14 @@ impl Clone for Clause { match self { Clause::Auto(t, b) => { let new_id = get_name(); - let new_body = apply_lambda(*uid, Clause::AutoArg(new_id).wrap(), b.clone()); + let new_body = + apply_lambda(*uid, Clause::AutoArg(new_id).wrap(), b.clone()); Clause::Auto(new_id, t.clone(), new_body) }, Clause::Lambda(uid, t, b) => { let new_id = get_name(); - let new_body = apply_lambda(*uid, Clause::LambdaArg(new_id).wrap(), b.clone()); + let new_body = + apply_lambda(*uid, Clause::LambdaArg(new_id).wrap(), b.clone()); Clause::Lambda(new_id, t.clone(), new_body) }, Clause::Literal(l) => Clause::Literal(l.clone()), @@ -112,7 +140,7 @@ impl Clone for Clause { Clause::Atom(a) => Clause::Atom(a.clone()), Clause::Apply(f, x) => Clause::Apply(Box::clone(&f), x.clone()), Clause::LambdaArg(id) => Clause::LambdaArg(*id), - Clause::AutoArg(id) => Clause::AutoArg(*id) + Clause::AutoArg(id) => Clause::AutoArg(*id), } } } @@ -142,21 +170,32 @@ pub fn is_used_clause(id: u64, is_auto: bool, clause: &Clause) -> bool { Clause::Atom(_) | Clause::ExternFn(_) | Clause::Literal(_) => false, Clause::AutoArg(x) => is_auto && *x == id, Clause::LambdaArg(x) => !is_auto && *x == id, - Clause::Apply(f, x) => is_used_expr(id, is_auto, &f) || is_used_expr(id, is_auto, &x), + Clause::Apply(f, x) => + is_used_expr(id, is_auto, &f) || is_used_expr(id, is_auto, &x), Clause::Auto(n, t, b) => { assert!(*n != id, "Shadowing should have been eliminated"); - if is_auto && t.iter().any(|c| is_used_clause(id, is_auto, c)) {return true}; + if is_auto && t.iter().any(|c| is_used_clause(id, is_auto, c)) { + return true; + }; is_used_expr(id, is_auto, b) - } + }, Clause::Lambda(n, t, b) => { assert!(*n != id, "Shadowing should have been eliminated"); - if is_auto && t.iter().any(|c| is_used_clause(id, is_auto, c)) {return true}; + if is_auto && t.iter().any(|c| is_used_clause(id, is_auto, c)) { + return true; + }; is_used_expr(id, is_auto, b) - } + }, } } -pub fn is_used_expr(id: u64, is_auto: bool, Expr(val, typ): &Expr) -> bool { - if is_auto && typ.iter().any(|c| is_used_clause(id, is_auto, c)) {return true}; +pub fn is_used_expr( + id: u64, + is_auto: bool, + Expr(val, typ): &Expr, +) -> bool { + if is_auto && typ.iter().any(|c| is_used_clause(id, is_auto, c)) { + return true; + }; is_used_clause(id, is_auto, val) -} \ No newline at end of file +} diff --git a/src/rule/matcher.rs b/src/rule/matcher.rs index 303b565..e29f9f8 100644 --- a/src/rule/matcher.rs +++ b/src/rule/matcher.rs @@ -1,10 +1,9 @@ use std::rc::Rc; -use crate::ast::Expr; - use super::state::State; +use crate::ast::Expr; pub trait Matcher { fn new(pattern: Rc>) -> Self; fn apply<'a>(&self, source: &'a [Expr]) -> Option>; -} \ No newline at end of file +} diff --git a/src/rule/matcher_second/any_match.rs b/src/rule/matcher_second/any_match.rs index b1b4dba..812469d 100644 --- a/src/rule/matcher_second/any_match.rs +++ b/src/rule/matcher_second/any_match.rs @@ -1,20 +1,25 @@ -use crate::{ast::Expr, rule::state::State}; +use super::scal_match::scalv_match; +use super::shared::AnyMatcher; +use super::vec_match::vec_match; +use crate::ast::Expr; +use crate::rule::state::State; -use super::{shared::AnyMatcher, scal_match::scalv_match, vec_match::vec_match}; - -pub fn any_match<'a>(matcher: &AnyMatcher, seq: &'a [Expr]) --> Option> -{ +pub fn any_match<'a>( + matcher: &AnyMatcher, + seq: &'a [Expr], +) -> Option> { match matcher { AnyMatcher::Scalar(scalv) => scalv_match(scalv, seq), - AnyMatcher::Vec{ left, mid, right } => { - if seq.len() < left.len() + right.len() {return None}; + AnyMatcher::Vec { left, mid, right } => { + if seq.len() < left.len() + right.len() { + return None; + }; let left_split = left.len(); let right_split = seq.len() - right.len(); let mut state = scalv_match(left, &seq[..left_split])?; state.extend(scalv_match(right, &seq[right_split..])?); state.extend(vec_match(mid, &seq[left_split..right_split])?); Some(state) - } + }, } -} \ No newline at end of file +} diff --git a/src/rule/matcher_second/build.rs b/src/rule/matcher_second/build.rs index 44e19fb..b886987 100644 --- a/src/rule/matcher_second/build.rs +++ b/src/rule/matcher_second/build.rs @@ -1,34 +1,28 @@ use itertools::Itertools; -use crate::{rule::vec_attrs::vec_attrs, ast::Clause}; -use crate::utils::Side; -use crate::interner::Token; -use crate::ast::{Expr, Placeholder, PHClass}; - use super::shared::{AnyMatcher, ScalMatcher, VecMatcher}; +use crate::ast::{Clause, Expr, PHClass, Placeholder}; +use crate::interner::Tok; +use crate::rule::vec_attrs::vec_attrs; +use crate::utils::Side; +pub type MaxVecSplit<'a> = (&'a [Expr], (Tok, u64, bool), &'a [Expr]); -pub type MaxVecSplit<'a> = (&'a [Expr], (Token, u64, bool), &'a [Expr]); - -/// Derive the details of the central vectorial and the two sides from a slice of Expr's +/// Derive the details of the central vectorial and the two sides from a +/// slice of Expr's fn split_at_max_vec(pattern: &[Expr]) -> Option { - let rngidx = pattern.iter() - .position_max_by_key(|expr| { - vec_attrs(expr) - .map(|attrs| attrs.1 as i64) - .unwrap_or(-1) - })?; + let rngidx = pattern.iter().position_max_by_key(|expr| { + vec_attrs(expr).map(|attrs| attrs.1 as i64).unwrap_or(-1) + })?; let (left, not_left) = pattern.split_at(rngidx); - let (placeh, right) = not_left.split_first() + let (placeh, right) = not_left + .split_first() .expect("The index of the greatest element must be less than the length"); - vec_attrs(placeh) - .map(|attrs| (left, attrs, right)) + vec_attrs(placeh).map(|attrs| (left, attrs, right)) } fn scal_cnt<'a>(iter: impl Iterator) -> usize { - iter - .take_while(|expr| vec_attrs(expr).is_none()) - .count() + iter.take_while(|expr| vec_attrs(expr).is_none()).count() } /// Recursively convert this pattern into a matcher that can be @@ -36,7 +30,7 @@ fn scal_cnt<'a>(iter: impl Iterator) -> usize { pub fn mk_matcher(pattern: &[Expr]) -> AnyMatcher { let left_split = scal_cnt(pattern.iter()); if pattern.len() <= left_split { - return AnyMatcher::Scalar(mk_scalv(pattern)) + return AnyMatcher::Scalar(mk_scalv(pattern)); } let (left, not_left) = pattern.split_at(left_split); let right_split = not_left.len() - scal_cnt(pattern.iter().rev()); @@ -56,11 +50,19 @@ fn mk_scalv(pattern: &[Expr]) -> Vec { /// Pattern MUST start and end with a vectorial placeholder fn mk_vec(pattern: &[Expr]) -> VecMatcher { debug_assert!(!pattern.is_empty(), "pattern cannot be empty"); - debug_assert!(pattern.first().map(vec_attrs).is_some(), "pattern must start with a vectorial"); - debug_assert!(pattern.last().map(vec_attrs).is_some(), "pattern must end with a vectorial"); + debug_assert!( + pattern.first().map(vec_attrs).is_some(), + "pattern must start with a vectorial" + ); + debug_assert!( + pattern.last().map(vec_attrs).is_some(), + "pattern must end with a vectorial" + ); let (left, (key, prio, nonzero), right) = split_at_max_vec(pattern) .expect("pattern must have vectorial placeholders at least at either end"); - if prio >= 1 {println!("Nondefault priority {} found", prio)} + if prio >= 1 { + println!("Nondefault priority {} found", prio) + } let r_sep_size = scal_cnt(right.iter()); let (r_sep, r_side) = right.split_at(r_sep_size); let l_sep_size = scal_cnt(left.iter().rev()); @@ -80,10 +82,11 @@ fn mk_vec(pattern: &[Expr]) -> VecMatcher { sep: mk_scalv(l_sep), right: Box::new(main), }, - (_, _) => { - let mut key_order = l_side.iter() + (..) => { + let mut key_order = l_side + .iter() .chain(r_side.iter()) - .filter_map(|e| vec_attrs(e)) + .filter_map(vec_attrs) .collect::>(); key_order.sort_by_key(|(_, prio, _)| -(*prio as i64)); VecMatcher::Middle { @@ -92,9 +95,9 @@ fn mk_vec(pattern: &[Expr]) -> VecMatcher { mid: Box::new(main), right_sep: mk_scalv(r_sep), right: Box::new(mk_vec(r_side)), - key_order: key_order.into_iter().map(|(n, ..)| n).collect() + key_order: key_order.into_iter().map(|(n, ..)| n).collect(), } - } + }, } } @@ -103,15 +106,16 @@ fn mk_scalar(pattern: &Expr) -> ScalMatcher { match &pattern.value { Clause::P(p) => ScalMatcher::P(p.clone()), Clause::Name(n) => ScalMatcher::Name(*n), - Clause::Placeh(Placeholder{ name, class }) => { - debug_assert!(!matches!(class, PHClass::Vec{..}), "Scalar matcher cannot be built from vector pattern"); + Clause::Placeh(Placeholder { name, class }) => { + debug_assert!( + !matches!(class, PHClass::Vec { .. }), + "Scalar matcher cannot be built from vector pattern" + ); ScalMatcher::Placeh(*name) - } - Clause::S(c, body) => ScalMatcher::S(*c, Box::new(mk_matcher(&body))), - Clause::Lambda(arg, body) => ScalMatcher::Lambda( - Box::new(mk_scalar(&arg)), - Box::new(mk_matcher(&body)) - ) + }, + Clause::S(c, body) => ScalMatcher::S(*c, Box::new(mk_matcher(body))), + Clause::Lambda(arg, body) => + ScalMatcher::Lambda(Box::new(mk_scalar(arg)), Box::new(mk_matcher(body))), } } @@ -119,37 +123,44 @@ fn mk_scalar(pattern: &Expr) -> ScalMatcher { mod test { use std::rc::Rc; - use crate::interner::{Interner, InternedDisplay}; - use crate::ast::{Clause, Placeholder, PHClass}; - use super::mk_matcher; + use crate::ast::{Clause, PHClass, Placeholder}; + use crate::interner::{InternedDisplay, Interner}; #[test] fn test_scan() { let i = Interner::new(); let pattern = vec![ - Clause::Placeh(Placeholder{ - class: PHClass::Vec{ nonzero: false, prio: 0 }, - name: i.i("::prefix"), - }).into_expr(), - Clause::Name(i.i(&[i.i("prelude"), i.i("do")][..])).into_expr(), - Clause::S('(', Rc::new(vec![ - Clause::Placeh(Placeholder{ - class: PHClass::Vec{ nonzero: false, prio: 0 }, - name: i.i("expr"), - }).into_expr(), - Clause::Name(i.i(&[i.i("prelude"), i.i(";")][..])).into_expr(), - Clause::Placeh(Placeholder { - class: PHClass::Vec{ nonzero: false, prio: 1 }, - name: i.i("rest"), - }).into_expr() - ])).into_expr(), Clause::Placeh(Placeholder { - class: PHClass::Vec{ nonzero: false, prio: 0 }, + class: PHClass::Vec { nonzero: false, prio: 0 }, + name: i.i("::prefix"), + }) + .into_expr(), + Clause::Name(i.i(&[i.i("prelude"), i.i("do")][..])).into_expr(), + Clause::S( + '(', + Rc::new(vec![ + Clause::Placeh(Placeholder { + class: PHClass::Vec { nonzero: false, prio: 0 }, + name: i.i("expr"), + }) + .into_expr(), + Clause::Name(i.i(&[i.i("prelude"), i.i(";")][..])).into_expr(), + Clause::Placeh(Placeholder { + class: PHClass::Vec { nonzero: false, prio: 1 }, + name: i.i("rest"), + }) + .into_expr(), + ]), + ) + .into_expr(), + Clause::Placeh(Placeholder { + class: PHClass::Vec { nonzero: false, prio: 0 }, name: i.i("::suffix"), - }).into_expr(), + }) + .into_expr(), ]; let matcher = mk_matcher(&pattern); println!("{}", matcher.bundle(&i)); } -} \ No newline at end of file +} diff --git a/src/rule/matcher_second/mod.rs b/src/rule/matcher_second/mod.rs index 06a5bee..1fe701c 100644 --- a/src/rule/matcher_second/mod.rs +++ b/src/rule/matcher_second/mod.rs @@ -1,22 +1,20 @@ -/* -Construction: -convert pattern into hierarchy of plain, scan, middle - - plain: accept any sequence or any non-empty sequence - - scan: a single scalar pattern moves LTR or RTL, submatchers on either - side - - middle: two scalar patterns walk over all permutations of matches - while getting progressively closer to each other +// Construction: +// convert pattern into hierarchy of plain, scan, middle +// - plain: accept any sequence or any non-empty sequence +// - scan: a single scalar pattern moves LTR or RTL, submatchers on either +// side +// - middle: two scalar patterns walk over all permutations of matches +// while getting progressively closer to each other +// +// Application: +// walk over the current matcher's valid options and poll the submatchers +// for each of them -Application: -walk over the current matcher's valid options and poll the submatchers - for each of them -*/ - -mod shared; -mod vec_match; -mod scal_match; mod any_match; mod build; +mod scal_match; +mod shared; +mod vec_match; +pub use build::mk_matcher; pub use shared::AnyMatcher; -pub use build::mk_matcher; \ No newline at end of file diff --git a/src/rule/matcher_second/scal_match.rs b/src/rule/matcher_second/scal_match.rs index 3f9f35c..8e19539 100644 --- a/src/rule/matcher_second/scal_match.rs +++ b/src/rule/matcher_second/scal_match.rs @@ -1,32 +1,38 @@ -use crate::{rule::state::{State, StateEntry}, ast::{Expr, Clause}}; +use super::any_match::any_match; +use super::shared::ScalMatcher; +use crate::ast::{Clause, Expr}; +use crate::rule::state::{State, StateEntry}; -use super::{shared::ScalMatcher, any_match::any_match}; - -pub fn scal_match<'a>(matcher: &ScalMatcher, expr: &'a Expr) --> Option> { +pub fn scal_match<'a>( + matcher: &ScalMatcher, + expr: &'a Expr, +) -> Option> { match (matcher, &expr.value) { (ScalMatcher::P(p1), Clause::P(p2)) if p1 == p2 => Some(State::new()), - (ScalMatcher::Name(n1), Clause::Name(n2)) if n1 == n2 - => Some(State::new()), - (ScalMatcher::Placeh(key), _) - => Some(State::from([(*key, StateEntry::Scalar(expr))])), - (ScalMatcher::S(c1, b_mat), Clause::S(c2, body)) if c1 == c2 - => any_match(b_mat, &body[..]), + (ScalMatcher::Name(n1), Clause::Name(n2)) if n1 == n2 => Some(State::new()), + (ScalMatcher::Placeh(key), _) => + Some(State::from([(*key, StateEntry::Scalar(expr))])), + (ScalMatcher::S(c1, b_mat), Clause::S(c2, body)) if c1 == c2 => + any_match(b_mat, &body[..]), (ScalMatcher::Lambda(arg_mat, b_mat), Clause::Lambda(arg, body)) => { - let mut state = scal_match(&*arg_mat, &*arg)?; - state.extend(any_match(&*b_mat, &*body)?); + let mut state = scal_match(arg_mat, arg)?; + state.extend(any_match(b_mat, body)?); Some(state) - } - _ => None + }, + _ => None, } } -pub fn scalv_match<'a>(matchers: &[ScalMatcher], seq: &'a [Expr]) --> Option> { - if seq.len() != matchers.len() {return None} +pub fn scalv_match<'a>( + matchers: &[ScalMatcher], + seq: &'a [Expr], +) -> Option> { + if seq.len() != matchers.len() { + return None; + } let mut state = State::new(); for (matcher, expr) in matchers.iter().zip(seq.iter()) { state.extend(scal_match(matcher, expr)?); } Some(state) -} \ No newline at end of file +} diff --git a/src/rule/matcher_second/shared.rs b/src/rule/matcher_second/shared.rs index 38bfe8a..2d971de 100644 --- a/src/rule/matcher_second/shared.rs +++ b/src/rule/matcher_second/shared.rs @@ -1,36 +1,37 @@ use std::fmt::Write; use std::rc::Rc; +use super::any_match::any_match; +use super::build::mk_matcher; use crate::ast::Expr; -use crate::rule::{matcher::Matcher, state::State}; -use crate::unwrap_or; -use crate::utils::{Side, print_nname}; -use crate::interner::{Token, InternedDisplay, Interner}; +use crate::interner::{InternedDisplay, Interner, Sym, Tok}; use crate::representations::Primitive; - -use super::{build::mk_matcher, any_match::any_match}; +use crate::rule::matcher::Matcher; +use crate::rule::state::State; +use crate::unwrap_or; +use crate::utils::{sym2string, Side}; pub enum ScalMatcher { P(Primitive), - Name(Token>>), + Name(Sym), S(char, Box), Lambda(Box, Box), - Placeh(Token), + Placeh(Tok), } pub enum VecMatcher { - Placeh{ - key: Token, - nonzero: bool + Placeh { + key: Tok, + nonzero: bool, }, - Scan{ + Scan { left: Box, sep: Vec, right: Box, /// The separator traverses the sequence towards this side - direction: Side + direction: Side, }, - Middle{ + Middle { /// Matches the left outer region left: Box, /// Matches the left separator @@ -43,19 +44,15 @@ pub enum VecMatcher { right: Box, /// Order of significance for sorting equally good solutions based on /// the length of matches on either side. - /// + /// /// Vectorial keys that appear on either side, in priority order - key_order: Vec> - } + key_order: Vec>, + }, } pub enum AnyMatcher { Scalar(Vec), - Vec{ - left: Vec, - mid: VecMatcher, - right: Vec - } + Vec { left: Vec, mid: VecMatcher, right: Vec }, } impl Matcher for AnyMatcher { fn new(pattern: Rc>) -> Self { @@ -70,9 +67,9 @@ impl Matcher for AnyMatcher { // ################ InternedDisplay ################ fn disp_scalv( - scalv: &Vec, + scalv: &[ScalMatcher], f: &mut std::fmt::Formatter<'_>, - i: &Interner + i: &Interner, ) -> std::fmt::Result { let (head, tail) = unwrap_or!(scalv.split_first(); return Ok(())); head.fmt_i(f, i)?; @@ -84,37 +81,52 @@ fn disp_scalv( } impl InternedDisplay for ScalMatcher { - fn fmt_i(&self, f: &mut std::fmt::Formatter<'_>, i: &Interner) -> std::fmt::Result { + fn fmt_i( + &self, + f: &mut std::fmt::Formatter<'_>, + i: &Interner, + ) -> std::fmt::Result { match self { Self::P(p) => write!(f, "{:?}", p), Self::Placeh(n) => write!(f, "${}", i.r(*n)), - Self::Name(n) => write!(f, "{}", print_nname(*n, i)), + Self::Name(n) => write!(f, "{}", sym2string(*n, i)), Self::S(c, body) => { f.write_char(*c)?; body.fmt_i(f, i)?; - f.write_char(match c {'('=>')','['=>']','{'=>'}',_=>unreachable!()}) + f.write_char(match c { + '(' => ')', + '[' => ']', + '{' => '}', + _ => unreachable!(), + }) }, Self::Lambda(arg, body) => { f.write_char('\\')?; arg.fmt_i(f, i)?; f.write_char('.')?; body.fmt_i(f, i) - } + }, } } } impl InternedDisplay for VecMatcher { - fn fmt_i(&self, f: &mut std::fmt::Formatter<'_>, i: &Interner) -> std::fmt::Result { + fn fmt_i( + &self, + f: &mut std::fmt::Formatter<'_>, + i: &Interner, + ) -> std::fmt::Result { match self { Self::Placeh { key, nonzero } => { - if *nonzero {f.write_char('.')?;}; + if *nonzero { + f.write_char('.')?; + }; write!(f, "..${}", i.r(*key)) - } + }, Self::Scan { left, sep, right, direction } => { let arrow = match direction { Side::Left => " <== ", - Side::Right => " ==> " + Side::Right => " ==> ", }; write!(f, "Scan{{")?; left.fmt_i(f, i)?; @@ -136,19 +148,23 @@ impl InternedDisplay for VecMatcher { f.write_str("|")?; right.fmt_i(f, i)?; write!(f, "}}") - } + }, } } } impl InternedDisplay for AnyMatcher { - fn fmt_i(&self, f: &mut std::fmt::Formatter<'_>, i: &Interner) -> std::fmt::Result { + fn fmt_i( + &self, + f: &mut std::fmt::Formatter<'_>, + i: &Interner, + ) -> std::fmt::Result { match self { Self::Scalar(s) => { write!(f, "(")?; disp_scalv(s, f, i)?; write!(f, ")") - } + }, Self::Vec { left, mid, right } => { write!(f, "[")?; disp_scalv(left, f, i)?; @@ -157,7 +173,7 @@ impl InternedDisplay for AnyMatcher { write!(f, "|")?; disp_scalv(right, f, i)?; write!(f, "]") - } + }, } } -} \ No newline at end of file +} diff --git a/src/rule/matcher_second/vec_match.rs b/src/rule/matcher_second/vec_match.rs index 488e80e..bfaca95 100644 --- a/src/rule/matcher_second/vec_match.rs +++ b/src/rule/matcher_second/vec_match.rs @@ -2,41 +2,46 @@ use std::cmp::Ordering; use itertools::Itertools; -use crate::unwrap_or; -use crate::ast::Expr; -use crate::rule::state::{State, StateEntry}; - use super::scal_match::scalv_match; use super::shared::VecMatcher; +use crate::ast::Expr; +use crate::rule::state::{State, StateEntry}; +use crate::unwrap_or; -pub fn vec_match<'a>(matcher: &VecMatcher, seq: &'a [Expr]) --> Option> { +pub fn vec_match<'a>( + matcher: &VecMatcher, + seq: &'a [Expr], +) -> Option> { match matcher { VecMatcher::Placeh { key, nonzero } => { - if *nonzero && seq.len() == 0 {return None} - return Some(State::from([(*key, StateEntry::Vec(seq))])) - } + if *nonzero && seq.is_empty() { + return None; + } + return Some(State::from([(*key, StateEntry::Vec(seq))])); + }, VecMatcher::Scan { left, sep, right, direction } => { - if seq.len() < sep.len() {return None} + if seq.len() < sep.len() { + return None; + } for lpos in direction.walk(0..=seq.len() - sep.len()) { let rpos = lpos + sep.len(); let mut state = unwrap_or!(vec_match(left, &seq[..lpos]); continue); state.extend(unwrap_or!(scalv_match(sep, &seq[lpos..rpos]); continue)); state.extend(unwrap_or!(vec_match(right, &seq[rpos..]); continue)); - return Some(state) + return Some(state); } None - } + }, // XXX predict heap space usage and allocation count VecMatcher::Middle { left, left_sep, mid, right_sep, right, key_order } => { - if seq.len() < left_sep.len() + right_sep.len() {return None} + if seq.len() < left_sep.len() + right_sep.len() { + return None; + } // Valid locations for the left separator let lposv = seq[..seq.len() - right_sep.len()] .windows(left_sep.len()) .enumerate() - .filter_map(|(i, window)| { - scalv_match(left_sep, window).map(|s| (i, s)) - }) + .filter_map(|(i, window)| scalv_match(left_sep, window).map(|s| (i, s))) .collect::>(); // Valid locations for the right separator let rposv = seq[left_sep.len()..] @@ -47,7 +52,8 @@ pub fn vec_match<'a>(matcher: &VecMatcher, seq: &'a [Expr]) }) .collect::>(); // Valid combinations of locations for the separators - let mut pos_pairs = lposv.into_iter() + let mut pos_pairs = lposv + .into_iter() .cartesian_product(rposv.into_iter()) .filter(|((lpos, _), (rpos, _))| lpos + left_sep.len() <= *rpos) .map(|((lpos, mut lstate), (rpos, rstate))| { @@ -57,10 +63,10 @@ pub fn vec_match<'a>(matcher: &VecMatcher, seq: &'a [Expr]) .collect::>(); // In descending order of size pos_pairs.sort_by_key(|(l, r, _)| -((r - l) as i64)); - let eql_clusters = pos_pairs.into_iter() - .group_by(|(al, ar, _)| ar - al); + let eql_clusters = pos_pairs.into_iter().group_by(|(al, ar, _)| ar - al); for (_gap_size, cluster) in eql_clusters.into_iter() { - let best_candidate = cluster.into_iter() + let best_candidate = cluster + .into_iter() .filter_map(|(lpos, rpos, mut state)| { state.extend(vec_match(left, &seq[..lpos])?); state.extend(vec_match(mid, &seq[lpos + left_sep.len()..rpos])?); @@ -69,22 +75,28 @@ pub fn vec_match<'a>(matcher: &VecMatcher, seq: &'a [Expr]) }) .max_by(|a, b| { for key in key_order { - let aslc = if let Some(StateEntry::Vec(s)) = a.get(key) {s} - else {panic!("key_order references scalar or missing")}; - let bslc = if let Some(StateEntry::Vec(s)) = b.get(key) {s} - else {panic!("key_order references scalar or missing")}; + let aslc = if let Some(StateEntry::Vec(s)) = a.get(key) { + s + } else { + panic!("key_order references scalar or missing") + }; + let bslc = if let Some(StateEntry::Vec(s)) = b.get(key) { + s + } else { + panic!("key_order references scalar or missing") + }; match aslc.len().cmp(&bslc.len()) { Ordering::Equal => (), - any => return any + any => return any, } } Ordering::Equal }); if let Some(state) = best_candidate { - return Some(state) + return Some(state); } } None - } + }, } -} \ No newline at end of file +} diff --git a/src/rule/mod.rs b/src/rule/mod.rs index 60f404f..1619d33 100644 --- a/src/rule/mod.rs +++ b/src/rule/mod.rs @@ -1,15 +1,12 @@ -// mod executor; -mod rule_error; -mod repository; -mod prepare_rule; mod matcher; -mod update_first_seq; -mod state; mod matcher_second; +mod prepare_rule; +mod repository; +mod rule_error; +mod state; +mod update_first_seq; mod vec_attrs; -// pub use rule::Rule; +pub use matcher_second::AnyMatcher; +pub use repository::{Repo, Repository}; pub use rule_error::RuleError; -pub use repository::{Repository, Repo}; - -pub use matcher_second::AnyMatcher; \ No newline at end of file diff --git a/src/rule/prepare_rule.rs b/src/rule/prepare_rule.rs index b413a68..38dda41 100644 --- a/src/rule/prepare_rule.rs +++ b/src/rule/prepare_rule.rs @@ -3,51 +3,46 @@ use std::rc::Rc; use hashbrown::HashMap; use itertools::Itertools; -use crate::representations::location::Location; -use crate::interner::{Token, Interner}; -use crate::ast::{PHClass, Expr, Clause, Placeholder, Rule}; - -use super::RuleError; use super::vec_attrs::vec_attrs; +use super::RuleError; +use crate::ast::{Clause, Expr, PHClass, Placeholder, Rule}; +use crate::interner::{Interner, Tok}; +use crate::representations::location::Location; /// Ensure that the rule's source begins and ends with a vectorial without /// changing its meaning fn pad(mut rule: Rule, i: &Interner) -> Rule { let class: PHClass = PHClass::Vec { nonzero: false, prio: 0 }; - let empty_exprv: &[Expr] = &[]; - let prefix_exprv: &[Expr] = &[Expr{ + let empty: &[Expr] = &[]; + let prefix: &[Expr] = &[Expr { location: Location::Unknown, - value: Clause::Placeh(Placeholder{ - name: i.i("::prefix"), - class - }), + value: Clause::Placeh(Placeholder { name: i.i("::prefix"), class }), }]; - let suffix_exprv: &[Expr] = &[Expr{ + let suffix: &[Expr] = &[Expr { location: Location::Unknown, - value: Clause::Placeh(Placeholder{ - name: i.i("::suffix"), - class - }), + value: Clause::Placeh(Placeholder { name: i.i("::suffix"), class }), }]; let rule_head = rule.source.first().expect("Src can never be empty!"); - let head_explicit = vec_attrs(rule_head).is_some(); + let prefix_explicit = vec_attrs(rule_head).is_some(); let rule_tail = rule.source.last().expect("Unreachable branch!"); - let tail_explicit = vec_attrs(rule_tail).is_some(); - let prefix_vec = if head_explicit {empty_exprv} else {prefix_exprv}; - let suffix_vec = if tail_explicit {empty_exprv} else {suffix_exprv}; + let suffix_explicit = vec_attrs(rule_tail).is_some(); + let prefix_v = if prefix_explicit { empty } else { prefix }; + let suffix_v = if suffix_explicit { empty } else { suffix }; rule.source = Rc::new( - prefix_vec.iter() + prefix_v + .iter() .chain(rule.source.iter()) - .chain(suffix_vec.iter()) + .chain(suffix_v.iter()) .cloned() - .collect() + .collect(), ); rule.target = Rc::new( - prefix_vec.iter() + prefix_v + .iter() .chain(rule.target.iter()) - .chain(suffix_vec.iter()) + .chain(suffix_v.iter()) .cloned() - .collect() + .collect(), ); rule } @@ -55,59 +50,69 @@ fn pad(mut rule: Rule, i: &Interner) -> Rule { #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum PHType { Scalar, - Vec{ nonzero: bool } + Vec { nonzero: bool }, } impl From for PHType { fn from(value: PHClass) -> Self { match value { PHClass::Scalar => Self::Scalar, - PHClass::Vec { nonzero, .. } => Self::Vec{ nonzero } + PHClass::Vec { nonzero, .. } => Self::Vec { nonzero }, } } } fn check_rec_expr( expr: &Expr, - types: &mut HashMap, PHType>, - in_template: bool + types: &mut HashMap, PHType>, + in_template: bool, ) -> Result<(), RuleError> { match &expr.value { Clause::Name(_) | Clause::P(_) => Ok(()), - Clause::Placeh(Placeholder{ name, class }) => { - let typ = class.clone().into(); + Clause::Placeh(Placeholder { name, class }) => { + let typ = (*class).into(); // in a template, the type must be known and identical // outside template (in pattern) the type must be unknown if let Some(known) = types.insert(*name, typ) { - if !in_template { Err(RuleError::Multiple(*name)) } - else if known != typ { Err(RuleError::TypeMismatch(*name)) } - else { Ok(()) } - } else if in_template { Err(RuleError::Missing(*name)) } - else { Ok(()) } - } + if !in_template { + Err(RuleError::Multiple(*name)) + } else if known != typ { + Err(RuleError::TypeMismatch(*name)) + } else { + Ok(()) + } + } else if in_template { + Err(RuleError::Missing(*name)) + } else { + Ok(()) + } + }, Clause::Lambda(arg, body) => { check_rec_expr(arg.as_ref(), types, in_template)?; - check_rec_exprv(&body, types, in_template) - } - Clause::S(_, body) => check_rec_exprv(&body, types, in_template) + check_rec_exprv(body, types, in_template) + }, + Clause::S(_, body) => check_rec_exprv(body, types, in_template), } } fn check_rec_exprv( exprv: &[Expr], - types: &mut HashMap, PHType>, - in_template: bool + types: &mut HashMap, PHType>, + in_template: bool, ) -> Result<(), RuleError> { for (l, r) in exprv.iter().tuple_windows::<(_, _)>() { check_rec_expr(l, types, in_template)?; - if !in_template { // in a pattern vectorials cannot follow each other + if !in_template { + // in a pattern vectorials cannot follow each other if let (Some(ld), Some(rd)) = (vec_attrs(l), vec_attrs(r)) { - return Err(RuleError::VecNeighbors(ld.0, rd.0)) + return Err(RuleError::VecNeighbors(ld.0, rd.0)); } } } if let Some(e) = exprv.last() { check_rec_expr(e, types, in_template) - } else { Ok(()) } + } else { + Ok(()) + } } pub fn prepare_rule(rule: Rule, i: &Interner) -> Result { @@ -117,4 +122,4 @@ pub fn prepare_rule(rule: Rule, i: &Interner) -> Result { check_rec_exprv(&rule.target, &mut types, true)?; // Padding Ok(pad(rule, i)) -} \ No newline at end of file +} diff --git a/src/rule/repository.rs b/src/rule/repository.rs index acc2af4..9cf679f 100644 --- a/src/rule/repository.rs +++ b/src/rule/repository.rs @@ -1,29 +1,31 @@ +use std::fmt::{Debug, Write}; use std::format; use std::rc::Rc; -use std::fmt::{Debug, Write}; use hashbrown::HashSet; use ordered_float::NotNan; -use crate::interner::{Token, Interner, InternedDisplay}; -use crate::utils::Substack; -use crate::ast::{Expr, Rule}; - -use super::state::apply_exprv; -use super::{update_first_seq, AnyMatcher}; use super::matcher::Matcher; use super::prepare_rule::prepare_rule; -use super::RuleError; +use super::state::apply_exprv; +use super::{update_first_seq, AnyMatcher, RuleError}; +use crate::ast::{Expr, Rule}; +use crate::interner::{InternedDisplay, Interner, Sym}; +use crate::utils::Substack; #[derive(Debug)] pub struct CachedRule { matcher: M, source: Rc>, - template: Rc> + template: Rc>, } impl InternedDisplay for CachedRule { - fn fmt_i(&self, f: &mut std::fmt::Formatter<'_>, i: &Interner) -> std::fmt::Result { + fn fmt_i( + &self, + f: &mut std::fmt::Formatter<'_>, + i: &Interner, + ) -> std::fmt::Result { for item in self.source.iter() { item.fmt_i(f, i)?; f.write_char(' ')?; @@ -33,20 +35,22 @@ impl InternedDisplay for CachedRule { } } -/// Manages a priority queue of substitution rules and allows to apply them +/// Manages a priority queue of substitution rules and allows to apply +/// them pub struct Repository { - cache: Vec<(CachedRule, HashSet>>>, NotNan)> + cache: Vec<(CachedRule, HashSet, NotNan)>, } -impl Repository { - pub fn new(mut rules: Vec, i: &Interner) - -> Result - { +impl Repository { + pub fn new( + mut rules: Vec, + i: &Interner, + ) -> Result { rules.sort_by_key(|r| -r.prio); - let cache = rules.into_iter() + let cache = rules + .into_iter() .map(|r| { let prio = r.prio; - let rule = prepare_rule(r.clone(), i) - .map_err(|e| (r, e))?; + let rule = prepare_rule(r.clone(), i).map_err(|e| (r, e))?; let mut glossary = HashSet::new(); for e in rule.source.iter() { e.visit_names(Substack::Bottom, &mut |op| { @@ -54,30 +58,32 @@ impl Repository { }) } let matcher = M::new(rule.source.clone()); - let prep = CachedRule{ - matcher, - source: rule.source, - template: rule.target - }; + let prep = + CachedRule { matcher, source: rule.source, template: rule.target }; Ok((prep, glossary, prio)) }) .collect::, _>>()?; - Ok(Self{cache}) + Ok(Self { cache }) } /// Attempt to run each rule in priority order once pub fn step(&self, code: &Expr) -> Option { let mut glossary = HashSet::new(); - code.visit_names(Substack::Bottom, &mut |op| { glossary.insert(op); }); - // println!("Glossary for code: {:?}", print_nname_seq(glossary.iter(), i)); + code.visit_names(Substack::Bottom, &mut |op| { + glossary.insert(op); + }); for (rule, deps, _) in self.cache.iter() { - if !deps.is_subset(&glossary) { continue; } + if !deps.is_subset(&glossary) { + continue; + } let product = update_first_seq::expr(code, &mut |exprv| { let state = rule.matcher.apply(exprv.as_slice())?; let result = apply_exprv(&rule.template, &state); Some(Rc::new(result)) }); - if let Some(newcode) = product {return Some(newcode)} + if let Some(newcode) = product { + return Some(newcode); + } } None } @@ -86,33 +92,43 @@ impl Repository { /// rules match. WARNING: this function might not terminate #[allow(unused)] pub fn pass(&self, code: &Expr) -> Option { - todo!() - // if let Some(mut processed) = self.step(code) { - // while let Some(out) = self.step(&processed) { - // processed = out - // } - // Some(processed) - // } else {None} + if let Some(mut processed) = self.step(code) { + while let Some(out) = self.step(&processed) { + processed = out + } + Some(processed) + } else { + None + } } - /// Attempt to run each rule in priority order `limit` times. Returns the final - /// tree and the number of iterations left to the limit. + /// Attempt to run each rule in priority order `limit` times. Returns + /// the final tree and the number of iterations left to the limit. #[allow(unused)] - pub fn long_step(&self, code: &Expr, mut limit: usize) - -> Result<(Expr, usize), RuleError> - { - todo!() - // if limit == 0 {return Ok((code.clone(), 0))} - // if let Some(mut processed) = self.step(code) { - // limit -= 1; - // if limit == 0 {return Ok((processed.clone(), 0))} - // while let Some(out) = self.step(&processed) { - // limit -= 1; - // if limit == 0 { return Ok((out, 0)) } - // processed = out; - // } - // Ok((processed, limit)) - // } else {Ok((code.clone(), limit))} + pub fn long_step( + &self, + code: &Expr, + mut limit: usize, + ) -> Result<(Expr, usize), RuleError> { + if limit == 0 { + return Ok((code.clone(), 0)); + } + if let Some(mut processed) = self.step(code) { + limit -= 1; + if limit == 0 { + return Ok((processed, 0)); + } + while let Some(out) = self.step(&processed) { + limit -= 1; + if limit == 0 { + return Ok((out, 0)); + } + processed = out; + } + Ok((processed, limit)) + } else { + Ok((code.clone(), limit)) + } } } @@ -122,7 +138,7 @@ impl Debug for Repository { writeln!(f, "{rule:?}")? } Ok(()) - } + } } fn fmt_hex(num: f64) -> String { @@ -132,7 +148,11 @@ fn fmt_hex(num: f64) -> String { } impl InternedDisplay for Repository { - fn fmt_i(&self, f: &mut std::fmt::Formatter<'_>, i: &Interner) -> std::fmt::Result { + fn fmt_i( + &self, + f: &mut std::fmt::Formatter<'_>, + i: &Interner, + ) -> std::fmt::Result { writeln!(f, "Repository[")?; for (item, _, p) in self.cache.iter() { write!(f, "\t{}", fmt_hex(f64::from(*p)))?; @@ -143,4 +163,4 @@ impl InternedDisplay for Repository { } } -pub type Repo = Repository; \ No newline at end of file +pub type Repo = Repository; diff --git a/src/rule/rule_error.rs b/src/rule/rule_error.rs index 584309b..701c7ee 100644 --- a/src/rule/rule_error.rs +++ b/src/rule/rule_error.rs @@ -1,36 +1,35 @@ use std::fmt; -use crate::interner::{Token, InternedDisplay, Interner}; +use crate::interner::{InternedDisplay, Interner, Tok}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RuleError { - Missing(Token), - TypeMismatch(Token), + Missing(Tok), + TypeMismatch(Tok), /// Multiple occurences of a placeholder in a pattern are no longer /// supported. - Multiple(Token), - VecNeighbors(Token, Token), + Multiple(Tok), + VecNeighbors(Tok, Tok), } impl InternedDisplay for RuleError { fn fmt_i(&self, f: &mut fmt::Formatter<'_>, i: &Interner) -> fmt::Result { match *self { - Self::Missing(key) => write!(f, - "Key {:?} not in match pattern", - i.r(key) - ), - Self::TypeMismatch(key) => write!(f, + Self::Missing(key) => + write!(f, "Key {:?} not in match pattern", i.r(key)), + Self::TypeMismatch(key) => write!( + f, "Key {:?} used inconsistently with and without ellipsis", i.r(key) ), - Self::Multiple(key) => write!(f, - "Key {:?} appears multiple times in match pattern", - i.r(key) - ), - Self::VecNeighbors(left, right) => write!(f, + Self::Multiple(key) => + write!(f, "Key {:?} appears multiple times in match pattern", i.r(key)), + Self::VecNeighbors(left, right) => write!( + f, "Keys {:?} and {:?} are two vectorials right next to each other", - i.r(left), i.r(right) - ) + i.r(left), + i.r(right) + ), } } -} \ No newline at end of file +} diff --git a/src/rule/state.rs b/src/rule/state.rs index a4d7240..e0055b3 100644 --- a/src/rule/state.rs +++ b/src/rule/state.rs @@ -3,49 +3,54 @@ use std::rc::Rc; use hashbrown::HashMap; use itertools::Itertools; -use crate::interner::Token; -use crate::ast::{Expr, Clause, Placeholder, PHClass}; +use crate::ast::{Clause, Expr, PHClass, Placeholder}; +use crate::interner::Tok; +use crate::unwrap_or; #[derive(Clone, Copy, Debug, PartialEq)] pub enum StateEntry<'a> { - Vec(&'a[Expr]), - Scalar(&'a Expr) + Vec(&'a [Expr]), + Scalar(&'a Expr), } -pub type State<'a> = HashMap, StateEntry<'a>>; +pub type State<'a> = HashMap, StateEntry<'a>>; pub fn apply_exprv(template: &[Expr], state: &State) -> Vec { - template.iter() + template + .iter() .map(|e| apply_expr(e, state)) .flat_map(Vec::into_iter) .collect() } pub fn apply_expr(template: &Expr, state: &State) -> Vec { - let Expr{ location, value } = template; + let Expr { location, value } = template; match value { Clause::P(_) | Clause::Name(_) => vec![template.clone()], - Clause::S(c, body) => vec![Expr{ + Clause::S(c, body) => vec![Expr { location: location.clone(), - value: Clause::S(*c, Rc::new(apply_exprv(body.as_slice(), state))) + value: Clause::S(*c, Rc::new(apply_exprv(body.as_slice(), state))), }], - Clause::Placeh(Placeholder{ name, class }) => { - let value = if let Some(&v) = state.get(name) {v} - else {panic!("Placeholder does not have a value in state")}; + Clause::Placeh(Placeholder { name, class }) => { + let value = *unwrap_or!(state.get(name); + panic!("Placeholder does not have a value in state") + ); match (class, value) { (PHClass::Scalar, StateEntry::Scalar(item)) => vec![item.clone()], - (PHClass::Vec{..}, StateEntry::Vec(chunk)) => chunk.to_vec(), - _ => panic!("Type mismatch between template and state") + (PHClass::Vec { .. }, StateEntry::Vec(chunk)) => chunk.to_vec(), + _ => panic!("Type mismatch between template and state"), } - } - Clause::Lambda(arg, body) => vec![Expr{ + }, + Clause::Lambda(arg, body) => vec![Expr { location: location.clone(), value: Clause::Lambda( - Rc::new(apply_expr(arg.as_ref(), state).into_iter() - .exactly_one() - .expect("Lambda arguments can only ever be scalar") + Rc::new( + apply_expr(arg.as_ref(), state) + .into_iter() + .exactly_one() + .expect("Lambda arguments can only ever be scalar"), ), - Rc::new(apply_exprv(&body[..], state)) - ) - }] + Rc::new(apply_exprv(&body[..], state)), + ), + }], } } diff --git a/src/rule/update_first_seq.rs b/src/rule/update_first_seq.rs index 2a60c56..5044440 100644 --- a/src/rule/update_first_seq.rs +++ b/src/rule/update_first_seq.rs @@ -1,39 +1,42 @@ use std::rc::Rc; +use crate::ast::{Clause, Expr}; use crate::utils::replace_first; -use crate::ast::{Expr, Clause}; /// Traverse the tree, calling pred on every sibling list until it returns /// some vec then replace the sibling list with that vec and return true /// return false if pred never returned some -pub fn exprv< - F: FnMut(Rc>) -> Option>> ->(input: Rc>, pred: &mut F) -> Option>> { - if let o@Some(_) = pred(input.clone()) {return o} +pub fn exprv>) -> Option>>>( + input: Rc>, + pred: &mut F, +) -> Option>> { + if let Some(v) = pred(input.clone()) { + return Some(v); + } replace_first(input.as_ref(), |ex| expr(ex, pred)) .map(|i| Rc::new(i.collect())) } -pub fn expr< - F: FnMut(Rc>) -> Option>> ->(input: &Expr, pred: &mut F) -> Option { - if let Some(value) = clause(&input.value, pred) { - Some(Expr{ value, location: input.location.clone() }) - } else {None} +pub fn expr>) -> Option>>>( + input: &Expr, + pred: &mut F, +) -> Option { + clause(&input.value, pred) + .map(|value| Expr { value, location: input.location.clone() }) } -pub fn clause< - F: FnMut(Rc>) -> Option>> ->(c: &Clause, pred: &mut F) -> Option { +pub fn clause>) -> Option>>>( + c: &Clause, + pred: &mut F, +) -> Option { match c { - Clause::P(_) | Clause::Placeh {..} | Clause::Name {..} => None, - Clause::Lambda(arg, body) => { + Clause::P(_) | Clause::Placeh { .. } | Clause::Name { .. } => None, + Clause::Lambda(arg, body) => if let Some(arg) = expr(arg.as_ref(), pred) { Some(Clause::Lambda(Rc::new(arg), body.clone())) - } else if let Some(body) = exprv(body.clone(), pred) { - Some(Clause::Lambda(arg.clone(), body)) - } else {None} - } + } else { + exprv(body.clone(), pred).map(|body| Clause::Lambda(arg.clone(), body)) + }, Clause::S(c, body) => Some(Clause::S(*c, exprv(body.clone(), pred)?)), } -} \ No newline at end of file +} diff --git a/src/rule/vec_attrs.rs b/src/rule/vec_attrs.rs index f83a561..3f7582b 100644 --- a/src/rule/vec_attrs.rs +++ b/src/rule/vec_attrs.rs @@ -1,10 +1,16 @@ -use crate::interner::Token; -use crate::ast::{Expr, PHClass, Placeholder, Clause}; +use crate::ast::{Clause, Expr, PHClass, Placeholder}; +use crate::interner::Tok; /// Returns the name, priority and nonzero of the expression if it is /// a vectorial placeholder -pub fn vec_attrs(expr: &Expr) -> Option<(Token, u64, bool)> { - if let Clause::Placeh( - Placeholder{ class: PHClass::Vec{ prio, nonzero }, name } - ) = expr.value {Some((name, prio, nonzero))} else {None} -} \ No newline at end of file +pub fn vec_attrs(expr: &Expr) -> Option<(Tok, u64, bool)> { + if let Clause::Placeh(Placeholder { + class: PHClass::Vec { prio, nonzero }, + name, + }) = expr.value + { + Some((name, prio, nonzero)) + } else { + None + } +} diff --git a/src/run_dir.rs b/src/run_dir.rs index 5b4d201..f243fab 100644 --- a/src/run_dir.rs +++ b/src/run_dir.rs @@ -5,16 +5,18 @@ use hashbrown::HashMap; use itertools::Itertools; use crate::external::handle; +use crate::interner::{InternedDisplay, Interner, Sym}; use crate::interpreter::Return; -use crate::representations::{ast_to_postmacro, postmacro_to_interpreted}; -use crate::{external, xloop, interpreter}; -use crate::pipeline::{from_const_tree, ProjectTree, parse_layer, collect_rules, collect_consts}; -use crate::pipeline::file_loader::{Loaded, mk_cache}; +use crate::pipeline::file_loader::{mk_cache, Loaded}; +use crate::pipeline::{ + collect_consts, collect_rules, from_const_tree, parse_layer, ProjectTree, +}; use crate::representations::sourcefile::{FileEntry, Import}; +use crate::representations::{ast_to_postmacro, postmacro_to_interpreted}; use crate::rule::Repo; -use crate::interner::{Token, Interner, InternedDisplay}; +use crate::{external, interpreter, xloop}; -static PRELUDE_TXT:&str = r#" +static PRELUDE_TXT: &str = r#" import std::( add, subtract, multiply, remainder, divide, equals, ifthenelse, @@ -49,18 +51,23 @@ export if ...$cond then ...$true else ...$false:1 =0x1p84=> ( export ::(,) "#; -fn prelude_path(i: &Interner) -> Token>> -{ i.i(&[ i.i("prelude") ][..]) } -fn mainmod_path(i: &Interner) -> Token>> -{ i.i(&[ i.i("main") ][..]) } -fn entrypoint(i: &Interner) -> Token>> -{ i.i(&[ i.i("main"), i.i("main") ][..]) } +fn prelude_path(i: &Interner) -> Sym { + i.i(&[i.i("prelude")][..]) +} +fn mainmod_path(i: &Interner) -> Sym { + i.i(&[i.i("main")][..]) +} +fn entrypoint(i: &Interner) -> Sym { + i.i(&[i.i("main"), i.i("main")][..]) +} fn load_environment(i: &Interner) -> ProjectTree { - let env = from_const_tree(HashMap::from([ - (i.i("std"), external::std::std(i)) - ]), &[i.i("std")], i); - let loader = |path: Token>>| { + let env = from_const_tree( + HashMap::from([(i.i("std"), external::std::std(i))]), + &[i.i("std")], + i, + ); + let loader = |path: Sym| { if path == prelude_path(i) { Ok(Loaded::Code(Rc::new(PRELUDE_TXT.to_string()))) } else { @@ -70,17 +77,15 @@ fn load_environment(i: &Interner) -> ProjectTree { ) } }; - parse_layer(&[prelude_path(i)], &loader, &env, &[], i) - .expect("prelude error") + parse_layer(&[prelude_path(i)], &loader, &env, &[], i).expect("prelude error") } fn load_dir(i: &Interner, dir: &Path) -> ProjectTree { let environment = load_environment(i); let file_cache = mk_cache(dir.to_path_buf(), i); let loader = |path| file_cache.find(&path); - let prelude = [FileEntry::Import(vec![Import{ - path: prelude_path(i), name: None - }])]; + let prelude = + [FileEntry::Import(vec![Import { path: prelude_path(i), name: None }])]; parse_layer(&[mainmod_path(i)], &loader, &environment, &prelude, i) .expect("Failed to load source code") } @@ -92,14 +97,14 @@ pub fn run_dir(dir: &Path) { let rules = collect_rules(&project); let consts = collect_consts(&project, &i); println!("Initializing rule repository with {} rules", rules.len()); - let repo = Repo::new(rules, &i) - .unwrap_or_else(|(rule, error)| { - panic!("Rule error: {} + let repo = Repo::new(rules, &i).unwrap_or_else(|(rule, error)| { + panic!( + "Rule error: {} Offending rule: {}", - error.bundle(&i), - rule.bundle(&i) - ) - }); + error.bundle(&i), + rule.bundle(&i) + ) + }); println!("Repo dump: {}", repo.bundle(&i)); let mut exec_table = HashMap::new(); for (name, source) in consts.iter() { @@ -107,7 +112,7 @@ pub fn run_dir(dir: &Path) { let mut tree = source.clone(); let displayname = i.extern_vec(*name).join("::"); let macro_timeout = 100; - println!("Executing macros in {displayname}...", ); + println!("Executing macros in {displayname}...",); let unmatched = xloop!(let mut idx = 0; idx < macro_timeout; idx += 1; { match repo.step(&tree) { None => break tree, @@ -123,29 +128,29 @@ pub fn run_dir(dir: &Path) { exec_table.insert(*name, runtree); } println!("macro execution complete"); - let ctx = interpreter::Context { - symbols: &exec_table, - interner: &i, - gas: None - }; - let entrypoint = exec_table.get(&entrypoint(&i)) - .unwrap_or_else(|| { - panic!("entrypoint not found, known keys are: {}", - exec_table.keys() - .map(|t| i.r(*t).iter().map(|t| i.r(*t)).join("::")) - .join(", ") - ) - }); + let ctx = + interpreter::Context { symbols: &exec_table, interner: &i, gas: None }; + let entrypoint = exec_table.get(&entrypoint(&i)).unwrap_or_else(|| { + panic!( + "entrypoint not found, known keys are: {}", + exec_table + .keys() + .map(|t| i.r(*t).iter().map(|t| i.r(*t)).join("::")) + .join(", ") + ) + }); let io_handler = handle; let ret = interpreter::run_handler(entrypoint.clone(), io_handler, ctx); - let Return{ gas, state, inert } = ret - .unwrap_or_else(|e| panic!("Runtime error: {}", e)); + let Return { gas, state, inert } = + ret.unwrap_or_else(|e| panic!("Runtime error: {}", e)); if inert { println!("Settled at {}", state.expr().clause.bundle(&i)); - println!("Remaining gas: {}", - gas.map(|g| g.to_string()) - .unwrap_or(String::from("∞")) + println!( + "Remaining gas: {}", + gas.map(|g| g.to_string()).unwrap_or(String::from("∞")) ); } - if gas == Some(0) {println!("Ran out of gas!")} -} \ No newline at end of file + if gas == Some(0) { + println!("Ran out of gas!") + } +} diff --git a/src/utils/cache.rs b/src/utils/cache.rs index 35354a3..4823cd5 100644 --- a/src/utils/cache.rs +++ b/src/utils/cache.rs @@ -1,72 +1,41 @@ use std::cell::RefCell; use std::hash::Hash; -use std::rc::Rc; + use hashbrown::HashMap; // TODO: make this a crate -pub trait Callback<'a, I, O: 'static> = - Fn(I, &Cache<'a, I, O>) -> O; +pub trait Callback<'a, I, O: 'static> = Fn(I, &Cache<'a, I, O>) -> O; -pub type CbBox<'a, I, O> = - Box + 'a>; +pub type CbBox<'a, I, O> = Box + 'a>; /// Cache the return values of an effectless closure in a hashmap /// Inspired by the closure_cacher crate. pub struct Cache<'a, I, O: 'static> { store: RefCell>, - closure: CbBox<'a, I, O> + closure: CbBox<'a, I, O>, } -impl<'a, - I: Eq + Hash + Clone, - O: Clone -> Cache<'a, I, O> { +impl<'a, I: Eq + Hash + Clone, O: Clone> Cache<'a, I, O> { pub fn new>(closure: F) -> Self { - Self { - store: RefCell::new(HashMap::new()), - closure: Box::new(closure) - } - } - - #[allow(unused)] - pub fn rc< - F: 'a + Callback<'a, I, O> - >(closure: F) -> Rc { - Rc::new(Self::new(closure)) + Self { store: RefCell::new(HashMap::new()), closure: Box::new(closure) } } /// Produce and cache a result by cloning I if necessary pub fn find(&self, i: &I) -> O { let closure = &self.closure; if let Some(v) = self.store.borrow().get(i) { - return v.clone() + return v.clone(); } // In the moment of invocation the refcell is on immutable // this is important for recursive calculations let result = closure(i.clone(), self); let mut store = self.store.borrow_mut(); - store.raw_entry_mut().from_key(i) - .or_insert_with(|| (i.clone(), result)).1.clone() - } - - #[allow(dead_code)] - /// Return the result if it has already been computed - pub fn known(&self, i: &I) -> Option { - let store = self.store.borrow(); - store.get(i).cloned() - } - - - /// Convert this cache into a cached [Fn(&I) -> O] - #[allow(unused)] - pub fn into_fn(self) -> impl Fn(&I) -> O + 'a where I: 'a { - move |i| self.find(i) - } - - /// Borrow this cache with a cached [Fn(&I) -> O] - #[allow(unused)] - pub fn as_fn<'b: 'a>(&'b self) -> impl Fn(&I) -> O + 'b where I: 'b { - move |i| self.find(i) + store + .raw_entry_mut() + .from_key(i) + .or_insert_with(|| (i.clone(), result)) + .1 + .clone() } } @@ -74,8 +43,8 @@ impl<'a, I, O> IntoIterator for Cache<'a, I, O> { type IntoIter = hashbrown::hash_map::IntoIter; type Item = (I, O); fn into_iter(self) -> Self::IntoIter { - let Cache{ store, .. } = self; + let Cache { store, .. } = self; let map = store.into_inner(); map.into_iter() } -} \ No newline at end of file +} diff --git a/src/utils/coprefix.rs b/src/utils/coprefix.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/utils/iter.rs b/src/utils/iter.rs index 0491532..3672a3e 100644 --- a/src/utils/iter.rs +++ b/src/utils/iter.rs @@ -1,18 +1,22 @@ -/// Utility functions to get rid of explicit casts to BoxedIter which are tedious - +/// Utility functions to get rid of tedious explicit casts to +/// BoxedIter use std::iter; +/// A trait object of [Iterator] to be assigned to variables that may be +/// initialized form multiple iterators of incompatible types pub type BoxedIter<'a, T> = Box + 'a>; +/// A [BoxedIter] of [BoxedIter]. pub type BoxedIterIter<'a, T> = BoxedIter<'a, BoxedIter<'a, T>>; -/// BoxedIter of a single element +/// creates a [BoxedIter] of a single element pub fn box_once<'a, T: 'a>(t: T) -> BoxedIter<'a, T> { Box::new(iter::once(t)) } -/// BoxedIter of no elements +/// creates an empty [BoxedIter] pub fn box_empty<'a, T: 'a>() -> BoxedIter<'a, T> { Box::new(iter::empty()) } +/// Chain various iterators into a [BoxedIter] #[macro_export] macro_rules! box_chain { ($curr:expr) => { @@ -23,16 +27,22 @@ macro_rules! box_chain { }; } -pub fn box_flatten<'a, +/// Flatten an iterator of iterators into a boxed iterator of the inner +/// nested values +pub fn box_flatten< + 'a, T: 'a, I: 'a + Iterator, - J: 'a + Iterator ->(i: I) -> BoxedIter<'a, T> { + J: 'a + Iterator, +>( + i: I, +) -> BoxedIter<'a, T> { Box::new(i.flatten()) } -pub fn into_boxed_iter<'a, - T: 'a + IntoIterator ->(t: T) -> BoxedIter<'a, ::Item> { +/// Convert an iterator into a Box +pub fn into_boxed_iter<'a, T: 'a + IntoIterator>( + t: T, +) -> BoxedIter<'a, ::Item> { Box::new(t.into_iter()) -} \ No newline at end of file +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 4645880..b7955c2 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,25 +1,19 @@ mod cache; -mod replace_first; -// mod interned_display; -// mod interner; -mod variant; mod print_nname; mod pushed; -pub use pushed::pushed; -pub use print_nname::{print_nname_seq, print_nname}; -// pub use interner::*; -// pub use interned_display::InternedDisplay; -pub use replace_first::replace_first; -pub use cache::Cache; -mod substack; -pub use substack::{Stackframe, Substack, SubstackIterator}; +mod replace_first; mod side; -pub use side::Side; +mod string_from_charset; +mod substack; mod unwrap_or; +mod xloop; + +pub use cache::Cache; +pub use print_nname::sym2string; +pub use pushed::pushed; +pub use replace_first::replace_first; +pub use side::Side; +pub use substack::{Stackframe, Substack, SubstackIterator}; pub mod iter; pub use iter::BoxedIter; -mod string_from_charset; pub use string_from_charset::string_from_charset; -mod xloop; -mod protomap; -pub use protomap::ProtoMap; \ No newline at end of file diff --git a/src/utils/print_nname.rs b/src/utils/print_nname.rs index 4f4846c..27c2203 100644 --- a/src/utils/print_nname.rs +++ b/src/utils/print_nname.rs @@ -1,16 +1,8 @@ use itertools::Itertools; -use crate::interner::{Interner, Token}; +use crate::interner::{Interner, Sym}; -#[allow(unused)] -pub fn print_nname(t: Token>>, i: &Interner) -> String { +/// Print symbols to :: delimited strings +pub fn sym2string(t: Sym, i: &Interner) -> String { i.r(t).iter().map(|t| i.r(*t)).join("::") } - -#[allow(unused)] -pub fn print_nname_seq<'a>( - tv: impl Iterator>>>, - i: &Interner -) -> String { - tv.map(|t| print_nname(*t, i)).join(", ") -} \ No newline at end of file diff --git a/src/utils/protomap.rs b/src/utils/protomap.rs deleted file mode 100644 index 86b4822..0000000 --- a/src/utils/protomap.rs +++ /dev/null @@ -1,172 +0,0 @@ -use std::{iter, ops::{Index, Add}, borrow::Borrow}; - -use smallvec::SmallVec; - -// TODO: make this a crate alongside substack - -/// Linked-array-list of key-value pairs. -/// - Lookup and modification is O(n + cachemiss * n / m) -/// - Can be extended by reference in O(m) < O(n) -/// -/// The number of elements stored inline in a stackframe is 2 by default, -/// which is enough for most recursive algorithms. -/// - The cost of overruns is a heap allocation and subsequent -/// heap indirections, plus wasted stack space which is likely wasted L1 -/// as well. -/// - The cost of underruns is wasted stack space. -pub struct ProtoMap<'a, K, V, const STACK_COUNT: usize = 2> { - entries: SmallVec<[(K, Option); STACK_COUNT]>, - prototype: Option<&'a ProtoMap<'a, K, V, STACK_COUNT>> -} - -impl<'a, K, V, const STACK_COUNT: usize> ProtoMap<'a, K, V, STACK_COUNT> { - pub fn new() -> Self { - Self { - entries: SmallVec::new(), - prototype: None - } - } - - /// Mutable reference to entry without checking proto in O(m) - fn local_entry_mut<'b, Q: ?Sized + Eq>(&'b mut self, query: &Q) - -> Option<(usize, &'b mut K, &'b mut Option)> - where K: Borrow - { - self.entries.iter_mut().enumerate().find_map(|(i, (k, v))| { - if query.eq((*k).borrow()) { Some((i, k, v)) } else { None } - }) - } - - /// Entry without checking proto in O(m) - fn local_entry<'b, Q: ?Sized + Eq>(&'b self, query: &Q) - -> Option<(usize, &'b K, &'b Option)> - where K: Borrow - { - self.entries.iter().enumerate().find_map(|(i, (k, v))| { - if query.eq((*k).borrow()) { Some((i, k, v)) } else { None } - }) - } - - /// Find entry in prototype chain in O(n) - pub fn get<'b, Q: ?Sized + Eq>(&'b self, query: &Q) -> Option<&'b V> - where K: Borrow - { - if let Some((_, _, v)) = self.local_entry(query) { - v.as_ref() - } else { - self.prototype?.get(query) - } - } - - /// Record a value for the given key in O(m) - pub fn set(&mut self, key: &K, value: V) where K: Eq + Clone { - if let Some((_, _, v)) = self.local_entry_mut(key) { - *v = Some(value); - } else { - self.entries.push((key.clone(), Some(value))) - } - } - - /// Delete in a memory-efficient way in O(n) - pub fn delete_small(&mut self, key: &K) where K: Eq + Clone { - let exists_up = self.prototype.and_then(|p| p.get(key)).is_some(); - let local_entry = self.local_entry_mut(key); - match (exists_up, local_entry) { - (false, None) => (), // nothing to do - (false, Some((i, _, _))) => { self.entries.remove(i); }, // forget locally - (true, Some((_, _, v))) => *v = None, // update local override to cover - (true, None) => self.entries.push((key.clone(), None)), // create new - } - } - - /// Delete in O(m) without checking the prototype chain - /// May produce unnecessary cover over previously unknown key - pub fn delete_fast(&mut self, key: &K) where K: Eq + Clone { - if let Some((_, _, v)) = self.local_entry_mut(key) { - *v = None - } else { - self.entries.push((key.clone(), None)) - } - } - - /// Iterate over the values defined herein and on the prototype chain - /// Note that this will visit keys multiple times - pub fn iter(&self) -> impl Iterator)> { - let mut map = self; - iter::from_fn(move || { - let pairs = map.entries.iter(); - map = map.prototype?; - Some(pairs) - }).flatten() - } - - /// Visit the keys in an unsafe random order, repeated arbitrarily many times - pub fn keys(&self) -> impl Iterator { - self.iter().map(|(k, _)| k) - } - - /// Visit the values in random order - pub fn values(&self) -> impl Iterator { - self.iter().filter_map(|(_, v)| v.as_ref()) - } - - /// Update the prototype, and correspondingly the lifetime of the map - pub fn set_proto<'b>(self, proto: &'b ProtoMap<'b, K, V, STACK_COUNT>) - -> ProtoMap<'b, K, V, STACK_COUNT> { - ProtoMap { - entries: self.entries, - prototype: Some(proto) - } - } -} - -impl< - K, V, - T: IntoIterator, - const STACK_COUNT: usize -> From -for ProtoMap<'_, K, V, STACK_COUNT> { - fn from(value: T) -> Self { - Self { - entries: value.into_iter().map(|(k, v)| (k, Some(v))).collect(), - prototype: None - } - } -} - -impl, V, const STACK_COUNT: usize> Index<&Q> -for ProtoMap<'_, K, V, STACK_COUNT> { - type Output = V; - fn index(&self, index: &Q) -> &Self::Output { - self.get(index).expect("Index not found in map") - } -} - -impl -Clone for ProtoMap<'_, K, V, STACK_COUNT> { - fn clone(&self) -> Self { - Self { - entries: self.entries.clone(), - prototype: self.prototype - } - } -} - -impl<'a, K: 'a, V: 'a, const STACK_COUNT: usize> -Add<(K, V)> for &'a ProtoMap<'a, K, V, STACK_COUNT> { - type Output = ProtoMap<'a, K, V, STACK_COUNT>; - fn add(self, rhs: (K, V)) -> Self::Output { - ProtoMap::from([rhs]).set_proto(self) - } -} - -impl<'a, K, V, const STACK_COUNT: usize> Default for ProtoMap<'a, K, V, STACK_COUNT> { - fn default() -> Self { Self::new() } -} - -#[macro_export] -macro_rules! protomap { - ($($ent:expr),*) => { - ProtoMap::from([$($ent:expr),*]) - }; -} diff --git a/src/utils/pushed.rs b/src/utils/pushed.rs index 29b24d5..15aa084 100644 --- a/src/utils/pushed.rs +++ b/src/utils/pushed.rs @@ -1,3 +1,5 @@ +/// Pure version of [Vec::push] +/// /// Create a new vector consisting of the provided vector with the /// element appended pub fn pushed(vec: &Vec, t: T) -> Vec { @@ -5,4 +7,4 @@ pub fn pushed(vec: &Vec, t: T) -> Vec { next.extend_from_slice(&vec[..]); next.push(t); next -} \ No newline at end of file +} diff --git a/src/utils/replace_first.rs b/src/utils/replace_first.rs index 9b3bb93..abbb0a0 100644 --- a/src/utils/replace_first.rs +++ b/src/utils/replace_first.rs @@ -1,20 +1,20 @@ use std::iter; -/// Iterate over a sequence with the first element the function returns -/// Some() for updated, but only if there is such an element. -pub fn replace_first< - T: Clone, - F: FnMut(&T) -> Option ->( - slice: &[T], mut f: F +/// Iterate over a sequence with the first element updated for which the +/// function returns Some(), but only if there is such an element. +pub fn replace_first Option>( + slice: &[T], + mut f: F, ) -> Option + '_> { for i in 0..slice.len() { if let Some(new) = f(&slice[i]) { - let subbed_iter = slice[0..i].iter().cloned() + let subbed_iter = slice[0..i] + .iter() + .cloned() .chain(iter::once(new)) - .chain(slice[i+1..].iter().cloned()); - return Some(subbed_iter) + .chain(slice[i + 1..].iter().cloned()); + return Some(subbed_iter); } } None -} \ No newline at end of file +} diff --git a/src/utils/side.rs b/src/utils/side.rs index 022a800..620fa78 100644 --- a/src/utils/side.rs +++ b/src/utils/side.rs @@ -1,11 +1,15 @@ use std::fmt::Display; +use std::ops::Not; use super::BoxedIter; /// A primitive for encoding the two sides Left and Right. While booleans -/// are technically usable for this purpose, they're less descriptive. +/// are technically usable for this purpose, they're very easy to confuse #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Side {Left, Right} +pub enum Side { + Left, + Right, +} impl Display for Side { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -19,17 +23,19 @@ impl Display for Side { impl Side { pub fn opposite(&self) -> Self { match self { - Self::Left => Self::Right, - Self::Right => Self::Left + Self::Left => Self::Right, + Self::Right => Self::Left, } } /// Shorthand for opposite - pub fn inv(&self) -> Self { self.opposite() } + pub fn inv(&self) -> Self { + self.opposite() + } /// take N elements from this end of a slice pub fn slice<'a, T>(&self, size: usize, slice: &'a [T]) -> &'a [T] { match self { Side::Left => &slice[..size], - Side::Right => &slice[slice.len() - size..] + Side::Right => &slice[slice.len() - size..], } } /// ignore N elements from this end of a slice @@ -38,8 +44,11 @@ impl Side { } /// ignore N elements from this end and M elements from the other end /// of a slice - pub fn crop_both<'a, T>(&self, - margin: usize, opposite: usize, slice: &'a [T] + pub fn crop_both<'a, T>( + &self, + margin: usize, + opposite: usize, + slice: &'a [T], ) -> &'a [T] { self.crop(margin, self.opposite().crop(opposite, slice)) } @@ -47,21 +56,22 @@ impl Side { pub fn pick(&self, pair: (T, T)) -> T { match self { Side::Left => pair.0, - Side::Right => pair.1 + Side::Right => pair.1, } } /// Make a pair with the first element on this side pub fn pair(&self, this: T, opposite: T) -> (T, T) { match self { Side::Left => (this, opposite), - Side::Right => (opposite, this) + Side::Right => (opposite, this), } } /// Produces an increasing sequence on Right, and a decreasing sequence /// on Left - pub fn walk<'a, I: DoubleEndedIterator + 'a>(&self, iter: I) - -> BoxedIter<'a, I::Item> - { + pub fn walk<'a, I: DoubleEndedIterator + 'a>( + &self, + iter: I, + ) -> BoxedIter<'a, I::Item> { match self { Side::Right => Box::new(iter) as BoxedIter, Side::Left => Box::new(iter.rev()), @@ -69,9 +79,18 @@ impl Side { } } +impl Not for Side { + type Output = Side; + + fn not(self) -> Self::Output { + self.opposite() + } +} + #[cfg(test)] mod test { use itertools::Itertools; + use super::*; /// I apparently have a tendency to mix these up so it's best if diff --git a/src/utils/string_from_charset.rs b/src/utils/string_from_charset.rs index 0815558..423cbd1 100644 --- a/src/utils/string_from_charset.rs +++ b/src/utils/string_from_charset.rs @@ -2,10 +2,13 @@ fn string_from_charset_rec(val: u64, digits: &str) -> String { let radix = digits.len() as u64; let mut prefix = if val > radix { string_from_charset_rec(val / radix, digits) - } else {String::new()}; - prefix.push(digits.chars().nth(val as usize - 1).unwrap_or_else( - || panic!("Overindexed digit set \"{}\" with {}", digits, val - 1) - )); + } else { + String::new() + }; + let digit = digits.chars().nth(val as usize - 1).unwrap_or_else(|| { + panic!("Overindexed digit set \"{}\" with {}", digits, val - 1) + }); + prefix.push(digit); prefix } @@ -13,4 +16,4 @@ fn string_from_charset_rec(val: u64, digits: &str) -> String { /// characters pub fn string_from_charset(val: u64, digits: &str) -> String { string_from_charset_rec(val + 1, digits) -} \ No newline at end of file +} diff --git a/src/utils/substack.rs b/src/utils/substack.rs index 084557f..f1c9571 100644 --- a/src/utils/substack.rs +++ b/src/utils/substack.rs @@ -3,28 +3,32 @@ use std::fmt::Debug; // TODO: extract to crate -/// A FILO stack that lives on the regular call stack as a linked list. -/// Mainly useful to detect loops in recursive algorithms where -/// the recursion isn't deep enough to warrant a heap-allocated set. +/// A frame of [Substack] which contains an element and a reference to the +/// rest of the stack. #[derive(Clone, Copy)] pub struct Stackframe<'a, T> { pub item: T, pub prev: &'a Substack<'a, T>, - pub len: usize + pub len: usize, } +/// A FILO stack that lives on the regular call stack as a linked list. +/// Mainly useful to detect loops in recursive algorithms where +/// the recursion isn't deep enough to warrant a heap-allocated set. #[derive(Clone, Copy)] pub enum Substack<'a, T> { Frame(Stackframe<'a, T>), - Bottom + Bottom, } impl<'a, T> Substack<'a, T> { /// Convert the substack into an option of stackframe - pub fn opt(&'a self) -> Option<&'a Stackframe<'a, T>> { match self { - Self::Frame(f) => Some(f), - Self::Bottom => None - } } + pub fn opt(&'a self) -> Option<&'a Stackframe<'a, T>> { + match self { + Self::Frame(f) => Some(f), + Self::Bottom => None, + } + } /// Construct an iterator over the listlike, very fast O(1) pub fn iter(&self) -> SubstackIterator { SubstackIterator { curr: self } @@ -33,22 +37,25 @@ impl<'a, T> Substack<'a, T> { Self::Frame(self.new_frame(item)) } pub fn new_frame(&'a self, item: T) -> Stackframe<'a, T> { - Stackframe { - item, - prev: self, - len: self.opt().map_or(1, |s| s.len) - } + Stackframe { item, prev: self, len: self.opt().map_or(1, |s| s.len) } } pub fn pop(&'a self, count: usize) -> Option<&'a Stackframe<'a, T>> { if let Self::Frame(p) = self { - if count == 0 {Some(p)} - else {p.prev.pop(count - 1)} - } else {None} + if count == 0 { + Some(p) + } else { + p.prev.pop(count - 1) + } + } else { + None + } + } + pub fn len(&self) -> usize { + match self { + Self::Frame(f) => f.len, + Self::Bottom => 0, + } } - pub fn len(&self) -> usize { match self { - Self::Frame(f) => f.len, - Self::Bottom => 0 - } } } impl<'a, T: Debug> Debug for Substack<'a, T> { @@ -58,29 +65,31 @@ impl<'a, T: Debug> Debug for Substack<'a, T> { } } +/// Iterates over a substack from the top down pub struct SubstackIterator<'a, T> { - curr: &'a Substack<'a, T> + curr: &'a Substack<'a, T>, } impl<'a, T> SubstackIterator<'a, T> { - #[allow(unused)] - pub fn first_some Option - >(&mut self, f: F) -> Option { + pub fn first_some Option>(&mut self, f: F) -> Option { for x in self.by_ref() { if let Some(result) = f(x) { - return Some(result) + return Some(result); } } None } /// Returns an iterator that starts from the bottom of the stack - /// and ends at the current position. This moves all items to the - /// heap by copying them to a [Vec] - pub fn rev_vec_clone(self) -> Vec where T: Clone { + /// and ends at the current position. + pub fn rev_vec_clone(self) -> Vec + where + T: Clone, + { let mut deque = VecDeque::with_capacity(self.curr.len()); - for item in self { deque.push_front(item.clone()) } + for item in self { + deque.push_front(item.clone()) + } deque.into() } } @@ -106,6 +115,3 @@ impl<'a, T> Iterator for SubstackIterator<'a, T> { (self.curr.len(), Some(self.curr.len())) } } - - - diff --git a/src/utils/unwrap_or.rs b/src/utils/unwrap_or.rs index 5fa6bd2..acad1de 100644 --- a/src/utils/unwrap_or.rs +++ b/src/utils/unwrap_or.rs @@ -1,9 +1,8 @@ -/// A macro version of [Option::unwrap_or_else] which supports -/// flow control statements such as `return` and `break` in the "else" -/// branch. +/// A macro version of [Option::unwrap_or_else] which supports flow +/// control statements such as `return` and `break` in the "else" branch. #[macro_export] macro_rules! unwrap_or { - ($m:expr; $fail:expr) => { - { if let Some(res) = ($m) {res} else {$fail} } - } -} \ No newline at end of file + ($m:expr; $fail:expr) => {{ + if let Some(res) = ($m) { res } else { $fail } + }}; +} diff --git a/src/utils/variant.rs b/src/utils/variant.rs deleted file mode 100644 index fe5638c..0000000 --- a/src/utils/variant.rs +++ /dev/null @@ -1,19 +0,0 @@ -// trait Var { -// type With: Var; - -// fn map(self, f: impl FnOnce(T) -> U) -> Self::With; -// fn map_multi + Var>( -// self, f: impl FnOnce(T) -> Ret -// ) -> as Var>::With; -// } - -// enum Variant { -// Head(T), -// Tail(U) -// } - -// impl> Var for Variant { -// fn map(self, f: impl FnOnce(H) -> U) -> Self::With { -// match -// } -// } diff --git a/src/utils/xloop.rs b/src/utils/xloop.rs index d5d5d86..e4b6c1d 100644 --- a/src/utils/xloop.rs +++ b/src/utils/xloop.rs @@ -1,14 +1,16 @@ -/// Imitates a regular for loop with an exit clause using Rust's `loop` keyword. -/// This macro brings the break value to all existing Rust loops, by allowing you to specify -/// an exit expression in case the loop was broken by the condition and not an explicit `break`. -/// -/// Since the exit expression can also be a block, this also allows you to execute other code when -/// the condition fails. This can also be used to re-enter the loop with an explicit `continue` -/// statement. -/// -/// The macro also adds support for classic for loops familiar to everyone since C, except with -/// the addition of an exit statement these too can be turned into expressions. -/// +/// Imitates a regular for loop with an exit clause using Rust's `loop` +/// keyword. This macro brings the break value to all existing Rust loops, +/// by allowing you to specify an exit expression in case the loop was +/// broken by the condition and not an explicit `break`. +/// +/// Since the exit expression can also be a block, this also allows you to +/// execute other code when the condition fails. This can also be used to +/// re-enter the loop with an explicit `continue` statement. +/// +/// The macro also adds support for classic for loops familiar to everyone +/// since C, except with the addition of an exit statement these too can +/// be turned into expressions. +/// /// ``` /// xloop!(for i in 0..10; { /// connection.try_connect() @@ -17,9 +19,10 @@ /// } /// }; None) /// ``` -/// -/// While loop with reentry. This is a very convoluted example but displays the idea quite clearly. -/// +/// +/// While loop with reentry. This is a very convoluted example but +/// displays the idea quite clearly. +/// /// ``` /// xloop!(while socket.is_open(); { /// let (data, is_end) = socket.read(); @@ -35,19 +38,19 @@ /// } /// }) /// ``` -/// +/// /// CUDA algorythm for O(log n) summation using a C loop -/// +/// /// ``` /// xloop!(let mut leap = 1; own_id*2 + leap < batch_size; leap *= 2; { /// batch[own_id*2] += batch[own_id*2 + leap] /// }) /// ``` -/// -/// The above loop isn't used as an expression, but an exit expression - or block - can be added -/// to these as well just like the others. In all cases the exit expression is optional, its -/// default value is `()`. -/// +/// +/// The above loop isn't used as an expression, but an exit expression - +/// or block - can be added to these as well just like the others. In all +/// cases the exit expression is optional, its default value is `()`. +/// /// TODO: find a valid use case for While let for a demo /// TODO: break out into crate #[macro_export] @@ -89,4 +92,4 @@ macro_rules! xloop { ($init:stmt; $cond:expr; $step:stmt; $body:stmt; $exit:stmt) => { { $init; xloop!(while $cond; { $body; $step }; $exit) } }; -} \ No newline at end of file +}