The pipeline is finally reasonably clean

This commit is contained in:
2023-09-12 01:26:46 +01:00
parent 6693d93944
commit 8c866967a9
86 changed files with 1959 additions and 1393 deletions

1
clippy.toml Normal file
View File

@@ -0,0 +1 @@
type-complexity-threshold = 300

View File

@@ -116,6 +116,7 @@ pub fn macro_debug(premacro: PreMacro, sym: Sym) {
"Available commands: "Available commands:
\t<blank>, n, next\t\ttake a step \t<blank>, n, next\t\ttake a step
\tp, print\t\tprint the current state \tp, print\t\tprint the current state
\td, dump\t\tprint the rule table
\tq, quit\t\texit \tq, quit\t\texit
\th, help\t\tprint this text" \th, help\t\tprint this text"
), ),

View File

@@ -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<Location>,
}
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<ErrorPosition> {
Box::new(
(self.locations.iter())
.map(|l| ErrorPosition { location: l.clone(), message: None }),
)
}
}

View File

@@ -10,7 +10,7 @@ use crate::VName;
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ImportAll { pub struct ImportAll {
/// The file containing the offending import /// The file containing the offending import
pub offender_file: Rc<Vec<String>>, pub offender_file: Rc<VName>,
/// The module containing the offending import /// The module containing the offending import
pub offender_mod: Rc<VName>, pub offender_mod: Rc<VName>,
} }

View File

@@ -2,17 +2,18 @@
mod import_all; mod import_all;
mod no_targets; mod no_targets;
mod not_exported; mod not_exported;
mod not_found; // mod not_found;
mod conflicting_roles;
mod parse_error_with_tokens; mod parse_error_with_tokens;
mod project_error; mod project_error;
mod too_many_supers; mod too_many_supers;
mod unexpected_directory; mod unexpected_directory;
mod visibility_mismatch; mod visibility_mismatch;
pub use conflicting_roles::ConflictingRoles;
pub use import_all::ImportAll; pub use import_all::ImportAll;
pub use no_targets::NoTargets; pub use no_targets::NoTargets;
pub use not_exported::NotExported; pub use not_exported::NotExported;
pub use not_found::NotFound;
pub use parse_error_with_tokens::ParseErrorWithTokens; pub use parse_error_with_tokens::ParseErrorWithTokens;
pub use project_error::{ErrorPosition, ProjectError, ProjectResult}; pub use project_error::{ErrorPosition, ProjectError, ProjectResult};
pub use too_many_supers::TooManySupers; pub use too_many_supers::TooManySupers;

View File

