The pipeline is finally reasonably clean
This commit is contained in:
1
clippy.toml
Normal file
1
clippy.toml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
type-complexity-threshold = 300
|
||||||
@@ -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"
|
||||||
),
|
),
|
||||||
|
|||||||
31
src/error/conflicting_roles.rs
Normal file
31
src/error/conflicting_roles.rs
Normal 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 }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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("::")
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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",
|
||||||
/// )?
|
/// )
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// );
|
/// );
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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};
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
31
src/parse/operators.rs
Normal 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[$ |> =>]"))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
59
src/pipeline/dealias/alias_cache.rs
Normal file
59
src/pipeline/dealias/alias_cache.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/pipeline/dealias/mod.rs
Normal file
5
src/pipeline/dealias/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// mod alias_cache;
|
||||||
|
mod resolve_aliases;
|
||||||
|
mod walk_with_links;
|
||||||
|
|
||||||
|
pub use resolve_aliases::resolve_aliases;
|
||||||
95
src/pipeline/dealias/resolve_aliases.rs
Normal file
95
src/pipeline/dealias/resolve_aliases.rs
Normal 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))
|
||||||
|
}
|
||||||
124
src/pipeline/dealias/walk_with_links.rs
Normal file
124
src/pipeline/dealias/walk_with_links.rs
Normal 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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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::representations::sourcefile::{absolute_path, FileEntry, Member};
|
|
||||||
use crate::representations::tree::{ModEntry, ModMember, Module};
|
|
||||||
use crate::representations::{NameLike, VName};
|
|
||||||
use crate::tree::{WalkError, WalkErrorKind};
|
|
||||||
use crate::utils::iter::{box_empty, box_once};
|
|
||||||
use crate::utils::{pushed, unwrap_or, Substack};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct ParsedSource<'a> {
|
|
||||||
path: Vec<Tok<String>>,
|
|
||||||
loaded: &'a LoadedSource,
|
|
||||||
parsed: Vec<FileEntry>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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);
|
use crate::sourcefile::{
|
||||||
Some(Ok((ns.name.clone(), ModEntry { exported, member })))
|
FileEntry, FileEntryKind, Member, MemberKind, ModuleBlock,
|
||||||
},
|
|
||||||
Member::Constant(Constant { name, value }) => {
|
|
||||||
let member = ModMember::Item(value);
|
|
||||||
Some(Ok((name, ModEntry { exported, member })))
|
|
||||||
},
|
|
||||||
_ => None,
|
|
||||||
};
|
};
|
||||||
match ent {
|
use crate::tree::{ModEntry, ModMember, Module};
|
||||||
FileEntry::Exported(member) => member_to_item(true, member),
|
use crate::utils::get_or_default;
|
||||||
FileEntry::Internal(member) => member_to_item(false, member),
|
use crate::utils::pushed::pushed_ref;
|
||||||
_ => None,
|
use crate::{Tok, VName};
|
||||||
}
|
|
||||||
})
|
|
||||||
.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(
|
pub struct TreeReport {
|
||||||
path: Substack<Tok<String>>,
|
pub entries: HashMap<Tok<String>, ProjectEntry<VName>>,
|
||||||
files: Vec<ParsedSource>,
|
pub rules: Vec<Rule<VName>>,
|
||||||
i: &Interner,
|
/// Maps imported symbols to the absolute paths of the modules they are
|
||||||
) -> ProjectResult<Module<Expr<VName>, ProjectExt<VName>>> {
|
/// imported from
|
||||||
let lvl = path.len();
|
pub imports_from: HashMap<Tok<String>, ImpReport<VName>>,
|
||||||
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)),
|
||||||
})
|
},
|
||||||
.collect::<ProjectResult<Vec<_>>>()?;
|
);
|
||||||
// sort by similarity, then longest-first
|
let mut rule_fragments = Vec::new();
|
||||||
entries.sort_unstable_by(|a, b| a.0.cmp(b.0).reverse());
|
let mut submodules = HashMap::<_, Vec<_>>::new();
|
||||||
let files = entries
|
let mut consts = HashMap::new();
|
||||||
.into_iter()
|
for FileEntry { kind, locations: _ } in source {
|
||||||
.map(|(path, loaded, parsed)| ParsedSource {
|
match kind {
|
||||||
loaded,
|
FileEntryKind::Import(_) => (),
|
||||||
parsed,
|
FileEntryKind::Comment(_) => (),
|
||||||
path: path.clone(),
|
FileEntryKind::Export(_) => (),
|
||||||
})
|
FileEntryKind::Member(Member { kind, .. }) => match kind {
|
||||||
.collect::<Vec<_>>();
|
MemberKind::Module(ModuleBlock { body, name }) => {
|
||||||
Ok(ProjectTree(files_to_module(Substack::Bottom, files, i)?))
|
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();
|
||||||
|
let (pre_subs, pre_items) = (entries.into_iter())
|
||||||
|
.partition_map::<HashMap<_, _>, HashMap<_, _>, _, _, _>(
|
||||||
|
|(k, ModEntry { exported, member })| match member {
|
||||||
|
ModMember::Sub(s) => Either::Left((k, (exported, s))),
|
||||||
|
ModMember::Item(it) => Either::Right((k, (exported, it))),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let mut entries = (pre_subs.into_iter())
|
||||||
|
.map(|(k, (exported, pre_member))| {
|
||||||
|
let impmod = (submod_imports.remove(&k))
|
||||||
|
.expect("Imports and preparsed should line up");
|
||||||
|
(k, exported, pre_member, impmod)
|
||||||
|
})
|
||||||
|
.map(|(k, exported, pre, imp)| {
|
||||||
|
let source = submodules
|
||||||
|
.remove(&k)
|
||||||
|
.expect("Submodules should not disappear after reparsing");
|
||||||
|
(k, exported, pre, imp, source)
|
||||||
|
})
|
||||||
|
.map(|(k, exported, pre, imp, source)| {
|
||||||
|
let path = pushed_ref(path, k.clone());
|
||||||
|
let TreeReport { entries, imports_from, rules } =
|
||||||
|
build_tree(&path, source, pre, imp, prelude)?;
|
||||||
|
let extra = ProjectExt { path, file: None, imports_from, rules };
|
||||||
|
let member = ModMember::Sub(Module { entries, extra });
|
||||||
|
Ok((k, ModEntry { exported, member }))
|
||||||
|
})
|
||||||
|
.chain((pre_items.into_iter()).map(
|
||||||
|
|(k, (exported, PreItem { has_value, is_op, location }))| {
|
||||||
|
let item = match imports_from.get(&k) {
|
||||||
|
Some(_) if has_value => {
|
||||||
|
// Local value cannot be assigned to imported key
|
||||||
|
let const_loc =
|
||||||
|
consts.remove(&k).expect("has_value is true").location;
|
||||||
|
let err = ConflictingRoles {
|
||||||
|
locations: vec![location, const_loc],
|
||||||
|
name: pushed_ref(path, k),
|
||||||
|
};
|
||||||
|
return Err(err.rc());
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
let k = consts.remove(&k).map_or(ItemKind::None, ItemKind::Const);
|
||||||
|
ProjectItem { is_op, kind: k }
|
||||||
|
},
|
||||||
|
Some(report) => ProjectItem {
|
||||||
|
is_op: is_op | report.is_op,
|
||||||
|
kind: ItemKind::Alias(report.source.clone()),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Ok((k, ModEntry { exported, member: ModMember::Item(item) }))
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.collect::<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 })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
@@ -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()))
|
|
||||||
}
|
|
||||||
169
src/pipeline/project_tree/import_tree.rs
Normal file
169
src/pipeline/project_tree/import_tree.rs
Normal 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 })
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
131
src/pipeline/project_tree/rebuild_tree.rs
Normal file
131
src/pipeline/project_tree/rebuild_tree.rs
Normal 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)
|
||||||
|
}
|
||||||
@@ -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()) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
92
src/pipeline/source_loader/types.rs
Normal file
92
src/pipeline/source_loader/types.rs
Normal 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);
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 =>
|
||||||
|
|||||||
@@ -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(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) }
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
export ::[,]
|
export operators[ , ]
|
||||||
|
|
||||||
|
export const foo := \a.a
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
20
src/utils/get_or_default.rs
Normal file
20
src/utils/get_or_default.rs
Normal 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
|
||||||
|
}
|
||||||
@@ -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
17
src/utils/never.rs
Normal 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"))
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user