From 8c866967a966e954b4f0ddd4029bb50eec2073ca Mon Sep 17 00:00:00 2001 From: Lawrence Bethlenfalvy Date: Tue, 12 Sep 2023 01:26:46 +0100 Subject: [PATCH] The pipeline is finally reasonably clean --- clippy.toml | 1 + src/bin/orcx.rs | 1 + src/error/conflicting_roles.rs | 31 ++ src/error/import_all.rs | 2 +- src/error/mod.rs | 5 +- src/error/not_exported.rs | 6 +- src/error/project_error.rs | 3 +- src/error/too_many_supers.rs | 13 +- src/error/unexpected_directory.rs | 2 +- src/error/visibility_mismatch.rs | 4 +- src/facade/environment.rs | 3 +- src/facade/pre_macro.rs | 9 +- src/facade/system.rs | 4 +- src/foreign/cps_box.rs | 7 +- src/foreign_macros/atomic_impl.rs | 10 +- src/foreign_macros/define_fn.rs | 19 +- src/foreign_macros/write_fn_step.rs | 11 +- src/interner/token.rs | 6 +- src/interpreter/apply.rs | 10 +- src/lib.rs | 3 +- src/parse/context.rs | 29 +- src/parse/errors.rs | 6 +- src/parse/facade.rs | 7 +- src/parse/lexer.rs | 31 +- src/parse/mod.rs | 4 +- src/parse/multiname.rs | 25 +- src/parse/name.rs | 33 +- src/parse/operators.rs | 31 ++ src/parse/sourcefile.rs | 58 ++- src/pipeline/dealias/alias_cache.rs | 59 +++ src/pipeline/dealias/mod.rs | 5 + src/pipeline/dealias/resolve_aliases.rs | 95 +++++ src/pipeline/dealias/walk_with_links.rs | 124 ++++++ src/pipeline/file_loader.rs | 15 +- src/pipeline/import_abs_path.rs | 21 +- src/pipeline/import_resolution/alias_map.rs | 10 +- .../import_resolution/apply_aliases.rs | 87 ++-- .../import_resolution/collect_aliases.rs | 130 +----- src/pipeline/import_resolution/decls.rs | 4 +- src/pipeline/import_resolution/mod.rs | 1 + .../import_resolution/resolve_imports.rs | 12 +- src/pipeline/mod.rs | 3 +- src/pipeline/parse_layer.rs | 31 +- src/pipeline/project_tree/add_prelude.rs | 46 --- src/pipeline/project_tree/build_tree.rs | 370 +++++++----------- .../project_tree/collect_ops/exported_ops.rs | 79 ---- src/pipeline/project_tree/collect_ops/mod.rs | 8 - .../project_tree/collect_ops/ops_for.rs | 53 --- src/pipeline/project_tree/import_tree.rs | 169 ++++++++ src/pipeline/project_tree/mod.rs | 25 +- .../project_tree/normalize_imports.rs | 94 ----- src/pipeline/project_tree/parse_file.rs | 46 --- src/pipeline/project_tree/prefix.rs | 73 ---- src/pipeline/project_tree/rebuild_tree.rs | 131 +++++++ src/pipeline/source_loader/load_source.rs | 108 ++--- src/pipeline/source_loader/loaded_source.rs | 2 - src/pipeline/source_loader/mod.rs | 3 +- src/pipeline/source_loader/preparse.rs | 214 ++++++---- src/pipeline/source_loader/types.rs | 92 +++++ src/representations/ast.rs | 9 +- src/representations/ast_to_postmacro.rs | 4 +- src/representations/const_tree.rs | 24 +- src/representations/interpreted.rs | 2 - src/representations/location.rs | 18 +- src/representations/mod.rs | 4 +- src/representations/namelike.rs | 5 +- src/representations/project.rs | 146 +++++-- src/representations/sourcefile.rs | 201 +++++++--- src/representations/string.rs | 16 +- src/representations/tree.rs | 279 +++++++++---- src/rule/matcher_vectree/shared.rs | 4 +- src/systems/io/facade.rs | 16 +- src/systems/stl/bool.orc | 6 +- src/systems/stl/fn.orc | 10 +- src/systems/stl/known.orc | 4 +- src/systems/stl/mod.rs | 2 +- src/systems/stl/num.orc | 12 +- src/systems/stl/proc.orc | 4 + src/systems/stl/stl_system.rs | 17 +- src/systems/stl/str.orc | 2 + src/utils/get_or_default.rs | 20 + src/utils/mod.rs | 6 +- src/utils/never.rs | 17 + src/utils/pushed.rs | 17 +- src/utils/split_max_prefix.rs | 12 +- src/utils/substack.rs | 11 +- 86 files changed, 1959 insertions(+), 1393 deletions(-) create mode 100644 clippy.toml create mode 100644 src/error/conflicting_roles.rs create mode 100644 src/parse/operators.rs create mode 100644 src/pipeline/dealias/alias_cache.rs create mode 100644 src/pipeline/dealias/mod.rs create mode 100644 src/pipeline/dealias/resolve_aliases.rs create mode 100644 src/pipeline/dealias/walk_with_links.rs delete mode 100644 src/pipeline/project_tree/add_prelude.rs delete mode 100644 src/pipeline/project_tree/collect_ops/exported_ops.rs delete mode 100644 src/pipeline/project_tree/collect_ops/mod.rs delete mode 100644 src/pipeline/project_tree/collect_ops/ops_for.rs create mode 100644 src/pipeline/project_tree/import_tree.rs delete mode 100644 src/pipeline/project_tree/normalize_imports.rs delete mode 100644 src/pipeline/project_tree/parse_file.rs delete mode 100644 src/pipeline/project_tree/prefix.rs create mode 100644 src/pipeline/project_tree/rebuild_tree.rs create mode 100644 src/pipeline/source_loader/types.rs create mode 100644 src/utils/get_or_default.rs create mode 100644 src/utils/never.rs diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..5e30523 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +type-complexity-threshold = 300 diff --git a/src/bin/orcx.rs b/src/bin/orcx.rs index fd922f6..caa5718 100644 --- a/src/bin/orcx.rs +++ b/src/bin/orcx.rs @@ -116,6 +116,7 @@ pub fn macro_debug(premacro: PreMacro, sym: Sym) { "Available commands: \t, n, next\t\ttake a step \tp, print\t\tprint the current state + \td, dump\t\tprint the rule table \tq, quit\t\texit \th, help\t\tprint this text" ), diff --git a/src/error/conflicting_roles.rs b/src/error/conflicting_roles.rs new file mode 100644 index 0000000..e0e184e --- /dev/null +++ b/src/error/conflicting_roles.rs @@ -0,0 +1,31 @@ +use itertools::Itertools; + +use super::{ErrorPosition, ProjectError}; +use crate::utils::BoxedIter; +use crate::{Location, VName}; + +/// Error raised if the same name ends up assigned to more than one thing. +/// A name in Orchid has exactly one meaning, either a value or a module. +pub struct ConflictingRoles { + /// Name assigned to multiple things + pub name: VName, + /// Location of at least two occurrences + pub locations: Vec, +} +impl ProjectError for ConflictingRoles { + fn description(&self) -> &str { + "The same name is assigned multiple times to conflicting items" + } + fn message(&self) -> String { + format!( + "{} has multiple conflicting meanings", + self.name.iter().map(|t| t.as_str()).join("::") + ) + } + fn positions(&self) -> BoxedIter { + Box::new( + (self.locations.iter()) + .map(|l| ErrorPosition { location: l.clone(), message: None }), + ) + } +} diff --git a/src/error/import_all.rs b/src/error/import_all.rs index 6dd344b..b7f0a18 100644 --- a/src/error/import_all.rs +++ b/src/error/import_all.rs @@ -10,7 +10,7 @@ use crate::VName; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ImportAll { /// The file containing the offending import - pub offender_file: Rc>, + pub offender_file: Rc, /// The module containing the offending import pub offender_mod: Rc, } diff --git a/src/error/mod.rs b/src/error/mod.rs index 01e502a..c31c5c5 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -2,17 +2,18 @@ mod import_all; mod no_targets; mod not_exported; -mod not_found; +// mod not_found; +mod conflicting_roles; mod parse_error_with_tokens; mod project_error; mod too_many_supers; mod unexpected_directory; mod visibility_mismatch; +pub use conflicting_roles::ConflictingRoles; pub use import_all::ImportAll; pub use no_targets::NoTargets; pub use not_exported::NotExported; -pub use not_found::NotFound; pub use parse_error_with_tokens::ParseErrorWithTokens; pub use project_error::{ErrorPosition, ProjectError, ProjectResult}; pub use too_many_supers::TooManySupers; diff --git a/src/error/not_exported.rs b/src/error/not_exported.rs index 007366e..8545f04 100644 --- a/src/error/not_exported.rs +++ b/src/error/not_exported.rs @@ -25,16 +25,14 @@ impl ProjectError for NotExported { Box::new( [ ErrorPosition { - location: Location::File(Rc::new(Interner::extern_all(&self.file))), + location: Location::File(Rc::new(self.file.clone())), message: Some(format!( "{} isn't exported", Interner::extern_all(&self.subpath).join("::") )), }, ErrorPosition { - location: Location::File(Rc::new(Interner::extern_all( - &self.referrer_file, - ))), + location: Location::File(Rc::new(self.referrer_file.clone())), message: Some(format!( "{} cannot see this symbol", Interner::extern_all(&self.referrer_subpath).join("::") diff --git a/src/error/project_error.rs b/src/error/project_error.rs index bfb4a46..91edbb6 100644 --- a/src/error/project_error.rs +++ b/src/error/project_error.rs @@ -1,5 +1,4 @@ -use std::fmt::Debug; -use std::fmt::Display; +use std::fmt::{Debug, Display}; use std::rc::Rc; use crate::representations::location::Location; diff --git a/src/error/too_many_supers.rs b/src/error/too_many_supers.rs index c87ee0d..d5fd27f 100644 --- a/src/error/too_many_supers.rs +++ b/src/error/too_many_supers.rs @@ -1,5 +1,3 @@ -use std::rc::Rc; - use super::ProjectError; use crate::representations::location::Location; use crate::{Interner, VName}; @@ -10,10 +8,8 @@ use crate::{Interner, VName}; pub struct TooManySupers { /// The offending import path pub path: VName, - /// The file containing the offending import - pub offender_file: VName, - /// The module containing the offending import - pub offender_mod: VName, + /// The faulty import statement + pub location: Location, } impl ProjectError for TooManySupers { fn description(&self) -> &str { @@ -22,13 +18,12 @@ impl ProjectError for TooManySupers { } fn message(&self) -> String { format!( - "path {} in {} contains too many `super` steps.", + "path {} contains too many `super` steps.", Interner::extern_all(&self.path).join("::"), - Interner::extern_all(&self.offender_mod).join("::") ) } fn one_position(&self) -> Location { - Location::File(Rc::new(Interner::extern_all(&self.offender_file))) + self.location.clone() } } diff --git a/src/error/unexpected_directory.rs b/src/error/unexpected_directory.rs index 637f5b5..1365aef 100644 --- a/src/error/unexpected_directory.rs +++ b/src/error/unexpected_directory.rs @@ -16,7 +16,7 @@ impl ProjectError for UnexpectedDirectory { to a directory" } fn one_position(&self) -> crate::Location { - Location::File(Rc::new(Interner::extern_all(&self.path))) + Location::File(Rc::new(self.path.clone())) } fn message(&self) -> String { diff --git a/src/error/visibility_mismatch.rs b/src/error/visibility_mismatch.rs index 04b8caf..f9de177 100644 --- a/src/error/visibility_mismatch.rs +++ b/src/error/visibility_mismatch.rs @@ -10,7 +10,7 @@ pub struct VisibilityMismatch { /// The namespace with ambiguous visibility pub namespace: VName, /// The file containing the namespace - pub file: Rc>, + pub file: VName, } impl ProjectError for VisibilityMismatch { fn description(&self) -> &str { @@ -23,6 +23,6 @@ impl ProjectError for VisibilityMismatch { ) } fn one_position(&self) -> Location { - Location::File(self.file.clone()) + Location::File(Rc::new(self.file.clone())) } } diff --git a/src/facade/environment.rs b/src/facade/environment.rs index 9059740..bf43c1c 100644 --- a/src/facade/environment.rs +++ b/src/facade/environment.rs @@ -8,6 +8,7 @@ use super::PreMacro; use crate::error::ProjectResult; use crate::pipeline::file_loader; use crate::sourcefile::FileEntry; +use crate::utils::never; use crate::{ from_const_tree, parse_layer, vname_to_sym_tree, Interner, ProjectTree, Stok, VName, @@ -39,7 +40,7 @@ impl<'a> Environment<'a> { let mut tree = from_const_tree(HashMap::new(), &[i.i("none")]); for sys in systems.iter() { let system_tree = from_const_tree(sys.constants.clone(), &sys.vname(i)); - tree = ProjectTree(tree.0.overlay(system_tree.0)); + tree = ProjectTree(never::unwrap_always(tree.0.overlay(system_tree.0))); } let mut prelude = vec![]; for sys in systems.iter() { diff --git a/src/facade/pre_macro.rs b/src/facade/pre_macro.rs index b4438a1..376f598 100644 --- a/src/facade/pre_macro.rs +++ b/src/facade/pre_macro.rs @@ -42,12 +42,13 @@ impl<'a> PreMacro<'a> { repo, consts: (consts.into_iter()) .map(|(name, expr)| { + // Figure out the location of the constant let location = (name.split_last()) .and_then(|(_, path)| { - let origin = (tree.0.walk_ref(path, false)) - .expect("path sourced from symbol names"); - (origin.extra.file.as_ref()) - .map(|path| Interner::extern_all(&path[..])) + let origin = (tree.0) + .walk_ref(&[], path, false) + .unwrap_or_else(|_| panic!("path sourced from symbol names")); + (origin.extra.file.as_ref()).cloned() }) .map(|p| Location::File(Rc::new(p))) .unwrap_or(Location::Unknown); diff --git a/src/facade/system.rs b/src/facade/system.rs index 9db03c0..df03450 100644 --- a/src/facade/system.rs +++ b/src/facade/system.rs @@ -16,7 +16,7 @@ pub struct System<'a> { /// External functions and other constant values defined in AST form pub constants: HashMap, ConstTree>, /// Orchid libraries defined by this system - pub code: HashMap>, Loaded>, + pub code: HashMap, /// Prelude lines to be added to **subsequent** systems and usercode to /// expose the functionality of this system. The prelude is not added during /// the loading of this system @@ -27,7 +27,7 @@ pub struct System<'a> { impl<'a> System<'a> { /// Intern the name of the system so that it can be used as an Orchid /// namespace - pub fn vname(&self, i: &Interner) -> Vec> { + pub fn vname(&self, i: &Interner) -> VName { self.name.iter().map(|s| i.i(s)).collect::>() } diff --git a/src/foreign/cps_box.rs b/src/foreign/cps_box.rs index 6607a19..5ac7735 100644 --- a/src/foreign/cps_box.rs +++ b/src/foreign/cps_box.rs @@ -1,13 +1,13 @@ //! Automated wrappers to make working with CPS commands easier. use std::fmt::Debug; -use std::iter; use trait_set::trait_set; use super::{Atomic, AtomicResult, AtomicReturn, ExternFn, XfnResult}; use crate::interpreted::{Clause, ExprInst}; use crate::interpreter::{Context, HandlerRes}; +use crate::utils::pushed::pushed_ref; use crate::{atomic_defaults, ConstTree}; trait_set! { @@ -39,10 +39,7 @@ impl ExternFn for CPSFn { } fn apply(&self, arg: ExprInst, _ctx: Context) -> XfnResult { let payload = self.payload.clone(); - let continuations = (self.continuations.iter()) - .cloned() - .chain(iter::once(arg)) - .collect::>(); + let continuations = pushed_ref(&self.continuations, arg); if self.argc == 1 { Ok(CPSBox { payload, continuations }.atom_cls()) } else { diff --git a/src/foreign_macros/atomic_impl.rs b/src/foreign_macros/atomic_impl.rs index 3398865..9d816b4 100644 --- a/src/foreign_macros/atomic_impl.rs +++ b/src/foreign_macros/atomic_impl.rs @@ -40,7 +40,7 @@ use crate::Primitive; /// _definition of the `add` function in the STL_ /// ``` /// use orchidlang::{Literal}; -/// use orchidlang::interpreted::ExprInst; +/// use orchidlang::interpreted::{ExprInst, Clause}; /// use orchidlang::systems::cast_exprinst::with_lit; /// use orchidlang::{atomic_impl, atomic_redirect, externfn_impl}; /// @@ -63,10 +63,10 @@ use crate::Primitive; /// atomic_redirect!(InternalToString, expr_inst); /// atomic_impl!(InternalToString, |Self { expr_inst }: &Self, _|{ /// with_lit(expr_inst, |l| Ok(match l { -/// Literal::Uint(i) => i.to_string(), -/// Literal::Num(n) => n.to_string(), -/// Literal::Str(s) => s.clone(), -/// })).map(|s| Literal::Str(s).into()) +/// Literal::Uint(i) => Literal::Str(i.to_string().into()), +/// Literal::Num(n) => Literal::Str(n.to_string().into()), +/// s@Literal::Str(_) => s.clone(), +/// })).map(Clause::from) /// }); /// ``` #[macro_export] diff --git a/src/foreign_macros/define_fn.rs b/src/foreign_macros/define_fn.rs index e873cd5..8c05dc6 100644 --- a/src/foreign_macros/define_fn.rs +++ b/src/foreign_macros/define_fn.rs @@ -42,15 +42,17 @@ use crate::write_fn_step; /// ``` /// use orchidlang::interpreted::Clause; /// use orchidlang::systems::cast_exprinst::with_str; -/// use orchidlang::{define_fn, Literal, Primitive}; +/// use orchidlang::{define_fn, Literal, OrcString, Primitive}; /// /// define_fn! {expr=x in /// /// Append a string to another /// pub Concatenate { -/// a: String as with_str(x, |s| Ok(s.clone())), -/// b: String as with_str(x, |s| Ok(s.clone())) +/// a: OrcString as with_str(x, |s| Ok(s.clone())), +/// b: OrcString as with_str(x, |s| Ok(s.clone())) /// } => { -/// Ok(Clause::P(Primitive::Literal(Literal::Str(a.to_owned() + &b)))) +/// Ok(Clause::P(Primitive::Literal(Literal::Str( +/// OrcString::from(a.get_string() + &b) +/// )))) /// } /// } /// ``` @@ -58,6 +60,7 @@ use crate::write_fn_step; /// A simpler format is also offered for unary functions: /// /// ``` +/// use orchidlang::interpreted::Clause; /// use orchidlang::systems::cast_exprinst::with_lit; /// use orchidlang::{define_fn, Literal}; /// @@ -65,10 +68,10 @@ use crate::write_fn_step; /// /// Convert a literal to a string using Rust's conversions for floats, /// /// chars and uints respectively /// ToString = |x| with_lit(x, |l| Ok(match l { -/// Literal::Uint(i) => i.to_string(), -/// Literal::Num(n) => n.to_string(), -/// Literal::Str(s) => s.clone(), -/// })).map(|s| Literal::Str(s).into()) +/// Literal::Uint(i) => Literal::Str(i.to_string().into()), +/// Literal::Num(n) => Literal::Str(n.to_string().into()), +/// s@Literal::Str(_) => s.clone(), +/// })).map(Clause::from) /// } /// ``` #[macro_export] diff --git a/src/foreign_macros/write_fn_step.rs b/src/foreign_macros/write_fn_step.rs index 897ca51..47a991c 100644 --- a/src/foreign_macros/write_fn_step.rs +++ b/src/foreign_macros/write_fn_step.rs @@ -19,10 +19,9 @@ use crate::interpreted::ExprInst; /// also receive type annotations. /// /// ``` -/// // FIXME this is a very old example that wouldn't compile now /// use unicode_segmentation::UnicodeSegmentation; /// -/// use orchidlang::{write_fn_step, Literal, Primitive}; +/// use orchidlang::{write_fn_step, Literal, Primitive, OrcString}; /// use orchidlang::interpreted::Clause; /// use orchidlang::systems::cast_exprinst::{with_str, with_uint}; /// use orchidlang::systems::RuntimeError; @@ -32,20 +31,20 @@ use crate::interpreted::ExprInst; /// // Middle state /// write_fn_step!( /// CharAt1 {} -/// CharAt0 where s: String = x => with_str(x, |s| Ok(s.clone())); +/// CharAt0 where s: OrcString = x => with_str(x, |s| Ok(s.clone())); /// ); /// // Exit state /// write_fn_step!( -/// CharAt0 { s: String } +/// CharAt0 { s: OrcString } /// i = x => with_uint(x, Ok); /// { /// if let Some(c) = s.graphemes(true).nth(*i as usize) { -/// Ok(Literal::Str(c.to_string()).into()) +/// Ok(Literal::Str(OrcString::from(c.to_string())).into()) /// } else { /// RuntimeError::fail( /// "Character index out of bounds".to_string(), /// "indexing string", -/// )? +/// ) /// } /// } /// ); diff --git a/src/interner/token.rs b/src/interner/token.rs index 05652b1..3764f4b 100644 --- a/src/interner/token.rs +++ b/src/interner/token.rs @@ -55,15 +55,15 @@ impl Deref for Tok { } } -impl Debug for Tok { +impl Debug for Tok { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Token({})", self.id()) + write!(f, "Token({} -> {:?})", self.id(), self.data.as_ref()) } } impl Display for Tok { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", *self) + write!(f, "{}", **self) } } diff --git a/src/interpreter/apply.rs b/src/interpreter/apply.rs index 8d16c91..bb2c95f 100644 --- a/src/interpreter/apply.rs +++ b/src/interpreter/apply.rs @@ -4,6 +4,7 @@ use super::Return; use crate::foreign::AtomicReturn; use crate::representations::interpreted::{Clause, ExprInst}; use crate::representations::{PathSet, Primitive}; +use crate::utils::never::{unwrap_always, Always}; use crate::utils::Side; /// Process the clause at the end of the provided path. Note that paths always @@ -53,16 +54,12 @@ fn map_at( .map(|p| p.0) } -/// TODO replace when `!` gets stabilized -#[derive(Debug)] -enum Never {} - /// 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 { + unwrap_always(map_at(steps, body, &mut |checkpoint| -> Always { match (checkpoint, next) { (Clause::Lambda { .. }, _) => unreachable!("Handled by map_at"), (Clause::Apply { f, x }, Some((left, right))) => Ok(Clause::Apply { @@ -77,8 +74,7 @@ fn substitute(paths: &PathSet, value: Clause, body: ExprInst) -> ExprInst { panic!("Substitution path leads into something other than Apply") }, } - }) - .unwrap() + })) } /// Apply a function-like expression to a parameter. diff --git a/src/lib.rs b/src/lib.rs index c35ca6d..a4b35db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,7 +23,6 @@ mod utils; pub use interner::{Interner, Tok}; pub use pipeline::file_loader::{mk_dir_cache, mk_embed_cache}; pub use pipeline::parse_layer; -pub use representations::{NameLike, Sym, VName}; /// Element of VName and a common occurrence in the API pub type Stok = Tok; pub use representations::ast_to_interpreted::ast_to_interpreted; @@ -32,6 +31,6 @@ pub use representations::project::{ }; pub use representations::{ ast, from_const_tree, interpreted, sourcefile, tree, ConstTree, Literal, - Location, PathSet, Primitive, + Location, NameLike, OrcString, PathSet, Primitive, Sym, VName, }; pub use utils::{thread_pool, Side, Substack}; diff --git a/src/parse/context.rs b/src/parse/context.rs index df0d40b..b0e7dd7 100644 --- a/src/parse/context.rs +++ b/src/parse/context.rs @@ -1,16 +1,15 @@ use std::rc::Rc; use crate::interner::Interner; +use crate::{Tok, VName}; /// 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(&self) -> &[Self::Op]; - fn file(&self) -> Rc>; + fn ops(&self) -> &[Tok]; + fn file(&self) -> Rc; fn interner(&self) -> &Interner; } @@ -18,38 +17,36 @@ pub trait Context: Clone { /// /// Hiding type parameters in associated types allows for simpler /// parser definitions -pub struct ParsingContext<'a, Op> { - pub ops: &'a [Op], +pub struct ParsingContext<'a> { + pub ops: &'a [Tok], pub interner: &'a Interner, - pub file: Rc>, + pub file: Rc, } -impl<'a, Op> ParsingContext<'a, Op> { +impl<'a> ParsingContext<'a> { pub fn new( - ops: &'a [Op], + ops: &'a [Tok], interner: &'a Interner, - file: Rc>, + file: Rc, ) -> Self { Self { ops, interner, file } } } -impl<'a, Op> Clone for ParsingContext<'a, Op> { +impl<'a> Clone for ParsingContext<'a> { fn clone(&self) -> Self { Self { ops: self.ops, interner: self.interner, file: self.file.clone() } } } -impl> Context for ParsingContext<'_, Op> { - type Op = Op; - +impl Context for ParsingContext<'_> { fn interner(&self) -> &Interner { self.interner } - fn file(&self) -> Rc> { + fn file(&self) -> Rc { self.file.clone() } - fn ops(&self) -> &[Self::Op] { + fn ops(&self) -> &[Tok] { self.ops } } diff --git a/src/parse/errors.rs b/src/parse/errors.rs index c0dceb2..6f7bac4 100644 --- a/src/parse/errors.rs +++ b/src/parse/errors.rs @@ -6,7 +6,7 @@ use itertools::Itertools; use super::{Entry, Lexeme}; use crate::error::{ErrorPosition, ProjectError}; use crate::utils::BoxedIter; -use crate::{Location, Tok}; +use crate::{Location, Tok, VName}; #[derive(Debug)] pub struct LineNeedsPrefix { @@ -234,7 +234,7 @@ impl ProjectError for GlobExport { pub struct LexError { pub errors: Vec>, pub source: Rc, - pub file: Rc>, + pub file: VName, } impl ProjectError for LexError { fn description(&self) -> &str { @@ -244,7 +244,7 @@ impl ProjectError for LexError { let file = self.file.clone(); Box::new(self.errors.iter().map(move |s| ErrorPosition { location: Location::Range { - file: file.clone(), + file: Rc::new(file.clone()), range: s.span(), source: self.source.clone(), }, diff --git a/src/parse/facade.rs b/src/parse/facade.rs index 08f03cb..fb9f3c1 100644 --- a/src/parse/facade.rs +++ b/src/parse/facade.rs @@ -14,7 +14,12 @@ pub fn parse2(data: &str, ctx: impl Context) -> ProjectResult> { let source = Rc::new(data.to_string()); let lexie = lexer(ctx.clone(), source.clone()); let tokens = (lexie.parse(data)).map_err(|errors| { - LexError { errors, file: ctx.file(), source: source.clone() }.rc() + LexError { + errors, + file: ctx.file().as_ref().clone(), + source: source.clone(), + } + .rc() })?; if tokens.is_empty() { Ok(Vec::new()) diff --git a/src/parse/lexer.rs b/src/parse/lexer.rs index 5734edd..33f7360 100644 --- a/src/parse/lexer.rs +++ b/src/parse/lexer.rs @@ -14,8 +14,9 @@ use super::number::print_nat16; use super::{comment, name, number, placeholder, string}; use crate::ast::{PHClass, Placeholder}; use crate::interner::Tok; +use crate::parse::operators::operators_parser; use crate::representations::Literal; -use crate::Location; +use crate::{Interner, Location, VName}; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Entry { @@ -47,7 +48,7 @@ impl Entry { self.location.range().expect("An Entry can only have a known location") } - pub fn file(&self) -> Rc> { + pub fn file(&self) -> Rc { self.location.file().expect("An Entry can only have a range location") } } @@ -106,14 +107,15 @@ pub enum Lexeme { /// Backslash BS, At, - Dot, + // Dot, Type, // type operator - Comment(String), + Comment(Rc), Export, Import, Module, Macro, Const, + Operators(Rc), Placeh(Placeholder), } @@ -135,7 +137,6 @@ impl Display for Lexeme { Self::BR => writeln!(f), Self::BS => write!(f, "\\"), Self::At => write!(f, "@"), - Self::Dot => write!(f, "."), Self::Type => write!(f, ":"), Self::Comment(text) => write!(f, "--[{}]--", text), Self::Export => write!(f, "export"), @@ -143,6 +144,8 @@ impl Display for Lexeme { Self::Module => write!(f, "module"), Self::Const => write!(f, "const"), Self::Macro => write!(f, "macro"), + Self::Operators(ops) => + write!(f, "operators[{}]", Interner::extern_all(ops).join(" ")), Self::Placeh(Placeholder { name, class }) => match *class { PHClass::Scalar => write!(f, "${}", **name), PHClass::Vec { nonzero, prio } => { @@ -185,16 +188,19 @@ 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<'a>(ctx: impl Context + 'a) -> impl SimpleParser + 'a { +pub fn literal_parser<'a>( + ctx: impl Context + 'a, +) -> impl SimpleParser + 'a { choice(( // all ints are valid floats so it takes precedence number::int_parser().map(Literal::Uint), number::float_parser().map(Literal::Num), - string::str_parser().map(move |s| Literal::Str(ctx.interner().i(&s).into())), + string::str_parser() + .map(move |s| Literal::Str(ctx.interner().i(&s).into())), )) } -pub static BASE_OPS: &[&str] = &[",", ".", "..", "..."]; +pub static BASE_OPS: &[&str] = &[",", ".", "..", "...", "*"]; pub fn lexer<'a>( ctx: impl Context + 'a, @@ -213,6 +219,11 @@ pub fn lexer<'a>( keyword("import").to(Lexeme::Import), keyword("macro").to(Lexeme::Macro), keyword("const").to(Lexeme::Const), + operators_parser({ + let ctx = ctx.clone(); + move |s| ctx.interner().i(&s) + }) + .map(|v| Lexeme::Operators(Rc::new(v))), paren_parser('(', ')'), paren_parser('[', ']'), paren_parser('{', '}'), @@ -221,14 +232,14 @@ pub fn lexer<'a>( .ignore_then(number::float_parser()) .then_ignore(just("=>")) .map(Lexeme::rule), - comment::comment_parser().map(Lexeme::Comment), + comment::comment_parser().map(|s| Lexeme::Comment(Rc::new(s))), placeholder::placeholder_parser(ctx.clone()).map(Lexeme::Placeh), just("::").to(Lexeme::NS), just('\\').to(Lexeme::BS), just('@').to(Lexeme::At), just(':').to(Lexeme::Type), just('\n').to(Lexeme::BR), - just('.').to(Lexeme::Dot), + // just('.').to(Lexeme::Dot), literal_parser(ctx.clone()).map(Lexeme::Literal), name::name_parser(&all_ops).map({ let ctx = ctx.clone(); diff --git a/src/parse/mod.rs b/src/parse/mod.rs index f82795b..4f97d9d 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -7,6 +7,7 @@ mod lexer; mod multiname; mod name; mod number; +mod operators; mod placeholder; mod sourcefile; mod stream; @@ -15,5 +16,4 @@ mod string; pub use context::ParsingContext; pub use facade::parse2; pub use lexer::{lexer, Entry, Lexeme}; -pub use name::is_op; -pub use number::{float_parser, int_parser}; +pub use number::{float_parser, int_parser, print_nat16}; diff --git a/src/parse/multiname.rs b/src/parse/multiname.rs index 140a5b3..2beb905 100644 --- a/src/parse/multiname.rs +++ b/src/parse/multiname.rs @@ -8,19 +8,20 @@ use crate::error::{ProjectError, ProjectResult}; use crate::sourcefile::Import; use crate::utils::iter::{box_chain, box_once}; use crate::utils::BoxedIter; -use crate::Tok; +use crate::{Location, Tok}; struct Subresult { glob: bool, deque: VecDeque>, + location: Location, } impl Subresult { - fn new_glob() -> Self { - Self { glob: true, deque: VecDeque::new() } + fn new_glob(location: Location) -> Self { + Self { glob: true, deque: VecDeque::new(), location } } - fn new_named(name: Tok) -> Self { - Self { glob: false, deque: VecDeque::from([name]) } + fn new_named(name: Tok, location: Location) -> Self { + Self { location, glob: false, deque: VecDeque::from([name]) } } fn push_front(mut self, name: Tok) -> Self { @@ -29,10 +30,10 @@ impl Subresult { } fn finalize(self) -> Import { - let Self { mut deque, glob } = self; + let Self { mut deque, glob, location } = self; debug_assert!(glob || !deque.is_empty(), "The constructors forbid this"); let name = if glob { None } else { deque.pop_back() }; - Import { name, path: deque.into() } + Import { name, location, path: deque.into() } } } @@ -75,7 +76,7 @@ fn parse_multiname_rec( let head; (head, cursor) = cursor.trim().pop()?; match &head.lexeme { - Lexeme::Name(n) => names.push(n), + Lexeme::Name(n) => names.push((n, head.location())), Lexeme::RP('[') => break, _ => { let err = Expected { @@ -88,12 +89,14 @@ fn parse_multiname_rec( } } Ok(( - Box::new(names.into_iter().map(|n| Subresult::new_named(n.clone()))), + Box::new(names.into_iter().map(|(name, location)| { + Subresult::new_named(name.clone(), location) + })), cursor, )) }, Lexeme::Name(n) if *n == star => - Ok((box_once(Subresult::new_glob()), cursor)), + Ok((box_once(Subresult::new_glob(head.location())), cursor)), Lexeme::Name(n) if ![comma, star].contains(n) => { let cursor = cursor.trim(); if cursor.get(0).ok().map(|e| &e.lexeme) == Some(&Lexeme::NS) { @@ -102,7 +105,7 @@ fn parse_multiname_rec( let out = Box::new(out.map(|sr| sr.push_front(n.clone()))); Ok((out, cursor)) } else { - Ok((box_once(Subresult::new_named(n.clone())), cursor)) + Ok((box_once(Subresult::new_named(n.clone(), head.location())), cursor)) } }, _ => Err( diff --git a/src/parse/name.rs b/src/parse/name.rs index 6ecd0c9..66fc7ae 100644 --- a/src/parse/name.rs +++ b/src/parse/name.rs @@ -30,7 +30,7 @@ pub static NOT_NAME_CHAR: &[char] = &[ '"', // parsed as primitive and therefore would never match '(', ')', '[', ']', '{', '}', // must be strictly balanced '.', // Argument-body separator in parametrics - ',', // used in imports + ',', // Import separator ]; /// Matches anything that's allowed as an operator @@ -39,18 +39,20 @@ pub static NOT_NAME_CHAR: &[char] = &[ /// 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 SimpleParser + 'a { - filter(move |c| !NOT_NAME_CHAR.contains(c) && !c.is_whitespace()) - .repeated() - .at_least(1) - .collect() - .labelled("modname") +pub fn anyop_parser<'a>() -> impl SimpleParser + 'a { + filter(move |c| { + !NOT_NAME_CHAR.contains(c) + && !c.is_whitespace() + && !c.is_alphanumeric() + && c != &'_' + }) + .repeated() + .at_least(1) + .collect() + .labelled("anyop") } /// Parse an operator or name. Failing both, parse everything up to @@ -61,16 +63,7 @@ pub fn name_parser<'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 + anyop_parser(), // Finally parse everything until tne next forbidden char )) .labelled("name") } - -/// Decide if a string can be an operator. Operators can include digits -/// 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, - }; -} diff --git a/src/parse/operators.rs b/src/parse/operators.rs new file mode 100644 index 0000000..1a31754 --- /dev/null +++ b/src/parse/operators.rs @@ -0,0 +1,31 @@ +use chumsky::prelude::*; + +use super::decls::SimpleParser; + +pub fn operators_parser( + f: impl Fn(String) -> T, +) -> impl SimpleParser> { + filter(|c: &char| c != &']' && !c.is_whitespace()) + .repeated() + .at_least(1) + .collect() + .map(f) + .separated_by(text::whitespace()) + .allow_leading() + .allow_trailing() + .at_least(1) + .delimited_by(just("operators["), just(']')) +} + +#[cfg(test)] +mod test { + use chumsky::Parser; + + use super::operators_parser; + + #[test] + fn operators_scratchpad() { + let parsely = operators_parser(|s| s); + println!("{:?}", parsely.parse("operators[$ |> =>]")) + } +} diff --git a/src/parse/sourcefile.rs b/src/parse/sourcefile.rs index fbfd579..a6a9deb 100644 --- a/src/parse/sourcefile.rs +++ b/src/parse/sourcefile.rs @@ -15,9 +15,9 @@ use super::Entry; use crate::ast::{Clause, Constant, Expr, Rule}; use crate::error::{ProjectError, ProjectResult}; use crate::representations::location::Location; -use crate::representations::sourcefile::{FileEntry, Member, ModuleBlock}; +use crate::representations::sourcefile::{FileEntry, MemberKind, ModuleBlock}; use crate::representations::VName; -use crate::sourcefile::Import; +use crate::sourcefile::{FileEntryKind, Import, Member}; use crate::Primitive; pub fn split_lines(module: Stream<'_>) -> impl Iterator> { @@ -57,23 +57,31 @@ pub fn parse_module_body( split_lines(cursor) .map(Stream::trim) .filter(|l| !l.data.is_empty()) - .map(|l| parse_line(l, ctx.clone())) + .map(|l| { + Ok(FileEntry { + locations: vec![l.location()], + kind: parse_line(l, ctx.clone())?, + }) + }) .collect() } pub fn parse_line( cursor: Stream<'_>, ctx: impl Context, -) -> ProjectResult { +) -> ProjectResult { match cursor.get(0)?.lexeme { Lexeme::BR | Lexeme::Comment(_) => parse_line(cursor.step()?, ctx), Lexeme::Export => parse_export_line(cursor.step()?, ctx), - Lexeme::Const | Lexeme::Macro | Lexeme::Module => - Ok(FileEntry::Internal(parse_member(cursor, ctx)?)), + Lexeme::Const | Lexeme::Macro | Lexeme::Module | Lexeme::Operators(_) => + Ok(FileEntryKind::Member(Member { + kind: parse_member(cursor, ctx)?, + exported: false, + })), Lexeme::Import => { let (imports, cont) = parse_multiname(cursor.step()?, ctx)?; cont.expect_empty()?; - Ok(FileEntry::Import(imports)) + Ok(FileEntryKind::Import(imports)) }, _ => { let err = BadTokenInRegion { @@ -88,23 +96,26 @@ pub fn parse_line( pub fn parse_export_line( cursor: Stream<'_>, ctx: impl Context, -) -> ProjectResult { +) -> ProjectResult { let cursor = cursor.trim(); match cursor.get(0)?.lexeme { Lexeme::NS => { let (names, cont) = parse_multiname(cursor.step()?, ctx)?; cont.expect_empty()?; let names = (names.into_iter()) - .map(|Import { name, path }| match (name, &path[..]) { - (Some(n), []) => Ok(n), - (None, _) => Err(GlobExport { location: cursor.location() }.rc()), - _ => Err(NamespacedExport { location: cursor.location() }.rc()), + .map(|Import { name, path, location }| match (name, &path[..]) { + (Some(n), []) => Ok((n, location)), + (None, _) => Err(GlobExport { location }.rc()), + _ => Err(NamespacedExport { location }.rc()), }) .collect::, _>>()?; - Ok(FileEntry::Export(names)) + Ok(FileEntryKind::Export(names)) }, - Lexeme::Const | Lexeme::Macro | Lexeme::Module => - Ok(FileEntry::Exported(parse_member(cursor, ctx)?)), + Lexeme::Const | Lexeme::Macro | Lexeme::Module | Lexeme::Operators(_) => + Ok(FileEntryKind::Member(Member { + kind: parse_member(cursor, ctx)?, + exported: true, + })), _ => { let err = BadTokenInRegion { entry: cursor.get(0)?.clone(), @@ -118,20 +129,24 @@ pub fn parse_export_line( fn parse_member( cursor: Stream<'_>, ctx: impl Context, -) -> ProjectResult { +) -> ProjectResult { let (typemark, cursor) = cursor.trim().pop()?; - match typemark.lexeme { + match &typemark.lexeme { Lexeme::Const => { let constant = parse_const(cursor, ctx)?; - Ok(Member::Constant(constant)) + Ok(MemberKind::Constant(constant)) }, Lexeme::Macro => { let rule = parse_rule(cursor, ctx)?; - Ok(Member::Rule(rule)) + Ok(MemberKind::Rule(rule)) }, Lexeme::Module => { let module = parse_module(cursor, ctx)?; - Ok(Member::Module(module)) + Ok(MemberKind::Module(module)) + }, + Lexeme::Operators(ops) => { + cursor.trim().expect_empty()?; + Ok(MemberKind::Operators(ops[..].to_vec())) }, _ => { let err = @@ -234,8 +249,9 @@ fn parse_exprv( cursor = leftover; }, Lexeme::BS => { + let dot = ctx.interner().i("."); let (arg, body) = - cursor.step()?.find("A '.'", |l| l == &Lexeme::Dot)?; + cursor.step()?.find("A '.'", |l| l == &Lexeme::Name(dot.clone()))?; let (arg, _) = parse_exprv(arg, None, ctx.clone())?; let (body, leftover) = parse_exprv(body, paren, ctx)?; output.push(Expr { diff --git a/src/pipeline/dealias/alias_cache.rs b/src/pipeline/dealias/alias_cache.rs new file mode 100644 index 0000000..5835966 --- /dev/null +++ b/src/pipeline/dealias/alias_cache.rs @@ -0,0 +1,59 @@ +use std::slice; + +use chumsky::primitive::Container; +use hashbrown::HashMap; + +use crate::representations::project::{ProjectMod, ItemKind, ProjectEntry}; +use crate::tree::ModMember; +use crate::utils::{pushed, unwrap_or}; +use crate::{ProjectTree, VName, Tok, NameLike}; + +use super::walk_with_links::{walk_with_links, Target}; + +pub struct AliasCache { + data: HashMap>, Option>>>, +} +impl AliasCache { + pub fn new() -> Self { + Self { data: HashMap::new() } + } + + /// Finds the absolute nsname corresponding to the given name in the given + /// context, if it's imported. If the name is defined locally, returns None + /// to avoid allocating several vectors for every local variable. + pub fn resolv_name<'a>( + &'a mut self, + root: &ProjectMod, + location: &[Tok], + name: Tok + ) -> Option<&'a [Tok]> { + let full_path = pushed(location, name); + if let Some(result) = self.data.get(&full_path) { + return result.as_deref(); + } + let (ent, finalp) = walk_with_links(root, location.iter().cloned()) + .expect("This path should be valid"); + let m = unwrap_or!{ent => Target::Mod; panic!("Must be a module")}; + let result = m.extra.imports_from.get(&name).map(|next| { + self.resolv_name(root, &next, name).unwrap_or(&next) + }); + self.data.insert(full_path, result.map(|s| s.to_vec())); + return result; + } + + /// Find the absolute target of a + pub fn resolv_vec<'a>( + &'a mut self, + root: &ProjectMod, + modname: &[Tok], + vname: &[Tok], + ) -> Option<&'a [Tok]> { + let (name, ns) = vname.split_last().expect("name cannot be empty"); + if ns.is_empty() { + self.resolv_name(modname, name) + } else { + let origin = self.resolv_vec(modname, ns)?; + self.resolv_name(origin, name) + } + } +} diff --git a/src/pipeline/dealias/mod.rs b/src/pipeline/dealias/mod.rs new file mode 100644 index 0000000..d8cef8e --- /dev/null +++ b/src/pipeline/dealias/mod.rs @@ -0,0 +1,5 @@ +// mod alias_cache; +mod resolve_aliases; +mod walk_with_links; + +pub use resolve_aliases::resolve_aliases; diff --git a/src/pipeline/dealias/resolve_aliases.rs b/src/pipeline/dealias/resolve_aliases.rs new file mode 100644 index 0000000..f11294b --- /dev/null +++ b/src/pipeline/dealias/resolve_aliases.rs @@ -0,0 +1,95 @@ +use std::iter; + +use itertools::Itertools; + +use super::walk_with_links::walk_with_links; +use crate::ast::{Expr, Rule}; +use crate::representations::project::{ + ItemKind, ProjectExt, ProjectItem, ProjectMod, +}; +use crate::tree::{ModEntry, ModMember, Module}; +use crate::utils::pushed::pushed; +use crate::{Interner, ProjectTree, Tok, VName}; + +fn resolve_aliases_rec( + root: &ProjectMod, + module: &ProjectMod, + updated: &impl Fn(&[Tok]) -> bool, + is_root: bool, +) -> ProjectMod { + if !is_root && !updated(&module.extra.path) { + return module.clone(); + } + let process_expr = |expr: &Expr| { + expr + .map_names(&|n| { + let full_name = (module.extra.path.iter()).chain(n.iter()).cloned(); + match walk_with_links(root, full_name, false) { + Ok(rep) => Some(rep.abs_path), + // Ok(_) => None, + Err(e) => { + let leftovers = e.tail.collect::>(); + if !leftovers.is_empty() { + let full_name = (module.extra.path.iter()) + .chain(n.iter()) + .cloned() + .collect::>(); + let _ = walk_with_links(root, full_name.iter().cloned(), true); + panic!( + "Invalid path {} while resolving {} should have been noticed \ + earlier", + (e.abs_path.into_iter()) + .chain(iter::once(e.name)) + .chain(leftovers.into_iter()) + .join("::"), + Interner::extern_all(&full_name).join("::"), + ); + } + Some(pushed(e.abs_path, e.name)) + }, + } + }) + .unwrap_or_else(|| expr.clone()) + }; + Module { + extra: ProjectExt { + path: module.extra.path.clone(), + file: module.extra.file.clone(), + imports_from: module.extra.imports_from.clone(), + rules: (module.extra.rules.iter()) + .map(|Rule { pattern, prio, template }| Rule { + pattern: pattern.iter().map(process_expr).collect(), + template: template.iter().map(process_expr).collect(), + prio: *prio, + }) + .collect(), + }, + entries: module + .entries + .iter() + .map(|(k, v)| { + (k.clone(), ModEntry { + exported: v.exported, + member: match &v.member { + ModMember::Sub(module) => + ModMember::Sub(resolve_aliases_rec(root, module, updated, false)), + ModMember::Item(item) => ModMember::Item(ProjectItem { + is_op: item.is_op, + kind: match &item.kind { + ItemKind::Const(value) => ItemKind::Const(process_expr(value)), + other => other.clone(), + }, + }), + }, + }) + }) + .collect(), + } +} + +pub fn resolve_aliases( + project: ProjectTree, + updated: &impl Fn(&[Tok]) -> bool, +) -> ProjectTree { + ProjectTree(resolve_aliases_rec(&project.0, &project.0, updated, true)) +} diff --git a/src/pipeline/dealias/walk_with_links.rs b/src/pipeline/dealias/walk_with_links.rs new file mode 100644 index 0000000..08ba53a --- /dev/null +++ b/src/pipeline/dealias/walk_with_links.rs @@ -0,0 +1,124 @@ +#[allow(unused)] // for doc +use crate::representations::project::ProjectEntry; +use crate::representations::project::{ItemKind, ProjectItem, ProjectMod}; +use crate::tree::ModMember; +use crate::utils::{unwrap_or, BoxedIter}; +use crate::{Interner, NameLike, Tok, VName}; + +/// The destination of a linked walk. [ProjectEntry] cannot be used for this +/// purpose because it might be the project root. +pub enum Target<'a, N: NameLike> { + Mod(&'a ProjectMod), + Leaf(&'a ProjectItem), +} + +pub struct WalkReport<'a, N: NameLike> { + pub target: Target<'a, N>, + pub abs_path: VName, + pub aliased: bool, +} + +pub struct LinkWalkError<'a> { + /// The last known valid path + pub abs_path: VName, + /// The name that wasn't found + pub name: Tok, + /// Leftover steps + pub tail: BoxedIter<'a, Tok>, + /// Whether an alias was ever encountered + pub aliased: bool, +} + +fn walk_with_links_rec<'a, 'b, N: NameLike>( + mut abs_path: VName, + root: &'a ProjectMod, + cur: &'a ProjectMod, + prev_tgt: Target<'a, N>, + aliased: bool, + mut path: impl Iterator> + 'b, + l: bool, +) -> Result, LinkWalkError<'b>> { + let name = unwrap_or! {path.next(); + // ends on this module + return Ok(WalkReport{ target: prev_tgt, abs_path, aliased }) + }; + if l { + eprintln!( + "Resolving {} in {}", + name, + Interner::extern_all(&abs_path).join("::") + ) + } + let entry = unwrap_or! {cur.entries.get(&name); { + // panic!("No entry {name} on {}", Interner::extern_all(&cur.extra.path).join("::")); + // leads into a missing branch + return Err(LinkWalkError{ abs_path, aliased, name, tail: Box::new(path) }) + }}; + match &entry.member { + ModMember::Sub(m) => { + // leads into submodule + abs_path.push(name); + walk_with_links_rec(abs_path, root, m, Target::Mod(m), aliased, path, l) + }, + ModMember::Item(item) => match &item.kind { + ItemKind::Alias(alias) => { + // leads into alias (reset acc, cur, cur_entry) + if l { + eprintln!( + "{} points to {}", + Interner::extern_all(&abs_path).join("::"), + Interner::extern_all(alias).join("::") + ) + } + abs_path.clone_from(alias); + abs_path.extend(path); + let path_acc = Vec::with_capacity(abs_path.len()); + let new_path = abs_path.into_iter(); + let tgt = Target::Mod(root); + walk_with_links_rec(path_acc, root, root, tgt, true, new_path, l) + }, + ItemKind::Const(_) | ItemKind::None => { + abs_path.push(name); + match path.next() { + Some(name) => { + // leads into leaf + let tail = Box::new(path); + Err(LinkWalkError { abs_path, aliased, name, tail }) + }, + None => { + // ends on leaf + let target = Target::Leaf(item); + Ok(WalkReport { target, abs_path, aliased }) + }, + } + }, + }, + } +} + +/// Execute a walk down the tree, following aliases. +/// If the path ends on an alias, that alias is also resolved. +/// If the path leads out of the tree, the shortest failing path is returned +pub fn walk_with_links<'a, N: NameLike>( + root: &ProjectMod, + path: impl Iterator> + 'a, + l: bool, +) -> Result, LinkWalkError<'a>> { + let path_acc = path.size_hint().1.map_or_else(Vec::new, Vec::with_capacity); + let mut result = walk_with_links_rec( + path_acc, + root, + root, + Target::Mod(root), + false, + path, + l, + ); + // cut off excess preallocated space within normal vector growth policy + let abs_path = match &mut result { + Ok(rep) => &mut rep.abs_path, + Err(err) => &mut err.abs_path, + }; + abs_path.shrink_to(abs_path.len().next_power_of_two()); + result +} diff --git a/src/pipeline/file_loader.rs b/src/pipeline/file_loader.rs index 7180e28..bbe5cc3 100644 --- a/src/pipeline/file_loader.rs +++ b/src/pipeline/file_loader.rs @@ -11,14 +11,14 @@ use crate::error::{ProjectError, ProjectResult}; use crate::facade::System; use crate::interner::Interner; use crate::utils::Cache; -use crate::{Location, Stok, VName}; +use crate::{Location, Stok, Tok, VName}; /// All the data available about a failed source load call #[derive(Debug)] pub struct FileLoadingError { file: io::Error, dir: io::Error, - path: Vec, + path: VName, } impl ProjectError for FileLoadingError { fn description(&self) -> &str { @@ -54,8 +54,8 @@ pub type IOResult = ProjectResult; /// 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 full_path = path.iter().fold(root.to_owned(), |p, s| p.join(s.as_ref())); +pub fn load_file(root: &Path, path: &[Tok]) -> IOResult { + let full_path = path.iter().fold(root.to_owned(), |p, t| p.join(t.as_str())); let file_path = full_path.with_extension("orc"); let file_error = match fs::read_to_string(file_path) { Ok(string) => return Ok(Loaded::Code(Rc::new(string))), @@ -68,7 +68,7 @@ pub fn load_file(root: &Path, path: &[impl AsRef]) -> IOResult { FileLoadingError { file: file_error, dir: dir_error, - path: path.iter().map(|s| s.as_ref().to_string()).collect(), + path: path.to_vec(), } .rc(), ), @@ -90,10 +90,7 @@ pub fn load_file(root: &Path, path: &[impl AsRef]) -> IOResult { /// Generates a cached file loader for a directory pub fn mk_dir_cache(root: PathBuf) -> Cache<'static, VName, IOResult> { - Cache::new(move |vname: VName, _this| -> IOResult { - let path = vname.iter().map(|t| t.as_str()).collect::>(); - load_file(&root, &path) - }) + Cache::new(move |vname: VName, _this| load_file(&root, &vname)) } /// Load a file from the specified path from an embed table diff --git a/src/pipeline/import_abs_path.rs b/src/pipeline/import_abs_path.rs index ebc796e..512a273 100644 --- a/src/pipeline/import_abs_path.rs +++ b/src/pipeline/import_abs_path.rs @@ -1,29 +1,22 @@ -use crate::error::{ProjectError, ProjectResult, TooManySupers}; +use crate::error::ProjectResult; use crate::interner::{Interner, Tok}; use crate::representations::sourcefile::absolute_path; use crate::utils::Substack; +use crate::{Location, VName}; pub fn import_abs_path( src_path: &[Tok], mod_stack: Substack>, import_path: &[Tok], i: &Interner, -) -> ProjectResult>> { + location: &Location, +) -> ProjectResult { // 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()) - .chain(mod_pathv.iter()) - .cloned() - .collect::>(); + let abs_pathv = + (src_path.iter()).chain(mod_pathv.iter()).cloned().collect::>(); // preload-target path relative to module // preload-target path within compilation - absolute_path(&abs_pathv, import_path, i).map_err(|_| { - TooManySupers { - path: import_path.to_vec(), - offender_file: src_path.to_vec(), - offender_mod: mod_pathv, - } - .rc() - }) + absolute_path(&abs_pathv, import_path, i, location) } diff --git a/src/pipeline/import_resolution/alias_map.rs b/src/pipeline/import_resolution/alias_map.rs index 751a999..1cdf559 100644 --- a/src/pipeline/import_resolution/alias_map.rs +++ b/src/pipeline/import_resolution/alias_map.rs @@ -2,19 +2,19 @@ use std::hash::Hash; use hashbrown::{HashMap, HashSet}; -use crate::interner::Tok; +use crate::{interner::Tok, VName}; #[derive(Clone, Debug, Default)] pub struct AliasMap { - pub targets: HashMap>, Vec>>, - pub aliases: HashMap>, HashSet>>>, + pub targets: HashMap, + pub aliases: HashMap>, } impl AliasMap { pub fn new() -> Self { Self::default() } - pub fn link(&mut self, alias: Vec>, target: Vec>) { + pub fn link(&mut self, alias: VName, target: VName) { let prev = self.targets.insert(alias.clone(), target.clone()); debug_assert!(prev.is_none(), "Alias already has a target"); multimap_entry(&mut self.aliases, &target).insert(alias.clone()); @@ -36,7 +36,7 @@ impl AliasMap { } } - pub fn resolve(&self, alias: &[Tok]) -> Option<&Vec>> { + pub fn resolve(&self, alias: &[Tok]) -> Option<&VName> { self.targets.get(alias) } } diff --git a/src/pipeline/import_resolution/apply_aliases.rs b/src/pipeline/import_resolution/apply_aliases.rs index 2c4470e..5a4f4e4 100644 --- a/src/pipeline/import_resolution/apply_aliases.rs +++ b/src/pipeline/import_resolution/apply_aliases.rs @@ -1,18 +1,16 @@ -use hashbrown::HashMap; - use super::alias_map::AliasMap; use super::decls::{InjectedAsFn, UpdatedFn}; use crate::ast::{Expr, Rule}; use crate::interner::Tok; -use crate::representations::project::{ProjectExt, ProjectModule}; -use crate::representations::tree::{ModEntry, ModMember}; +use crate::representations::project::{ItemKind, ProjectMod}; +use crate::representations::tree::ModMember; use crate::representations::VName; use crate::utils::Substack; fn resolve_rec( namespace: &[Tok], alias_map: &AliasMap, -) -> Option>> { +) -> Option { if let Some(alias) = alias_map.resolve(namespace) { Some(alias.clone()) } else if let Some((foot, body)) = namespace.split_last() { @@ -28,7 +26,7 @@ fn resolve( namespace: &[Tok], alias_map: &AliasMap, injected_as: &impl InjectedAsFn, -) -> Option>> { +) -> Option { injected_as(namespace).or_else(|| { let next_v = resolve_rec(namespace, alias_map)?; Some(injected_as(&next_v).unwrap_or(next_v)) @@ -45,69 +43,44 @@ fn process_expr( .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>, - module: &ProjectModule, + module: &mut ProjectMod, alias_map: &AliasMap, injected_as: &impl InjectedAsFn, updated: &impl UpdatedFn, -) -> 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)), - ModMember::Sub(module) => { - let subpath = path.push(name.clone()); - let new_mod = if !updated(&subpath.iter().rev_vec_clone()) { - module.clone() - } else { - apply_aliases_rec(subpath, module, alias_map, injected_as, updated) - }; - ModMember::Sub(new_mod) - }, - }; - (name.clone(), ModEntry { exported: *exported, member }) - }) - .collect::>(); - let rules = (module.extra.rules.iter()) - .map(|rule| { - let Rule { pattern, prio, template } = rule; - Rule { - prio: *prio, - pattern: (pattern.iter()) - .map(|expr| process_expr(expr, alias_map, injected_as)) - .collect::>(), - template: (template.iter()) - .map(|expr| process_expr(expr, alias_map, injected_as)) - .collect::>(), - } - }) - .collect::>(); - ProjectModule { - items, - imports: module.imports.clone(), - extra: ProjectExt { - rules, - exports: (module.extra.exports.iter()) - .map(|(k, v)| { - (k.clone(), resolve(v, alias_map, injected_as).unwrap_or(v.clone())) - }) - .collect(), - file: module.extra.file.clone(), - imports_from: module.extra.imports_from.clone(), - }, +) { + for (name, entry) in module.entries.iter_mut() { + match &mut entry.member { + ModMember::Sub(sub) => { + let subpath = path.push(name.clone()); + apply_aliases_rec(subpath, sub, alias_map, injected_as, updated) + }, + ModMember::Item(it) => match &mut it.kind { + ItemKind::None => (), + ItemKind::Const(expr) => + *expr = process_expr(expr, alias_map, injected_as), + ItemKind::Alias(name) => + if let Some(alt) = alias_map.resolve(&name) { + *name = alt.clone() + }, + }, + _ => (), + } + } + for Rule { pattern, prio, template } in module.extra.rules.iter_mut() { + for expr in pattern.iter_mut().chain(template.iter_mut()) { + *expr = process_expr(expr, alias_map, injected_as) + } } } pub fn apply_aliases( - module: &ProjectModule, + module: &mut ProjectMod, alias_map: &AliasMap, injected_as: &impl InjectedAsFn, updated: &impl UpdatedFn, -) -> ProjectModule { +) { apply_aliases_rec(Substack::Bottom, module, alias_map, injected_as, updated) } diff --git a/src/pipeline/import_resolution/collect_aliases.rs b/src/pipeline/import_resolution/collect_aliases.rs index f95daed..b63fcc3 100644 --- a/src/pipeline/import_resolution/collect_aliases.rs +++ b/src/pipeline/import_resolution/collect_aliases.rs @@ -1,134 +1,32 @@ use super::alias_map::AliasMap; use super::decls::UpdatedFn; -use crate::error::{NotExported, NotFound, ProjectError, ProjectResult}; +use crate::error::ProjectResult; use crate::interner::Tok; -use crate::pipeline::project_tree::split_path; -use crate::representations::project::{ProjectModule, ProjectTree}; -use crate::representations::tree::{ModMember, WalkErrorKind}; +use crate::representations::project::{ProjectMod, ProjectTree}; +use crate::representations::tree::ModMember; use crate::representations::VName; -use crate::utils::{pushed, unwrap_or, Substack}; - -/// Assert that a module identified by a path can see a given symbol -fn assert_visible( - source: &[Tok], // must point to a file or submodule - target: &[Tok], // may point to a symbol or module of any kind - project: &ProjectTree, -) -> ProjectResult<()> { - let (tgt_item, tgt_path) = unwrap_or! { - target.split_last(); - return Ok(()) - }; - let shared_len = - source.iter().zip(tgt_path.iter()).take_while(|(a, b)| a == b).count(); - let vis_ignored_len = usize::min(tgt_path.len(), shared_len + 1); - let private_root = (project.0) - .walk_ref(&tgt_path[..vis_ignored_len], false) - .map_err(|e| match e.kind { - WalkErrorKind::Private => - unreachable!("visibility is not being checked here"), - WalkErrorKind::Missing => NotFound::from_walk_error( - source, - &[], - &tgt_path[..vis_ignored_len], - &project.0, - e, - ) - .rc(), - })?; - let direct_parent = private_root - .walk_ref(&tgt_path[vis_ignored_len..], true) - .map_err(|e| match e.kind { - WalkErrorKind::Missing => NotFound::from_walk_error( - source, - &tgt_path[..vis_ignored_len], - &tgt_path[vis_ignored_len..], - &project.0, - e, - ) - .rc(), - WalkErrorKind::Private => { - let full_path = &tgt_path[..shared_len + e.pos]; - // These errors are encountered during error reporting but they're more - // fundamental / higher prio than the error to be raised and would - // emerge nonetheless so they take over and the original error is - // swallowed - match split_path(full_path, project) { - Err(e) => - NotFound::from_walk_error(source, &[], full_path, &project.0, e) - .rc(), - Ok((file, sub)) => { - let (ref_file, ref_sub) = split_path(source, project) - .expect("Source path assumed to be valid"); - NotExported { - file: file.to_vec(), - subpath: sub.to_vec(), - referrer_file: ref_file.to_vec(), - referrer_subpath: ref_sub.to_vec(), - } - .rc() - }, - } - }, - })?; - let tgt_item_exported = direct_parent.extra.exports.contains_key(tgt_item); - let target_prefixes_source = shared_len == tgt_path.len(); - if !tgt_item_exported && !target_prefixes_source { - let (file, sub) = split_path(target, project).map_err(|e| { - NotFound::from_walk_error(source, &[], target, &project.0, e).rc() - })?; - let (ref_file, ref_sub) = split_path(source, project) - .expect("The source path is assumed to be valid"); - Err( - NotExported { - file: file.to_vec(), - subpath: sub.to_vec(), - referrer_file: ref_file.to_vec(), - referrer_subpath: ref_sub.to_vec(), - } - .rc(), - ) - } else { - Ok(()) - } -} +use crate::utils::{pushed, unwrap_or}; /// Populate target and alias maps from the module tree recursively fn collect_aliases_rec( - path: Substack>, - module: &ProjectModule, + path: Vec>, + module: &ProjectMod, project: &ProjectTree, alias_map: &mut AliasMap, updated: &impl UpdatedFn, ) -> ProjectResult<()> { // Assume injected module has been alias-resolved - let mod_path_v = path.iter().rev_vec_clone(); - if !updated(&mod_path_v) { + if !updated(&path) { return Ok(()); }; - for (name, target_mod_name) in module.extra.imports_from.iter() { - let target_sym_v = pushed(target_mod_name, name.clone()); - assert_visible(&mod_path_v, &target_sym_v, project)?; - let sym_path_v = pushed(&mod_path_v, name.clone()); - let target_mod = (project.0.walk_ref(target_mod_name, false)) - .expect("checked above in assert_visible"); - let target_sym = (target_mod.extra.exports.get(name)) - .ok_or_else(|| { - let file_len = - target_mod.extra.file.as_ref().unwrap_or(target_mod_name).len(); - NotFound { - source: Some(mod_path_v.clone()), - file: target_mod_name[..file_len].to_vec(), - subpath: target_sym_v[file_len..].to_vec(), - } - .rc() - })? - .clone(); - alias_map.link(sym_path_v, target_sym); + for (name, target_sym_v) in module.extra.imports_from.iter() { + let sym_path_v = pushed(&path, name.clone()); + alias_map.link(sym_path_v, target_sym_v.clone()); } - for (name, entry) in module.items.iter() { + for (name, entry) in module.entries.iter() { let submodule = unwrap_or!(&entry.member => ModMember::Sub; continue); collect_aliases_rec( - path.push(name.clone()), + pushed(&path, name.clone()), submodule, project, alias_map, @@ -140,10 +38,10 @@ fn collect_aliases_rec( /// Populate target and alias maps from the module tree pub fn collect_aliases( - module: &ProjectModule, + module: &ProjectMod, project: &ProjectTree, alias_map: &mut AliasMap, updated: &impl UpdatedFn, ) -> ProjectResult<()> { - collect_aliases_rec(Substack::Bottom, module, project, alias_map, updated) + collect_aliases_rec(Vec::new(), module, project, alias_map, updated) } diff --git a/src/pipeline/import_resolution/decls.rs b/src/pipeline/import_resolution/decls.rs index f464131..b52f206 100644 --- a/src/pipeline/import_resolution/decls.rs +++ b/src/pipeline/import_resolution/decls.rs @@ -1,8 +1,8 @@ use trait_set::trait_set; -use crate::interner::Tok; +use crate::{interner::Tok, VName}; trait_set! { - pub trait InjectedAsFn = Fn(&[Tok]) -> Option>>; + pub trait InjectedAsFn = Fn(&[Tok]) -> Option; pub trait UpdatedFn = Fn(&[Tok]) -> bool; } diff --git a/src/pipeline/import_resolution/mod.rs b/src/pipeline/import_resolution/mod.rs index ddc665e..1904825 100644 --- a/src/pipeline/import_resolution/mod.rs +++ b/src/pipeline/import_resolution/mod.rs @@ -3,5 +3,6 @@ mod apply_aliases; mod collect_aliases; mod decls; mod resolve_imports; +mod alias_cache; 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 a1d6f0c..ec09559 100644 --- a/src/pipeline/import_resolution/resolve_imports.rs +++ b/src/pipeline/import_resolution/resolve_imports.rs @@ -1,3 +1,4 @@ +use super::alias_cache::AliasCache; use super::alias_map::AliasMap; use super::apply_aliases::apply_aliases; use super::collect_aliases::collect_aliases; @@ -9,12 +10,13 @@ use crate::representations::VName; /// 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( - project: ProjectTree, + mut project: ProjectTree, injected_as: &impl InjectedAsFn, updated: &impl UpdatedFn, ) -> ProjectResult> { - let mut map = AliasMap::new(); - collect_aliases(&project.0, &project, &mut map, updated)?; - let new_mod = apply_aliases(&project.0, &map, injected_as, updated); - Ok(ProjectTree(new_mod)) + let mut cache = AliasCache::new(&project); + // let mut map = AliasMap::new(); + // collect_aliases(&project.0, &project, &mut map, updated)?; + // apply_aliases(&mut project.0, &map, injected_as, updated); + Ok(project) } diff --git a/src/pipeline/mod.rs b/src/pipeline/mod.rs index 2e142ab..60a5816 100644 --- a/src/pipeline/mod.rs +++ b/src/pipeline/mod.rs @@ -1,7 +1,8 @@ //! Loading Orchid modules from source pub mod file_loader; mod import_abs_path; -mod import_resolution; +// mod import_resolution; +mod dealias; mod parse_layer; mod project_tree; mod source_loader; diff --git a/src/pipeline/parse_layer.rs b/src/pipeline/parse_layer.rs index 6a2acd1..ffea7bb 100644 --- a/src/pipeline/parse_layer.rs +++ b/src/pipeline/parse_layer.rs @@ -1,11 +1,11 @@ -use std::rc::Rc; - +use super::dealias::resolve_aliases; use super::file_loader::IOResult; -use super::{import_resolution, project_tree, source_loader}; +use super::{project_tree, source_loader}; use crate::error::ProjectResult; use crate::interner::{Interner, Tok}; use crate::representations::sourcefile::FileEntry; use crate::representations::VName; +use crate::utils::never; use crate::ProjectTree; /// Using an IO callback, produce a project tree that includes the given @@ -23,26 +23,17 @@ pub fn parse_layer<'a>( prelude: &[FileEntry], i: &Interner, ) -> ProjectResult> { - // A path is injected if it is walkable in the injected tree - let injected_as = |path: &[Tok]| { - let (item, modpath) = path.split_last()?; - let module = environment.0.walk_ref(modpath, false).ok()?; - module.extra.exports.get(item).cloned() - }; - let injected_names = |path: Tok>>| { - let module = environment.0.walk_ref(&path, false).ok()?; - Some(Rc::new(module.extra.exports.keys().cloned().collect())) - }; - let source = + let (preparsed, source) = source_loader::load_source(targets, prelude, i, loader, &|path| { - environment.0.walk_ref(path, false).is_ok() + environment.0.walk_ref(&[], path, false).is_ok() })?; - let tree = project_tree::build_tree(source, i, prelude, &injected_names)?; - let sum = ProjectTree(environment.0.clone().overlay(tree.0.clone())); + let tree = + project_tree::rebuild_tree(&source, preparsed, environment, prelude, i)?; + let sum = ProjectTree(never::unwrap_always( + environment.0.clone().overlay(tree.0.clone()), + )); let resolvd = - import_resolution::resolve_imports(sum, &injected_as, &|path| { - tree.0.walk_ref(path, false).is_ok() - })?; + resolve_aliases(sum, &|path| tree.0.walk1_ref(&[], path, false).is_ok()); // Addition among modules favours the left hand side. Ok(resolvd) } diff --git a/src/pipeline/project_tree/add_prelude.rs b/src/pipeline/project_tree/add_prelude.rs deleted file mode 100644 index 0334429..0000000 --- a/src/pipeline/project_tree/add_prelude.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::interner::Tok; -use crate::representations::sourcefile::{FileEntry, Member, ModuleBlock}; - -fn member_rec( - // object - member: Member, - // context - path: &[Tok], - prelude: &[FileEntry], -) -> Member { - match member { - Member::Module(ModuleBlock { name, body }) => { - let new_body = entv_rec(body, path, prelude); - Member::Module(ModuleBlock { name, body: new_body }) - }, - any => any, - } -} - -fn entv_rec( - // object - data: Vec, - // context - 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, - })) - .collect() -} - -pub fn add_prelude( - data: Vec, - path: &[Tok], - prelude: &[FileEntry], -) -> Vec { - entv_rec(data, path, prelude) -} diff --git a/src/pipeline/project_tree/build_tree.rs b/src/pipeline/project_tree/build_tree.rs index 01f588d..a8c9efd 100644 --- a/src/pipeline/project_tree/build_tree.rs +++ b/src/pipeline/project_tree/build_tree.rs @@ -1,246 +1,144 @@ use hashbrown::HashMap; -use itertools::Itertools; +use itertools::{Either, Itertools}; -use super::collect_ops; -use super::collect_ops::InjectedOperatorsFn; -use super::parse_file::parse_file; -use crate::ast::{Clause, Constant, Expr}; -use crate::error::{ProjectError, ProjectResult, TooManySupers}; -use crate::interner::{Interner, Tok}; -use crate::pipeline::source_loader::{LoadedSource, LoadedSourceTable}; -use crate::representations::project::{ProjectExt, ProjectTree}; -use crate::representations::sourcefile::{absolute_path, FileEntry, Member}; -use crate::representations::tree::{ModEntry, ModMember, Module}; -use crate::representations::{NameLike, VName}; -use crate::tree::{WalkError, WalkErrorKind}; -use crate::utils::iter::{box_empty, box_once}; -use crate::utils::{pushed, unwrap_or, Substack}; +use super::import_tree::ImpMod; +use crate::ast::{Constant, Rule}; +use crate::error::{ConflictingRoles, ProjectError, ProjectResult}; +use crate::pipeline::source_loader::{PreItem, PreMod}; +use crate::representations::project::{ + ImpReport, ItemKind, ProjectEntry, ProjectExt, ProjectItem, +}; +use crate::sourcefile::{ + FileEntry, FileEntryKind, Member, MemberKind, ModuleBlock, +}; +use crate::tree::{ModEntry, ModMember, Module}; +use crate::utils::get_or_default; +use crate::utils::pushed::pushed_ref; +use crate::{Tok, VName}; -#[derive(Debug)] -struct ParsedSource<'a> { - path: Vec>, - loaded: &'a LoadedSource, - parsed: Vec, -} - -/// Split a path into file- and subpath in knowledge -/// -/// # Errors -/// -/// if the path is invalid -#[allow(clippy::type_complexity)] // bit too sensitive here IMO -pub fn split_path<'a>( - path: &'a [Tok], - proj: &'a ProjectTree, -) -> Result<(&'a [Tok], &'a [Tok]), WalkError> { - let (end, body) = unwrap_or!(path.split_last(); { - return Ok((&[], &[])) - }); - let mut module = (proj.0.walk_ref(body, false))?; - let entry = (module.items.get(end)) - .ok_or(WalkError { pos: path.len() - 1, kind: WalkErrorKind::Missing })?; - if let ModMember::Sub(m) = &entry.member { - module = m; - } - let file = - module.extra.file.as_ref().map(|s| &path[..s.len()]).unwrap_or(path); - let subpath = &path[file.len()..]; - Ok((file, subpath)) -} - -/// Convert normalized, prefixed source into a module -/// -/// # Panics -/// -/// - if there are imports with too many "super" prefixes (this is normally -/// detected in preparsing) -/// - if the preparsed tree is missing a module that exists in the source -fn source_to_module( - // level - path: Substack>, - preparsed: &Module, - // data - data: Vec, - // context - i: &Interner, - filepath_len: usize, -) -> ProjectResult, ProjectExt>> { - 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 } - }) - .flatten() - .cloned() - .collect::>(); - let imports_from = (imports.iter()) - .map(|imp| -> ProjectResult<_> { - let mut imp_path_v = imp.path.clone(); - imp_path_v - .push(imp.name.clone().expect("glob imports had just been resolved")); - let mut abs_path = absolute_path(&path_v, &imp_path_v, i) - .expect("should have failed in preparsing"); - let name = abs_path.pop().ok_or_else(|| { - TooManySupers { - offender_file: path_v[..filepath_len].to_vec(), - offender_mod: path_v[filepath_len..].to_vec(), - path: imp_path_v, - } - .rc() - })?; - Ok((name, abs_path)) - }) - .collect::, _>>()?; - let exports = (data.iter()) - .flat_map(|ent| { - let mk_ent = |name: Tok| (name.clone(), pushed(&path_v, name)); - match ent { - FileEntry::Export(names) => Box::new(names.iter().cloned().map(mk_ent)), - FileEntry::Exported(mem) => match mem { - Member::Constant(constant) => box_once(mk_ent(constant.name.clone())), - Member::Module(ns) => box_once(mk_ent(ns.name.clone())), - Member::Rule(rule) => { - let mut names = Vec::new(); - for e in rule.pattern.iter() { - e.search_all(&mut |e| { - if let Clause::Name(n) = &e.value { - if let Some([name]) = n.strip_prefix(&path_v[..]) { - names.push((name.clone(), n.clone())) - } - } - None::<()> - }); - } - Box::new(names.into_iter()) - }, - }, - _ => box_empty(), - } - }) - .collect::>(); - let rules = (data.iter()) - .filter_map(|ent| match ent { - FileEntry::Exported(Member::Rule(rule)) => Some(rule), - FileEntry::Internal(Member::Rule(rule)) => Some(rule), - _ => None, - }) - .cloned() - .collect::>(); - let items = (data.into_iter()) - .filter_map(|ent| { - let member_to_item = |exported, member| match member { - Member::Module(ns) => { - let new_prep = unwrap_or!( - &preparsed.items[&ns.name].member => ModMember::Sub; - panic!("Preparsed should include entries for all submodules") - ); - let module = match source_to_module( - path.push(ns.name.clone()), - new_prep, - ns.body, - i, - filepath_len, - ) { - Err(e) => return Some(Err(e)), - Ok(t) => t, - }; - let member = ModMember::Sub(module); - Some(Ok((ns.name.clone(), ModEntry { exported, member }))) - }, - Member::Constant(Constant { name, value }) => { - let member = ModMember::Item(value); - Some(Ok((name, ModEntry { exported, member }))) - }, - _ => None, - }; - match ent { - FileEntry::Exported(member) => member_to_item(true, member), - FileEntry::Internal(member) => member_to_item(false, member), - _ => None, - } - }) - .collect::, _>>()?; - Ok(Module { - imports, - items, - extra: ProjectExt { - imports_from, - exports, - rules, - file: Some(path_v[..filepath_len].to_vec()), - }, - }) -} - -fn files_to_module( - path: Substack>, - files: Vec, - i: &Interner, -) -> ProjectResult, ProjectExt>> { - let lvl = path.len(); - debug_assert!( - files.iter().map(|f| f.path.len()).max().unwrap() >= lvl, - "path is longer than any of the considered file paths" - ); - let path_v = path.iter().rev_vec_clone(); - if files.len() == 1 && files[0].path.len() == lvl { - return source_to_module( - path.clone(), - &files[0].loaded.preparsed.0, - files[0].parsed.clone(), - i, - path.len(), - ); - } - let items = (files.into_iter()) - .group_by(|f| f.path[lvl].clone()) - .into_iter() - .map(|(namespace, files)| -> ProjectResult<_> { - let subpath = path.push(namespace.clone()); - let files_v = files.collect::>(); - let module = files_to_module(subpath, files_v, i)?; - let member = ModMember::Sub(module); - Ok((namespace, ModEntry { exported: true, member })) - }) - .collect::, _>>()?; - let exports: HashMap<_, _> = (items.keys()) - .map(|name| (name.clone(), pushed(&path_v, name.clone()))) - .collect(); - Ok(Module { - items, - imports: vec![], - extra: ProjectExt { - exports, - imports_from: HashMap::new(), - rules: vec![], - file: None, - }, - }) +pub struct TreeReport { + pub entries: HashMap, ProjectEntry>, + pub rules: Vec>, + /// Maps imported symbols to the absolute paths of the modules they are + /// imported from + pub imports_from: HashMap, ImpReport>, } pub fn build_tree( - files: LoadedSourceTable, - i: &Interner, + path: &VName, + source: Vec, + Module { entries, extra }: PreMod, + imports: ImpMod, prelude: &[FileEntry], - injected: &impl InjectedOperatorsFn, -) -> ProjectResult> { - assert!(!files.is_empty(), "A tree requires at least one module"); - let ops_cache = collect_ops::mk_cache(&files, injected); - let mut entries = files - .iter() - .map(|(path, loaded)| { - Ok((path, loaded, parse_file(path, &files, &ops_cache, i, prelude)?)) +) -> ProjectResult { + let source = + source.into_iter().chain(prelude.iter().cloned()).collect::>(); + let (imports_from, mut submod_imports) = (imports.entries.into_iter()) + .partition_map::, HashMap<_, _>, _, _, _>( + |(n, ent)| match ent.member { + ModMember::Item(it) => Either::Left((n, it)), + ModMember::Sub(s) => Either::Right((n, s)), + }, + ); + let mut rule_fragments = Vec::new(); + let mut submodules = HashMap::<_, Vec<_>>::new(); + let mut consts = HashMap::new(); + for FileEntry { kind, locations: _ } in source { + match kind { + FileEntryKind::Import(_) => (), + FileEntryKind::Comment(_) => (), + FileEntryKind::Export(_) => (), + FileEntryKind::Member(Member { kind, .. }) => match kind { + MemberKind::Module(ModuleBlock { body, name }) => { + get_or_default(&mut submodules, &name).extend(body.into_iter()); + }, + MemberKind::Constant(Constant { name, value }) => { + consts.insert(name, value /* .prefix(path, &|_| false) */); + }, + MemberKind::Operators(_) => (), + MemberKind::Rule(rule) => rule_fragments.push(rule), + }, + } + } + let mod_details = extra.details().expect("Directories handled elsewhere"); + let rules = (mod_details.patterns.iter()) + .zip(rule_fragments.into_iter()) + .map(|(p, Rule { prio, template: t, .. })| { + // let p = p.iter().map(|e| e.prefix(path, &|_| false)).collect(); + // let t = t.into_iter().map(|e| e.prefix(path, &|_| false)).collect(); + Rule { pattern: p.clone(), prio, template: t } }) - .collect::>>()?; - // 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(), + .collect(); + let (pre_subs, pre_items) = (entries.into_iter()) + .partition_map::, HashMap<_, _>, _, _, _>( + |(k, ModEntry { exported, member })| match member { + ModMember::Sub(s) => Either::Left((k, (exported, s))), + ModMember::Item(it) => Either::Right((k, (exported, it))), + }, + ); + let mut entries = (pre_subs.into_iter()) + .map(|(k, (exported, pre_member))| { + let impmod = (submod_imports.remove(&k)) + .expect("Imports and preparsed should line up"); + (k, exported, pre_member, impmod) }) - .collect::>(); - Ok(ProjectTree(files_to_module(Substack::Bottom, files, i)?)) + .map(|(k, exported, pre, imp)| { + let source = submodules + .remove(&k) + .expect("Submodules should not disappear after reparsing"); + (k, exported, pre, imp, source) + }) + .map(|(k, exported, pre, imp, source)| { + let path = pushed_ref(path, k.clone()); + let TreeReport { entries, imports_from, rules } = + build_tree(&path, source, pre, imp, prelude)?; + let extra = ProjectExt { path, file: None, imports_from, rules }; + let member = ModMember::Sub(Module { entries, extra }); + Ok((k, ModEntry { exported, member })) + }) + .chain((pre_items.into_iter()).map( + |(k, (exported, PreItem { has_value, is_op, location }))| { + let item = match imports_from.get(&k) { + Some(_) if has_value => { + // Local value cannot be assigned to imported key + let const_loc = + consts.remove(&k).expect("has_value is true").location; + let err = ConflictingRoles { + locations: vec![location, const_loc], + name: pushed_ref(path, k), + }; + return Err(err.rc()); + }, + None => { + let k = consts.remove(&k).map_or(ItemKind::None, ItemKind::Const); + ProjectItem { is_op, kind: k } + }, + Some(report) => ProjectItem { + is_op: is_op | report.is_op, + kind: ItemKind::Alias(report.source.clone()), + }, + }; + Ok((k, ModEntry { exported, member: ModMember::Item(item) })) + }, + )) + .collect::, _>>()?; + for (k, from) in imports_from.iter() { + let (_, ent) = entries.raw_entry_mut().from_key(k).or_insert_with(|| { + (k.clone(), ModEntry { + exported: false, + member: ModMember::Item(ProjectItem { + kind: ItemKind::Alias(from.source.clone()), + is_op: from.is_op, + }), + }) + }); + debug_assert!( + matches!( + ent.member, + ModMember::Item(ProjectItem { kind: ItemKind::Alias(_), .. }) + ), + "Should have emerged in the processing of pre_items" + ) + } + Ok(TreeReport { entries, rules, imports_from }) } diff --git a/src/pipeline/project_tree/collect_ops/exported_ops.rs b/src/pipeline/project_tree/collect_ops/exported_ops.rs deleted file mode 100644 index a6827b0..0000000 --- a/src/pipeline/project_tree/collect_ops/exported_ops.rs +++ /dev/null @@ -1,79 +0,0 @@ -use std::rc::Rc; - -use hashbrown::HashSet; -use trait_set::trait_set; - -use crate::error::{NotFound, ProjectError, ProjectResult}; -use crate::interner::Tok; -use crate::pipeline::source_loader::LoadedSourceTable; -use crate::representations::tree::WalkErrorKind; -use crate::utils::{split_max_prefix, Cache}; -use crate::Sym; - -pub type OpsResult = ProjectResult>>>; -pub type ExportedOpsCache<'a> = Cache<'a, Sym, OpsResult>; - -trait_set! { - pub trait InjectedOperatorsFn = Fn(Sym) -> Option>>>; -} - -fn coprefix( - l: 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: Sym, - loaded: &LoadedSourceTable, - injected: &impl InjectedOperatorsFn, -) -> OpsResult { - let injected = - injected(path.clone()).unwrap_or_else(|| Rc::new(HashSet::new())); - match split_max_prefix(&path, &|n| loaded.contains_key(n)) { - None => { - let ops = (loaded.keys()) - .filter_map(|modname| { - if path.len() == coprefix(path.iter(), modname.iter()) { - Some(modname[path.len()].clone()) - } else { - None - } - }) - .chain(injected.iter().cloned()) - .collect::>(); - Ok(Rc::new(ops)) - }, - Some((fpath, subpath)) => { - let preparsed = &loaded[fpath].preparsed; - let module = preparsed.0.walk_ref(subpath, false).map_err( - |walk_err| match walk_err.kind { - WalkErrorKind::Private => { - unreachable!("visibility is not being checked here") - }, - WalkErrorKind::Missing => NotFound { - source: None, - file: fpath.to_vec(), - subpath: subpath[..walk_err.pos].to_vec(), - } - .rc(), - }, - )?; - let out = (module.items.iter()) - .filter(|(_, v)| v.exported) - .map(|(k, _)| k.clone()) - .chain(injected.iter().cloned()) - .collect::>(); - Ok(Rc::new(out)) - }, - } -} - -pub fn mk_cache<'a>( - loaded: &'a LoadedSourceTable, - injected: &'a impl InjectedOperatorsFn, -) -> ExportedOpsCache<'a> { - Cache::new(|path, _this| collect_exported_ops(path, loaded, injected)) -} diff --git a/src/pipeline/project_tree/collect_ops/mod.rs b/src/pipeline/project_tree/collect_ops/mod.rs deleted file mode 100644 index 340860b..0000000 --- a/src/pipeline/project_tree/collect_ops/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -mod exported_ops; -mod ops_for; - -pub use exported_ops::{ - collect_exported_ops, mk_cache, ExportedOpsCache, InjectedOperatorsFn, - OpsResult, -}; -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 deleted file mode 100644 index 28e90d2..0000000 --- a/src/pipeline/project_tree/collect_ops/ops_for.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::rc::Rc; - -use hashbrown::HashSet; - -use super::exported_ops::{ExportedOpsCache, OpsResult}; -use crate::error::ProjectResult; -use crate::interner::{Interner, Tok}; -use crate::parse::is_op; -use crate::pipeline::import_abs_path::import_abs_path; -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.extend(module.items.keys().cloned()); - for ent in module.items.values() { - if let ModMember::Sub(m) = &ent.member { - tree_all_ops(m, ops); - } - } -} - -/// Collect all names visible in this file -/// -/// # Panics -/// -/// if any import contains too many Super calls. This should be caught during -/// preparsing -pub fn collect_ops_for( - file: &[Tok], - loaded: &LoadedSourceTable, - ops_cache: &ExportedOpsCache, - i: &Interner, -) -> OpsResult { - let tree = &loaded[file].preparsed.0; - let mut ret = HashSet::new(); - tree_all_ops(tree, &mut ret); - tree.visit_all_imports(&mut |modpath, _m, import| -> ProjectResult<()> { - if let Some(n) = &import.name { - ret.insert(n.clone()); - } else { - let path = import_abs_path(file, modpath, &import.path, i) - .expect("This error should have been caught during loading"); - ret.extend(ops_cache.find(&i.i(&path))?.iter().cloned()); - } - Ok(()) - })?; - Ok(Rc::new(ret.into_iter().filter(|t| is_op(&**t)).collect())) -} diff --git a/src/pipeline/project_tree/import_tree.rs b/src/pipeline/project_tree/import_tree.rs new file mode 100644 index 0000000..1a27462 --- /dev/null +++ b/src/pipeline/project_tree/import_tree.rs @@ -0,0 +1,169 @@ +use std::cmp::Ordering; +use std::fmt::Display; +use std::rc::Rc; + +use hashbrown::HashMap; +use itertools::Itertools; + +use crate::error::{ProjectError, ProjectResult}; +use crate::pipeline::source_loader::{PreMod, Preparsed}; +use crate::representations::project::ImpReport; +use crate::sourcefile::{absolute_path, Import}; +use crate::tree::{ErrKind, ModEntry, ModMember, Module, WalkError}; +use crate::utils::iter::{box_chain, box_once}; +use crate::utils::pushed::pushed_ref; +use crate::utils::{unwrap_or, BoxedIter}; +use crate::{Interner, ProjectTree, Tok, VName}; + +pub type ImpMod = Module, ()>; + +/// Assert that a module identified by a path can see a given symbol +fn assert_visible<'a>( + source: &'a [Tok], // must point to a file or submodule + target: &'a [Tok], + root: &'a Module, +) -> Result<(), WalkError<'a>> { + if target.split_last().map_or(true, |(_, m)| source.starts_with(m)) { + // The global module (empty path) is always visible + return Ok(()); // Symbols in ancestor modules are always visible + } + // walk the first section where visibility is ignored + let shared_len = + source.iter().zip(target.iter()).take_while(|(a, b)| a == b).count(); + let (shared_path, deep_path) = target.split_at(shared_len + 1); + let private_root = root.walk_ref(&[], shared_path, false)?; + // walk the second part where visibility matters + private_root.walk1_ref(shared_path, deep_path, true)?; + Ok(()) +} + +pub fn assert_visible_overlay<'a>( + source: &'a [Tok], // must point to a file or submodule + target: &'a [Tok], + first: &'a Module, + fallback: &'a Module, +) -> Result<(), WalkError<'a>> { + assert_visible(source, target, first).or_else(|e1| { + if e1.kind == ErrKind::Missing { + match assert_visible(source, target, fallback) { + // if both are walk errors, report the longer of the two + Err(mut e2) if e2.kind == ErrKind::Missing => + Err(match e1.depth().cmp(&e2.depth()) { + Ordering::Less => e2, + Ordering::Greater => e1, + Ordering::Equal => { + e2.options = box_chain!(e2.options, e1.options); + e2 + }, + }), + // otherwise return the parent's result + x => x, + } + } else { + Err(e1) + } + }) +} + +pub fn process_donor_module<'a, TItem: Clone>( + module: &'a Module, + abs_path: Rc, + is_op: impl Fn(&TItem) -> bool + 'a, +) -> impl Iterator, VName, bool)> + 'a { + (module.entries.iter()).filter(|(_, ent)| ent.exported).map( + move |(n, ent)| { + let is_op = ent.item().map_or(false, &is_op); + (n.clone(), pushed_ref(abs_path.as_ref(), n.clone()), is_op) + }, + ) +} + +pub fn import_tree( + modpath: VName, + pre: &PreMod, + root: &Preparsed, + prev_root: &ProjectTree, + i: &Interner, +) -> ProjectResult { + let imports = pre.extra.details().map_or(&[][..], |e| &e.imports[..]); + let entries = (imports.iter()) + // imports become leaf sets + .map(|Import { name, path, location }| -> ProjectResult> { + let mut abs_path = absolute_path(&modpath, path, i, location)?; + Ok(if let Some(name) = name { + // named imports are validated and translated 1->1 + abs_path.push(name.clone()); + assert_visible_overlay(&modpath, &abs_path, &root.0, &prev_root.0) + .map_err(|e| -> Rc { + println!("Current root: {}", &root.0); + // println!("Old root: {:#?}", &prev_root.0); + panic!("{}", e.at(location)) + })?; + let is_op = (root.0.walk1_ref(&[], &abs_path, false)) + .map(|(ent, _)| ent.item().map_or(false, |i| i.is_op)) + .or_else(|e| if e.kind == ErrKind::Missing { + (prev_root.0.walk1_ref(&[], &abs_path, false)) + .map(|(ent, _)| ent.item().map_or(false, |i| i.is_op)) + } else {Err(e)}) + .map_err(|e| e.at(location))?; + box_once((name.clone(), abs_path, is_op)) + } else { + let rc_path = Rc::new(abs_path); + // wildcard imports are validated + assert_visible_overlay(&modpath, &rc_path, &root.0, &prev_root.0) + .map_err(|e| e.at(location))?; + // and instantiated for all exports of the target 1->n + let new_imports = match (root.0).walk_ref(&[], &rc_path, false) { + Err(e) if e.kind == ErrKind::Missing => Err(e), + Err(e) => return Err(e.at(location)), + Ok(module) + => Ok(process_donor_module(module, rc_path.clone(), |i| i.is_op)) + }; + let old_m = match (prev_root.0).walk_ref(&[], &rc_path, false) { + Err(e) if e.kind != ErrKind::Missing => return Err(e.at(location)), + Err(e1) => match new_imports { + Ok(it) => return Ok(Box::new(it)), + Err(mut e2) => return Err(match e1.depth().cmp(&e2.depth()) { + Ordering::Less => e2.at(location), + Ordering::Greater => e1.at(location), + Ordering::Equal => { + e2.options = box_chain!(e2.options, e1.options); + e2.at(location) + }, + }), + }, + Ok(old_m) => old_m, + }; + let it1 = process_donor_module(old_m, rc_path.clone(), |i| i.is_op); + match new_imports { + Err(_) => Box::new(it1), + Ok(it2) => box_chain!(it1, it2) + } + }) + }) + // leaf sets flattened to leaves + .flatten_ok() + // translated to entries + .map_ok(|(name, source, is_op)| { + (name, ModEntry { + exported: false, // this is irrelevant but needed + member: ModMember::Item(ImpReport { source, is_op }), + }) + }) + .chain( + (pre.entries.iter()) + // recurse on submodules + .filter_map(|(k, v)| { + Some((k, v, unwrap_or!(&v.member => ModMember::Sub; return None))) + }) + .map(|(k, v, pre)| { + let path = pushed_ref(&modpath, k.clone()); + Ok((k.clone(), ModEntry { + exported: v.exported, + member: ModMember::Sub(import_tree(path, pre, root, prev_root, i)?), + })) + }), + ) + .collect::, _>>()?; + Ok(Module { extra: (), entries }) +} diff --git a/src/pipeline/project_tree/mod.rs b/src/pipeline/project_tree/mod.rs index b90962e..84be367 100644 --- a/src/pipeline/project_tree/mod.rs +++ b/src/pipeline/project_tree/mod.rs @@ -1,24 +1,5 @@ -// 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 - -mod add_prelude; mod build_tree; -mod collect_ops; -mod normalize_imports; -mod parse_file; -mod prefix; +mod import_tree; +mod rebuild_tree; -pub use build_tree::{build_tree, split_path}; -pub use collect_ops::InjectedOperatorsFn; +pub use rebuild_tree::rebuild_tree; diff --git a/src/pipeline/project_tree/normalize_imports.rs b/src/pipeline/project_tree/normalize_imports.rs deleted file mode 100644 index eecca6d..0000000 --- a/src/pipeline/project_tree/normalize_imports.rs +++ /dev/null @@ -1,94 +0,0 @@ -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, ModuleBlock, -}; -use crate::representations::tree::{ModMember, Module}; -use crate::utils::iter::box_once; -use crate::utils::{unwrap_or, BoxedIter, Substack}; - -fn member_rec( - // level - mod_stack: Substack>, - preparsed: &Module, - // object - member: Member, - // context - path: &[Tok], - ops_cache: &ExportedOpsCache, - i: &Interner, -) -> Member { - match member { - Member::Module(ModuleBlock { name, body }) => { - let subprep = unwrap_or!( - &preparsed.items[&name].member => ModMember::Sub; - unreachable!("This name must point to a namespace") - ); - let new_stack = mod_stack.push(name.clone()); - let new_body = entv_rec(new_stack, subprep, body, path, ops_cache, i); - Member::Module(ModuleBlock { name, body: new_body }) - }, - any => any, - } -} - -/// Normalize imports in the FileEntry list recursively -/// -/// # Panics -/// -/// - if a path contains too many "super" prefixes -/// - if the exported operators in a module cannot be determined -fn entv_rec( - // level - mod_stack: Substack>, - preparsed: &Module, - // object - data: Vec, - // context - mod_path: &[Tok], - ops_cache: &ExportedOpsCache, - i: &Interner, -) -> Vec { - 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.clone(), &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(|n| Import { name: Some(n.clone()), path: path.clone() }) - .collect::>(); - Box::new(imports.into_iter()) as BoxedIter - } else { - box_once(import) - } - }) - .collect(), - ), - FileEntry::Exported(mem) => FileEntry::Exported(member_rec( - mod_stack.clone(), preparsed, mem, mod_path, ops_cache, i, - )), - FileEntry::Internal(mem) => FileEntry::Internal(member_rec( - mod_stack.clone(), preparsed, mem, mod_path, ops_cache, i, - )), - any => any, - }) - .collect() -} - -pub fn normalize_imports( - preparsed: &Module, - data: Vec, - path: &[Tok], - ops_cache: &ExportedOpsCache, - i: &Interner, -) -> Vec { - entv_rec(Substack::Bottom, preparsed, data, path, ops_cache, i) -} diff --git a/src/pipeline/project_tree/parse_file.rs b/src/pipeline/project_tree/parse_file.rs deleted file mode 100644 index ab70ce1..0000000 --- a/src/pipeline/project_tree/parse_file.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::rc::Rc; - -use super::add_prelude::add_prelude; -use super::collect_ops::{collect_ops_for, ExportedOpsCache}; -use super::normalize_imports::normalize_imports; -use super::prefix::prefix; -use crate::error::ProjectResult; -use crate::interner::{Interner, Tok}; -use crate::parse; -use crate::pipeline::source_loader::LoadedSourceTable; -use crate::representations::sourcefile::{normalize_namespaces, FileEntry}; - -/// Parses a file with the correct operator set -/// -/// # Panics -/// -/// - on syntax error -/// - if namespaces are exported inconsistently -/// -/// These are both checked in the preparsing stage -pub fn parse_file( - path: &[Tok], - loaded: &LoadedSourceTable, - ops_cache: &ExportedOpsCache, - i: &Interner, - prelude: &[FileEntry], -) -> ProjectResult> { - let ld = &loaded[path]; - // let ops_cache = collect_ops::mk_cache(loaded, i); - let ops = collect_ops_for(path, loaded, ops_cache, i)?; - let ops_vec = ops.iter().map(|t| (**t).clone()).collect::>(); - let ctx = parse::ParsingContext { - interner: i, - ops: &ops_vec, - file: Rc::new(Interner::extern_all(path)), - }; - let entries = parse::parse2(ld.text.as_str(), ctx) - .expect("This error should have been caught during loading"); - let with_prelude = add_prelude(entries, path, prelude); - let impnormalized = - normalize_imports(&ld.preparsed.0, with_prelude, path, ops_cache, i); - let nsnormalized = normalize_namespaces(Box::new(impnormalized.into_iter())) - .expect("This error should have been caught during preparsing"); - let prefixed = prefix(nsnormalized, path, ops_cache, i); - Ok(prefixed) -} diff --git a/src/pipeline/project_tree/prefix.rs b/src/pipeline/project_tree/prefix.rs deleted file mode 100644 index 8bbf63b..0000000 --- a/src/pipeline/project_tree/prefix.rs +++ /dev/null @@ -1,73 +0,0 @@ -use super::collect_ops::ExportedOpsCache; -use crate::ast::{Constant, Rule}; -use crate::interner::{Interner, Tok}; -use crate::representations::sourcefile::{FileEntry, Member, ModuleBlock}; -use crate::utils::Substack; - -fn member_rec( - // level - mod_stack: Substack>, - // object - data: Member, - // context - path: &[Tok], - ops_cache: &ExportedOpsCache, - i: &Interner, -) -> Member { - let prefix = (path.iter()) - .cloned() - .chain(mod_stack.iter().rev_vec_clone().into_iter()) - .collect::>(); - match data { - Member::Module(ModuleBlock { name, body }) => { - let new_stack = mod_stack.push(name.clone()); - let new_body = entv_rec(new_stack, body, path, ops_cache, i); - Member::Module(ModuleBlock { name, body: new_body }) - }, - Member::Constant(constant) => Member::Constant(Constant { - name: constant.name, - value: constant.value.prefix(&prefix, &|_| false), - }), - Member::Rule(rule) => Member::Rule(Rule { - prio: rule.prio, - pattern: (rule.pattern.into_iter()) - .map(|e| e.prefix(&prefix, &|_| false)) - .collect(), - template: (rule.template.into_iter()) - .map(|e| e.prefix(&prefix, &|_| false)) - .collect(), - }), - } -} - -fn entv_rec( - // level - mod_stack: Substack>, - // object - data: Vec, - // context - path: &[Tok], - ops_cache: &ExportedOpsCache, - i: &Interner, -) -> Vec { - (data.into_iter()) - .map(|fe| { - let (mem, wrapper): (Member, fn(Member) -> FileEntry) = match fe { - FileEntry::Exported(mem) => (mem, FileEntry::Exported), - FileEntry::Internal(mem) => (mem, FileEntry::Internal), - // XXX should [FileEntry::Export] be prefixed? - any => return any, - }; - wrapper(member_rec(mod_stack.clone(), mem, path, ops_cache, i)) - }) - .collect() -} - -pub fn prefix( - data: Vec, - path: &[Tok], - ops_cache: &ExportedOpsCache, - i: &Interner, -) -> Vec { - entv_rec(Substack::Bottom, data, path, ops_cache, i) -} diff --git a/src/pipeline/project_tree/rebuild_tree.rs b/src/pipeline/project_tree/rebuild_tree.rs new file mode 100644 index 0000000..3f30e64 --- /dev/null +++ b/src/pipeline/project_tree/rebuild_tree.rs @@ -0,0 +1,131 @@ +use std::rc::Rc; + +use hashbrown::HashMap; +use itertools::Itertools; + +use super::build_tree::{build_tree, TreeReport}; +use super::import_tree::{import_tree, ImpMod}; +use crate::error::ProjectResult; +use crate::pipeline::source_loader::{ + LoadedSourceTable, PreExtra, PreItem, PreMod, Preparsed, +}; +use crate::representations::project::{ImpReport, ProjectExt, ProjectMod}; +use crate::sourcefile::FileEntry; +use crate::tree::{ModEntry, ModMember, Module}; +use crate::utils::never::{always, unwrap_always}; +use crate::utils::pushed::pushed_ref; +use crate::utils::unwrap_or; +use crate::{parse, Interner, ProjectTree, Tok, VName}; + +pub fn rebuild_file( + path: Vec>, + pre: PreMod, + imports: ImpMod, + source: &LoadedSourceTable, + prelude: &[FileEntry], + i: &Interner, +) -> ProjectResult> { + let file = match &pre.extra { + PreExtra::Dir => panic!("Dir should not hand this node off"), + PreExtra::Submod(_) => panic!("should not have received this"), + PreExtra::File(f) => f, + }; + let mut ops = Vec::new(); + unwrap_always(imports.search_all((), &mut |_, module, ()| { + ops.extend( + (module.entries.iter()) + .filter(|(_, ent)| { + matches!(ent.member, ModMember::Item(ImpReport { is_op: true, .. })) + }) + .map(|(name, _)| name.clone()), + ); + always(()) + })); + unwrap_always(pre.search_all((), &mut |_, module, ()| { + ops.extend( + (module.entries.iter()) + .filter(|(_, ent)| { + matches!(ent.member, ModMember::Item(PreItem { is_op: true, .. })) + }) + .map(|(name, _)| name.clone()), + ); + always(()) + })); + let ctx = parse::ParsingContext::new(&ops, i, Rc::new(path.clone())); + let src = source.get(&file.name).unwrap_or_else(|| { + panic!( + "{} should have been preparsed already. Preparsed files are {}", + Interner::extern_all(&file.name).join("/") + ".orc", + source + .keys() + .map(|f| Interner::extern_all(f).join("/") + ".orc") + .join(", ") + ) + }); + let entries = parse::parse2(&src.text, ctx)?; + let TreeReport { entries: items, rules, imports_from } = + build_tree(&path, entries, pre, imports, prelude)?; + Ok(Module { + entries: items, + extra: ProjectExt { file: Some(path.clone()), path, imports_from, rules }, + }) +} + +pub fn rebuild_dir( + path: Vec>, + pre: PreMod, + mut imports: ImpMod, + source: &LoadedSourceTable, + prelude: &[FileEntry], + i: &Interner, +) -> ProjectResult> { + match pre.extra { + PreExtra::Dir => (), + PreExtra::File(_) => + return rebuild_file(path, pre, imports, source, prelude, i), + PreExtra::Submod(_) => panic!("Dirs contain dirs and files"), + } + let items = (pre.entries.into_iter()) + .map(|(name, entry)| { + match imports.entries.remove(&name).map(|e| e.member) { + Some(ModMember::Sub(impmod)) => (name, entry, impmod), + _ => panic!("Imports must line up with modules"), + } + }) + .map(|(name, ModEntry { member, exported }, impmod)| -> ProjectResult<_> { + let path = pushed_ref(&path, name.clone()); + let pre = unwrap_or!(member => ModMember::Sub; + panic!("Dirs can only contain submodules") + ); + Ok((name, ModEntry { + exported, + member: ModMember::Sub(rebuild_dir( + path, pre, impmod, source, prelude, i, + )?), + })) + }) + .collect::, _>>()?; + Ok(Module { + extra: ProjectExt { + path, + imports_from: HashMap::new(), + rules: Vec::new(), + file: None, + }, + entries: items, + }) +} + +/// Rebuild the entire tree +pub fn rebuild_tree( + source: &LoadedSourceTable, + preparsed: Preparsed, + prev_root: &ProjectTree, + prelude: &[FileEntry], + i: &Interner, +) -> ProjectResult> { + let imports = + import_tree(Vec::new(), &preparsed.0, &preparsed, prev_root, i)?; + rebuild_dir(Vec::new(), preparsed.0, imports, source, prelude, i) + .map(ProjectTree) +} diff --git a/src/pipeline/source_loader/load_source.rs b/src/pipeline/source_loader/load_source.rs index 746f265..f376c79 100644 --- a/src/pipeline/source_loader/load_source.rs +++ b/src/pipeline/source_loader/load_source.rs @@ -1,7 +1,8 @@ -use std::iter; +use hashbrown::HashMap; use super::loaded_source::{LoadedSource, LoadedSourceTable}; use super::preparse::preparse; +use super::{PreExtra, Preparsed}; use crate::error::{ NoTargets, ProjectError, ProjectResult, UnexpectedDirectory, }; @@ -9,67 +10,79 @@ use crate::interner::{Interner, Tok}; use crate::pipeline::file_loader::{IOResult, Loaded}; use crate::pipeline::import_abs_path::import_abs_path; use crate::representations::sourcefile::FileEntry; +use crate::tree::Module; +use crate::utils::pushed::pushed_ref; use crate::utils::{split_max_prefix, unwrap_or}; +use crate::Location; /// 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: &[Tok], - table: &mut LoadedSourceTable, + mut all: Preparsed, + source: &mut LoadedSourceTable, prelude: &[FileEntry], i: &Interner, get_source: &impl Fn(&[Tok]) -> IOResult, is_injected_module: &impl Fn(&[Tok]) -> bool, -) -> ProjectResult<()> { +) -> ProjectResult { // # Termination // // Every recursion of this function either - // - adds one of the files in the source directory to `table` or + // - adds one of the files in the source directory to `visited` or // - recursively traverses a directory tree // therefore eventually the function exits, assuming that the directory tree // contains no cycles. - // Termination: exit if entry already visited - if table.contains_key(abs_path) { - return Ok(()); - } - // try splitting the path to file, swallowing any IO errors let name_split = split_max_prefix(abs_path, &|p| { get_source(p).map(|l| l.is_code()).unwrap_or(false) }); if let Some((filename, _)) = name_split { + // Termination: exit if entry already visited + if source.contains_key(filename) { + return Ok(all); + } // if the filename is valid, load, preparse and record this file let text = unwrap_or!(get_source(filename)? => Loaded::Code; { return Err(UnexpectedDirectory { path: filename.to_vec() }.rc()) }); - let preparsed = preparse( - Interner::extern_all(filename), - text.as_str(), - prelude, - i, - )?; - table.insert(filename.to_vec(), LoadedSource { - text, - preparsed: preparsed.clone(), - }); + source.insert(filename.to_vec(), LoadedSource { text: text.clone() }); + let preparsed = preparse(filename.to_vec(), text.as_str(), prelude, i)?; // recurse on all imported modules - preparsed.0.visit_all_imports(&mut |modpath, _module, import| { - let abs_pathv = - import_abs_path(filename, modpath, &import.nonglob_path(), i)?; - if abs_path.starts_with(&abs_pathv) { - return Ok(()); + // will be taken and returned by the closure. None iff an error is thrown + all = preparsed.0.search_all(all, &mut |modpath, + module, + mut all| + -> ProjectResult<_> { + let details = unwrap_or!(module.extra.details(); return Ok(all)); + for import in &details.imports { + let origin = &Location::Unknown; + let abs_pathv = import_abs_path( + filename, + modpath.clone(), + &import.nonglob_path(), + i, + origin, + )?; + if abs_path.starts_with(&abs_pathv) { + continue; + } + // recurse on imported module + all = load_abs_path_rec( + &abs_pathv, + all, + source, + prelude, + i, + get_source, + is_injected_module, + )?; } - // recurse on imported module - load_abs_path_rec( - &abs_pathv, - table, - prelude, - i, - get_source, - is_injected_module, - ) - }) + Ok(all) + })?; + // Combine the trees + all.0.overlay(preparsed.0).map(Preparsed) } else { // If the path is not within a file, load it as directory let coll = match get_source(abs_path) { @@ -81,25 +94,23 @@ fn load_abs_path_rec( let parent = abs_path.split_last().expect("import path nonzero").1; // exit without error if it was injected, or raise any IO error that was // previously swallowed - return if is_injected_module(parent) { Ok(()) } else { Err(e) }; + return if is_injected_module(parent) { Ok(all) } else { Err(e) }; }, }; // recurse on all files and folders within for item in coll.iter() { - let abs_subpath = (abs_path.iter()) - .cloned() - .chain(iter::once(i.i(item))) - .collect::>(); - load_abs_path_rec( + let abs_subpath = pushed_ref(abs_path, i.i(item)); + all = load_abs_path_rec( &abs_subpath, - table, + all, + source, prelude, i, get_source, is_injected_module, - )? + )?; } - Ok(()) + Ok(all) } } @@ -115,19 +126,22 @@ pub fn load_source<'a>( i: &Interner, get_source: &impl Fn(&[Tok]) -> IOResult, is_injected_module: &impl Fn(&[Tok]) -> bool, -) -> ProjectResult { +) -> ProjectResult<(Preparsed, LoadedSourceTable)> { let mut table = LoadedSourceTable::new(); + let mut all = + Preparsed(Module { extra: PreExtra::Dir, entries: HashMap::new() }); let mut any_target = false; for target in targets { any_target |= true; - load_abs_path_rec( + all = load_abs_path_rec( target, + all, &mut table, prelude, i, get_source, is_injected_module, - )? + )?; } - if any_target { Ok(table) } else { Err(NoTargets.rc()) } + if any_target { Ok((all, table)) } else { Err(NoTargets.rc()) } } diff --git a/src/pipeline/source_loader/loaded_source.rs b/src/pipeline/source_loader/loaded_source.rs index 9b72764..7bfe542 100644 --- a/src/pipeline/source_loader/loaded_source.rs +++ b/src/pipeline/source_loader/loaded_source.rs @@ -1,13 +1,11 @@ use std::collections::HashMap; use std::rc::Rc; -use super::preparse::Preparsed; use crate::representations::VName; #[derive(Debug)] pub struct LoadedSource { pub text: Rc, - pub preparsed: Preparsed, } pub type LoadedSourceTable = HashMap; diff --git a/src/pipeline/source_loader/mod.rs b/src/pipeline/source_loader/mod.rs index 68ec4c1..b7c7c1b 100644 --- a/src/pipeline/source_loader/mod.rs +++ b/src/pipeline/source_loader/mod.rs @@ -18,7 +18,8 @@ mod load_source; mod loaded_source; mod preparse; +mod types; pub use load_source::load_source; pub use loaded_source::{LoadedSource, LoadedSourceTable}; -pub use preparse::Preparsed; +pub use types::{PreExtra, PreFileExt, PreItem, PreMod, PreSubExt, Preparsed}; diff --git a/src/pipeline/source_loader/preparse.rs b/src/pipeline/source_loader/preparse.rs index c9bc3a7..dc91e6d 100644 --- a/src/pipeline/source_loader/preparse.rs +++ b/src/pipeline/source_loader/preparse.rs @@ -1,99 +1,169 @@ -use std::hash::Hash; use std::rc::Rc; use hashbrown::HashMap; +use itertools::Itertools; -use crate::ast::Constant; -use crate::error::{ProjectError, ProjectResult, VisibilityMismatch}; +use super::types::{PreFileExt, PreItem, PreSubExt}; +use super::{PreExtra, Preparsed}; +use crate::ast::{Clause, Constant, Expr}; +use crate::error::{ + ConflictingRoles, ProjectError, ProjectResult, VisibilityMismatch, +}; use crate::interner::Interner; use crate::parse::{self, ParsingContext}; -use crate::representations::sourcefile::{ - imports, normalize_namespaces, FileEntry, Member, -}; +use crate::representations::sourcefile::{FileEntry, MemberKind}; use crate::representations::tree::{ModEntry, ModMember, Module}; +use crate::sourcefile::{FileEntryKind, Import, Member, ModuleBlock}; +use crate::utils::pushed::pushed; +use crate::utils::{get_or_default, get_or_make}; +use crate::{Location, Tok, VName}; -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Preparsed(pub Module<(), ()>); - -/// 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(()) }); -} - -/// Add an exported flat name or export any existing entry -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(()) }); - } +struct FileReport { + entries: HashMap, ModEntry>, + patterns: Vec>>, + imports: Vec, } /// Convert source lines into a module -fn to_module(src: &[FileEntry], prelude: &[FileEntry]) -> Module<(), ()> { - 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::Module(ns)) => { - let member = ModMember::Sub(to_module(&ns.body, prelude)); - let entry = ModEntry { exported: false, member }; - Some((ns.name.clone(), entry)) - }, - FileEntry::Exported(Member::Module(ns)) => { - let member = ModMember::Sub(to_module(&ns.body, prelude)); - let entry = ModEntry { exported: true, member }; - Some((ns.name.clone(), entry)) - }, - _ => None, - }) - .collect::>(); - for file_entry in all_src() { - match file_entry { - FileEntry::Comment(_) - | FileEntry::Import(_) - | FileEntry::Internal(Member::Module(_)) - | FileEntry::Exported(Member::Module(_)) => (), - FileEntry::Export(tokv) => - for tok in tokv { - add_export(&mut items, tok.clone()) +fn to_module( + file: &[Tok], + path: VName, + src: Vec, + prelude: &[FileEntry], +) -> ProjectResult { + let mut imports = Vec::new(); + let mut patterns = Vec::new(); + let mut items = HashMap::, (bool, PreItem)>::new(); + let mut to_export = HashMap::, Vec>::new(); + let mut submods = + HashMap::, (bool, Vec, Vec)>::new(); + let entries = prelude.iter().cloned().chain(src.into_iter()); + for FileEntry { kind, locations } in entries { + match kind { + FileEntryKind::Import(imp) => imports.extend(imp.into_iter()), + FileEntryKind::Export(names) => + for (t, l) in names { + get_or_default(&mut to_export, &t).push(l) + }, + FileEntryKind::Member(Member { exported, kind }) => match kind { + MemberKind::Constant(Constant { name, .. }) => { + let (prev_exported, it) = get_or_default(&mut items, &name); + if it.has_value { + let err = ConflictingRoles { name: pushed(path, name), locations }; + return Err(err.rc()); + } + if let Some(loc) = locations.get(0) { + it.location = it.location.clone().or(loc.clone()) + }; + it.has_value = true; + *prev_exported |= exported; + }, + MemberKind::Module(ModuleBlock { name, body }) => { + if let Some((prev_exported, locv, entv)) = submods.get_mut(&name) { + if *prev_exported != exported { + let mut namespace = path; + namespace.push(name.clone()); + let err = VisibilityMismatch { namespace, file: file.to_vec() }; + return Err(err.rc()); + } + locv.extend(locations.into_iter()); + entv.extend(body.into_iter()) + } else { + submods.insert(name.clone(), (exported, locations, body.clone())); + } + }, + MemberKind::Operators(ops) => + for op in ops { + let (prev_exported, it) = get_or_default(&mut items, &op); + if let Some(loc) = locations.get(0) { + it.location = it.location.clone().or(loc.clone()) + } + *prev_exported |= exported; + it.is_op = true; + }, + MemberKind::Rule(r) => { + patterns.push(r.pattern.clone()); + if exported { + for ex in r.pattern { + ex.search_all(&mut |ex| { + if let Clause::Name(vname) = &ex.value { + if let Ok(name) = vname.iter().exactly_one() { + get_or_default(&mut to_export, name) + .push(ex.location.clone()); + } + } + None::<()> + }); + } + } }, - FileEntry::Internal(Member::Constant(Constant { name, .. })) => - add_intern(&mut items, name.clone()), - FileEntry::Exported(Member::Constant(Constant { name, .. })) => - add_export(&mut items, name.clone()), - FileEntry::Internal(Member::Rule(rule)) => { - let names = rule.collect_single_names(); - for name in names { - add_intern(&mut items, name) - } - }, - FileEntry::Exported(Member::Rule(rule)) => { - let names = rule.collect_single_names(); - for name in names { - add_export(&mut items, name) - } }, + _ => (), } } - Module { imports, items, extra: () } + let mut entries = HashMap::with_capacity(items.len() + submods.len()); + entries.extend(items.into_iter().map(|(name, (exported, it))| { + (name, ModEntry { member: ModMember::Item(it), exported }) + })); + for (subname, (exported, locations, body)) in submods { + let mut name = path.clone(); + entries + .try_insert(subname.clone(), ModEntry { + member: ModMember::Sub({ + name.push(subname); + let FileReport { imports, entries: items, patterns } = + to_module(file, name.clone(), body, prelude)?; + Module { + entries: items, + extra: PreExtra::Submod(PreSubExt { imports, patterns }), + } + }), + exported, + }) + .map_err(|_| ConflictingRoles { locations, name }.rc())?; + } + for (item, locations) in to_export { + get_or_make(&mut entries, &item, || ModEntry { + member: ModMember::Item(PreItem { + is_op: false, + has_value: false, + location: locations[0].clone(), + }), + exported: true, + }) + .exported = true + } + Ok(FileReport { entries, imports, patterns }) } /// Preparse the module. At this stage, only the imports and /// names defined by the module can be parsed pub fn preparse( - file: Vec, + file: VName, source: &str, prelude: &[FileEntry], i: &Interner, ) -> ProjectResult { // Parse with no operators - let ctx = ParsingContext::<&str>::new(&[], i, Rc::new(file.clone())); + let ctx = ParsingContext::new(&[], i, Rc::new(file.clone())); let entries = parse::parse2(source, ctx)?; - let normalized = normalize_namespaces(Box::new(entries.into_iter())) - .map_err(|namespace| { - VisibilityMismatch { namespace, file: Rc::new(file.clone()) }.rc() - })?; - Ok(Preparsed(to_module(&normalized, prelude))) + let FileReport { entries, imports, patterns } = + to_module(&file, file.clone(), entries, prelude)?; + let mut module = Module { + entries, + extra: PreExtra::File(PreFileExt { + details: PreSubExt { patterns, imports }, + name: file.clone(), + }), + }; + for name in file.iter().rev() { + module = Module { + extra: PreExtra::Dir, + entries: HashMap::from([(name.clone(), ModEntry { + exported: true, + member: ModMember::Sub(module), + })]), + }; + } + Ok(Preparsed(module)) } diff --git a/src/pipeline/source_loader/types.rs b/src/pipeline/source_loader/types.rs new file mode 100644 index 0000000..7154e61 --- /dev/null +++ b/src/pipeline/source_loader/types.rs @@ -0,0 +1,92 @@ +use std::fmt::Display; +use std::ops::Add; + +use crate::ast::Expr; +use crate::error::ProjectResult; +use crate::sourcefile::Import; +use crate::tree::Module; +use crate::{Interner, Location, VName}; + +#[derive(Debug, Clone)] +pub struct PreItem { + pub is_op: bool, + pub has_value: bool, + pub location: Location, +} + +impl Display for PreItem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { has_value, is_op, location } = self; + let description = match (is_op, has_value) { + (true, true) => "operator with value", + (true, false) => "operator", + (false, true) => "value", + (false, false) => "keyword", + }; + write!(f, "{description} {location}") + } +} + +impl Default for PreItem { + fn default() -> Self { + PreItem { is_op: false, has_value: false, location: Location::Unknown } + } +} + +#[derive(Debug, Clone)] +pub struct PreSubExt { + pub imports: Vec, + pub patterns: Vec>>, +} + +#[derive(Debug, Clone)] +pub struct PreFileExt { + pub name: VName, + pub details: PreSubExt, +} + +#[derive(Debug, Clone)] +pub enum PreExtra { + File(PreFileExt), + Submod(PreSubExt), + Dir, +} + +impl PreExtra { + /// If the module is not a directory, returns the source-only details + pub fn details(&self) -> Option<&PreSubExt> { + match self { + Self::Submod(sub) => Some(sub), + Self::File(PreFileExt { details, .. }) => Some(details), + Self::Dir => None, + } + } +} + +impl Add for PreExtra { + type Output = ProjectResult; + + fn add(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (alt, Self::Dir) | (Self::Dir, alt) => Ok(alt), + (Self::File(_) | Self::Submod(_), Self::File(_) | Self::Submod(_)) => + panic!("Each file should be parsed once."), + } + } +} + +impl Display for PreExtra { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Dir => write!(f, "Directory"), + Self::File(PreFileExt { name, .. }) => + write!(f, "File({}.orc)", Interner::extern_all(name).join("/")), + Self::Submod(_) => write!(f, "Submodule"), + } + } +} + +pub type PreMod = Module; + +#[derive(Debug, Clone)] +pub struct Preparsed(pub PreMod); diff --git a/src/representations/ast.rs b/src/representations/ast.rs index 92e62de..c117335 100644 --- a/src/representations/ast.rs +++ b/src/representations/ast.rs @@ -17,6 +17,7 @@ use super::location::Location; use super::namelike::{NameLike, VName}; use super::primitive::Primitive; use crate::interner::Tok; +use crate::parse::print_nat16; use crate::utils::map_rc; /// A [Clause] with associated metadata @@ -365,7 +366,7 @@ impl Rule { /// Return a list of all names that don't contain a namespace separator `::`. /// These are exported when the rule is exported - pub fn collect_single_names(&self) -> Vec> { + pub fn collect_single_names(&self) -> VName { let mut names = Vec::new(); for e in self.pattern.iter() { e.search_all(&mut |e| { @@ -385,9 +386,9 @@ impl Display for Rule { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "{} ={}=> {}", + "rule {} ={}=> {}", self.pattern.iter().join(" "), - self.prio, + print_nat16(self.prio), self.template.iter().join(" ") ) } @@ -404,6 +405,6 @@ pub struct Constant { impl Display for Constant { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{} := {}", *self.name, self.value) + write!(f, "const {} := {}", *self.name, self.value) } } diff --git a/src/representations/ast_to_postmacro.rs b/src/representations/ast_to_postmacro.rs index a7ec1a4..858a8f9 100644 --- a/src/representations/ast_to_postmacro.rs +++ b/src/representations/ast_to_postmacro.rs @@ -34,8 +34,8 @@ impl ProjectError for Error { fn description(&self) -> &str { match self.kind { ErrorKind::BadGroup(_) => - "Only `(...)` may be converted to typed lambdas. `[...]` and `{{...}}` \ - left in the code are signs of incomplete macro execution", + "Only `(...)` may be converted to postmacro. `[...]` and `{...}` left \ + in the code are signs of incomplete macro execution", ErrorKind::EmptyS => "`()` as a clause is meaningless in lambda calculus", ErrorKind::InvalidArg => "Argument names can only be Name nodes", ErrorKind::Placeholder => diff --git a/src/representations/const_tree.rs b/src/representations/const_tree.rs index 8ffc427..5b5d354 100644 --- a/src/representations/const_tree.rs +++ b/src/representations/const_tree.rs @@ -2,14 +2,15 @@ use std::ops::Add; use hashbrown::HashMap; +use super::project::{ItemKind, ProjectItem}; use crate::ast::{Clause, Expr}; use crate::foreign::{Atom, Atomic, ExternFn}; use crate::interner::Tok; use crate::representations::location::Location; -use crate::representations::project::{ProjectExt, ProjectModule, ProjectTree}; +use crate::representations::project::{ProjectExt, ProjectMod, ProjectTree}; use crate::representations::tree::{ModEntry, ModMember, Module}; use crate::representations::{Primitive, VName}; -use crate::utils::{pushed, Substack}; +use crate::utils::Substack; /// A lightweight module tree that can be built declaratively by hand to /// describe libraries of external functions in Rust. It implements [Add] for @@ -90,29 +91,28 @@ fn from_const_tree_rec( path: Substack>, consts: HashMap, ConstTree>, file: &[Tok], -) -> ProjectModule { +) -> ProjectMod { let mut items = HashMap::new(); - let path_v = path.iter().rev_vec_clone(); for (name, item) in consts { items.insert(name.clone(), ModEntry { exported: true, member: match item { - ConstTree::Const(c) => ModMember::Item(c), + ConstTree::Const(c) => ModMember::Item(ProjectItem { + kind: ItemKind::Const(c), + is_op: false, + }), ConstTree::Tree(t) => ModMember::Sub(from_const_tree_rec(path.push(name), t, file)), }, }); } - let exports = (items.keys()) - .map(|name| (name.clone(), pushed(&path_v, name.clone()))) - .collect(); Module { - items, - imports: vec![], + entries: items, extra: ProjectExt { - exports, file: Some(file.to_vec()), - ..Default::default() + imports_from: HashMap::new(), + rules: Vec::new(), + path: path.iter().rev_vec_clone(), }, } } diff --git a/src/representations/interpreted.rs b/src/representations/interpreted.rs index 7fcdfc1..a9b3a79 100644 --- a/src/representations/interpreted.rs +++ b/src/representations/interpreted.rs @@ -15,8 +15,6 @@ use super::primitive::Primitive; use super::Literal; use crate::Sym; -// TODO: implement Debug, Eq and Hash with cycle detection - /// An expression with metadata pub struct Expr { /// The actual value diff --git a/src/representations/location.rs b/src/representations/location.rs index 878595d..285d845 100644 --- a/src/representations/location.rs +++ b/src/representations/location.rs @@ -1,21 +1,23 @@ -use std::fmt::Display; +use std::fmt::{Debug, Display}; use std::ops::Range; use std::rc::Rc; use itertools::Itertools; +use crate::VName; + /// A location in a file, identifies a sequence of suspect characters for any /// error. Meaningful within the context of a project. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Clone, PartialEq, Eq, Hash)] pub enum Location { /// Location information lost or code generated on the fly Unknown, /// Only the file is known - File(Rc>), + File(Rc), /// Character slice of the code Range { /// Argument to the file loading callback that produced this code - file: Rc>, + file: Rc, /// Index of the unicode code points associated with the code range: Range, /// The full source code as received by the parser @@ -34,7 +36,7 @@ impl Location { } /// File, if known - pub fn file(&self) -> Option>> { + pub fn file(&self) -> Option> { if let Self::File(file) | Self::Range { file, .. } = self { Some(file.clone()) } else { @@ -102,6 +104,12 @@ impl Display for Location { } } +impl Debug for Location { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self}") + } +} + fn pos2lc(s: &str, i: usize) -> (usize, usize) { s.chars().take(i).fold((1, 1), |(line, col), char| { if char == '\n' { (line + 1, 1) } else { (line, col + 1) } diff --git a/src/representations/mod.rs b/src/representations/mod.rs index 93d2fad..3b57986 100644 --- a/src/representations/mod.rs +++ b/src/representations/mod.rs @@ -12,13 +12,13 @@ pub mod postmacro_to_interpreted; pub mod primitive; pub mod project; pub mod sourcefile; -pub mod tree; mod string; +pub mod tree; pub use const_tree::{from_const_tree, ConstTree}; -pub use string::OrcString; pub use literal::Literal; pub use location::Location; pub use namelike::{NameLike, Sym, VName}; pub use path_set::PathSet; pub use primitive::Primitive; +pub use string::OrcString; diff --git a/src/representations/namelike.rs b/src/representations/namelike.rs index 700a138..48d6b84 100644 --- a/src/representations/namelike.rs +++ b/src/representations/namelike.rs @@ -1,3 +1,4 @@ +use std::fmt::Debug; use std::hash::Hash; use crate::interner::{Interner, Tok}; @@ -14,11 +15,11 @@ pub type VName = Vec>; /// These names are always absolute. /// /// See also [VName] -pub type Sym = Tok>>; +pub type Sym = Tok; /// An abstraction over tokenized vs non-tokenized names so that they can be /// handled together in datastructures -pub trait NameLike: 'static + Clone + Eq + Hash { +pub trait NameLike: 'static + Clone + Eq + Hash + Debug { /// Fully resolve the name for printing fn to_strv(&self) -> Vec; } diff --git a/src/representations/project.rs b/src/representations/project.rs index 034eaf3..3b9ee2f 100644 --- a/src/representations/project.rs +++ b/src/representations/project.rs @@ -1,3 +1,4 @@ +use std::fmt::Display; use std::ops::Add; use hashbrown::HashMap; @@ -7,51 +8,129 @@ use crate::interner::{Interner, Tok}; use crate::representations::tree::{ModMember, Module}; use crate::representations::NameLike; use crate::tree::ModEntry; +use crate::utils::never::{always, Always}; use crate::utils::Substack; use crate::{Sym, VName}; +#[derive(Debug, Clone)] +pub enum ItemKind { + /// An imported symbol or module. The value is the absolute path of + /// the symbol that should be used instead of this one. + /// + /// Notice that this is different from [ProjectExt::imports_from] the values + /// of which do not include the name they're keyed with. + Alias(VName), + None, + Const(Expr), +} + +impl Default for ItemKind { + fn default() -> Self { + Self::None + } +} + +#[derive(Debug, Clone, Default)] +pub struct ProjectItem { + pub kind: ItemKind, + pub is_op: bool, +} + +impl Display for ProjectItem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.kind { + ItemKind::None => match self.is_op { + true => write!(f, "operator"), + false => write!(f, "keyword"), + }, + ItemKind::Const(c) => match self.is_op { + true => write!(f, "operator with value {c}"), + false => write!(f, "constant {c}"), + }, + ItemKind::Alias(alias) => { + let origin = Interner::extern_all(alias).join("::"); + match self.is_op { + true => write!(f, "operator alias to {origin}"), + false => write!(f, "alias to {origin}"), + } + }, + } + } +} + +/// Information about an imported symbol +#[derive(Debug, Clone)] +pub struct ImpReport { + /// Absolute path of the module the symbol is imported from + pub source: N, + /// Whether this symbol should be treated as an operator for the purpose of + /// parsing + pub is_op: bool, +} + /// Additional data about a loaded module beyond the list of constants and /// submodules -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub struct ProjectExt { - /// Pairs each foreign token to the module it was imported from - pub imports_from: HashMap, N>, - /// Pairs each exported token to its original full name - pub exports: HashMap, N>, + /// Full path leading to this module + pub path: VName, + /// Pairs each imported token to the absolute path of the module it is + /// imported from. The path does not include the name of referencedthe + /// symbol. + pub imports_from: HashMap, ImpReport>, /// 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; + type Output = Always; fn add(mut self, rhs: Self) -> Self::Output { - let ProjectExt { imports_from, exports, rules, file } = rhs; + let ProjectExt { path, imports_from, rules, file } = rhs; + if path != self.path { + panic!( + "Differently named trees overlain: {} vs {}", + Interner::extern_all(&path).join("::"), + Interner::extern_all(&self.path).join("::") + ) + } 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 } - self + always(self) } } +impl Display for ProjectExt { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Project-module extras") + .field("path", &Interner::extern_all(&self.path).join("::")) + .field("imports_from", &self.imports_from) + .field("rules", &self.rules) + .field("file", &(Interner::extern_all(&self.path).join("/") + ".orc")) + .finish() + } +} + +/// A child to a [ProjectMod] +pub type ProjectEntry = ModEntry, ProjectExt>; /// A node in the tree describing the project -pub type ProjectModule = Module, ProjectExt>; +pub type ProjectMod = Module, ProjectExt>; /// Module corresponding to the root of a project #[derive(Debug, Clone)] -pub struct ProjectTree(pub ProjectModule); +pub struct ProjectTree(pub ProjectMod); fn collect_rules_rec( bag: &mut Vec>, - module: &ProjectModule, + module: &ProjectMod, ) { bag.extend(module.extra.rules.iter().cloned()); - for item in module.items.values() { + for item in module.entries.values() { if let ModMember::Sub(module) = &item.member { collect_rules_rec(bag, module); } @@ -69,16 +148,17 @@ pub fn collect_rules(project: &ProjectTree) -> Vec> { fn collect_consts_rec( path: Substack>, bag: &mut HashMap>, - module: &ProjectModule, + module: &ProjectMod, i: &Interner, ) { - for (key, entry) in module.items.iter() { + for (key, entry) in module.entries.iter() { match &entry.member { - ModMember::Item(expr) => { - let mut name = path.iter().rev_vec_clone(); - name.push(key.clone()); - bag.insert(i.i(&name), expr.clone()); - }, + ModMember::Item(it) => + if let ItemKind::Const(expr) = &it.kind { + let mut name = path.iter().rev_vec_clone(); + name.push(key.clone()); + bag.insert(i.i(&name), expr.clone()); + }, ModMember::Sub(module) => collect_consts_rec(path.push(key.clone()), bag, module, i), } @@ -96,30 +176,34 @@ pub fn collect_consts( } fn vname_to_sym_tree_rec( - tree: ProjectModule, + tree: ProjectMod, i: &Interner, -) -> ProjectModule { +) -> ProjectMod { let process_expr = |ex: Expr| ex.transform_names(&|n| i.i(&n)); - ProjectModule { - imports: tree.imports, - items: (tree.items.into_iter()) + ProjectMod { + entries: (tree.entries.into_iter()) .map(|(k, ModEntry { exported, member })| { (k, ModEntry { exported, member: match member { ModMember::Sub(module) => ModMember::Sub(vname_to_sym_tree_rec(module, i)), - ModMember::Item(ex) => ModMember::Item(process_expr(ex)), + ModMember::Item(ex) => ModMember::Item(ProjectItem { + is_op: ex.is_op, + kind: match ex.kind { + ItemKind::None => ItemKind::None, + ItemKind::Alias(n) => ItemKind::Alias(n), + ItemKind::Const(ex) => ItemKind::Const(process_expr(ex)), + }, + }), }, }) }) .collect(), extra: ProjectExt { + path: tree.extra.path, imports_from: (tree.extra.imports_from.into_iter()) - .map(|(k, v)| (k, i.i(&v))) - .collect(), - exports: (tree.extra.exports.into_iter()) - .map(|(k, v)| (k, i.i(&v))) + .map(|(k, v)| (k, ImpReport { is_op: v.is_op, source: i.i(&v.source) })) .collect(), rules: (tree.extra.rules.into_iter()) .map(|Rule { pattern, prio, template }| Rule { diff --git a/src/representations/sourcefile.rs b/src/representations/sourcefile.rs index 8516b08..74b7868 100644 --- a/src/representations/sourcefile.rs +++ b/src/representations/sourcefile.rs @@ -1,12 +1,16 @@ //! Building blocks of a source file +use std::fmt::Display; use std::iter; use itertools::{Either, Itertools}; use super::namelike::VName; use crate::ast::{Constant, Rule}; +use crate::error::{ProjectError, ProjectResult, TooManySupers}; use crate::interner::{Interner, Tok}; +use crate::utils::pushed::pushed; use crate::utils::{unwrap_or, BoxedIter}; +use crate::Location; /// An import pointing at another module, either specifying the symbol to be /// imported or importing all available symbols with a globstar (*) @@ -21,6 +25,8 @@ pub struct Import { pub path: VName, /// If name is None, this is a wildcard import pub name: Option>, + /// Location of the final name segment, which uniquely identifies this name + pub location: Location, } impl Import { /// Get the preload target space for this import - the prefix below @@ -28,7 +34,7 @@ impl Import { /// /// 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) -> Vec> { + pub fn nonglob_path(&self) -> VName { let mut path_vec = self.path.clone(); if let Some(n) = &self.name { path_vec.push(n.clone()) @@ -37,6 +43,14 @@ impl Import { } } +impl Display for Import { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let paths = self.path.iter().map(|t| &**t).join("::"); + let names = self.name.as_ref().map(|t| t.as_str()).unwrap_or("*"); + write!(f, "{paths}::{names}") + } +} + /// A namespace block #[derive(Debug, Clone)] pub struct ModuleBlock { @@ -46,9 +60,16 @@ pub struct ModuleBlock { pub body: Vec, } -/// Things that may be prefixed with an export +impl Display for ModuleBlock { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let bodys = self.body.iter().map(|e| e.to_string()).join("\n"); + write!(f, "module {} {{\n{}\n}}", self.name, bodys) + } +} + +/// see [Member] #[derive(Debug, Clone)] -pub enum Member { +pub enum MemberKind { /// A substitution rule. Rules apply even when they're not in scope, if the /// absolute names are present eg. because they're produced by other rules Rule(Rule), @@ -56,22 +77,81 @@ pub enum Member { Constant(Constant), /// A prefixed set of other entries Module(ModuleBlock), + /// Operator declarations + Operators(Vec>), } -/// Anything we might encounter in a file +impl Display for MemberKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Operators(opv) => + write!(f, "operators[{}]", opv.iter().map(|t| &**t).join(" ")), + Self::Constant(c) => c.fmt(f), + Self::Module(m) => m.fmt(f), + Self::Rule(r) => r.fmt(f), + } + } +} + +/// Things that may be prefixed with an export +/// see [MemberKind] #[derive(Debug, Clone)] -pub enum FileEntry { +pub struct Member { + /// Various members + pub kind: MemberKind, + /// Whether this member is exported or not + pub exported: bool, +} + +impl Display for Member { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self { exported: true, kind } => write!(f, "export {kind}"), + Self { exported: false, kind } => write!(f, "{kind}"), + } + } +} + +/// See [FileEntry] +#[derive(Debug, Clone)] +pub enum FileEntryKind { /// Imports one or all names in a module Import(Vec), /// Comments are kept here in case dev tooling wants to parse documentation Comment(String), - /// An element visible to the outside - Exported(Member), - /// An element only visible from local code - Internal(Member), + /// An element with visibility information + Member(Member), /// A list of tokens exported explicitly. This can also create new exported /// tokens that the local module doesn't actually define a role for - Export(Vec>), + Export(Vec<(Tok, Location)>), +} + +impl Display for FileEntryKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Comment(s) => write!(f, "--[{s}]--"), + Self::Export(s) => + write!(f, "export ::({})", s.iter().map(|t| &**t.0).join(", ")), + Self::Member(member) => write!(f, "{member}"), + Self::Import(i) => + write!(f, "import ({})", i.iter().map(|i| i.to_string()).join(", ")), + } + } +} + +/// Anything the parser might encounter in a file. See [FileEntryKind] +#[derive(Debug, Clone)] +pub struct FileEntry { + /// What we encountered + pub kind: FileEntryKind, + /// Where we encountered it + pub locations: Vec, +} + +impl Display for FileEntry { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.kind.fmt(f) + } } /// Summarize all imports from a file in a single list of qualified names @@ -79,8 +159,8 @@ pub fn imports<'a>( src: impl Iterator + 'a, ) -> impl Iterator + 'a { src - .filter_map(|ent| match ent { - FileEntry::Import(impv) => Some(impv.iter()), + .filter_map(|ent| match &ent.kind { + FileEntryKind::Import(impv) => Some(impv.iter()), _ => None, }) .flatten() @@ -90,56 +170,56 @@ pub fn imports<'a>( /// Error if they're inconsistently exported pub fn normalize_namespaces( src: BoxedIter, -) -> Result, Vec>> { +) -> Result, VName> { let (mut namespaces, mut rest) = src - .partition_map::, Vec<_>, _, _, _>(|ent| match ent { - FileEntry::Exported(Member::Module(ns)) => Either::Left((true, ns)), - FileEntry::Internal(Member::Module(ns)) => Either::Left((false, ns)), - other => Either::Right(other), + .partition_map::, Vec<_>, _, _, _>(|ent| { + match ent { + FileEntry { + kind: FileEntryKind::Member(Member { + kind: MemberKind::Module(ns), + exported, + }), + locations + } => Either::Left((exported, ns, locations)), + ent => Either::Right(ent) + } }); // Combine namespace blocks with the same name - namespaces.sort_unstable_by_key(|(_, ns)| ns.name.clone()); + namespaces.sort_unstable_by_key(|(_, ns, _)| ns.name.clone()); let mut lumped = namespaces .into_iter() - .group_by(|(_, ns)| ns.name.clone()) + .group_by(|(_, ns, _)| ns.name.clone()) .into_iter() .map(|(name, grp)| { - let mut any_exported = false; - let mut any_internal = false; - 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(|ns| ns.body.into_iter()); - // Apply the function to the contents of these blocks too - let body = normalize_namespaces(Box::new(grp_src)).map_err(|mut e| { - e.push(name.clone()); - e - })?; - let member = Member::Module(ModuleBlock { name: name.clone(), 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"), + let mut exported = false; + let mut internal = false; + let mut grouped_source = Vec::new(); + let mut locations = Vec::new(); + for (inst_exported, ns, locs) in grp { + if inst_exported { + exported = true + } else { + internal = true + }; + grouped_source.extend(ns.body.into_iter()); + locations.extend(locs.into_iter()); } + if exported == internal { + debug_assert!(exported && internal, "Both false is impossible"); + return Err(vec![name]); + } + // Apply the function to the contents of these blocks too + let body = normalize_namespaces(Box::new(grouped_source.into_iter())) + .map_err(|e| pushed(e, name.clone()))?; + let kind = MemberKind::Module(ModuleBlock { name, body }); + let kind = FileEntryKind::Member(Member { kind, exported }); + Ok(FileEntry { kind, locations }) }) .collect::, _>>()?; rest.append(&mut lumped); Ok(rest) } -/// Produced by [absolute_path] if there are more `super` segments in the -/// import than the length of the current absolute path -#[derive(Debug, Clone)] -pub struct TooManySupers; - /// Turn a relative (import) path into an absolute path. /// If the import path is empty, the return value is also empty. /// @@ -151,22 +231,33 @@ pub fn absolute_path( abs_location: &[Tok], rel_path: &[Tok], i: &Interner, -) -> Result>, TooManySupers> { + location: &Location, +) -> ProjectResult { + absolute_path_rec(abs_location, rel_path, i).ok_or_else(|| { + TooManySupers { path: rel_path.to_vec(), location: location.clone() }.rc() + }) +} + +fn absolute_path_rec( + abs_location: &[Tok], + rel_path: &[Tok], + i: &Interner, +) -> Option { let (head, tail) = unwrap_or!(rel_path.split_first(); - return Ok(vec![]) + return Some(vec![]) ); if *head == i.i("super") { - let (_, new_abs) = abs_location.split_last().ok_or(TooManySupers)?; + let (_, new_abs) = abs_location.split_last()?; if tail.is_empty() { - Ok(new_abs.to_vec()) + Some(new_abs.to_vec()) } else { let new_rel = iter::once(i.i("self")).chain(tail.iter().cloned()).collect::>(); - absolute_path(new_abs, &new_rel, i) + absolute_path_rec(new_abs, &new_rel, i) } } else if *head == i.i("self") { - Ok(abs_location.iter().chain(tail.iter()).cloned().collect()) + Some(abs_location.iter().chain(tail.iter()).cloned().collect()) } else { - Ok(rel_path.to_vec()) + Some(rel_path.to_vec()) } } diff --git a/src/representations/string.rs b/src/representations/string.rs index 78e8d05..5d89c4d 100644 --- a/src/representations/string.rs +++ b/src/representations/string.rs @@ -1,16 +1,30 @@ +use std::fmt::Debug; use std::hash::Hash; use std::ops::Deref; use std::rc::Rc; use crate::Tok; -#[derive(Clone, Debug, Eq)] +/// An Orchid string which may or may not be interned +#[derive(Clone, Eq)] pub enum OrcString { + /// An interned string. Equality-conpared by reference. Interned(Tok), + /// An uninterned bare string. Equality-compared by character Runtime(Rc), } +impl Debug for OrcString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Runtime(s) => write!(f, "r\"{s}\""), + Self::Interned(t) => write!(f, "i\"{t}\""), + } + } +} + impl OrcString { + /// Clone out the plain Rust [String] pub fn get_string(&self) -> String { self.as_str().to_owned() } diff --git a/src/representations/tree.rs b/src/representations/tree.rs index d60158c..708a8f9 100644 --- a/src/representations/tree.rs +++ b/src/representations/tree.rs @@ -1,14 +1,17 @@ //! Generic module tree structure //! //! Used by various stages of the pipeline with different parameters +use std::fmt::{Debug, Display}; use std::ops::Add; +use std::rc::Rc; -use duplicate::duplicate_item; use hashbrown::HashMap; -use super::sourcefile::Import; +use super::Location; +use crate::error::ProjectError; use crate::interner::Tok; -use crate::utils::Substack; +use crate::utils::{BoxedIter, Substack}; +use crate::{Interner, VName}; /// The member in a [ModEntry] which is associated with a name in a [Module] #[derive(Debug, Clone, PartialEq, Eq)] @@ -27,121 +30,247 @@ pub struct ModEntry { /// Whether the member is visible to modules other than the parent pub exported: bool, } +impl ModEntry { + /// Returns the item in this entry if it contains one. + pub fn item(&self) -> Option<&TItem> { + match &self.member { + ModMember::Item(it) => Some(it), + ModMember::Sub(_) => None, + } + } +} /// A module, containing imports, #[derive(Debug, Clone, PartialEq, Eq)] pub struct Module { - /// Import statements present this module - pub imports: Vec, /// Submodules and items by name - pub items: HashMap, ModEntry>, + pub entries: HashMap, ModEntry>, /// Additional information associated with the module pub extra: TExt, } -/// Possible causes why the path could not be walked -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum WalkErrorKind { - /// `require_exported` was set to `true` and a module wasn't exported - Private, - /// A module was not found - Missing, -} - -/// Error produced by [Module::walk] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct WalkError { - /// The 0-based index of the offending segment - pub pos: usize, - /// The cause of the error - pub kind: WalkErrorKind, -} - /// The path taken to reach a given module pub type ModPath<'a> = Substack<'a, Tok>; impl Module { + /// If the argument is false, returns all child names. + /// If the argument is true, returns all public child names. + pub fn keys(&self, public: bool) -> BoxedIter> { + match public { + false => Box::new(self.entries.keys().cloned()), + true => Box::new( + (self.entries.iter()) + .filter(|(_, v)| v.exported) + .map(|(k, _)| k.clone()), + ), + } + } + /// Return the module at the end of the given path - #[allow(clippy::needless_arbitrary_self_type)] // duplicate - #[duplicate_item( - method reference(type) dereference(expr) map_method; - [walk] [type] [expr] [remove]; - [walk_ref] [&type] [*expr] [get]; - [walk_mut] [&mut type] [*expr] [get_mut]; - )] - pub fn method( - self: reference([Self]), - path: &[Tok], - require_exported: bool, - ) -> Result { - let mut cur = self; + pub fn walk_ref<'a: 'b, 'b>( + &'a self, + prefix: &'b [Tok], + path: &'b [Tok], + public: bool, + ) -> Result<&'a Self, WalkError<'b>> { + let mut module = self; for (pos, step) in path.iter().enumerate() { - if let Some(ModEntry { member: ModMember::Sub(next), exported }) = - cur.items.map_method(step) - { - if require_exported && !dereference([exported]) { - return Err(WalkError { pos, kind: WalkErrorKind::Private }); - } - cur = next + let kind = match module.entries.get(step) { + None => ErrKind::Missing, + Some(ModEntry { exported: false, .. }) if public => ErrKind::Private, + Some(ModEntry { member: ModMember::Item(_), .. }) => ErrKind::NotModule, + Some(ModEntry { member: ModMember::Sub(next), .. }) => { + module = next; + continue; + }, + }; + let options = module.keys(public); + return Err(WalkError { kind, prefix, path, pos, options }); + } + Ok(module) + } + + /// Return the member at the end of the given path + /// + /// # Panics + /// + /// if path is empty, since the reference cannot be forwarded that way + #[allow(clippy::needless_arbitrary_self_type)] // duplicate + pub fn walk1_ref<'a: 'b, 'b>( + &'a self, + prefix: &'b [Tok], + path: &'b [Tok], + public: bool, + ) -> Result<(&'a ModEntry, &'a Self), WalkError<'b>> { + let (last, parent) = path.split_last().expect("Path cannot be empty"); + let pos = path.len() - 1; + let module = self.walk_ref(prefix, parent, public)?; + if let Some(entry) = &module.entries.get(last) { + if !entry.exported && public { + let options = module.keys(public); + Err(WalkError { kind: ErrKind::Private, options, prefix, path, pos }) } else { - return Err(WalkError { pos, kind: WalkErrorKind::Missing }); + Ok((entry, module)) } + } else { + let options = module.keys(public); + Err(WalkError { kind: ErrKind::Missing, options, prefix, path, pos }) } - Ok(cur) } - fn visit_all_imports_rec( - &self, + fn search_all_rec<'a, T, E>( + &'a self, path: ModPath, - callback: &mut impl FnMut(ModPath, &Self, &Import) -> Result<(), E>, - ) -> Result<(), E> { - for import in self.imports.iter() { - callback(path.clone(), self, import)? - } - for (name, entry) in self.items.iter() { + mut state: T, + callback: &mut impl FnMut(ModPath, &'a Self, T) -> Result, + ) -> Result { + state = callback(path.clone(), self, state)?; + for (name, entry) in &self.entries { if let ModMember::Sub(module) = &entry.member { - module.visit_all_imports_rec(path.push(name.clone()), callback)? + state = + module.search_all_rec(path.push(name.clone()), state, callback)?; } } - Ok(()) + Ok(state) } - /// Call the provided function on every import in the tree. Can be - /// short-circuited by returning Err - 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) + /// Visit every element in the tree with the provided function + /// + /// * init - can be used for reduce, otherwise pass `()` + /// * callback - a callback applied on every module. Can return [Err] to + /// short-circuit the walk + /// * [ModPath] - a substack indicating the path to the current module from + /// wherever the walk begun + /// * [Module] - the current module + /// * T - data for reduce. If not used, destructure `()` + pub fn search_all<'a, T, E>( + &'a self, + init: T, + callback: &mut impl FnMut(ModPath, &'a Self, T) -> Result, + ) -> Result { + self.search_all_rec(Substack::Bottom, init, callback) } /// Combine two module trees; wherever they conflict, the overlay is /// preferred. - pub fn overlay(mut self, overlay: Self) -> Self + pub fn overlay(mut self, overlay: Self) -> Result where - TExt: Add, + TExt: Add>, { - let Module { extra, imports, items } = overlay; + let Module { extra, entries: items } = overlay; let mut new_items = HashMap::new(); for (key, right) in items { // if both contain a submodule - match (self.items.remove(&key), right) { + match (self.entries.remove(&key), right) { ( Some(ModEntry { member: ModMember::Sub(lsub), .. }), ModEntry { member: ModMember::Sub(rsub), exported }, ) => new_items.insert(key, ModEntry { exported, - member: ModMember::Sub(lsub.overlay(rsub)), + member: ModMember::Sub(lsub.overlay(rsub)?), }), (_, right) => new_items.insert(key, right), }; } - new_items.extend(self.items.into_iter()); - self.imports.extend(imports.into_iter()); - Module { - items: new_items, - imports: self.imports, - extra: self.extra + extra, - } + new_items.extend(self.entries.into_iter()); + Ok(Module { entries: new_items, extra: (self.extra + extra)? }) + } +} + +impl Display + for Module +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Module {{\nchildren:")?; + for (name, entry) in &self.entries { + match entry.exported { + true => write!(f, "\npublic {name} = "), + false => write!(f, "\n{name} = "), + }?; + match &entry.member { + ModMember::Sub(module) => write!(f, "{module}"), + ModMember::Item(item) => write!(f, "{item}"), + }?; + } + write!(f, "\nextra: {}\n}}", &self.extra) + } +} + +/// Possible causes why the path could not be walked +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum ErrKind { + /// `require_exported` was set to `true` and a module wasn't exported + Private, + /// A module was not found + Missing, + /// The path leads into a leaf node + NotModule, +} + +/// All details about a failed tree-walk +pub struct WalkError<'a> { + /// Failure mode + pub kind: ErrKind, + /// Path to the module where the walk started + pub prefix: &'a [Tok], + /// Planned walk path + pub path: &'a [Tok], + /// Index into walked path where the error occurred + pub pos: usize, + /// Alternatives to the failed steps + pub options: BoxedIter<'a, Tok>, +} +impl<'a> WalkError<'a> { + /// Total length of the path represented by this error + pub fn depth(&self) -> usize { + self.prefix.len() + self.pos + 1 + } + + /// Attach a location to the error and convert into trait object for reporting + pub fn at(self, location: &Location) -> Rc { + // panic!("hello"); + WalkErrorWithLocation { + kind: self.kind, + location: location.clone(), + path: (self.prefix.iter()) + .chain(self.path.iter().take(self.pos + 1)) + .cloned() + .collect(), + options: self.options.collect(), + } + .rc() + } +} + +/// Error produced by [WalkError::at] +struct WalkErrorWithLocation { + path: VName, + kind: ErrKind, + options: VName, + location: Location, +} +impl ProjectError for WalkErrorWithLocation { + fn description(&self) -> &str { + match self.kind { + ErrKind::Missing => "Nonexistent path", + ErrKind::NotModule => "The path leads into a leaf", + ErrKind::Private => "The path leads into a private module", + } + } + + fn message(&self) -> String { + let paths = Interner::extern_all(&self.path).join("::"); + let options = Interner::extern_all(&self.options).join(", "); + match &self.kind { + ErrKind::Missing => + format!("{paths} does not exist, options are {options}"), + ErrKind::NotModule => + format!("{paths} is not a module, options are {options}"), + ErrKind::Private => format!("{paths} is private, options are {options}"), + } + } + + fn one_position(&self) -> Location { + self.location.clone() } } diff --git a/src/rule/matcher_vectree/shared.rs b/src/rule/matcher_vectree/shared.rs index cc3c882..1191f3a 100644 --- a/src/rule/matcher_vectree/shared.rs +++ b/src/rule/matcher_vectree/shared.rs @@ -10,7 +10,7 @@ use crate::representations::Primitive; use crate::rule::matcher::{Matcher, RuleExpr}; use crate::rule::state::State; use crate::utils::Side; -use crate::Sym; +use crate::{Sym, VName}; pub enum ScalMatcher { P(Primitive), @@ -47,7 +47,7 @@ pub enum VecMatcher { /// the length of matches on either side. /// /// Vectorial keys that appear on either side, in priority order - key_order: Vec>, + key_order: VName, }, } diff --git a/src/systems/io/facade.rs b/src/systems/io/facade.rs index 6695658..4d0ad4c 100644 --- a/src/systems/io/facade.rs +++ b/src/systems/io/facade.rs @@ -16,9 +16,9 @@ use crate::foreign::cps_box::CPSBox; use crate::foreign::{Atomic, ExternError}; use crate::interpreter::HandlerTable; use crate::pipeline::file_loader::embed_to_map; -use crate::sourcefile::{FileEntry, Import}; +use crate::sourcefile::{FileEntry, FileEntryKind, Import}; use crate::systems::asynch::{Asynch, MessagePort}; -use crate::Interner; +use crate::{Interner, Location}; trait_set! { pub trait StreamTable = IntoIterator @@ -144,10 +144,14 @@ impl<'a, P: MessagePort, ST: StreamTable + 'a> IntoSystem<'a> name: vec!["system".to_string(), "io".to_string()], constants: io_bindings(i, streams).unwrap_tree(), code: embed_to_map::(".orc", i), - prelude: vec![FileEntry::Import(vec![Import { - path: vec![i.i("system"), i.i("io"), i.i("prelude")], - name: None, - }])], + prelude: vec![FileEntry { + locations: vec![Location::Unknown], + kind: FileEntryKind::Import(vec![Import { + location: Location::Unknown, + path: vec![i.i("system"), i.i("io"), i.i("prelude")], + name: None, + }]), + }], handlers, } } diff --git a/src/systems/stl/bool.orc b/src/systems/stl/bool.orc index f86f260..03a2b1d 100644 --- a/src/systems/stl/bool.orc +++ b/src/systems/stl/bool.orc @@ -1,6 +1,8 @@ +export operators[ != == ] + export const not := \bool. if bool then false else true -export macro ...$a != ...$b =0x3p36=> (not (...$a == ...$b)) -export macro ...$a == ...$b =0x3p36=> (equals (...$a) (...$b)) +macro ...$a != ...$b =0x3p36=> (not (...$a == ...$b)) +macro ...$a == ...$b =0x3p36=> (equals (...$a) (...$b)) export macro if ...$cond then ...$true else ...$false:1 =0x1p84=> ( ifthenelse (...$cond) (...$true) (...$false) ) diff --git a/src/systems/stl/fn.orc b/src/systems/stl/fn.orc index 2e640ad..5daefc8 100644 --- a/src/systems/stl/fn.orc +++ b/src/systems/stl/fn.orc @@ -18,9 +18,11 @@ export const pass2 := \a.\b.\cont. cont a b ]-- export const return := \a. \b.a -export macro ...$prefix $ ...$suffix:1 =0x1p38=> ...$prefix (...$suffix) -export macro ...$prefix |> $fn ..$suffix:1 =0x2p32=> $fn (...$prefix) ..$suffix +export operators[$ |> =>] -export macro ($name) => ...$body =0x2p129=> (\$name. ...$body) -export macro ($name, ...$argv) => ...$body =0x2p129=> (\$name. (...$argv) => ...$body) +macro ...$prefix $ ...$suffix:1 =0x1p38=> ...$prefix (...$suffix) +macro ...$prefix |> $fn ..$suffix:1 =0x2p32=> $fn (...$prefix) ..$suffix + +macro ($name) => ...$body =0x2p129=> (\$name. ...$body) +macro ($name, ...$argv) => ...$body =0x2p129=> (\$name. (...$argv) => ...$body) macro $name => ...$body =0x1p129=> (\$name. ...$body) diff --git a/src/systems/stl/known.orc b/src/systems/stl/known.orc index 7e4b7d2..a63e78f 100644 --- a/src/systems/stl/known.orc +++ b/src/systems/stl/known.orc @@ -1 +1,3 @@ -export ::[,] +export operators[ , ] + +export const foo := \a.a diff --git a/src/systems/stl/mod.rs b/src/systems/stl/mod.rs index 784fcbb..4e8554c 100644 --- a/src/systems/stl/mod.rs +++ b/src/systems/stl/mod.rs @@ -7,9 +7,9 @@ mod conv; mod inspect; mod num; mod panic; +mod state; mod stl_system; mod str; -mod state; pub use arithmetic_error::ArithmeticError; pub use bin::Binary; pub use num::Numeric; diff --git a/src/systems/stl/num.orc b/src/systems/stl/num.orc index 2abdafe..58c7bbd 100644 --- a/src/systems/stl/num.orc +++ b/src/systems/stl/num.orc @@ -1,5 +1,7 @@ -export macro ...$a + ...$b =0x2p36=> (add (...$a) (...$b)) -export macro ...$a - ...$b:1 =0x2p36=> (subtract (...$a) (...$b)) -export macro ...$a * ...$b =0x1p36=> (multiply (...$a) (...$b)) -export macro ...$a % ...$b:1 =0x1p36=> (remainder (...$a) (...$b)) -export macro ...$a / ...$b:1 =0x1p36=> (divide (...$a) (...$b)) +export operators[ + - * % / ] + +macro ...$a + ...$b =0x2p36=> (add (...$a) (...$b)) +macro ...$a - ...$b:1 =0x2p36=> (subtract (...$a) (...$b)) +macro ...$a * ...$b =0x1p36=> (multiply (...$a) (...$b)) +macro ...$a % ...$b:1 =0x1p36=> (remainder (...$a) (...$b)) +macro ...$a / ...$b:1 =0x1p36=> (divide (...$a) (...$b)) diff --git a/src/systems/stl/proc.orc b/src/systems/stl/proc.orc index a635f46..f79bcdf 100644 --- a/src/systems/stl/proc.orc +++ b/src/systems/stl/proc.orc @@ -1,5 +1,7 @@ import super::fn::=> +export operators[ ; = ] + -- remove duplicate ;-s export macro do { ...$statement ; ; ...$rest:1 @@ -11,6 +13,8 @@ export macro do { } =0x2p130=> statement (...$statement) do { ...$rest } export macro do { ...$return } =0x1p130=> ...$return +export ::do + export macro statement (let $name = ...$value) ...$next =0x1p230=> ( ( \$name. ...$next) (...$value) ) diff --git a/src/systems/stl/stl_system.rs b/src/systems/stl/stl_system.rs index 82e9bdd..fad381d 100644 --- a/src/systems/stl/stl_system.rs +++ b/src/systems/stl/stl_system.rs @@ -13,7 +13,8 @@ use super::str::str; use crate::facade::{IntoSystem, System}; use crate::interner::Interner; use crate::pipeline::file_loader::embed_to_map; -use crate::sourcefile::{FileEntry, Import}; +use crate::sourcefile::{FileEntry, FileEntryKind, Import}; +use crate::Location; /// Feature flags for the STL. #[derive(Default)] @@ -29,8 +30,6 @@ pub struct StlConfig { #[include = "*.orc"] struct StlEmbed; -// TODO: fix all orc modules to not rely on prelude - impl IntoSystem<'static> for StlConfig { fn into_system(self, i: &Interner) -> System<'static> { let pure_fns = @@ -41,10 +40,14 @@ impl IntoSystem<'static> for StlConfig { name: vec!["std".to_string()], constants: HashMap::from([(i.i("std"), fns)]), code: embed_to_map::(".orc", i), - prelude: vec![FileEntry::Import(vec![Import { - path: vec![i.i("std"), i.i("prelude")], - name: None, - }])], + prelude: vec![FileEntry { + locations: vec![Location::Unknown], + kind: FileEntryKind::Import(vec![Import { + location: Location::Unknown, + path: vec![i.i("std"), i.i("prelude")], + name: None, + }]), + }], handlers: state_handlers(), } } diff --git a/src/systems/stl/str.orc b/src/systems/stl/str.orc index c04f31b..1868b84 100644 --- a/src/systems/stl/str.orc +++ b/src/systems/stl/str.orc @@ -1,5 +1,7 @@ import super::(proc::*, bool::*, panic) +export operators[++] + export macro ...$a ++ ...$b =0x4p36=> (concat (...$a) (...$b)) export const char_at := \s.\i. do{ diff --git a/src/utils/get_or_default.rs b/src/utils/get_or_default.rs new file mode 100644 index 0000000..1bfc3e2 --- /dev/null +++ b/src/utils/get_or_default.rs @@ -0,0 +1,20 @@ +use std::hash::Hash; + +use hashbrown::HashMap; + +/// Return the given value from the map or default-initialize if it doesn't +/// exist, then retunrn a mutable reference. +pub fn get_or_default<'a, K: Eq + Hash + Clone, V: Default>( + map: &'a mut HashMap, + k: &K, +) -> &'a mut V { + get_or_make(map, k, || V::default()) +} + +pub fn get_or_make<'a, K: Eq + Hash + Clone, V>( + map: &'a mut HashMap, + k: &K, + make: impl FnOnce() -> V, +) -> &'a mut V { + map.raw_entry_mut().from_key(k).or_insert_with(|| (k.clone(), make())).1 +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index b54a04c..ad88a5b 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,8 +1,10 @@ mod cache; mod delete_cell; mod event_poller; +mod get_or_default; mod iter_find; -mod pushed; +pub mod never; +pub mod pushed; mod rc_to_owned; mod replace_first; mod side; @@ -14,7 +16,6 @@ pub mod thread_pool; mod unwrap_or; pub use cache::Cache; -pub use pushed::pushed; pub use rc_to_owned::{map_rc, rc_to_owned}; pub use replace_first::replace_first; pub use side::Side; @@ -24,6 +25,7 @@ pub(crate) use unwrap_or::unwrap_or; pub mod iter; pub use delete_cell::DeleteCell; pub use event_poller::{PollEvent, Poller}; +pub use get_or_default::{get_or_default, get_or_make}; pub use iter::BoxedIter; pub use iter_find::iter_find; pub use string_from_charset::string_from_charset; diff --git a/src/utils/never.rs b/src/utils/never.rs new file mode 100644 index 0000000..8e3eae3 --- /dev/null +++ b/src/utils/never.rs @@ -0,0 +1,17 @@ +//! A stable implementation of the never and infallible results + +/// An enum with no values +pub enum Never {} + +/// An infallible result +pub type Always = Result; + +/// Wrap value in a result with an impossible failure mode +pub fn always(t: T) -> Result { + Ok(t) +} + +/// Take success value out of a result with an impossible failure mode +pub fn unwrap_always(result: Result) -> T { + result.unwrap_or_else(|_| unreachable!("Never has no values")) +} diff --git a/src/utils/pushed.rs b/src/utils/pushed.rs index d84771b..267c1c4 100644 --- a/src/utils/pushed.rs +++ b/src/utils/pushed.rs @@ -3,7 +3,18 @@ use std::iter; /// Pure version of [Vec::push] /// /// Create a new vector consisting of the provided vector with the -/// element appended -pub fn pushed(vec: &[T], t: T) -> Vec { - vec.iter().cloned().chain(iter::once(t)).collect() +/// element appended. See [pushed_ref] to use it with a slice +pub fn pushed(vec: impl IntoIterator, t: T) -> Vec { + vec.into_iter().chain(iter::once(t)).collect() +} + +/// Pure version of [Vec::push] +/// +/// Create a new vector consisting of the provided slice with the +/// element appended. See [pushed] for the owned version +pub fn pushed_ref<'a, T: Clone + 'a>( + vec: impl IntoIterator, + t: T, +) -> Vec { + vec.into_iter().cloned().chain(iter::once(t)).collect() } diff --git a/src/utils/split_max_prefix.rs b/src/utils/split_max_prefix.rs index d5c01c8..26a0572 100644 --- a/src/utils/split_max_prefix.rs +++ b/src/utils/split_max_prefix.rs @@ -1,14 +1,10 @@ /// Split off the longest prefix accepted by the validator -#[allow(clippy::type_complexity)] // FIXME couldn't find a good factoring pub fn split_max_prefix<'a, T>( path: &'a [T], is_valid: &impl Fn(&[T]) -> bool, ) -> Option<(&'a [T], &'a [T])> { - for split in (0..=path.len()).rev() { - let (filename, subpath) = path.split_at(split); - if is_valid(filename) { - return Some((filename, subpath)); - } - } - None + (0..=path.len()) + .rev() + .map(|i| path.split_at(i)) + .find(|(file, _)| is_valid(file)) } diff --git a/src/utils/substack.rs b/src/utils/substack.rs index 3f807c3..28c0d41 100644 --- a/src/utils/substack.rs +++ b/src/utils/substack.rs @@ -44,12 +44,13 @@ impl<'a, T> Substack<'a, T> { Stackframe { item, prev: self, len: self.opt().map_or(1, |s| s.len + 1) } } /// obtain the previous stackframe if one exists - /// TODO: this should return a [Substack] - 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) } + pub fn pop(&'a self, count: usize) -> &'a Substack<'a, T> { + if count == 0 { + self + } else if let Self::Frame(p) = self { + p.prev.pop(count - 1) } else { - None + &Substack::Bottom } } /// number of stackframes