@@ -25,16 +25,14 @@ impl ProjectError for NotExported {
Box::new( Box::new(
[ [
ErrorPosition { ErrorPosition {
location: Location::File(Rc::new(Interner::extern_all(&self.file))), location: Location::File(Rc::new(self.file.clone())),
message: Some(format!( message: Some(format!(
"{} isn't exported", "{} isn't exported",
Interner::extern_all(&self.subpath).join("::") Interner::extern_all(&self.subpath).join("::")
)), )),
}, },
ErrorPosition { ErrorPosition {
location: Location::File(Rc::new(Interner::extern_all( location: Location::File(Rc::new(self.referrer_file.clone())),
&self.referrer_file,
))),
message: Some(format!( message: Some(format!(
"{} cannot see this symbol", "{} cannot see this symbol",
Interner::extern_all(&self.referrer_subpath).join("::") Interner::extern_all(&self.referrer_subpath).join("::")

View File

@@ -1,5 +1,4 @@
use std::fmt::Debug; use std::fmt::{Debug, Display};
use std::fmt::Display;
use std::rc::Rc; use std::rc::Rc;
use crate::representations::location::Location; use crate::representations::location::Location;

View File

@@ -1,5 +1,3 @@
use std::rc::Rc;
use super::ProjectError; use super::ProjectError;
use crate::representations::location::Location; use crate::representations::location::Location;
use crate::{Interner, VName}; use crate::{Interner, VName};
@@ -10,10 +8,8 @@ use crate::{Interner, VName};
pub struct TooManySupers { pub struct TooManySupers {
/// The offending import path /// The offending import path
pub path: VName, pub path: VName,
/// The file containing the offending import /// The faulty import statement
pub offender_file: VName, pub location: Location,
/// The module containing the offending import
pub offender_mod: VName,
} }
impl ProjectError for TooManySupers { impl ProjectError for TooManySupers {
fn description(&self) -> &str { fn description(&self) -> &str {
@@ -22,13 +18,12 @@ impl ProjectError for TooManySupers {
} }
fn message(&self) -> String { fn message(&self) -> String {
format!( format!(
"path {} in {} contains too many `super` steps.", "path {} contains too many `super` steps.",
Interner::extern_all(&self.path).join("::"), Interner::extern_all(&self.path).join("::"),
Interner::extern_all(&self.offender_mod).join("::")
) )
} }
fn one_position(&self) -> Location { fn one_position(&self) -> Location {
Location::File(Rc::new(Interner::extern_all(&self.offender_file))) self.location.clone()
} }
} }

View File

@@ -16,7 +16,7 @@ impl ProjectError for UnexpectedDirectory {
to a directory" to a directory"
} }
fn one_position(&self) -> crate::Location { 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 { fn message(&self) -> String {

View File

@@ -10,7 +10,7 @@ pub struct VisibilityMismatch {
/// The namespace with ambiguous visibility /// The namespace with ambiguous visibility
pub namespace: VName, pub namespace: VName,
/// The file containing the namespace /// The file containing the namespace
pub file: Rc<Vec<String>>, pub file: VName,
} }
impl ProjectError for VisibilityMismatch { impl ProjectError for VisibilityMismatch {
fn description(&self) -> &str { fn description(&self) -> &str {
@@ -23,6 +23,6 @@ impl ProjectError for VisibilityMismatch {
) )
} }
fn one_position(&self) -> Location { fn one_position(&self) -> Location {
Location::File(self.file.clone()) Location::File(Rc::new(self.file.clone()))
} }
} }

View File

@@ -8,6 +8,7 @@ use super::PreMacro;
use crate::error::ProjectResult; use crate::error::ProjectResult;
use crate::pipeline::file_loader; use crate::pipeline::file_loader;
use crate::sourcefile::FileEntry; use crate::sourcefile::FileEntry;
use crate::utils::never;
use crate::{ use crate::{
from_const_tree, parse_layer, vname_to_sym_tree, Interner, ProjectTree, Stok, from_const_tree, parse_layer, vname_to_sym_tree, Interner, ProjectTree, Stok,
VName, VName,
@@ -39,7 +40,7 @@ impl<'a> Environment<'a> {
let mut tree = from_const_tree(HashMap::new(), &[i.i("none")]); let mut tree = from_const_tree(HashMap::new(), &[i.i("none")]);
for sys in systems.iter() { for sys in systems.iter() {
let system_tree = from_const_tree(sys.constants.clone(), &sys.vname(i)); 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![]; let mut prelude = vec![];
for sys in systems.iter() { for sys in systems.iter() {

View File

@@ -42,12 +42,13 @@ impl<'a> PreMacro<'a> {
repo, repo,
consts: (consts.into_iter()) consts: (consts.into_iter())
.map(|(name, expr)| { .map(|(name, expr)| {
// Figure out the location of the constant
let location = (name.split_last()) let location = (name.split_last())
.and_then(|(_, path)| { .and_then(|(_, path)| {
let origin = (tree.0.walk_ref(path, false)) let origin = (tree.0)
.expect("path sourced from symbol names"); .walk_ref(&[], path, false)
(origin.extra.file.as_ref()) .unwrap_or_else(|_| panic!("path sourced from symbol names"));
.map(|path| Interner::extern_all(&path[..])) (origin.extra.file.as_ref()).cloned()
}) })
.map(|p| Location::File(Rc::new(p))) .map(|p| Location::File(Rc::new(p)))
.unwrap_or(Location::Unknown); .unwrap_or(Location::Unknown);

View File

@@ -16,7 +16,7 @@ pub struct System<'a> {
/// External functions and other constant values defined in AST form /// External functions and other constant values defined in AST form
pub constants: HashMap<Tok<String>, ConstTree>, pub constants: HashMap<Tok<String>, ConstTree>,
/// Orchid libraries defined by this system /// Orchid libraries defined by this system
pub code: HashMap<Vec<Tok<String>>, Loaded>, pub code: HashMap<VName, Loaded>,
/// Prelude lines to be added to **subsequent** systems and usercode to /// Prelude lines to be added to **subsequent** systems and usercode to
/// expose the functionality of this system. The prelude is not added during /// expose the functionality of this system. The prelude is not added during
/// the loading of this system /// the loading of this system
@@ -27,7 +27,7 @@ pub struct System<'a> {
impl<'a> System<'a> { impl<'a> System<'a> {
/// Intern the name of the system so that it can be used as an Orchid /// Intern the name of the system so that it can be used as an Orchid
/// namespace /// namespace
pub fn vname(&self, i: &Interner) -> Vec<Tok<String>> { pub fn vname(&self, i: &Interner) -> VName {
self.name.iter().map(|s| i.i(s)).collect::<Vec<_>>() self.name.iter().map(|s| i.i(s)).collect::<Vec<_>>()
} }

View File

@@ -1,13 +1,13 @@
//! Automated wrappers to make working with CPS commands easier. //! Automated wrappers to make working with CPS commands easier.
use std::fmt::Debug; use std::fmt::Debug;
use std::iter;
use trait_set::trait_set; use trait_set::trait_set;
use super::{Atomic, AtomicResult, AtomicReturn, ExternFn, XfnResult}; use super::{Atomic, AtomicResult, AtomicReturn, ExternFn, XfnResult};
use crate::interpreted::{Clause, ExprInst}; use crate::interpreted::{Clause, ExprInst};
use crate::interpreter::{Context, HandlerRes}; use crate::interpreter::{Context, HandlerRes};
use crate::utils::pushed::pushed_ref;
use crate::{atomic_defaults, ConstTree}; use crate::{atomic_defaults, ConstTree};
trait_set! { trait_set! {
@@ -39,10 +39,7 @@ impl<T: CPSPayload> ExternFn for CPSFn<T> {
} }
fn apply(&self, arg: ExprInst, _ctx: Context) -> XfnResult { fn apply(&self, arg: ExprInst, _ctx: Context) -> XfnResult {
let payload = self.payload.clone(); let payload = self.payload.clone();
let continuations = (self.continuations.iter()) let continuations = pushed_ref(&self.continuations, arg);
.cloned()
.chain(iter::once(arg))
.collect::<Vec<_>>();
if self.argc == 1 { if self.argc == 1 {
Ok(CPSBox { payload, continuations }.atom_cls()) Ok(CPSBox { payload, continuations }.atom_cls())
} else { } else {

View File

@@ -40,7 +40,7 @@ use crate::Primitive;
/// _definition of the `add` function in the STL_ /// _definition of the `add` function in the STL_
/// ``` /// ```
/// use orchidlang::{Literal}; /// use orchidlang::{Literal};
/// use orchidlang::interpreted::ExprInst; /// use orchidlang::interpreted::{ExprInst, Clause};
/// use orchidlang::systems::cast_exprinst::with_lit; /// use orchidlang::systems::cast_exprinst::with_lit;
/// use orchidlang::{atomic_impl, atomic_redirect, externfn_impl}; /// use orchidlang::{atomic_impl, atomic_redirect, externfn_impl};
/// ///
@@ -63,10 +63,10 @@ use crate::Primitive;
/// atomic_redirect!(InternalToString, expr_inst); /// atomic_redirect!(InternalToString, expr_inst);
/// atomic_impl!(InternalToString, |Self { expr_inst }: &Self, _|{ /// atomic_impl!(InternalToString, |Self { expr_inst }: &Self, _|{
/// with_lit(expr_inst, |l| Ok(match l { /// with_lit(expr_inst, |l| Ok(match l {
/// Literal::Uint(i) => i.to_string(), /// Literal::Uint(i) => Literal::Str(i.to_string().into()),
/// Literal::Num(n) => n.to_string(), /// Literal::Num(n) => Literal::Str(n.to_string().into()),
/// Literal::Str(s) => s.clone(), /// s@Literal::Str(_) => s.clone(),
/// })).map(|s| Literal::Str(s).into()) /// })).map(Clause::from)
/// }); /// });
/// ``` /// ```
#[macro_export] #[macro_export]

View File

@@ -42,15 +42,17 @@ use crate::write_fn_step;
/// ``` /// ```
/// use orchidlang::interpreted::Clause; /// use orchidlang::interpreted::Clause;
/// use orchidlang::systems::cast_exprinst::with_str; /// 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 /// define_fn! {expr=x in
/// /// Append a string to another /// /// Append a string to another
/// pub Concatenate { /// pub Concatenate {
/// a: String as with_str(x, |s| Ok(s.clone())), /// a: OrcString as with_str(x, |s| Ok(s.clone())),
/// b: String 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: /// A simpler format is also offered for unary functions:
/// ///
/// ``` /// ```
/// use orchidlang::interpreted::Clause;
/// use orchidlang::systems::cast_exprinst::with_lit; /// use orchidlang::systems::cast_exprinst::with_lit;
/// use orchidlang::{define_fn, Literal}; /// 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, /// /// Convert a literal to a string using Rust's conversions for floats,
/// /// chars and uints respectively /// /// chars and uints respectively
/// ToString = |x| with_lit(x, |l| Ok(match l { /// ToString = |x| with_lit(x, |l| Ok(match l {
/// Literal::Uint(i) => i.to_string(), /// Literal::Uint(i) => Literal::Str(i.to_string().into()),
/// Literal::Num(n) => n.to_string(), /// Literal::Num(n) => Literal::Str(n.to_string().into()),
/// Literal::Str(s) => s.clone(), /// s@Literal::Str(_) => s.clone(),
/// })).map(|s| Literal::Str(s).into()) /// })).map(Clause::from)
/// } /// }
/// ``` /// ```
#[macro_export] #[macro_export]

View File

@@ -19,10 +19,9 @@ use crate::interpreted::ExprInst;
/// also receive type annotations. /// also receive type annotations.
/// ///
/// ``` /// ```
/// // FIXME this is a very old example that wouldn't compile now
/// use unicode_segmentation::UnicodeSegmentation; /// 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::interpreted::Clause;
/// use orchidlang::systems::cast_exprinst::{with_str, with_uint}; /// use orchidlang::systems::cast_exprinst::{with_str, with_uint};
/// use orchidlang::systems::RuntimeError; /// use orchidlang::systems::RuntimeError;
@@ -32,20 +31,20 @@ use crate::interpreted::ExprInst;
/// // Middle state /// // Middle state
/// write_fn_step!( /// write_fn_step!(
/// CharAt1 {} /// 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 /// // Exit state
/// write_fn_step!( /// write_fn_step!(
/// CharAt0 { s: String } /// CharAt0 { s: OrcString }
/// i = x => with_uint(x, Ok); /// i = x => with_uint(x, Ok);
/// { /// {
/// if let Some(c) = s.graphemes(true).nth(*i as usize) { /// 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 { /// } else {
/// RuntimeError::fail( /// RuntimeError::fail(
/// "Character index out of bounds".to_string(), /// "Character index out of bounds".to_string(),
/// "indexing string", /// "indexing string",
/// )? /// )
/// } /// }
/// } /// }
/// ); /// );

View File

@@ -55,15 +55,15 @@ impl<T: Eq + Hash + Clone + 'static> Deref for Tok<T> {
} }
} }
impl<T: Eq + Hash + Clone + 'static> Debug for Tok<T> { impl<T: Eq + Hash + Clone + 'static + Debug> Debug for Tok<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 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<T: Eq + Hash + Clone + Display + 'static> Display for Tok<T> { impl<T: Eq + Hash + Clone + Display + 'static> Display for Tok<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", *self) write!(f, "{}", **self)
} }
} }

View File

@@ -4,6 +4,7 @@ use super::Return;
use crate::foreign::AtomicReturn; use crate::foreign::AtomicReturn;
use crate::representations::interpreted::{Clause, ExprInst}; use crate::representations::interpreted::{Clause, ExprInst};
use crate::representations::{PathSet, Primitive}; use crate::representations::{PathSet, Primitive};
use crate::utils::never::{unwrap_always, Always};
use crate::utils::Side; use crate::utils::Side;
/// Process the clause at the end of the provided path. Note that paths always /// Process the clause at the end of the provided path. Note that paths always
@@ -53,16 +54,12 @@ fn map_at<E>(
.map(|p| p.0) .map(|p| p.0)
} }
/// TODO replace when `!` gets stabilized
#[derive(Debug)]
enum Never {}
/// Replace the [Clause::LambdaArg] placeholders at the ends of the [PathSet] /// 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 /// with the value in the body. Note that a path may point to multiple
/// placeholders. /// placeholders.
fn substitute(paths: &PathSet, value: Clause, body: ExprInst) -> ExprInst { fn substitute(paths: &PathSet, value: Clause, body: ExprInst) -> ExprInst {
let PathSet { steps, next } = paths; let PathSet { steps, next } = paths;
map_at(steps, body, &mut |checkpoint| -> Result<Clause, Never> { unwrap_always(map_at(steps, body, &mut |checkpoint| -> Always<Clause> {
match (checkpoint, next) { match (checkpoint, next) {
(Clause::Lambda { .. }, _) => unreachable!("Handled by map_at"), (Clause::Lambda { .. }, _) => unreachable!("Handled by map_at"),
(Clause::Apply { f, x }, Some((left, right))) => Ok(Clause::Apply { (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") panic!("Substitution path leads into something other than Apply")
}, },
} }
}) }))
.unwrap()
} }
/// Apply a function-like expression to a parameter. /// Apply a function-like expression to a parameter.

View File

@@ -23,7 +23,6 @@ mod utils;
pub use interner::{Interner, Tok}; pub use interner::{Interner, Tok};
pub use pipeline::file_loader::{mk_dir_cache, mk_embed_cache}; pub use pipeline::file_loader::{mk_dir_cache, mk_embed_cache};
pub use pipeline::parse_layer; pub use pipeline::parse_layer;
pub use representations::{NameLike, Sym, VName};
/// Element of VName and a common occurrence in the API /// Element of VName and a common occurrence in the API
pub type Stok = Tok<String>; pub type Stok = Tok<String>;
pub use representations::ast_to_interpreted::ast_to_interpreted; pub use representations::ast_to_interpreted::ast_to_interpreted;
@@ -32,6 +31,6 @@ pub use representations::project::{
}; };
pub use representations::{ pub use representations::{
ast, from_const_tree, interpreted, sourcefile, tree, ConstTree, Literal, 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}; pub use utils::{thread_pool, Side, Substack};

View File

@@ -1,16 +1,15 @@
use std::rc::Rc; use std::rc::Rc;
use crate::interner::Interner; use crate::interner::Interner;
use crate::{Tok, VName};
/// Trait enclosing all context features /// Trait enclosing all context features
/// ///
/// Hiding type parameters in associated types allows for simpler /// Hiding type parameters in associated types allows for simpler
/// parser definitions /// parser definitions
pub trait Context: Clone { pub trait Context: Clone {
type Op: AsRef<str>; fn ops(&self) -> &[Tok<String>];
fn file(&self) -> Rc<VName>;
fn ops(&self) -> &[Self::Op];
fn file(&self) -> Rc<Vec<String>>;
fn interner(&self) -> &Interner; fn interner(&self) -> &Interner;
} }
@@ -18,38 +17,36 @@ pub trait Context: Clone {
/// ///
/// Hiding type parameters in associated types allows for simpler /// Hiding type parameters in associated types allows for simpler
/// parser definitions /// parser definitions
pub struct ParsingContext<'a, Op> { pub struct ParsingContext<'a> {
pub ops: &'a [Op], pub ops: &'a [Tok<String>],
pub interner: &'a Interner, pub interner: &'a Interner,
pub file: Rc<Vec<String>>, pub file: Rc<VName>,
} }
impl<'a, Op> ParsingContext<'a, Op> { impl<'a> ParsingContext<'a> {
pub fn new( pub fn new(
ops: &'a [Op], ops: &'a [Tok<String>],
interner: &'a Interner, interner: &'a Interner,
file: Rc<Vec<String>>, file: Rc<VName>,
) -> Self { ) -> Self {
Self { ops, interner, file } Self { ops, interner, file }
} }
} }
impl<'a, Op> Clone for ParsingContext<'a, Op> { impl<'a> Clone for ParsingContext<'a> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { ops: self.ops, interner: self.interner, file: self.file.clone() } Self { ops: self.ops, interner: self.interner, file: self.file.clone() }
} }
} }
impl<Op: AsRef<str>> Context for ParsingContext<'_, Op> { impl Context for ParsingContext<'_> {
type Op = Op;
fn interner(&self) -> &Interner { fn interner(&self) -> &Interner {
self.interner self.interner
} }
fn file(&self) -> Rc<Vec<String>> { fn file(&self) -> Rc<VName> {
self.file.clone() self.file.clone()
} }
fn ops(&self) -> &[Self::Op] { fn ops(&self) -> &[Tok<String>] {
self.ops self.ops
} }
} }

View File

@@ -6,7 +6,7 @@ use itertools::Itertools;
use super::{Entry, Lexeme}; use super::{Entry, Lexeme};
use crate::error::{ErrorPosition, ProjectError}; use crate::error::{ErrorPosition, ProjectError};
use crate::utils::BoxedIter; use crate::utils::BoxedIter;
use crate::{Location, Tok}; use crate::{Location, Tok, VName};
#[derive(Debug)] #[derive(Debug)]
pub struct LineNeedsPrefix { pub struct LineNeedsPrefix {
@@ -234,7 +234,7 @@ impl ProjectError for GlobExport {
pub struct LexError { pub struct LexError {
pub errors: Vec<Simple<char>>, pub errors: Vec<Simple<char>>,
pub source: Rc<String>, pub source: Rc<String>,
pub file: Rc<Vec<String>>, pub file: VName,
} }
impl ProjectError for LexError { impl ProjectError for LexError {
fn description(&self) -> &str { fn description(&self) -> &str {
@@ -244,7 +244,7 @@ impl ProjectError for LexError {
let file = self.file.clone(); let file = self.file.clone();
Box::new(self.errors.iter().map(move |s| ErrorPosition { Box::new(self.errors.iter().map(move |s| ErrorPosition {
location: Location::Range { location: Location::Range {
file: file.clone(), file: Rc::new(file.clone()),
range: s.span(), range: s.span(),
source: self.source.clone(), source: self.source.clone(),
}, },

View File

@@ -14,7 +14,12 @@ pub fn parse2(data: &str, ctx: impl Context) -> ProjectResult<Vec<FileEntry>> {
let source = Rc::new(data.to_string()); let source = Rc::new(data.to_string());
let lexie = lexer(ctx.clone(), source.clone()); let lexie = lexer(ctx.clone(), source.clone());
let tokens = (lexie.parse(data)).map_err(|errors| { 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() { if tokens.is_empty() {
Ok(Vec::new()) Ok(Vec::new())

View File

@@ -14,8 +14,9 @@ use super::number::print_nat16;
use super::{comment, name, number, placeholder, string}; use super::{comment, name, number, placeholder, string};
use crate::ast::{PHClass, Placeholder}; use crate::ast::{PHClass, Placeholder};
use crate::interner::Tok; use crate::interner::Tok;
use crate::parse::operators::operators_parser;
use crate::representations::Literal; use crate::representations::Literal;
use crate::Location; use crate::{Interner, Location, VName};
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Entry { pub struct Entry {
@@ -47,7 +48,7 @@ impl Entry {
self.location.range().expect("An Entry can only have a known location") self.location.range().expect("An Entry can only have a known location")
} }
pub fn file(&self) -> Rc<Vec<String>> { pub fn file(&self) -> Rc<VName> {
self.location.file().expect("An Entry can only have a range location") self.location.file().expect("An Entry can only have a range location")
} }
} }
@@ -106,14 +107,15 @@ pub enum Lexeme {
/// Backslash /// Backslash
BS, BS,
At, At,
Dot, // Dot,
Type, // type operator Type, // type operator
Comment(String), Comment(Rc<String>),
Export, Export,
Import, Import,
Module, Module,
Macro, Macro,
Const, Const,
Operators(Rc<VName>),
Placeh(Placeholder), Placeh(Placeholder),
} }
@@ -135,7 +137,6 @@ impl Display for Lexeme {
Self::BR => writeln!(f), Self::BR => writeln!(f),
Self::BS => write!(f, "\\"), Self::BS => write!(f, "\\"),
Self::At => write!(f, "@"), Self::At => write!(f, "@"),
Self::Dot => write!(f, "."),
Self::Type => write!(f, ":"), Self::Type => write!(f, ":"),
Self::Comment(text) => write!(f, "--[{}]--", text), Self::Comment(text) => write!(f, "--[{}]--", text),
Self::Export => write!(f, "export"), Self::Export => write!(f, "export"),
@@ -143,6 +144,8 @@ impl Display for Lexeme {
Self::Module => write!(f, "module"), Self::Module => write!(f, "module"),
Self::Const => write!(f, "const"), Self::Const => write!(f, "const"),
Self::Macro => write!(f, "macro"), Self::Macro => write!(f, "macro"),
Self::Operators(ops) =>
write!(f, "operators[{}]", Interner::extern_all(ops).join(" ")),
Self::Placeh(Placeholder { name, class }) => match *class { Self::Placeh(Placeholder { name, class }) => match *class {
PHClass::Scalar => write!(f, "${}", **name), PHClass::Scalar => write!(f, "${}", **name),
PHClass::Vec { nonzero, prio } => { PHClass::Vec { nonzero, prio } => {
@@ -185,16 +188,19 @@ fn paren_parser(lp: char, rp: char) -> impl SimpleParser<char, Lexeme> {
just(lp).to(Lexeme::LP(lp)).or(just(rp).to(Lexeme::RP(lp))) just(lp).to(Lexeme::LP(lp)).or(just(rp).to(Lexeme::RP(lp)))
} }
pub fn literal_parser<'a>(ctx: impl Context + 'a) -> impl SimpleParser<char, Literal> + 'a { pub fn literal_parser<'a>(
ctx: impl Context + 'a,
) -> impl SimpleParser<char, Literal> + 'a {
choice(( choice((
// all ints are valid floats so it takes precedence // all ints are valid floats so it takes precedence
number::int_parser().map(Literal::Uint), number::int_parser().map(Literal::Uint),
number::float_parser().map(Literal::Num), 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>( pub fn lexer<'a>(
ctx: impl Context + 'a, ctx: impl Context + 'a,
@@ -213,6 +219,11 @@ pub fn lexer<'a>(
keyword("import").to(Lexeme::Import), keyword("import").to(Lexeme::Import),
keyword("macro").to(Lexeme::Macro), keyword("macro").to(Lexeme::Macro),
keyword("const").to(Lexeme::Const), 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('[', ']'), paren_parser('[', ']'),
paren_parser('{', '}'), paren_parser('{', '}'),
@@ -221,14 +232,14 @@ pub fn lexer<'a>(
.ignore_then(number::float_parser()) .ignore_then(number::float_parser())
.then_ignore(just("=>")) .then_ignore(just("=>"))
.map(Lexeme::rule), .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), placeholder::placeholder_parser(ctx.clone()).map(Lexeme::Placeh),
just("::").to(Lexeme::NS), just("::").to(Lexeme::NS),
just('\\').to(Lexeme::BS), just('\\').to(Lexeme::BS),
just('@').to(Lexeme::At), just('@').to(Lexeme::At),
just(':').to(Lexeme::Type), just(':').to(Lexeme::Type),
just('\n').to(Lexeme::BR), just('\n').to(Lexeme::BR),
just('.').to(Lexeme::Dot), // just('.').to(Lexeme::Dot),
literal_parser(ctx.clone()).map(Lexeme::Literal), literal_parser(ctx.clone()).map(Lexeme::Literal),
name::name_parser(&all_ops).map({ name::name_parser(&all_ops).map({
let ctx = ctx.clone(); let ctx = ctx.clone();

View File

@@ -7,6 +7,7 @@ mod lexer;
mod multiname; mod multiname;
mod name; mod name;
mod number; mod number;
mod operators;
mod placeholder; mod placeholder;
mod sourcefile; mod sourcefile;
mod stream; mod stream;
@@ -15,5 +16,4 @@ mod string;
pub use context::ParsingContext; pub use context::ParsingContext;
pub use facade::parse2; pub use facade::parse2;
pub use lexer::{lexer, Entry, Lexeme}; pub use lexer::{lexer, Entry, Lexeme};
pub use name::is_op; pub use number::{float_parser, int_parser, print_nat16};
pub use number::{float_parser, int_parser};

View File

@@ -8,19 +8,20 @@ use crate::error::{ProjectError, ProjectResult};
use crate::sourcefile::Import; use crate::sourcefile::Import;
use crate::utils::iter::{box_chain, box_once}; use crate::utils::iter::{box_chain, box_once};
use crate::utils::BoxedIter; use crate::utils::BoxedIter;
use crate::Tok; use crate::{Location, Tok};
struct Subresult { struct Subresult {
glob: bool, glob: bool,
deque: VecDeque<Tok<String>>, deque: VecDeque<Tok<String>>,
location: Location,
} }
impl Subresult { impl Subresult {
fn new_glob() -> Self { fn new_glob(location: Location) -> Self {
Self { glob: true, deque: VecDeque::new() } Self { glob: true, deque: VecDeque::new(), location }
} }
fn new_named(name: Tok<String>) -> Self { fn new_named(name: Tok<String>, location: Location) -> Self {
Self { glob: false, deque: VecDeque::from([name]) } Self { location, glob: false, deque: VecDeque::from([name]) }
} }
fn push_front(mut self, name: Tok<String>) -> Self { fn push_front(mut self, name: Tok<String>) -> Self {
@@ -29,10 +30,10 @@ impl Subresult {
} }
fn finalize(self) -> Import { 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"); debug_assert!(glob || !deque.is_empty(), "The constructors forbid this");
let name = if glob { None } else { deque.pop_back() }; 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; let head;
(head, cursor) = cursor.trim().pop()?; (head, cursor) = cursor.trim().pop()?;
match &head.lexeme { match &head.lexeme {
Lexeme::Name(n) => names.push(n), Lexeme::Name(n) => names.push((n, head.location())),
Lexeme::RP('[') => break, Lexeme::RP('[') => break,
_ => { _ => {
let err = Expected { let err = Expected {
@@ -88,12 +89,14 @@ fn parse_multiname_rec(
} }
} }
Ok(( 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, cursor,
)) ))
}, },
Lexeme::Name(n) if *n == star => 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) => { Lexeme::Name(n) if ![comma, star].contains(n) => {
let cursor = cursor.trim(); let cursor = cursor.trim();
if cursor.get(0).ok().map(|e| &e.lexeme) == Some(&Lexeme::NS) { 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()))); let out = Box::new(out.map(|sr| sr.push_front(n.clone())));
Ok((out, cursor)) Ok((out, cursor))
} else { } else {
Ok((box_once(Subresult::new_named(n.clone())), cursor)) Ok((box_once(Subresult::new_named(n.clone(), head.location())), cursor))
} }
}, },
_ => Err( _ => Err(

View File

@@ -30,7 +30,7 @@ pub static NOT_NAME_CHAR: &[char] = &[
'"', // parsed as primitive and therefore would never match '"', // parsed as primitive and therefore would never match
'(', ')', '[', ']', '{', '}', // must be strictly balanced '(', ')', '[', ']', '{', '}', // must be strictly balanced
'.', // Argument-body separator in parametrics '.', // Argument-body separator in parametrics
',', // used in imports ',', // Import separator
]; ];
/// Matches anything that's allowed as an operator /// 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, /// Could be an operator but then parametrics should take precedence,
/// which might break stuff. investigate. /// 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. /// TODO: `.` could possibly be parsed as an operator in some contexts.
/// This operator is very common in maths so it's worth a try. /// This operator is very common in maths so it's worth a try.
/// Investigate. /// Investigate.
pub fn modname_parser<'a>() -> impl SimpleParser<char, String> + 'a { pub fn anyop_parser<'a>() -> impl SimpleParser<char, String> + 'a {
filter(move |c| !NOT_NAME_CHAR.contains(c) && !c.is_whitespace()) filter(move |c| {
!NOT_NAME_CHAR.contains(c)
&& !c.is_whitespace()
&& !c.is_alphanumeric()
&& c != &'_'
})
.repeated() .repeated()
.at_least(1) .at_least(1)
.collect() .collect()
.labelled("modname") .labelled("anyop")
} }
/// Parse an operator or name. Failing both, parse everything up to /// Parse an operator or name. Failing both, parse everything up to
@@ -61,16 +63,7 @@ pub fn name_parser<'a>(
choice(( choice((
op_parser(ops), // First try to parse a known operator op_parser(ops), // First try to parse a known operator
text::ident().labelled("plain text"), // Failing that, parse plain text 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") .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<str>) -> bool {
return match s.as_ref().chars().next() {
Some(x) => !x.is_alphanumeric(),
None => false,
};
}

31
src/parse/operators.rs Normal file
View File

@@ -0,0 +1,31 @@
use chumsky::prelude::*;
use super::decls::SimpleParser;
pub fn operators_parser<T>(
f: impl Fn(String) -> T,
) -> impl SimpleParser<char, Vec<T>> {
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[$ |> =>]"))
}
}

View File

@@ -15,9 +15,9 @@ use super::Entry;
use crate::ast::{Clause, Constant, Expr, Rule}; use crate::ast::{Clause, Constant, Expr, Rule};
use crate::error::{ProjectError, ProjectResult}; use crate::error::{ProjectError, ProjectResult};
use crate::representations::location::Location; 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::representations::VName;
use crate::sourcefile::Import; use crate::sourcefile::{FileEntryKind, Import, Member};
use crate::Primitive; use crate::Primitive;
pub fn split_lines(module: Stream<'_>) -> impl Iterator<Item = Stream<'_>> { pub fn split_lines(module: Stream<'_>) -> impl Iterator<Item = Stream<'_>> {
@@ -57,23 +57,31 @@ pub fn parse_module_body(
split_lines(cursor) split_lines(cursor)
.map(Stream::trim) .map(Stream::trim)
.filter(|l| !l.data.is_empty()) .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() .collect()
} }
pub fn parse_line( pub fn parse_line(
cursor: Stream<'_>, cursor: Stream<'_>,
ctx: impl Context, ctx: impl Context,
) -> ProjectResult<FileEntry> { ) -> ProjectResult<FileEntryKind> {
match cursor.get(0)?.lexeme { match cursor.get(0)?.lexeme {
Lexeme::BR | Lexeme::Comment(_) => parse_line(cursor.step()?, ctx), Lexeme::BR | Lexeme::Comment(_) => parse_line(cursor.step()?, ctx),
Lexeme::Export => parse_export_line(cursor.step()?, ctx), Lexeme::Export => parse_export_line(cursor.step()?, ctx),
Lexeme::Const | Lexeme::Macro | Lexeme::Module => Lexeme::Const | Lexeme::Macro | Lexeme::Module | Lexeme::Operators(_) =>
Ok(FileEntry::Internal(parse_member(cursor, ctx)?)), Ok(FileEntryKind::Member(Member {
kind: parse_member(cursor, ctx)?,
exported: false,
})),
Lexeme::Import => { Lexeme::Import => {
let (imports, cont) = parse_multiname(cursor.step()?, ctx)?; let (imports, cont) = parse_multiname(cursor.step()?, ctx)?;
cont.expect_empty()?; cont.expect_empty()?;
Ok(FileEntry::Import(imports)) Ok(FileEntryKind::Import(imports))
}, },
_ => { _ => {
let err = BadTokenInRegion { let err = BadTokenInRegion {
@@ -88,23 +96,26 @@ pub fn parse_line(
pub fn parse_export_line( pub fn parse_export_line(
cursor: Stream<'_>, cursor: Stream<'_>,
ctx: impl Context, ctx: impl Context,
) -> ProjectResult<FileEntry> { ) -> ProjectResult<FileEntryKind> {
let cursor = cursor.trim(); let cursor = cursor.trim();
match cursor.get(0)?.lexeme { match cursor.get(0)?.lexeme {
Lexeme::NS => { Lexeme::NS => {
let (names, cont) = parse_multiname(cursor.step()?, ctx)?; let (names, cont) = parse_multiname(cursor.step()?, ctx)?;
cont.expect_empty()?; cont.expect_empty()?;
let names = (names.into_iter()) let names = (names.into_iter())
.map(|Import { name, path }| match (name, &path[..]) { .map(|Import { name, path, location }| match (name, &path[..]) {
(Some(n), []) => Ok(n), (Some(n), []) => Ok((n, location)),
(None, _) => Err(GlobExport { location: cursor.location() }.rc()), (None, _) => Err(GlobExport { location }.rc()),
_ => Err(NamespacedExport { location: cursor.location() }.rc()), _ => Err(NamespacedExport { location }.rc()),
}) })
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
Ok(FileEntry::Export(names)) Ok(FileEntryKind::Export(names))
}, },
Lexeme::Const | Lexeme::Macro | Lexeme::Module => Lexeme::Const | Lexeme::Macro | Lexeme::Module | Lexeme::Operators(_) =>
Ok(FileEntry::Exported(parse_member(cursor, ctx)?)), Ok(FileEntryKind::Member(Member {
kind: parse_member(cursor, ctx)?,
exported: true,
})),
_ => { _ => {
let err = BadTokenInRegion { let err = BadTokenInRegion {
entry: cursor.get(0)?.clone(), entry: cursor.get(0)?.clone(),
@@ -118,20 +129,24 @@ pub fn parse_export_line(
fn parse_member( fn parse_member(
cursor: Stream<'_>, cursor: Stream<'_>,
ctx: impl Context, ctx: impl Context,
) -> ProjectResult<Member> { ) -> ProjectResult<MemberKind> {
let (typemark, cursor) = cursor.trim().pop()?; let (typemark, cursor) = cursor.trim().pop()?;
match typemark.lexeme { match &typemark.lexeme {
Lexeme::Const => { Lexeme::Const => {
let constant = parse_const(cursor, ctx)?; let constant = parse_const(cursor, ctx)?;
Ok(Member::Constant(constant)) Ok(MemberKind::Constant(constant))
}, },
Lexeme::Macro => { Lexeme::Macro => {
let rule = parse_rule(cursor, ctx)?; let rule = parse_rule(cursor, ctx)?;
Ok(Member::Rule(rule)) Ok(MemberKind::Rule(rule))
}, },
Lexeme::Module => { Lexeme::Module => {
let module = parse_module(cursor, ctx)?; 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 = let err =
@@ -234,8 +249,9 @@ fn parse_exprv(
cursor = leftover; cursor = leftover;
}, },
Lexeme::BS => { Lexeme::BS => {
let dot = ctx.interner().i(".");
let (arg, body) = 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 (arg, _) = parse_exprv(arg, None, ctx.clone())?;
let (body, leftover) = parse_exprv(body, paren, ctx)?; let (body, leftover) = parse_exprv(body, paren, ctx)?;
output.push(Expr { output.push(Expr {

View File

@@ -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<Vec<Tok<String>>, Option<Vec<Tok<String>>>>,
}
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<VName>,
location: &[Tok<String>],
name: Tok<String>
) -> Option<&'a [Tok<String>]> {
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<VName>,
modname: &[Tok<String>],
vname: &[Tok<String>],
) -> Option<&'a [Tok<String>]> {
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)
}
}
}

View File

@@ -0,0 +1,5 @@
// mod alias_cache;
mod resolve_aliases;
mod walk_with_links;
pub use resolve_aliases::resolve_aliases;

View File

@@ -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<VName>,
module: &ProjectMod<VName>,
updated: &impl Fn(&[Tok<String>]) -> bool,
is_root: bool,
) -> ProjectMod<VName> {
if !is_root && !updated(&module.extra.path) {
return module.clone();
}
let process_expr = |expr: &Expr<VName>| {
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::<Vec<_>>();
if !leftovers.is_empty() {
let full_name = (module.extra.path.iter())
.chain(n.iter())
.cloned()
.collect::<Vec<_>>();
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<VName>,
updated: &impl Fn(&[Tok<String>]) -> bool,
) -> ProjectTree<VName> {
ProjectTree(resolve_aliases_rec(&project.0, &project.0, updated, true))
}

View File

@@ -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<N>),
Leaf(&'a ProjectItem<N>),
}
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<String>,
/// Leftover steps
pub tail: BoxedIter<'a, Tok<String>>,
/// 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<N>,
cur: &'a ProjectMod<N>,
prev_tgt: Target<'a, N>,
aliased: bool,
mut path: impl Iterator<Item = Tok<String>> + 'b,
l: bool,
) -> Result<WalkReport<'a, N>, 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<N>,
path: impl Iterator<Item = Tok<String>> + 'a,
l: bool,
) -> Result<WalkReport<'_, N>, 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
}

View File

@@ -11,14 +11,14 @@ use crate::error::{ProjectError, ProjectResult};
use crate::facade::System; use crate::facade::System;
use crate::interner::Interner; use crate::interner::Interner;
use crate::utils::Cache; 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 /// All the data available about a failed source load call
#[derive(Debug)] #[derive(Debug)]
pub struct FileLoadingError { pub struct FileLoadingError {
file: io::Error, file: io::Error,
dir: io::Error, dir: io::Error,
path: Vec<String>, path: VName,
} }
impl ProjectError for FileLoadingError { impl ProjectError for FileLoadingError {
fn description(&self) -> &str { fn description(&self) -> &str {
@@ -54,8 +54,8 @@ pub type IOResult = ProjectResult<Loaded>;
/// Load a file from a path expressed in Rust strings, but relative to /// Load a file from a path expressed in Rust strings, but relative to
/// a root expressed as an OS Path. /// a root expressed as an OS Path.
pub fn load_file(root: &Path, path: &[impl AsRef<str>]) -> IOResult { pub fn load_file(root: &Path, path: &[Tok<String>]) -> IOResult {
let full_path = path.iter().fold(root.to_owned(), |p, s| p.join(s.as_ref())); let full_path = path.iter().fold(root.to_owned(), |p, t| p.join(t.as_str()));
let file_path = full_path.with_extension("orc"); let file_path = full_path.with_extension("orc");
let file_error = match fs::read_to_string(file_path) { let file_error = match fs::read_to_string(file_path) {
Ok(string) => return Ok(Loaded::Code(Rc::new(string))), Ok(string) => return Ok(Loaded::Code(Rc::new(string))),
@@ -68,7 +68,7 @@ pub fn load_file(root: &Path, path: &[impl AsRef<str>]) -> IOResult {
FileLoadingError { FileLoadingError {
file: file_error, file: file_error,
dir: dir_error, dir: dir_error,
path: path.iter().map(|s| s.as_ref().to_string()).collect(), path: path.to_vec(),
} }
.rc(), .rc(),
), ),
@@ -90,10 +90,7 @@ pub fn load_file(root: &Path, path: &[impl AsRef<str>]) -> IOResult {
/// Generates a cached file loader for a directory /// Generates a cached file loader for a directory
pub fn mk_dir_cache(root: PathBuf) -> Cache<'static, VName, IOResult> { pub fn mk_dir_cache(root: PathBuf) -> Cache<'static, VName, IOResult> {
Cache::new(move |vname: VName, _this| -> IOResult { Cache::new(move |vname: VName, _this| load_file(&root, &vname))
let path = vname.iter().map(|t| t.as_str()).collect::<Vec<_>>();
load_file(&root, &path)
})
} }
/// Load a file from the specified path from an embed table /// Load a file from the specified path from an embed table

View File

@@ -1,29 +1,22 @@
use crate::error::{ProjectError, ProjectResult, TooManySupers}; use crate::error::ProjectResult;
use crate::interner::{Interner, Tok}; use crate::interner::{Interner, Tok};
use crate::representations::sourcefile::absolute_path; use crate::representations::sourcefile::absolute_path;
use crate::utils::Substack; use crate::utils::Substack;
use crate::{Location, VName};
pub fn import_abs_path( pub fn import_abs_path(
src_path: &[Tok<String>], src_path: &[Tok<String>],
mod_stack: Substack<Tok<String>>, mod_stack: Substack<Tok<String>>,
import_path: &[Tok<String>], import_path: &[Tok<String>],
i: &Interner, i: &Interner,
) -> ProjectResult<Vec<Tok<String>>> { location: &Location,
) -> ProjectResult<VName> {
// path of module within file // path of module within file
let mod_pathv = mod_stack.iter().rev_vec_clone(); let mod_pathv = mod_stack.iter().rev_vec_clone();
// path of module within compilation // path of module within compilation
let abs_pathv = (src_path.iter()) let abs_pathv =
.chain(mod_pathv.iter()) (src_path.iter()).chain(mod_pathv.iter()).cloned().collect::<Vec<_>>();
.cloned()
.collect::<Vec<_>>();
// preload-target path relative to module // preload-target path relative to module
// preload-target path within compilation // preload-target path within compilation
absolute_path(&abs_pathv, import_path, i).map_err(|_| { absolute_path(&abs_pathv, import_path, i, location)
TooManySupers {
path: import_path.to_vec(),
offender_file: src_path.to_vec(),
offender_mod: mod_pathv,
}
.rc()
})
} }

View File

@@ -2,19 +2,19 @@ use std::hash::Hash;
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use crate::interner::Tok; use crate::{interner::Tok, VName};
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct AliasMap { pub struct AliasMap {
pub targets: HashMap<Vec<Tok<String>>, Vec<Tok<String>>>, pub targets: HashMap<VName, VName>,
pub aliases: HashMap<Vec<Tok<String>>, HashSet<Vec<Tok<String>>>>, pub aliases: HashMap<VName, HashSet<VName>>,
} }
impl AliasMap { impl AliasMap {
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
pub fn link(&mut self, alias: Vec<Tok<String>>, target: Vec<Tok<String>>) { pub fn link(&mut self, alias: VName, target: VName) {
let prev = self.targets.insert(alias.clone(), target.clone()); let prev = self.targets.insert(alias.clone(), target.clone());
debug_assert!(prev.is_none(), "Alias already has a target"); debug_assert!(prev.is_none(), "Alias already has a target");
multimap_entry(&mut self.aliases, &target).insert(alias.clone()); multimap_entry(&mut self.aliases, &target).insert(alias.clone());
@@ -36,7 +36,7 @@ impl AliasMap {
} }
} }
pub fn resolve(&self, alias: &[Tok<String>]) -> Option<&Vec<Tok<String>>> { pub fn resolve(&self, alias: &[Tok<String>]) -> Option<&VName> {
self.targets.get(alias) self.targets.get(alias)
} }
} }

View File

@@ -1,18 +1,16 @@
use hashbrown::HashMap;
use super::alias_map::AliasMap; use super::alias_map::AliasMap;
use super::decls::{InjectedAsFn, UpdatedFn}; use super::decls::{InjectedAsFn, UpdatedFn};
use crate::ast::{Expr, Rule}; use crate::ast::{Expr, Rule};
use crate::interner::Tok; use crate::interner::Tok;
use crate::representations::project::{ProjectExt, ProjectModule}; use crate::representations::project::{ItemKind, ProjectMod};
use crate::representations::tree::{ModEntry, ModMember}; use crate::representations::tree::ModMember;
use crate::representations::VName; use crate::representations::VName;
use crate::utils::Substack; use crate::utils::Substack;
fn resolve_rec( fn resolve_rec(
namespace: &[Tok<String>], namespace: &[Tok<String>],
alias_map: &AliasMap, alias_map: &AliasMap,
) -> Option<Vec<Tok<String>>> { ) -> Option<VName> {
if let Some(alias) = alias_map.resolve(namespace) { if let Some(alias) = alias_map.resolve(namespace) {
Some(alias.clone()) Some(alias.clone())
} else if let Some((foot, body)) = namespace.split_last() { } else if let Some((foot, body)) = namespace.split_last() {
@@ -28,7 +26,7 @@ fn resolve(
namespace: &[Tok<String>], namespace: &[Tok<String>],
alias_map: &AliasMap, alias_map: &AliasMap,
injected_as: &impl InjectedAsFn, injected_as: &impl InjectedAsFn,
) -> Option<Vec<Tok<String>>> { ) -> Option<VName> {
injected_as(namespace).or_else(|| { injected_as(namespace).or_else(|| {
let next_v = resolve_rec(namespace, alias_map)?; let next_v = resolve_rec(namespace, alias_map)?;
Some(injected_as(&next_v).unwrap_or(next_v)) Some(injected_as(&next_v).unwrap_or(next_v))
@@ -45,69 +43,44 @@ fn process_expr(
.unwrap_or_else(|| expr.clone()) .unwrap_or_else(|| expr.clone())
} }
// TODO: replace is_injected with injected_as
/// Replace all aliases with the name they're originally defined as /// Replace all aliases with the name they're originally defined as
fn apply_aliases_rec( fn apply_aliases_rec(
path: Substack<Tok<String>>, path: Substack<Tok<String>>,
module: &ProjectModule<VName>, module: &mut ProjectMod<VName>,
alias_map: &AliasMap, alias_map: &AliasMap,
injected_as: &impl InjectedAsFn, injected_as: &impl InjectedAsFn,
updated: &impl UpdatedFn, updated: &impl UpdatedFn,
) -> ProjectModule<VName> { ) {
let items = (module.items.iter()) for (name, entry) in module.entries.iter_mut() {
.map(|(name, ent)| { match &mut entry.member {
let ModEntry { exported, member } = ent; ModMember::Sub(sub) => {
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 subpath = path.push(name.clone());
let new_mod = if !updated(&subpath.iter().rev_vec_clone()) { apply_aliases_rec(subpath, sub, alias_map, injected_as, updated)
module.clone()
} else {
apply_aliases_rec(subpath, module, alias_map, injected_as, updated)
};
ModMember::Sub(new_mod)
}, },
}; ModMember::Item(it) => match &mut it.kind {
(name.clone(), ModEntry { exported: *exported, member }) ItemKind::None => (),
}) ItemKind::Const(expr) =>
.collect::<HashMap<_, _>>(); *expr = process_expr(expr, alias_map, injected_as),
let rules = (module.extra.rules.iter()) ItemKind::Alias(name) =>
.map(|rule| { if let Some(alt) = alias_map.resolve(&name) {
let Rule { pattern, prio, template } = rule; *name = alt.clone()
Rule { },
prio: *prio, },
pattern: (pattern.iter()) _ => (),
.map(|expr| process_expr(expr, alias_map, injected_as)) }
.collect::<Vec<_>>(), }
template: (template.iter()) for Rule { pattern, prio, template } in module.extra.rules.iter_mut() {
.map(|expr| process_expr(expr, alias_map, injected_as)) for expr in pattern.iter_mut().chain(template.iter_mut()) {
.collect::<Vec<_>>(), *expr = process_expr(expr, alias_map, injected_as)
} }
})
.collect::<Vec<_>>();
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(),
},
} }
} }
pub fn apply_aliases( pub fn apply_aliases(
module: &ProjectModule<VName>, module: &mut ProjectMod<VName>,
alias_map: &AliasMap, alias_map: &AliasMap,
injected_as: &impl InjectedAsFn, injected_as: &impl InjectedAsFn,
updated: &impl UpdatedFn, updated: &impl UpdatedFn,
) -> ProjectModule<VName> { ) {
apply_aliases_rec(Substack::Bottom, module, alias_map, injected_as, updated) apply_aliases_rec(Substack::Bottom, module, alias_map, injected_as, updated)
} }

View File

@@ -1,134 +1,32 @@
use super::alias_map::AliasMap; use super::alias_map::AliasMap;
use super::decls::UpdatedFn; use super::decls::UpdatedFn;
use crate::error::{NotExported, NotFound, ProjectError, ProjectResult}; use crate::error::ProjectResult;
use crate::interner::Tok; use crate::interner::Tok;
use crate::pipeline::project_tree::split_path; use crate::representations::project::{ProjectMod, ProjectTree};
use crate::representations::project::{ProjectModule, ProjectTree}; use crate::representations::tree::ModMember;
use crate::representations::tree::{ModMember, WalkErrorKind};
use crate::representations::VName; use crate::representations::VName;
use crate::utils::{pushed, unwrap_or, Substack}; use crate::utils::{pushed, unwrap_or};
/// Assert that a module identified by a path can see a given symbol
fn assert_visible(
source: &[Tok<String>], // must point to a file or submodule
target: &[Tok<String>], // may point to a symbol or module of any kind
project: &ProjectTree<VName>,
) -> 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(())
}
}
/// Populate target and alias maps from the module tree recursively /// Populate target and alias maps from the module tree recursively
fn collect_aliases_rec( fn collect_aliases_rec(
path: Substack<Tok<String>>, path: Vec<Tok<String>>,
module: &ProjectModule<VName>, module: &ProjectMod<VName>,
project: &ProjectTree<VName>, project: &ProjectTree<VName>,
alias_map: &mut AliasMap, alias_map: &mut AliasMap,
updated: &impl UpdatedFn, updated: &impl UpdatedFn,
) -> ProjectResult<()> { ) -> ProjectResult<()> {
// Assume injected module has been alias-resolved // Assume injected module has been alias-resolved
let mod_path_v = path.iter().rev_vec_clone(); if !updated(&path) {
if !updated(&mod_path_v) {
return Ok(()); return Ok(());
}; };
for (name, target_mod_name) in module.extra.imports_from.iter() { for (name, target_sym_v) in module.extra.imports_from.iter() {
let target_sym_v = pushed(target_mod_name, name.clone()); let sym_path_v = pushed(&path, name.clone());
assert_visible(&mod_path_v, &target_sym_v, project)?; alias_map.link(sym_path_v, target_sym_v.clone());
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() for (name, entry) in module.entries.iter() {
})?
.clone();
alias_map.link(sym_path_v, target_sym);
}
for (name, entry) in module.items.iter() {
let submodule = unwrap_or!(&entry.member => ModMember::Sub; continue); let submodule = unwrap_or!(&entry.member => ModMember::Sub; continue);
collect_aliases_rec( collect_aliases_rec(
path.push(name.clone()), pushed(&path, name.clone()),
submodule, submodule,
project, project,
alias_map, alias_map,
@@ -140,10 +38,10 @@ fn collect_aliases_rec(
/// Populate target and alias maps from the module tree /// Populate target and alias maps from the module tree
pub fn collect_aliases( pub fn collect_aliases(
module: &ProjectModule<VName>, module: &ProjectMod<VName>,
project: &ProjectTree<VName>, project: &ProjectTree<VName>,
alias_map: &mut AliasMap, alias_map: &mut AliasMap,
updated: &impl UpdatedFn, updated: &impl UpdatedFn,
) -> ProjectResult<()> { ) -> ProjectResult<()> {
collect_aliases_rec(Substack::Bottom, module, project, alias_map, updated) collect_aliases_rec(Vec::new(), module, project, alias_map, updated)
} }

View File

@@ -1,8 +1,8 @@
use trait_set::trait_set; use trait_set::trait_set;
use crate::interner::Tok; use crate::{interner::Tok, VName};
trait_set! { trait_set! {
pub trait InjectedAsFn = Fn(&[Tok<String>]) -> Option<Vec<Tok<String>>>; pub trait InjectedAsFn = Fn(&[Tok<String>]) -> Option<VName>;
pub trait UpdatedFn = Fn(&[Tok<String>]) -> bool; pub trait UpdatedFn = Fn(&[Tok<String>]) -> bool;
} }

View File

@@ -3,5 +3,6 @@ mod apply_aliases;
mod collect_aliases; mod collect_aliases;
mod decls; mod decls;
mod resolve_imports; mod resolve_imports;
mod alias_cache;
pub use resolve_imports::resolve_imports; pub use resolve_imports::resolve_imports;

View File

@@ -1,3 +1,4 @@
use super::alias_cache::AliasCache;
use super::alias_map::AliasMap; use super::alias_map::AliasMap;
use super::apply_aliases::apply_aliases; use super::apply_aliases::apply_aliases;
use super::collect_aliases::collect_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 /// Follow import chains to locate the original name of all tokens, then
/// replace these aliases with the original names throughout the tree /// replace these aliases with the original names throughout the tree
pub fn resolve_imports( pub fn resolve_imports(
project: ProjectTree<VName>, mut project: ProjectTree<VName>,
injected_as: &impl InjectedAsFn, injected_as: &impl InjectedAsFn,
updated: &impl UpdatedFn, updated: &impl UpdatedFn,
) -> ProjectResult<ProjectTree<VName>> { ) -> ProjectResult<ProjectTree<VName>> {
let mut map = AliasMap::new(); let mut cache = AliasCache::new(&project);
collect_aliases(&project.0, &project, &mut map, updated)?; // let mut map = AliasMap::new();
let new_mod = apply_aliases(&project.0, &map, injected_as, updated); // collect_aliases(&project.0, &project, &mut map, updated)?;
Ok(ProjectTree(new_mod)) // apply_aliases(&mut project.0, &map, injected_as, updated);
Ok(project)
} }

View File

@@ -1,7 +1,8 @@
//! Loading Orchid modules from source //! Loading Orchid modules from source
pub mod file_loader; pub mod file_loader;
mod import_abs_path; mod import_abs_path;
mod import_resolution; // mod import_resolution;
mod dealias;
mod parse_layer; mod parse_layer;
mod project_tree; mod project_tree;
mod source_loader; mod source_loader;

View File

@@ -1,11 +1,11 @@
use std::rc::Rc; use super::dealias::resolve_aliases;
use super::file_loader::IOResult; 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::error::ProjectResult;
use crate::interner::{Interner, Tok}; use crate::interner::{Interner, Tok};
use crate::representations::sourcefile::FileEntry; use crate::representations::sourcefile::FileEntry;
use crate::representations::VName; use crate::representations::VName;
use crate::utils::never;
use crate::ProjectTree; use crate::ProjectTree;
/// Using an IO callback, produce a project tree that includes the given /// Using an IO callback, produce a project tree that includes the given
@@ -23,26 +23,17 @@ pub fn parse_layer<'a>(
prelude: &[FileEntry], prelude: &[FileEntry],
i: &Interner, i: &Interner,
) -> ProjectResult<ProjectTree<VName>> { ) -> ProjectResult<ProjectTree<VName>> {
// A path is injected if it is walkable in the injected tree let (preparsed, source) =
let injected_as = |path: &[Tok<String>]| {
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<Vec<Tok<String>>>| {
let module = environment.0.walk_ref(&path, false).ok()?;
Some(Rc::new(module.extra.exports.keys().cloned().collect()))
};
let source =
source_loader::load_source(targets, prelude, i, loader, &|path| { 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 tree =
let sum = ProjectTree(environment.0.clone().overlay(tree.0.clone())); project_tree::rebuild_tree(&source, preparsed, environment, prelude, i)?;
let sum = ProjectTree(never::unwrap_always(
environment.0.clone().overlay(tree.0.clone()),
));
let resolvd = let resolvd =
import_resolution::resolve_imports(sum, &injected_as, &|path| { resolve_aliases(sum, &|path| tree.0.walk1_ref(&[], path, false).is_ok());
tree.0.walk_ref(path, false).is_ok()
})?;
// Addition among modules favours the left hand side. // Addition among modules favours the left hand side.
Ok(resolvd) Ok(resolvd)
} }

View File

@@ -1,46 +0,0 @@
use crate::interner::Tok;
use crate::representations::sourcefile::{FileEntry, Member, ModuleBlock};
fn member_rec(
// object
member: Member,
// context
path: &[Tok<String>],
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<FileEntry>,
// context
mod_path: &[Tok<String>],
prelude: &[FileEntry],
) -> Vec<FileEntry> {
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<FileEntry>,
path: &[Tok<String>],
prelude: &[FileEntry],
) -> Vec<FileEntry> {
entv_rec(data, path, prelude)
}

View File

@@ -1,246 +1,144 @@
use hashbrown::HashMap; use hashbrown::HashMap;
use itertools::Itertools; use itertools::{Either, Itertools};
use super::collect_ops; use super::import_tree::ImpMod;
use super::collect_ops::InjectedOperatorsFn; use crate::ast::{Constant, Rule};
use super::parse_file::parse_file; use crate::error::{ConflictingRoles, ProjectError, ProjectResult};
use crate::ast::{Clause, Constant, Expr}; use crate::pipeline::source_loader::{PreItem, PreMod};
use crate::error::{ProjectError, ProjectResult, TooManySupers}; use crate::representations::project::{
use crate::interner::{Interner, Tok}; ImpReport, ItemKind, ProjectEntry, ProjectExt, ProjectItem,
use crate::pipeline::source_loader::{LoadedSource, LoadedSourceTable}; };
use crate::representations::project::{ProjectExt, ProjectTree}; use crate::sourcefile::{
use crate::representations::sourcefile::{absolute_path, FileEntry, Member}; FileEntry, FileEntryKind, Member, MemberKind, ModuleBlock,
use crate::representations::tree::{ModEntry, ModMember, Module}; };
use crate::representations::{NameLike, VName}; use crate::tree::{ModEntry, ModMember, Module};
use crate::tree::{WalkError, WalkErrorKind}; use crate::utils::get_or_default;
use crate::utils::iter::{box_empty, box_once}; use crate::utils::pushed::pushed_ref;
use crate::utils::{pushed, unwrap_or, Substack}; use crate::{Tok, VName};
#[derive(Debug)] pub struct TreeReport {
struct ParsedSource<'a> { pub entries: HashMap<Tok<String>, ProjectEntry<VName>>,
path: Vec<Tok<String>>, pub rules: Vec<Rule<VName>>,
loaded: &'a LoadedSource, /// Maps imported symbols to the absolute paths of the modules they are
parsed: Vec<FileEntry>, /// imported from
} pub imports_from: HashMap<Tok<String>, ImpReport<VName>>,
/// 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<String>],
proj: &'a ProjectTree<impl NameLike>,
) -> Result<(&'a [Tok<String>], &'a [Tok<String>]), 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<Tok<String>>,
preparsed: &Module<impl Clone, impl Clone>,
// data
data: Vec<FileEntry>,
// context
i: &Interner,
filepath_len: usize,
) -> ProjectResult<Module<Expr<VName>, ProjectExt<VName>>> {
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::<Vec<_>>();
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::<Result<HashMap<_, _>, _>>()?;
let exports = (data.iter())
.flat_map(|ent| {
let mk_ent = |name: Tok<String>| (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::<HashMap<_, _>>();
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::<Vec<_>>();
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::<Result<HashMap<_, _>, _>>()?;
Ok(Module {
imports,
items,
extra: ProjectExt {
imports_from,
exports,
rules,
file: Some(path_v[..filepath_len].to_vec()),
},
})
}
fn files_to_module(
path: Substack<Tok<String>>,
files: Vec<ParsedSource>,
i: &Interner,
) -> ProjectResult<Module<Expr<VName>, ProjectExt<VName>>> {
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::<Vec<_>>();
let module = files_to_module(subpath, files_v, i)?;
let member = ModMember::Sub(module);
Ok((namespace, ModEntry { exported: true, member }))
})
.collect::<Result<HashMap<_, _>, _>>()?;
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 fn build_tree( pub fn build_tree(
files: LoadedSourceTable, path: &VName,
i: &Interner, source: Vec<FileEntry>,
Module { entries, extra }: PreMod,
imports: ImpMod,
prelude: &[FileEntry], prelude: &[FileEntry],
injected: &impl InjectedOperatorsFn, ) -> ProjectResult<TreeReport> {
) -> ProjectResult<ProjectTree<VName>> { let source =
assert!(!files.is_empty(), "A tree requires at least one module"); source.into_iter().chain(prelude.iter().cloned()).collect::<Vec<_>>();
let ops_cache = collect_ops::mk_cache(&files, injected); let (imports_from, mut submod_imports) = (imports.entries.into_iter())
let mut entries = files .partition_map::<HashMap<_, _>, HashMap<_, _>, _, _, _>(
.iter() |(n, ent)| match ent.member {
.map(|(path, loaded)| { ModMember::Item(it) => Either::Left((n, it)),
Ok((path, loaded, parse_file(path, &files, &ops_cache, i, prelude)?)) 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::<ProjectResult<Vec<_>>>()?; .collect();
// sort by similarity, then longest-first let (pre_subs, pre_items) = (entries.into_iter())
entries.sort_unstable_by(|a, b| a.0.cmp(b.0).reverse()); .partition_map::<HashMap<_, _>, HashMap<_, _>, _, _, _>(
let files = entries |(k, ModEntry { exported, member })| match member {
.into_iter() ModMember::Sub(s) => Either::Left((k, (exported, s))),
.map(|(path, loaded, parsed)| ParsedSource { ModMember::Item(it) => Either::Right((k, (exported, it))),
loaded, },
parsed, );
path: path.clone(), 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::<Vec<_>>(); .map(|(k, exported, pre, imp)| {
Ok(ProjectTree(files_to_module(Substack::Bottom, files, i)?)) 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::<Result<HashMap<_, _>, _>>()?;
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 })
} }

View File

@@ -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<Rc<HashSet<Tok<String>>>>;
pub type ExportedOpsCache<'a> = Cache<'a, Sym, OpsResult>;
trait_set! {
pub trait InjectedOperatorsFn = Fn(Sym) -> Option<Rc<HashSet<Tok<String>>>>;
}
fn coprefix<T: Eq>(
l: impl Iterator<Item = T>,
r: impl Iterator<Item = T>,
) -> 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::<HashSet<_>>();
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::<HashSet<_>>();
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))
}

View File

@@ -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;

View File

@@ -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<impl Clone, impl Clone>,
ops: &mut HashSet<Tok<String>>,
) {
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<String>],
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()))
}

View File

@@ -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<ImpReport<VName>, ()>;
/// Assert that a module identified by a path can see a given symbol
fn assert_visible<'a>(
source: &'a [Tok<String>], // must point to a file or submodule
target: &'a [Tok<String>],
root: &'a Module<impl Clone + Display, impl Clone + Display>,
) -> 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<String>], // must point to a file or submodule
target: &'a [Tok<String>],
first: &'a Module<impl Clone + Display, impl Clone + Display>,
fallback: &'a Module<impl Clone + Display, impl Clone + Display>,
) -> 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<TItem, impl Clone>,
abs_path: Rc<VName>,
is_op: impl Fn(&TItem) -> bool + 'a,
) -> impl Iterator<Item = (Tok<String>, 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<VName>,
i: &Interner,
) -> ProjectResult<ImpMod> {
let imports = pre.extra.details().map_or(&[][..], |e| &e.imports[..]);
let entries = (imports.iter())
// imports become leaf sets
.map(|Import { name, path, location }| -> ProjectResult<BoxedIter<_>> {
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<dyn ProjectError> {
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::<Result<HashMap<_, _>, _>>()?;
Ok(Module { extra: (), entries })
}

View File

@@ -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 build_tree;
mod collect_ops; mod import_tree;
mod normalize_imports; mod rebuild_tree;
mod parse_file;
mod prefix;
pub use build_tree::{build_tree, split_path}; pub use rebuild_tree::rebuild_tree;
pub use collect_ops::InjectedOperatorsFn;

View File

@@ -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<Tok<String>>,
preparsed: &Module<impl Clone, impl Clone>,
// object
member: Member,
// context
path: &[Tok<String>],
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<Tok<String>>,
preparsed: &Module<impl Clone, impl Clone>,
// object
data: Vec<FileEntry>,
// context
mod_path: &[Tok<String>],
ops_cache: &ExportedOpsCache,
i: &Interner,
) -> Vec<FileEntry> {
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::<Vec<_>>();
Box::new(imports.into_iter()) as BoxedIter<Import>
} 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<impl Clone, impl Clone>,
data: Vec<FileEntry>,
path: &[Tok<String>],
ops_cache: &ExportedOpsCache,
i: &Interner,
) -> Vec<FileEntry> {
entv_rec(Substack::Bottom, preparsed, data, path, ops_cache, i)
}

View File

@@ -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<String>],
loaded: &LoadedSourceTable,
ops_cache: &ExportedOpsCache,
i: &Interner,
prelude: &[FileEntry],
) -> ProjectResult<Vec<FileEntry>> {
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::<Vec<_>>();
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)
}

View File

@@ -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<Tok<String>>,
// object
data: Member,
// context
path: &[Tok<String>],
ops_cache: &ExportedOpsCache,
i: &Interner,
) -> Member {
let prefix = (path.iter())
.cloned()
.chain(mod_stack.iter().rev_vec_clone().into_iter())
.collect::<Vec<_>>();
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<Tok<String>>,
// object
data: Vec<FileEntry>,
// context
path: &[Tok<String>],
ops_cache: &ExportedOpsCache,
i: &Interner,
) -> Vec<FileEntry> {
(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<FileEntry>,
path: &[Tok<String>],
ops_cache: &ExportedOpsCache,
i: &Interner,
) -> Vec<FileEntry> {
entv_rec(Substack::Bottom, data, path, ops_cache, i)
}

View File

@@ -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<Tok<String>>,
pre: PreMod,
imports: ImpMod,
source: &LoadedSourceTable,
prelude: &[FileEntry],
i: &Interner,
) -> ProjectResult<ProjectMod<VName>> {
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<Tok<String>>,
pre: PreMod,
mut imports: ImpMod,
source: &LoadedSourceTable,
prelude: &[FileEntry],
i: &Interner,
) -> ProjectResult<ProjectMod<VName>> {
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::<Result<HashMap<_, _>, _>>()?;
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<VName>,
prelude: &[FileEntry],
i: &Interner,
) -> ProjectResult<ProjectTree<VName>> {
let imports =
import_tree(Vec::new(), &preparsed.0, &preparsed, prev_root, i)?;
rebuild_dir(Vec::new(), preparsed.0, imports, source, prelude, i)
.map(ProjectTree)
}

View File

@@ -1,7 +1,8 @@
use std::iter; use hashbrown::HashMap;
use super::loaded_source::{LoadedSource, LoadedSourceTable}; use super::loaded_source::{LoadedSource, LoadedSourceTable};
use super::preparse::preparse; use super::preparse::preparse;
use super::{PreExtra, Preparsed};
use crate::error::{ use crate::error::{
NoTargets, ProjectError, ProjectResult, UnexpectedDirectory, NoTargets, ProjectError, ProjectResult, UnexpectedDirectory,
}; };
@@ -9,67 +10,79 @@ use crate::interner::{Interner, Tok};
use crate::pipeline::file_loader::{IOResult, Loaded}; use crate::pipeline::file_loader::{IOResult, Loaded};
use crate::pipeline::import_abs_path::import_abs_path; use crate::pipeline::import_abs_path::import_abs_path;
use crate::representations::sourcefile::FileEntry; 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::utils::{split_max_prefix, unwrap_or};
use crate::Location;
/// Load the source at the given path or all within if it's a collection, /// Load the source at the given path or all within if it's a collection,
/// and all sources imported from these. /// and all sources imported from these.
fn load_abs_path_rec( fn load_abs_path_rec(
abs_path: &[Tok<String>], abs_path: &[Tok<String>],
table: &mut LoadedSourceTable, mut all: Preparsed,
source: &mut LoadedSourceTable,
prelude: &[FileEntry], prelude: &[FileEntry],
i: &Interner, i: &Interner,
get_source: &impl Fn(&[Tok<String>]) -> IOResult, get_source: &impl Fn(&[Tok<String>]) -> IOResult,
is_injected_module: &impl Fn(&[Tok<String>]) -> bool, is_injected_module: &impl Fn(&[Tok<String>]) -> bool,
) -> ProjectResult<()> { ) -> ProjectResult<Preparsed> {
// # Termination // # Termination
// //
// Every recursion of this function either // 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 // - recursively traverses a directory tree
// therefore eventually the function exits, assuming that the directory tree // therefore eventually the function exits, assuming that the directory tree
// contains no cycles. // 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 // try splitting the path to file, swallowing any IO errors
let name_split = split_max_prefix(abs_path, &|p| { let name_split = split_max_prefix(abs_path, &|p| {
get_source(p).map(|l| l.is_code()).unwrap_or(false) get_source(p).map(|l| l.is_code()).unwrap_or(false)
}); });
if let Some((filename, _)) = name_split { 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 // if the filename is valid, load, preparse and record this file
let text = unwrap_or!(get_source(filename)? => Loaded::Code; { let text = unwrap_or!(get_source(filename)? => Loaded::Code; {
return Err(UnexpectedDirectory { path: filename.to_vec() }.rc()) return Err(UnexpectedDirectory { path: filename.to_vec() }.rc())
}); });
let preparsed = preparse( source.insert(filename.to_vec(), LoadedSource { text: text.clone() });
Interner::extern_all(filename), let preparsed = preparse(filename.to_vec(), text.as_str(), prelude, i)?;
text.as_str(),
prelude,
i,
)?;
table.insert(filename.to_vec(), LoadedSource {
text,
preparsed: preparsed.clone(),
});
// recurse on all imported modules // recurse on all imported modules
preparsed.0.visit_all_imports(&mut |modpath, _module, import| { // will be taken and returned by the closure. None iff an error is thrown
let abs_pathv = all = preparsed.0.search_all(all, &mut |modpath,
import_abs_path(filename, modpath, &import.nonglob_path(), i)?; 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) { if abs_path.starts_with(&abs_pathv) {
return Ok(()); continue;
} }
// recurse on imported module // recurse on imported module
load_abs_path_rec( all = load_abs_path_rec(
&abs_pathv, &abs_pathv,
table, all,
source,
prelude, prelude,
i, i,
get_source, get_source,
is_injected_module, is_injected_module,
) )?;
}) }
Ok(all)
})?;
// Combine the trees
all.0.overlay(preparsed.0).map(Preparsed)
} else { } else {
// If the path is not within a file, load it as directory // If the path is not within a file, load it as directory
let coll = match get_source(abs_path) { 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; 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 // exit without error if it was injected, or raise any IO error that was
// previously swallowed // 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 // recurse on all files and folders within
for item in coll.iter() { for item in coll.iter() {
let abs_subpath = (abs_path.iter()) let abs_subpath = pushed_ref(abs_path, i.i(item));
.cloned() all = load_abs_path_rec(
.chain(iter::once(i.i(item)))
.collect::<Vec<_>>();
load_abs_path_rec(
&abs_subpath, &abs_subpath,
table, all,
source,
prelude, prelude,
i, i,
get_source, get_source,
is_injected_module, is_injected_module,
)? )?;
} }
Ok(()) Ok(all)
} }
} }
@@ -115,19 +126,22 @@ pub fn load_source<'a>(
i: &Interner, i: &Interner,
get_source: &impl Fn(&[Tok<String>]) -> IOResult, get_source: &impl Fn(&[Tok<String>]) -> IOResult,
is_injected_module: &impl Fn(&[Tok<String>]) -> bool, is_injected_module: &impl Fn(&[Tok<String>]) -> bool,
) -> ProjectResult<LoadedSourceTable> { ) -> ProjectResult<(Preparsed, LoadedSourceTable)> {
let mut table = LoadedSourceTable::new(); let mut table = LoadedSourceTable::new();
let mut all =
Preparsed(Module { extra: PreExtra::Dir, entries: HashMap::new() });
let mut any_target = false; let mut any_target = false;
for target in targets { for target in targets {
any_target |= true; any_target |= true;
load_abs_path_rec( all = load_abs_path_rec(
target, target,
all,
&mut table, &mut table,
prelude, prelude,
i, i,
get_source, get_source,
is_injected_module, is_injected_module,
)? )?;
} }
if any_target { Ok(table) } else { Err(NoTargets.rc()) } if any_target { Ok((all, table)) } else { Err(NoTargets.rc()) }
} }

View File

@@ -1,13 +1,11 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use super::preparse::Preparsed;
use crate::representations::VName; use crate::representations::VName;
#[derive(Debug)] #[derive(Debug)]
pub struct LoadedSource { pub struct LoadedSource {
pub text: Rc<String>, pub text: Rc<String>,
pub preparsed: Preparsed,
} }
pub type LoadedSourceTable = HashMap<VName, LoadedSource>; pub type LoadedSourceTable = HashMap<VName, LoadedSource>;

View File

@@ -18,7 +18,8 @@
mod load_source; mod load_source;
mod loaded_source; mod loaded_source;
mod preparse; mod preparse;
mod types;
pub use load_source::load_source; pub use load_source::load_source;
pub use loaded_source::{LoadedSource, LoadedSourceTable}; pub use loaded_source::{LoadedSource, LoadedSourceTable};
pub use preparse::Preparsed; pub use types::{PreExtra, PreFileExt, PreItem, PreMod, PreSubExt, Preparsed};

View File

@@ -1,99 +1,169 @@
use std::hash::Hash;
use std::rc::Rc; use std::rc::Rc;
use hashbrown::HashMap; use hashbrown::HashMap;
use itertools::Itertools;
use crate::ast::Constant; use super::types::{PreFileExt, PreItem, PreSubExt};
use crate::error::{ProjectError, ProjectResult, VisibilityMismatch}; use super::{PreExtra, Preparsed};
use crate::ast::{Clause, Constant, Expr};
use crate::error::{
ConflictingRoles, ProjectError, ProjectResult, VisibilityMismatch,
};
use crate::interner::Interner; use crate::interner::Interner;
use crate::parse::{self, ParsingContext}; use crate::parse::{self, ParsingContext};
use crate::representations::sourcefile::{ use crate::representations::sourcefile::{FileEntry, MemberKind};
imports, normalize_namespaces, FileEntry, Member,
};
use crate::representations::tree::{ModEntry, ModMember, Module}; 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)] struct FileReport {
pub struct Preparsed(pub Module<(), ()>); entries: HashMap<Tok<String>, ModEntry<PreItem, PreExtra>>,
patterns: Vec<Vec<Expr<VName>>>,
/// Add an internal flat name if it does not exist yet imports: Vec<Import>,
fn add_intern<K: Eq + Hash>(map: &mut HashMap<K, ModEntry<(), ()>>, 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<K: Eq + Hash>(map: &mut HashMap<K, ModEntry<(), ()>>, k: K) {
if let Some(entry) = map.get_mut(&k) {
entry.exported = true
} else {
map.insert(k, ModEntry { exported: true, member: ModMember::Item(()) });
}
} }
/// Convert source lines into a module /// Convert source lines into a module
fn to_module(src: &[FileEntry], prelude: &[FileEntry]) -> Module<(), ()> { fn to_module(
let all_src = || src.iter().chain(prelude.iter()); file: &[Tok<String>],
let imports = imports(all_src()).cloned().collect::<Vec<_>>(); path: VName,
let mut items = all_src() src: Vec<FileEntry>,
.filter_map(|ent| match ent { prelude: &[FileEntry],
FileEntry::Internal(Member::Module(ns)) => { ) -> ProjectResult<FileReport> {
let member = ModMember::Sub(to_module(&ns.body, prelude)); let mut imports = Vec::new();
let entry = ModEntry { exported: false, member }; let mut patterns = Vec::new();
Some((ns.name.clone(), entry)) let mut items = HashMap::<Tok<String>, (bool, PreItem)>::new();
let mut to_export = HashMap::<Tok<String>, Vec<Location>>::new();
let mut submods =
HashMap::<Tok<String>, (bool, Vec<Location>, Vec<FileEntry>)>::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)
}, },
FileEntry::Exported(Member::Module(ns)) => { FileEntryKind::Member(Member { exported, kind }) => match kind {
let member = ModMember::Sub(to_module(&ns.body, prelude)); MemberKind::Constant(Constant { name, .. }) => {
let entry = ModEntry { exported: true, member }; let (prev_exported, it) = get_or_default(&mut items, &name);
Some((ns.name.clone(), entry)) 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;
}, },
_ => None, 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::<()>
});
}
}
},
},
_ => (),
}
}
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,
}) })
.collect::<HashMap<_, _>>(); .map_err(|_| ConflictingRoles { locations, name }.rc())?;
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())
},
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)
} }
}, for (item, locations) in to_export {
FileEntry::Exported(Member::Rule(rule)) => { get_or_make(&mut entries, &item, || ModEntry {
let names = rule.collect_single_names(); member: ModMember::Item(PreItem {
for name in names { is_op: false,
add_export(&mut items, name) has_value: false,
location: locations[0].clone(),
}),
exported: true,
})
.exported = true
} }
}, Ok(FileReport { entries, imports, patterns })
}
}
Module { imports, items, extra: () }
} }
/// Preparse the module. At this stage, only the imports and /// Preparse the module. At this stage, only the imports and
/// names defined by the module can be parsed /// names defined by the module can be parsed
pub fn preparse( pub fn preparse(
file: Vec<String>, file: VName,
source: &str, source: &str,
prelude: &[FileEntry], prelude: &[FileEntry],
i: &Interner, i: &Interner,
) -> ProjectResult<Preparsed> { ) -> ProjectResult<Preparsed> {
// Parse with no operators // 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 entries = parse::parse2(source, ctx)?;
let normalized = normalize_namespaces(Box::new(entries.into_iter())) let FileReport { entries, imports, patterns } =
.map_err(|namespace| { to_module(&file, file.clone(), entries, prelude)?;
VisibilityMismatch { namespace, file: Rc::new(file.clone()) }.rc() let mut module = Module {
})?; entries,
Ok(Preparsed(to_module(&normalized, prelude))) 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))
} }

View File

@@ -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<Import>,
pub patterns: Vec<Vec<Expr<VName>>>,
}
#[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<Self>;
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<PreItem, PreExtra>;
#[derive(Debug, Clone)]
pub struct Preparsed(pub PreMod);

View File

@@ -17,6 +17,7 @@ use super::location::Location;
use super::namelike::{NameLike, VName}; use super::namelike::{NameLike, VName};
use super::primitive::Primitive; use super::primitive::Primitive;
use crate::interner::Tok; use crate::interner::Tok;
use crate::parse::print_nat16;
use crate::utils::map_rc; use crate::utils::map_rc;
/// A [Clause] with associated metadata /// A [Clause] with associated metadata
@@ -365,7 +366,7 @@ impl Rule<VName> {
/// Return a list of all names that don't contain a namespace separator `::`. /// Return a list of all names that don't contain a namespace separator `::`.
/// These are exported when the rule is exported /// These are exported when the rule is exported
pub fn collect_single_names(&self) -> Vec<Tok<String>> { pub fn collect_single_names(&self) -> VName {
let mut names = Vec::new(); let mut names = Vec::new();
for e in self.pattern.iter() { for e in self.pattern.iter() {
e.search_all(&mut |e| { e.search_all(&mut |e| {
@@ -385,9 +386,9 @@ impl<N: NameLike> Display for Rule<N> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,
"{} ={}=> {}", "rule {} ={}=> {}",
self.pattern.iter().join(" "), self.pattern.iter().join(" "),
self.prio, print_nat16(self.prio),
self.template.iter().join(" ") self.template.iter().join(" ")
) )
} }
@@ -404,6 +405,6 @@ pub struct Constant {
impl Display for Constant { impl Display for Constant {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} := {}", *self.name, self.value) write!(f, "const {} := {}", *self.name, self.value)
} }
} }

View File

@@ -34,8 +34,8 @@ impl ProjectError for Error {
fn description(&self) -> &str { fn description(&self) -> &str {
match self.kind { match self.kind {
ErrorKind::BadGroup(_) => ErrorKind::BadGroup(_) =>
"Only `(...)` may be converted to typed lambdas. `[...]` and `{{...}}` \ "Only `(...)` may be converted to postmacro. `[...]` and `{...}` left \
left in the code are signs of incomplete macro execution", in the code are signs of incomplete macro execution",
ErrorKind::EmptyS => "`()` as a clause is meaningless in lambda calculus", ErrorKind::EmptyS => "`()` as a clause is meaningless in lambda calculus",
ErrorKind::InvalidArg => "Argument names can only be Name nodes", ErrorKind::InvalidArg => "Argument names can only be Name nodes",
ErrorKind::Placeholder => ErrorKind::Placeholder =>

View File

@@ -2,14 +2,15 @@ use std::ops::Add;
use hashbrown::HashMap; use hashbrown::HashMap;
use super::project::{ItemKind, ProjectItem};
use crate::ast::{Clause, Expr}; use crate::ast::{Clause, Expr};
use crate::foreign::{Atom, Atomic, ExternFn}; use crate::foreign::{Atom, Atomic, ExternFn};
use crate::interner::Tok; use crate::interner::Tok;
use crate::representations::location::Location; 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::tree::{ModEntry, ModMember, Module};
use crate::representations::{Primitive, VName}; 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 /// A lightweight module tree that can be built declaratively by hand to
/// describe libraries of external functions in Rust. It implements [Add] for /// describe libraries of external functions in Rust. It implements [Add] for
@@ -90,29 +91,28 @@ fn from_const_tree_rec(
path: Substack<Tok<String>>, path: Substack<Tok<String>>,
consts: HashMap<Tok<String>, ConstTree>, consts: HashMap<Tok<String>, ConstTree>,
file: &[Tok<String>], file: &[Tok<String>],
) -> ProjectModule<VName> { ) -> ProjectMod<VName> {
let mut items = HashMap::new(); let mut items = HashMap::new();
let path_v = path.iter().rev_vec_clone();
for (name, item) in consts { for (name, item) in consts {
items.insert(name.clone(), ModEntry { items.insert(name.clone(), ModEntry {
exported: true, exported: true,
member: match item { 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) => ConstTree::Tree(t) =>
ModMember::Sub(from_const_tree_rec(path.push(name), t, file)), 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 { Module {
items, entries: items,
imports: vec![],
extra: ProjectExt { extra: ProjectExt {
exports,
file: Some(file.to_vec()), file: Some(file.to_vec()),
..Default::default() imports_from: HashMap::new(),
rules: Vec::new(),
path: path.iter().rev_vec_clone(),
}, },
} }
} }

View File

@@ -15,8 +15,6 @@ use super::primitive::Primitive;
use super::Literal; use super::Literal;
use crate::Sym; use crate::Sym;
// TODO: implement Debug, Eq and Hash with cycle detection
/// An expression with metadata /// An expression with metadata
pub struct Expr { pub struct Expr {
/// The actual value /// The actual value

View File

@@ -1,21 +1,23 @@
use std::fmt::Display; use std::fmt::{Debug, Display};
use std::ops::Range; use std::ops::Range;
use std::rc::Rc; use std::rc::Rc;
use itertools::Itertools; use itertools::Itertools;
use crate::VName;
/// A location in a file, identifies a sequence of suspect characters for any /// A location in a file, identifies a sequence of suspect characters for any
/// error. Meaningful within the context of a project. /// error. Meaningful within the context of a project.
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Clone, PartialEq, Eq, Hash)]
pub enum Location { pub enum Location {
/// Location information lost or code generated on the fly /// Location information lost or code generated on the fly
Unknown, Unknown,
/// Only the file is known /// Only the file is known
File(Rc<Vec<String>>), File(Rc<VName>),
/// Character slice of the code /// Character slice of the code
Range { Range {
/// Argument to the file loading callback that produced this code /// Argument to the file loading callback that produced this code
file: Rc<Vec<String>>, file: Rc<VName>,
/// Index of the unicode code points associated with the code /// Index of the unicode code points associated with the code
range: Range<usize>, range: Range<usize>,
/// The full source code as received by the parser /// The full source code as received by the parser
@@ -34,7 +36,7 @@ impl Location {
} }
/// File, if known /// File, if known
pub fn file(&self) -> Option<Rc<Vec<String>>> { pub fn file(&self) -> Option<Rc<VName>> {
if let Self::File(file) | Self::Range { file, .. } = self { if let Self::File(file) | Self::Range { file, .. } = self {
Some(file.clone()) Some(file.clone())
} else { } 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) { fn pos2lc(s: &str, i: usize) -> (usize, usize) {
s.chars().take(i).fold((1, 1), |(line, col), char| { s.chars().take(i).fold((1, 1), |(line, col), char| {
if char == '\n' { (line + 1, 1) } else { (line, col + 1) } if char == '\n' { (line + 1, 1) } else { (line, col + 1) }

View File

@@ -12,13 +12,13 @@ pub mod postmacro_to_interpreted;
pub mod primitive; pub mod primitive;
pub mod project; pub mod project;
pub mod sourcefile; pub mod sourcefile;
pub mod tree;
mod string; mod string;
pub mod tree;
pub use const_tree::{from_const_tree, ConstTree}; pub use const_tree::{from_const_tree, ConstTree};
pub use string::OrcString;
pub use literal::Literal; pub use literal::Literal;
pub use location::Location; pub use location::Location;
pub use namelike::{NameLike, Sym, VName}; pub use namelike::{NameLike, Sym, VName};
pub use path_set::PathSet; pub use path_set::PathSet;
pub use primitive::Primitive; pub use primitive::Primitive;
pub use string::OrcString;

View File

@@ -1,3 +1,4 @@
use std::fmt::Debug;
use std::hash::Hash; use std::hash::Hash;
use crate::interner::{Interner, Tok}; use crate::interner::{Interner, Tok};
@@ -14,11 +15,11 @@ pub type VName = Vec<Tok<String>>;
/// These names are always absolute. /// These names are always absolute.
/// ///
/// See also [VName] /// See also [VName]
pub type Sym = Tok<Vec<Tok<String>>>; pub type Sym = Tok<VName>;
/// An abstraction over tokenized vs non-tokenized names so that they can be /// An abstraction over tokenized vs non-tokenized names so that they can be
/// handled together in datastructures /// 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 /// Fully resolve the name for printing
fn to_strv(&self) -> Vec<String>; fn to_strv(&self) -> Vec<String>;
} }

View File

@@ -1,3 +1,4 @@
use std::fmt::Display;
use std::ops::Add; use std::ops::Add;
use hashbrown::HashMap; use hashbrown::HashMap;
@@ -7,51 +8,129 @@ use crate::interner::{Interner, Tok};
use crate::representations::tree::{ModMember, Module}; use crate::representations::tree::{ModMember, Module};
use crate::representations::NameLike; use crate::representations::NameLike;
use crate::tree::ModEntry; use crate::tree::ModEntry;
use crate::utils::never::{always, Always};
use crate::utils::Substack; use crate::utils::Substack;
use crate::{Sym, VName}; use crate::{Sym, VName};
#[derive(Debug, Clone)]
pub enum ItemKind<N: NameLike> {
/// 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<N>),
}
impl<N: NameLike> Default for ItemKind<N> {
fn default() -> Self {
Self::None
}
}
#[derive(Debug, Clone, Default)]
pub struct ProjectItem<N: NameLike> {
pub kind: ItemKind<N>,
pub is_op: bool,
}
impl<N: NameLike> Display for ProjectItem<N> {
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<N: NameLike> {
/// 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 /// Additional data about a loaded module beyond the list of constants and
/// submodules /// submodules
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug)]
pub struct ProjectExt<N: NameLike> { pub struct ProjectExt<N: NameLike> {
/// Pairs each foreign token to the module it was imported from /// Full path leading to this module
pub imports_from: HashMap<Tok<String>, N>, pub path: VName,
/// Pairs each exported token to its original full name /// Pairs each imported token to the absolute path of the module it is
pub exports: HashMap<Tok<String>, N>, /// imported from. The path does not include the name of referencedthe
/// symbol.
pub imports_from: HashMap<Tok<String>, ImpReport<N>>,
/// All rules defined in this module, exported or not /// All rules defined in this module, exported or not
pub rules: Vec<Rule<N>>, pub rules: Vec<Rule<N>>,
/// Filename, if known, for error reporting /// Filename, if known, for error reporting
pub file: Option<Vec<Tok<String>>>, pub file: Option<VName>,
} }
impl<N: NameLike> Add for ProjectExt<N> { impl<N: NameLike> Add for ProjectExt<N> {
type Output = Self; type Output = Always<Self>;
fn add(mut self, rhs: Self) -> Self::Output { 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.imports_from.extend(imports_from.into_iter());
self.exports.extend(exports.into_iter());
self.rules.extend(rules.into_iter()); self.rules.extend(rules.into_iter());
if file.is_some() { if file.is_some() {
self.file = file self.file = file
} }
self always(self)
} }
} }
impl<N: NameLike> Display for ProjectExt<N> {
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<N> = ModEntry<ProjectItem<N>, ProjectExt<N>>;
/// A node in the tree describing the project /// A node in the tree describing the project
pub type ProjectModule<N> = Module<Expr<N>, ProjectExt<N>>; pub type ProjectMod<N> = Module<ProjectItem<N>, ProjectExt<N>>;
/// Module corresponding to the root of a project /// Module corresponding to the root of a project
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ProjectTree<N: NameLike>(pub ProjectModule<N>); pub struct ProjectTree<N: NameLike>(pub ProjectMod<N>);
fn collect_rules_rec<N: NameLike>( fn collect_rules_rec<N: NameLike>(
bag: &mut Vec<Rule<N>>, bag: &mut Vec<Rule<N>>,
module: &ProjectModule<N>, module: &ProjectMod<N>,
) { ) {
bag.extend(module.extra.rules.iter().cloned()); 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 { if let ModMember::Sub(module) = &item.member {
collect_rules_rec(bag, module); collect_rules_rec(bag, module);
} }
@@ -69,12 +148,13 @@ pub fn collect_rules<N: NameLike>(project: &ProjectTree<N>) -> Vec<Rule<N>> {
fn collect_consts_rec<N: NameLike>( fn collect_consts_rec<N: NameLike>(
path: Substack<Tok<String>>, path: Substack<Tok<String>>,
bag: &mut HashMap<Sym, Expr<N>>, bag: &mut HashMap<Sym, Expr<N>>,
module: &ProjectModule<N>, module: &ProjectMod<N>,
i: &Interner, i: &Interner,
) { ) {
for (key, entry) in module.items.iter() { for (key, entry) in module.entries.iter() {
match &entry.member { match &entry.member {
ModMember::Item(expr) => { ModMember::Item(it) =>
if let ItemKind::Const(expr) = &it.kind {
let mut name = path.iter().rev_vec_clone(); let mut name = path.iter().rev_vec_clone();
name.push(key.clone()); name.push(key.clone());
bag.insert(i.i(&name), expr.clone()); bag.insert(i.i(&name), expr.clone());
@@ -96,30 +176,34 @@ pub fn collect_consts<N: NameLike>(
} }
fn vname_to_sym_tree_rec( fn vname_to_sym_tree_rec(
tree: ProjectModule<VName>, tree: ProjectMod<VName>,
i: &Interner, i: &Interner,
) -> ProjectModule<Sym> { ) -> ProjectMod<Sym> {
let process_expr = |ex: Expr<VName>| ex.transform_names(&|n| i.i(&n)); let process_expr = |ex: Expr<VName>| ex.transform_names(&|n| i.i(&n));
ProjectModule { ProjectMod {
imports: tree.imports, entries: (tree.entries.into_iter())
items: (tree.items.into_iter())
.map(|(k, ModEntry { exported, member })| { .map(|(k, ModEntry { exported, member })| {
(k, ModEntry { (k, ModEntry {
exported, exported,
member: match member { member: match member {
ModMember::Sub(module) => ModMember::Sub(module) =>
ModMember::Sub(vname_to_sym_tree_rec(module, i)), 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(), .collect(),
extra: ProjectExt { extra: ProjectExt {
path: tree.extra.path,
imports_from: (tree.extra.imports_from.into_iter()) imports_from: (tree.extra.imports_from.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(),
exports: (tree.extra.exports.into_iter())
.map(|(k, v)| (k, i.i(&v)))
.collect(), .collect(),
rules: (tree.extra.rules.into_iter()) rules: (tree.extra.rules.into_iter())
.map(|Rule { pattern, prio, template }| Rule { .map(|Rule { pattern, prio, template }| Rule {

View File

@@ -1,12 +1,16 @@
//! Building blocks of a source file //! Building blocks of a source file
use std::fmt::Display;
use std::iter; use std::iter;
use itertools::{Either, Itertools}; use itertools::{Either, Itertools};
use super::namelike::VName; use super::namelike::VName;
use crate::ast::{Constant, Rule}; use crate::ast::{Constant, Rule};
use crate::error::{ProjectError, ProjectResult, TooManySupers};
use crate::interner::{Interner, Tok}; use crate::interner::{Interner, Tok};
use crate::utils::pushed::pushed;
use crate::utils::{unwrap_or, BoxedIter}; use crate::utils::{unwrap_or, BoxedIter};
use crate::Location;
/// An import pointing at another module, either specifying the symbol to be /// An import pointing at another module, either specifying the symbol to be
/// imported or importing all available symbols with a globstar (*) /// imported or importing all available symbols with a globstar (*)
@@ -21,6 +25,8 @@ pub struct Import {
pub path: VName, pub path: VName,
/// If name is None, this is a wildcard import /// If name is None, this is a wildcard import
pub name: Option<Tok<String>>, pub name: Option<Tok<String>>,
/// Location of the final name segment, which uniquely identifies this name
pub location: Location,
} }
impl Import { impl Import {
/// Get the preload target space for this import - the prefix below /// 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 /// Returns the path if this is a glob import, or the path plus the
/// name if this is a specific import /// name if this is a specific import
pub fn nonglob_path(&self) -> Vec<Tok<String>> { pub fn nonglob_path(&self) -> VName {
let mut path_vec = self.path.clone(); let mut path_vec = self.path.clone();
if let Some(n) = &self.name { if let Some(n) = &self.name {
path_vec.push(n.clone()) 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 /// A namespace block
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ModuleBlock { pub struct ModuleBlock {
@@ -46,9 +60,16 @@ pub struct ModuleBlock {
pub body: Vec<FileEntry>, pub body: Vec<FileEntry>,
} }
/// 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)] #[derive(Debug, Clone)]
pub enum Member { pub enum MemberKind {
/// A substitution rule. Rules apply even when they're not in scope, if the /// 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 /// absolute names are present eg. because they're produced by other rules
Rule(Rule<VName>), Rule(Rule<VName>),
@@ -56,22 +77,81 @@ pub enum Member {
Constant(Constant), Constant(Constant),
/// A prefixed set of other entries /// A prefixed set of other entries
Module(ModuleBlock), Module(ModuleBlock),
/// Operator declarations
Operators(Vec<Tok<String>>),
} }
/// 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)] #[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 /// Imports one or all names in a module
Import(Vec<Import>), Import(Vec<Import>),
/// Comments are kept here in case dev tooling wants to parse documentation /// Comments are kept here in case dev tooling wants to parse documentation
Comment(String), Comment(String),
/// An element visible to the outside /// An element with visibility information
Exported(Member), Member(Member),
/// An element only visible from local code
Internal(Member),
/// A list of tokens exported explicitly. This can also create new exported /// A list of tokens exported explicitly. This can also create new exported
/// tokens that the local module doesn't actually define a role for /// tokens that the local module doesn't actually define a role for
Export(Vec<Tok<String>>), Export(Vec<(Tok<String>, 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<Location>,
}
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 /// Summarize all imports from a file in a single list of qualified names
@@ -79,8 +159,8 @@ pub fn imports<'a>(
src: impl Iterator<Item = &'a FileEntry> + 'a, src: impl Iterator<Item = &'a FileEntry> + 'a,
) -> impl Iterator<Item = &'a Import> + 'a { ) -> impl Iterator<Item = &'a Import> + 'a {
src src
.filter_map(|ent| match ent { .filter_map(|ent| match &ent.kind {
FileEntry::Import(impv) => Some(impv.iter()), FileEntryKind::Import(impv) => Some(impv.iter()),
_ => None, _ => None,
}) })
.flatten() .flatten()
@@ -90,56 +170,56 @@ pub fn imports<'a>(
/// Error if they're inconsistently exported /// Error if they're inconsistently exported
pub fn normalize_namespaces( pub fn normalize_namespaces(
src: BoxedIter<FileEntry>, src: BoxedIter<FileEntry>,
) -> Result<Vec<FileEntry>, Vec<Tok<String>>> { ) -> Result<Vec<FileEntry>, VName> {
let (mut namespaces, mut rest) = src let (mut namespaces, mut rest) = src
.partition_map::<Vec<_>, Vec<_>, _, _, _>(|ent| match ent { .partition_map::<Vec<_>, Vec<_>, _, _, _>(|ent| {
FileEntry::Exported(Member::Module(ns)) => Either::Left((true, ns)), match ent {
FileEntry::Internal(Member::Module(ns)) => Either::Left((false, ns)), FileEntry {
other => Either::Right(other), 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 // 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 let mut lumped = namespaces
.into_iter() .into_iter()
.group_by(|(_, ns)| ns.name.clone()) .group_by(|(_, ns, _)| ns.name.clone())
.into_iter() .into_iter()
.map(|(name, grp)| { .map(|(name, grp)| {
let mut any_exported = false; let mut exported = false;
let mut any_internal = false; let mut internal = false;
let grp_src = grp let mut grouped_source = Vec::new();
.into_iter() let mut locations = Vec::new();
.map(|(exported, ns)| { for (inst_exported, ns, locs) in grp {
if exported { if inst_exported {
any_exported = true exported = true
} else { } else {
any_internal = true internal = true
}; };
ns // Impure map is less than ideal but works grouped_source.extend(ns.body.into_iter());
}) locations.extend(locs.into_iter());
.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"),
} }
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::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
rest.append(&mut lumped); rest.append(&mut lumped);
Ok(rest) 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. /// Turn a relative (import) path into an absolute path.
/// If the import path is empty, the return value is also empty. /// If the import path is empty, the return value is also empty.
/// ///
@@ -151,22 +231,33 @@ pub fn absolute_path(
abs_location: &[Tok<String>], abs_location: &[Tok<String>],
rel_path: &[Tok<String>], rel_path: &[Tok<String>],
i: &Interner, i: &Interner,
) -> Result<Vec<Tok<String>>, TooManySupers> { location: &Location,
) -> ProjectResult<VName> {
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<String>],
rel_path: &[Tok<String>],
i: &Interner,
) -> Option<VName> {
let (head, tail) = unwrap_or!(rel_path.split_first(); let (head, tail) = unwrap_or!(rel_path.split_first();
return Ok(vec![]) return Some(vec![])
); );
if *head == i.i("super") { 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() { if tail.is_empty() {
Ok(new_abs.to_vec()) Some(new_abs.to_vec())
} else { } else {
let new_rel = let new_rel =
iter::once(i.i("self")).chain(tail.iter().cloned()).collect::<Vec<_>>(); iter::once(i.i("self")).chain(tail.iter().cloned()).collect::<Vec<_>>();
absolute_path(new_abs, &new_rel, i) absolute_path_rec(new_abs, &new_rel, i)
} }
} else if *head == i.i("self") { } 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 { } else {
Ok(rel_path.to_vec()) Some(rel_path.to_vec())
} }
} }

View File

@@ -1,16 +1,30 @@
use std::fmt::Debug;
use std::hash::Hash; use std::hash::Hash;
use std::ops::Deref; use std::ops::Deref;
use std::rc::Rc; use std::rc::Rc;
use crate::Tok; use crate::Tok;
#[derive(Clone, Debug, Eq)] /// An Orchid string which may or may not be interned
#[derive(Clone, Eq)]
pub enum OrcString { pub enum OrcString {
/// An interned string. Equality-conpared by reference.
Interned(Tok<String>), Interned(Tok<String>),
/// An uninterned bare string. Equality-compared by character
Runtime(Rc<String>), Runtime(Rc<String>),
} }
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 { impl OrcString {
/// Clone out the plain Rust [String]
pub fn get_string(&self) -> String { pub fn get_string(&self) -> String {
self.as_str().to_owned() self.as_str().to_owned()
} }

View File

@@ -1,14 +1,17 @@
//! Generic module tree structure //! Generic module tree structure
//! //!
//! Used by various stages of the pipeline with different parameters //! Used by various stages of the pipeline with different parameters
use std::fmt::{Debug, Display};
use std::ops::Add; use std::ops::Add;
use std::rc::Rc;
use duplicate::duplicate_item;
use hashbrown::HashMap; use hashbrown::HashMap;
use super::sourcefile::Import; use super::Location;
use crate::error::ProjectError;
use crate::interner::Tok; 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] /// The member in a [ModEntry] which is associated with a name in a [Module]
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@@ -27,121 +30,247 @@ pub struct ModEntry<TItem: Clone, TExt: Clone> {
/// Whether the member is visible to modules other than the parent /// Whether the member is visible to modules other than the parent
pub exported: bool, pub exported: bool,
} }
impl<TItem: Clone, TExt: Clone> ModEntry<TItem, TExt> {
/// 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, /// A module, containing imports,
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct Module<TItem: Clone, TExt: Clone> { pub struct Module<TItem: Clone, TExt: Clone> {
/// Import statements present this module
pub imports: Vec<Import>,
/// Submodules and items by name /// Submodules and items by name
pub items: HashMap<Tok<String>, ModEntry<TItem, TExt>>, pub entries: HashMap<Tok<String>, ModEntry<TItem, TExt>>,
/// Additional information associated with the module /// Additional information associated with the module
pub extra: TExt, 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 /// The path taken to reach a given module
pub type ModPath<'a> = Substack<'a, Tok<String>>; pub type ModPath<'a> = Substack<'a, Tok<String>>;
impl<TItem: Clone, TExt: Clone> Module<TItem, TExt> { impl<TItem: Clone, TExt: Clone> Module<TItem, TExt> {
/// 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<Tok<String>> {
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 /// Return the module at the end of the given path
#[allow(clippy::needless_arbitrary_self_type)] // duplicate pub fn walk_ref<'a: 'b, 'b>(
#[duplicate_item( &'a self,
method reference(type) dereference(expr) map_method; prefix: &'b [Tok<String>],
[walk] [type] [expr] [remove]; path: &'b [Tok<String>],
[walk_ref] [&type] [*expr] [get]; public: bool,
[walk_mut] [&mut type] [*expr] [get_mut]; ) -> Result<&'a Self, WalkError<'b>> {
)] let mut module = self;
pub fn method(
self: reference([Self]),
path: &[Tok<String>],
require_exported: bool,
) -> Result<reference([Self]), WalkError> {
let mut cur = self;
for (pos, step) in path.iter().enumerate() { for (pos, step) in path.iter().enumerate() {
if let Some(ModEntry { member: ModMember::Sub(next), exported }) = let kind = match module.entries.get(step) {
cur.items.map_method(step) None => ErrKind::Missing,
{ Some(ModEntry { exported: false, .. }) if public => ErrKind::Private,
if require_exported && !dereference([exported]) { Some(ModEntry { member: ModMember::Item(_), .. }) => ErrKind::NotModule,
return Err(WalkError { pos, kind: WalkErrorKind::Private }); Some(ModEntry { member: ModMember::Sub(next), .. }) => {
module = next;
continue;
},
};
let options = module.keys(public);
return Err(WalkError { kind, prefix, path, pos, options });
} }
cur = next 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<String>],
path: &'b [Tok<String>],
public: bool,
) -> Result<(&'a ModEntry<TItem, TExt>, &'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 { } 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<E>( fn search_all_rec<'a, T, E>(
&self, &'a self,
path: ModPath, path: ModPath,
callback: &mut impl FnMut(ModPath, &Self, &Import) -> Result<(), E>, mut state: T,
) -> Result<(), E> { callback: &mut impl FnMut(ModPath, &'a Self, T) -> Result<T, E>,
for import in self.imports.iter() { ) -> Result<T, E> {
callback(path.clone(), self, import)? state = callback(path.clone(), self, state)?;
} for (name, entry) in &self.entries {
for (name, entry) in self.items.iter() {
if let ModMember::Sub(module) = &entry.member { 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 /// Visit every element in the tree with the provided function
/// short-circuited by returning Err ///
pub fn visit_all_imports<E>( /// * init - can be used for reduce, otherwise pass `()`
&self, /// * callback - a callback applied on every module. Can return [Err] to
callback: &mut impl FnMut(ModPath, &Self, &Import) -> Result<(), E>, /// short-circuit the walk
) -> Result<(), E> { /// * [ModPath] - a substack indicating the path to the current module from
self.visit_all_imports_rec(Substack::Bottom, callback) /// 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<T, E>,
) -> Result<T, E> {
self.search_all_rec(Substack::Bottom, init, callback)
} }
/// Combine two module trees; wherever they conflict, the overlay is /// Combine two module trees; wherever they conflict, the overlay is
/// preferred. /// preferred.
pub fn overlay(mut self, overlay: Self) -> Self pub fn overlay<E>(mut self, overlay: Self) -> Result<Self, E>
where where
TExt: Add<TExt, Output = TExt>, TExt: Add<TExt, Output = Result<TExt, E>>,
{ {
let Module { extra, imports, items } = overlay; let Module { extra, entries: items } = overlay;
let mut new_items = HashMap::new(); let mut new_items = HashMap::new();
for (key, right) in items { for (key, right) in items {
// if both contain a submodule // if both contain a submodule
match (self.items.remove(&key), right) { match (self.entries.remove(&key), right) {
( (
Some(ModEntry { member: ModMember::Sub(lsub), .. }), Some(ModEntry { member: ModMember::Sub(lsub), .. }),
ModEntry { member: ModMember::Sub(rsub), exported }, ModEntry { member: ModMember::Sub(rsub), exported },
) => new_items.insert(key, ModEntry { ) => new_items.insert(key, ModEntry {
exported, exported,
member: ModMember::Sub(lsub.overlay(rsub)), member: ModMember::Sub(lsub.overlay(rsub)?),
}), }),
(_, right) => new_items.insert(key, right), (_, right) => new_items.insert(key, right),
}; };
} }
new_items.extend(self.items.into_iter()); new_items.extend(self.entries.into_iter());
self.imports.extend(imports.into_iter()); Ok(Module { entries: new_items, extra: (self.extra + extra)? })
Module { }
items: new_items, }
imports: self.imports,
extra: self.extra + extra, impl<TItem: Clone + Display, TExt: Clone + Display> Display
} for Module<TItem, TExt>
{
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<String>],
/// Planned walk path
pub path: &'a [Tok<String>],
/// Index into walked path where the error occurred
pub pos: usize,
/// Alternatives to the failed steps
pub options: BoxedIter<'a, Tok<String>>,
}
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<dyn ProjectError> {
// 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()
} }
} }

View File

@@ -10,7 +10,7 @@ use crate::representations::Primitive;
use crate::rule::matcher::{Matcher, RuleExpr}; use crate::rule::matcher::{Matcher, RuleExpr};
use crate::rule::state::State; use crate::rule::state::State;
use crate::utils::Side; use crate::utils::Side;
use crate::Sym; use crate::{Sym, VName};
pub enum ScalMatcher { pub enum ScalMatcher {
P(Primitive), P(Primitive),
@@ -47,7 +47,7 @@ pub enum VecMatcher {
/// the length of matches on either side. /// the length of matches on either side.
/// ///
/// Vectorial keys that appear on either side, in priority order /// Vectorial keys that appear on either side, in priority order
key_order: Vec<Tok<String>>, key_order: VName,
}, },
} }

View File

@@ -16,9 +16,9 @@ use crate::foreign::cps_box::CPSBox;
use crate::foreign::{Atomic, ExternError}; use crate::foreign::{Atomic, ExternError};
use crate::interpreter::HandlerTable; use crate::interpreter::HandlerTable;
use crate::pipeline::file_loader::embed_to_map; 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::systems::asynch::{Asynch, MessagePort};
use crate::Interner; use crate::{Interner, Location};
trait_set! { trait_set! {
pub trait StreamTable = IntoIterator<Item = (&'static str, IOStream)> pub trait StreamTable = IntoIterator<Item = (&'static str, IOStream)>
@@ -144,10 +144,14 @@ impl<'a, P: MessagePort, ST: StreamTable + 'a> IntoSystem<'a>
name: vec!["system".to_string(), "io".to_string()], name: vec!["system".to_string(), "io".to_string()],
constants: io_bindings(i, streams).unwrap_tree(), constants: io_bindings(i, streams).unwrap_tree(),
code: embed_to_map::<IOEmbed>(".orc", i), code: embed_to_map::<IOEmbed>(".orc", i),
prelude: vec![FileEntry::Import(vec![Import { 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")], path: vec![i.i("system"), i.i("io"), i.i("prelude")],
name: None, name: None,
}])], }]),
}],
handlers, handlers,
} }
} }

View File

@@ -1,6 +1,8 @@
export operators[ != == ]
export const not := \bool. if bool then false else true export const not := \bool. if bool then false else true
export macro ...$a != ...$b =0x3p36=> (not (...$a == ...$b)) macro ...$a != ...$b =0x3p36=> (not (...$a == ...$b))
export macro ...$a == ...$b =0x3p36=> (equals (...$a) (...$b)) macro ...$a == ...$b =0x3p36=> (equals (...$a) (...$b))
export macro if ...$cond then ...$true else ...$false:1 =0x1p84=> ( export macro if ...$cond then ...$true else ...$false:1 =0x1p84=> (
ifthenelse (...$cond) (...$true) (...$false) ifthenelse (...$cond) (...$true) (...$false)
) )

View File

@@ -18,9 +18,11 @@ export const pass2 := \a.\b.\cont. cont a b
]-- ]--
export const return := \a. \b.a export const return := \a. \b.a
export macro ...$prefix $ ...$suffix:1 =0x1p38=> ...$prefix (...$suffix) export operators[$ |> =>]
export macro ...$prefix |> $fn ..$suffix:1 =0x2p32=> $fn (...$prefix) ..$suffix
export macro ($name) => ...$body =0x2p129=> (\$name. ...$body) macro ...$prefix $ ...$suffix:1 =0x1p38=> ...$prefix (...$suffix)
export macro ($name, ...$argv) => ...$body =0x2p129=> (\$name. (...$argv) => ...$body) 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) macro $name => ...$body =0x1p129=> (\$name. ...$body)

View File

@@ -1 +1,3 @@
export ::[,] export operators[ , ]
export const foo := \a.a

View File

@@ -7,9 +7,9 @@ mod conv;
mod inspect; mod inspect;
mod num; mod num;
mod panic; mod panic;
mod state;
mod stl_system; mod stl_system;
mod str; mod str;
mod state;
pub use arithmetic_error::ArithmeticError; pub use arithmetic_error::ArithmeticError;
pub use bin::Binary; pub use bin::Binary;
pub use num::Numeric; pub use num::Numeric;

View File

@@ -1,5 +1,7 @@
export macro ...$a + ...$b =0x2p36=> (add (...$a) (...$b)) export operators[ + - * % / ]
export macro ...$a - ...$b:1 =0x2p36=> (subtract (...$a) (...$b))
export macro ...$a * ...$b =0x1p36=> (multiply (...$a) (...$b)) macro ...$a + ...$b =0x2p36=> (add (...$a) (...$b))
export macro ...$a % ...$b:1 =0x1p36=> (remainder (...$a) (...$b)) macro ...$a - ...$b:1 =0x2p36=> (subtract (...$a) (...$b))
export macro ...$a / ...$b:1 =0x1p36=> (divide (...$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))

View File

@@ -1,5 +1,7 @@
import super::fn::=> import super::fn::=>
export operators[ ; = ]
-- remove duplicate ;-s -- remove duplicate ;-s
export macro do { export macro do {
...$statement ; ; ...$rest:1 ...$statement ; ; ...$rest:1
@@ -11,6 +13,8 @@ export macro do {
} =0x2p130=> statement (...$statement) do { ...$rest } } =0x2p130=> statement (...$statement) do { ...$rest }
export macro do { ...$return } =0x1p130=> ...$return export macro do { ...$return } =0x1p130=> ...$return
export ::do
export macro statement (let $name = ...$value) ...$next =0x1p230=> ( export macro statement (let $name = ...$value) ...$next =0x1p230=> (
( \$name. ...$next) (...$value) ( \$name. ...$next) (...$value)
) )

View File

@@ -13,7 +13,8 @@ use super::str::str;
use crate::facade::{IntoSystem, System}; use crate::facade::{IntoSystem, System};
use crate::interner::Interner; use crate::interner::Interner;
use crate::pipeline::file_loader::embed_to_map; 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. /// Feature flags for the STL.
#[derive(Default)] #[derive(Default)]
@@ -29,8 +30,6 @@ pub struct StlConfig {
#[include = "*.orc"] #[include = "*.orc"]
struct StlEmbed; struct StlEmbed;
// TODO: fix all orc modules to not rely on prelude
impl IntoSystem<'static> for StlConfig { impl IntoSystem<'static> for StlConfig {
fn into_system(self, i: &Interner) -> System<'static> { fn into_system(self, i: &Interner) -> System<'static> {
let pure_fns = let pure_fns =
@@ -41,10 +40,14 @@ impl IntoSystem<'static> for StlConfig {
name: vec!["std".to_string()], name: vec!["std".to_string()],
constants: HashMap::from([(i.i("std"), fns)]), constants: HashMap::from([(i.i("std"), fns)]),
code: embed_to_map::<StlEmbed>(".orc", i), code: embed_to_map::<StlEmbed>(".orc", i),
prelude: vec![FileEntry::Import(vec![Import { prelude: vec![FileEntry {
locations: vec![Location::Unknown],
kind: FileEntryKind::Import(vec![Import {
location: Location::Unknown,
path: vec![i.i("std"), i.i("prelude")], path: vec![i.i("std"), i.i("prelude")],
name: None, name: None,
}])], }]),
}],
handlers: state_handlers(), handlers: state_handlers(),
} }
} }

View File

@@ -1,5 +1,7 @@
import super::(proc::*, bool::*, panic) import super::(proc::*, bool::*, panic)
export operators[++]
export macro ...$a ++ ...$b =0x4p36=> (concat (...$a) (...$b)) export macro ...$a ++ ...$b =0x4p36=> (concat (...$a) (...$b))
export const char_at := \s.\i. do{ export const char_at := \s.\i. do{

View File

@@ -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, V>,
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, V>,
k: &K,
make: impl FnOnce() -> V,
) -> &'a mut V {
map.raw_entry_mut().from_key(k).or_insert_with(|| (k.clone(), make())).1
}

View File

@@ -1,8 +1,10 @@
mod cache; mod cache;
mod delete_cell; mod delete_cell;
mod event_poller; mod event_poller;
mod get_or_default;
mod iter_find; mod iter_find;
mod pushed; pub mod never;
pub mod pushed;
mod rc_to_owned; mod rc_to_owned;
mod replace_first; mod replace_first;
mod side; mod side;
@@ -14,7 +16,6 @@ pub mod thread_pool;
mod unwrap_or; mod unwrap_or;
pub use cache::Cache; pub use cache::Cache;
pub use pushed::pushed;
pub use rc_to_owned::{map_rc, rc_to_owned}; pub use rc_to_owned::{map_rc, rc_to_owned};
pub use replace_first::replace_first; pub use replace_first::replace_first;
pub use side::Side; pub use side::Side;
@@ -24,6 +25,7 @@ pub(crate) use unwrap_or::unwrap_or;
pub mod iter; pub mod iter;
pub use delete_cell::DeleteCell; pub use delete_cell::DeleteCell;
pub use event_poller::{PollEvent, Poller}; pub use event_poller::{PollEvent, Poller};
pub use get_or_default::{get_or_default, get_or_make};
pub use iter::BoxedIter; pub use iter::BoxedIter;
pub use iter_find::iter_find; pub use iter_find::iter_find;
pub use string_from_charset::string_from_charset; pub use string_from_charset::string_from_charset;

17
src/utils/never.rs Normal file
View File

@@ -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<T> = Result<T, Never>;
/// Wrap value in a result with an impossible failure mode
pub fn always<T>(t: T) -> Result<T, Never> {
Ok(t)
}
/// Take success value out of a result with an impossible failure mode
pub fn unwrap_always<T>(result: Result<T, Never>) -> T {
result.unwrap_or_else(|_| unreachable!("Never has no values"))
}

View File

@@ -3,7 +3,18 @@ use std::iter;
/// Pure version of [Vec::push] /// Pure version of [Vec::push]
/// ///
/// Create a new vector consisting of the provided vector with the /// Create a new vector consisting of the provided vector with the
/// element appended /// element appended. See [pushed_ref] to use it with a slice
pub fn pushed<T: Clone>(vec: &[T], t: T) -> Vec<T> { pub fn pushed<T: Clone>(vec: impl IntoIterator<Item = T>, t: T) -> Vec<T> {
vec.iter().cloned().chain(iter::once(t)).collect() 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<Item = &'a T>,
t: T,
) -> Vec<T> {
vec.into_iter().cloned().chain(iter::once(t)).collect()
} }

View File

@@ -1,14 +1,10 @@
/// Split off the longest prefix accepted by the validator /// 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>( pub fn split_max_prefix<'a, T>(
path: &'a [T], path: &'a [T],
is_valid: &impl Fn(&[T]) -> bool, is_valid: &impl Fn(&[T]) -> bool,
) -> Option<(&'a [T], &'a [T])> { ) -> Option<(&'a [T], &'a [T])> {
for split in (0..=path.len()).rev() { (0..=path.len())
let (filename, subpath) = path.split_at(split); .rev()
if is_valid(filename) { .map(|i| path.split_at(i))
return Some((filename, subpath)); .find(|(file, _)| is_valid(file))
}
}
None
} }

View File

@@ -44,12 +44,13 @@ impl<'a, T> Substack<'a, T> {
Stackframe { item, prev: self, len: self.opt().map_or(1, |s| s.len + 1) } Stackframe { item, prev: self, len: self.opt().map_or(1, |s| s.len + 1) }
} }
/// obtain the previous stackframe if one exists /// obtain the previous stackframe if one exists
/// TODO: this should return a [Substack] pub fn pop(&'a self, count: usize) -> &'a Substack<'a, T> {
pub fn pop(&'a self, count: usize) -> Option<&'a Stackframe<'a, T>> { if count == 0 {
if let Self::Frame(p) = self { self
if count == 0 { Some(p) } else { p.prev.pop(count - 1) } } else if let Self::Frame(p) = self {
p.prev.pop(count - 1)
} else { } else {
None &Substack::Bottom
} }
} }
/// number of stackframes /// number of stackframes