From 9186bce956af1eadef0fb909a82971d91d3532ba Mon Sep 17 00:00:00 2001 From: Lawrence Bethlenfalvy Date: Fri, 18 Aug 2023 21:10:29 +0100 Subject: [PATCH] Import and export improved - Import paths are now vname and not sym - Imports and exports accept multiple space-delimited operators in [] as a result, we can now reliably import and export the operator * - error reporting ergonomics --- src/error/import_all.rs | 14 +-- src/error/not_found.rs | 12 +-- src/error/project_error.rs | 20 +++-- src/error/too_many_supers.rs | 15 +--- src/error/unexpected_directory.rs | 13 +-- src/error/visibility_mismatch.rs | 20 ++--- src/facade/pre_macro.rs | 8 +- src/facade/process.rs | 8 +- src/foreign_macros/atomic_impl.rs | 2 +- src/foreign_macros/define_fn.rs | 4 +- src/foreign_macros/write_fn_step.rs | 8 +- src/interpreter/handler.rs | 25 ++---- src/lib.rs | 2 +- src/parse/errors.rs | 58 ++++++++----- src/parse/multiname.rs | 86 +++++++++++++------ src/parse/sourcefile.rs | 25 ++---- src/pipeline/file_loader.rs | 11 ++- .../import_resolution/collect_aliases.rs | 5 +- src/pipeline/project_tree/build_tree.rs | 2 +- .../project_tree/collect_ops/exported_ops.rs | 71 +++++++-------- .../project_tree/collect_ops/ops_for.rs | 2 +- .../project_tree/normalize_imports.rs | 4 +- src/pipeline/source_loader/load_source.rs | 2 +- src/representations/ast_to_postmacro.rs | 10 +-- src/representations/sourcefile.rs | 9 +- src/systems/io/facade.rs | 2 +- src/systems/io/flow.rs | 4 +- src/systems/stl/prelude.orc | 6 +- src/systems/stl/stl_system.rs | 2 +- src/utils/iter.rs | 2 - src/utils/mod.rs | 3 +- src/utils/thread_pool.rs | 36 ++++++-- src/utils/unwrap_or.rs | 6 +- 33 files changed, 269 insertions(+), 228 deletions(-) diff --git a/src/error/import_all.rs b/src/error/import_all.rs index 3c26fbb..a3f87c6 100644 --- a/src/error/import_all.rs +++ b/src/error/import_all.rs @@ -1,9 +1,7 @@ use std::rc::Rc; -use super::{ErrorPosition, ProjectError}; +use super::ProjectError; use crate::representations::location::Location; -use crate::utils::iter::box_once; -use crate::utils::BoxedIter; use crate::{Interner, VName}; /// Error produced for the statement `import *` @@ -22,13 +20,7 @@ impl ProjectError for ImportAll { format!("{} imports *", i.extern_all(&self.offender_mod).join("::")) } - fn positions(&self, i: &Interner) -> BoxedIter { - box_once(ErrorPosition { - location: Location::File(self.offender_file.clone()), - message: Some(format!( - "{} imports *", - i.extern_all(&self.offender_mod).join("::") - )), - }) + fn one_position(&self, _i: &Interner) -> Location { + Location::File(self.offender_file.clone()) } } diff --git a/src/error/not_found.rs b/src/error/not_found.rs index 5611bba..758dafe 100644 --- a/src/error/not_found.rs +++ b/src/error/not_found.rs @@ -1,11 +1,11 @@ -use super::{ErrorPosition, ProjectError}; +use std::rc::Rc; + +use super::ProjectError; use crate::representations::project::ProjectModule; #[allow(unused)] // For doc use crate::tree::Module; use crate::tree::WalkError; -use crate::utils::iter::box_once; -use crate::utils::BoxedIter; -use crate::{Interner, NameLike, Tok, VName}; +use crate::{Interner, Location, NameLike, Tok, VName}; /// Error produced when an import refers to a nonexistent module #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -64,7 +64,7 @@ impl ProjectError for NotFound { i.extern_all(&self.file).join("/"), ) } - fn positions(&self, i: &Interner) -> BoxedIter { - box_once(ErrorPosition::just_file(i.extern_all(&self.file))) + fn one_position(&self, i: &Interner) -> crate::Location { + Location::File(Rc::new(i.extern_all(&self.file))) } } diff --git a/src/error/project_error.rs b/src/error/project_error.rs index b2805f2..0f5a235 100644 --- a/src/error/project_error.rs +++ b/src/error/project_error.rs @@ -2,6 +2,7 @@ use std::rc::Rc; use crate::interner::InternedDisplay; use crate::representations::location::Location; +use crate::utils::iter::box_once; use crate::utils::BoxedIter; use crate::Interner; @@ -14,13 +15,6 @@ pub struct ErrorPosition { pub message: Option, } -impl ErrorPosition { - /// An error position referring to an entire file with no comment - pub fn just_file(file: Vec) -> Self { - Self { message: None, location: Location::File(Rc::new(file)) } - } -} - /// Errors addressed to the developer which are to be resolved with /// code changes pub trait ProjectError { @@ -30,8 +24,16 @@ pub trait ProjectError { fn message(&self, _i: &Interner) -> String { self.description().to_string() } - /// Code positions relevant to this error - fn positions(&self, i: &Interner) -> BoxedIter; + /// Code positions relevant to this error. If you don't implement this, you + /// must implement [ProjectError::one_position] + fn positions(&self, i: &Interner) -> BoxedIter { + box_once(ErrorPosition { location: self.one_position(i), message: None }) + } + /// Short way to provide a single location. If you don't implement this, you + /// must implement [ProjectError::positions] + fn one_position(&self, _i: &Interner) -> Location { + unimplemented!() + } /// Convert the error into an `Rc` to be able to /// handle various errors together fn rc(self) -> Rc diff --git a/src/error/too_many_supers.rs b/src/error/too_many_supers.rs index edb74a3..9c1d67e 100644 --- a/src/error/too_many_supers.rs +++ b/src/error/too_many_supers.rs @@ -1,9 +1,7 @@ use std::rc::Rc; -use super::{ErrorPosition, ProjectError}; +use super::ProjectError; use crate::representations::location::Location; -use crate::utils::iter::box_once; -use crate::utils::BoxedIter; use crate::{Interner, VName}; /// Error produced when an import path starts with more `super` segments @@ -30,14 +28,7 @@ impl ProjectError for TooManySupers { ) } - fn positions(&self, i: &Interner) -> BoxedIter { - box_once(ErrorPosition { - location: Location::File(Rc::new(i.extern_all(&self.offender_file))), - message: Some(format!( - "path {} in {} contains too many `super` steps.", - i.extern_all(&self.path).join("::"), - i.extern_all(&self.offender_mod).join("::") - )), - }) + fn one_position(&self, i: &Interner) -> Location { + Location::File(Rc::new(i.extern_all(&self.offender_file))) } } diff --git a/src/error/unexpected_directory.rs b/src/error/unexpected_directory.rs index fe7d569..9251fdc 100644 --- a/src/error/unexpected_directory.rs +++ b/src/error/unexpected_directory.rs @@ -1,7 +1,7 @@ -use super::{ErrorPosition, ProjectError}; -use crate::utils::iter::box_once; -use crate::utils::BoxedIter; -use crate::{Interner, VName}; +use std::rc::Rc; + +use super::ProjectError; +use crate::{Interner, Location, VName}; /// Produced when a stage that deals specifically with code encounters /// a path that refers to a directory @@ -15,9 +15,10 @@ impl ProjectError for UnexpectedDirectory { "A stage that deals specifically with code encountered a path that refers \ to a directory" } - fn positions(&self, i: &Interner) -> BoxedIter { - box_once(ErrorPosition::just_file(i.extern_all(&self.path))) + fn one_position(&self, i: &Interner) -> crate::Location { + Location::File(Rc::new(i.extern_all(&self.path))) } + fn message(&self, i: &Interner) -> String { format!( "{} was expected to be a file but a directory was found", diff --git a/src/error/visibility_mismatch.rs b/src/error/visibility_mismatch.rs index 32e0775..cf6f913 100644 --- a/src/error/visibility_mismatch.rs +++ b/src/error/visibility_mismatch.rs @@ -1,9 +1,7 @@ use std::rc::Rc; -use super::project_error::{ErrorPosition, ProjectError}; +use super::project_error::ProjectError; use crate::representations::location::Location; -use crate::utils::iter::box_once; -use crate::utils::BoxedIter; use crate::{Interner, VName}; /// Multiple occurences of the same namespace with different visibility @@ -18,13 +16,13 @@ impl ProjectError for VisibilityMismatch { fn description(&self) -> &str { "Some occurences of a namespace are exported but others are not" } - fn positions(&self, i: &Interner) -> BoxedIter { - box_once(ErrorPosition { - location: Location::File(self.file.clone()), - message: Some(format!( - "{} is opened multiple times with different visibilities", - i.extern_all(&self.namespace).join("::") - )), - }) + fn message(&self, i: &Interner) -> String { + format!( + "{} is opened multiple times with different visibilities", + i.extern_all(&self.namespace).join("::") + ) + } + fn one_position(&self, _i: &Interner) -> Location { + Location::File(self.file.clone()) } } diff --git a/src/facade/pre_macro.rs b/src/facade/pre_macro.rs index f7901e4..4da51d2 100644 --- a/src/facade/pre_macro.rs +++ b/src/facade/pre_macro.rs @@ -4,11 +4,9 @@ use std::rc::Rc; use hashbrown::HashMap; use super::{Process, System}; -use crate::error::{ErrorPosition, ProjectError, ProjectResult}; +use crate::error::{ProjectError, ProjectResult}; use crate::interpreter::HandlerTable; use crate::rule::Repo; -use crate::utils::iter::box_once; -use crate::utils::BoxedIter; use crate::{ ast, ast_to_interpreted, collect_consts, collect_rules, rule, Interner, Location, ProjectTree, Sym, @@ -128,7 +126,7 @@ impl ProjectError for MacroTimeout { ) } - fn positions(&self, _i: &Interner) -> BoxedIter { - box_once(ErrorPosition { location: self.location.clone(), message: None }) + fn one_position(&self, _i: &Interner) -> Location { + self.location.clone() } } diff --git a/src/facade/process.rs b/src/facade/process.rs index 857f5e4..4e68bfe 100644 --- a/src/facade/process.rs +++ b/src/facade/process.rs @@ -1,14 +1,12 @@ use hashbrown::HashMap; -use crate::error::{ErrorPosition, ProjectError, ProjectResult}; +use crate::error::{ProjectError, ProjectResult}; use crate::interpreted::{self, ExprInst}; #[allow(unused)] // for doc use crate::interpreter; use crate::interpreter::{ run_handler, Context, HandlerTable, Return, RuntimeError, }; -use crate::utils::iter::box_once; -use crate::utils::BoxedIter; use crate::{Interner, Location, Sym}; /// This struct ties the state of systems to loaded code, and allows to call @@ -84,7 +82,7 @@ impl ProjectError for MissingSymbol { ) } - fn positions(&self, _i: &Interner) -> BoxedIter { - box_once(ErrorPosition { location: self.location.clone(), message: None }) + fn one_position(&self, _i: &Interner) -> Location { + self.location.clone() } } diff --git a/src/foreign_macros/atomic_impl.rs b/src/foreign_macros/atomic_impl.rs index e9d6ae5..3398865 100644 --- a/src/foreign_macros/atomic_impl.rs +++ b/src/foreign_macros/atomic_impl.rs @@ -41,7 +41,7 @@ use crate::Primitive; /// ``` /// use orchidlang::{Literal}; /// use orchidlang::interpreted::ExprInst; -/// use orchidlang::stl::litconv::with_lit; +/// use orchidlang::systems::cast_exprinst::with_lit; /// use orchidlang::{atomic_impl, atomic_redirect, externfn_impl}; /// /// /// Convert a literal to a string using Rust's conversions for floats, chars and diff --git a/src/foreign_macros/define_fn.rs b/src/foreign_macros/define_fn.rs index 6fa1276..e873cd5 100644 --- a/src/foreign_macros/define_fn.rs +++ b/src/foreign_macros/define_fn.rs @@ -41,7 +41,7 @@ use crate::write_fn_step; /// /// ``` /// use orchidlang::interpreted::Clause; -/// use orchidlang::stl::litconv::with_str; +/// use orchidlang::systems::cast_exprinst::with_str; /// use orchidlang::{define_fn, Literal, Primitive}; /// /// define_fn! {expr=x in @@ -58,7 +58,7 @@ use crate::write_fn_step; /// A simpler format is also offered for unary functions: /// /// ``` -/// use orchidlang::stl::litconv::with_lit; +/// use orchidlang::systems::cast_exprinst::with_lit; /// use orchidlang::{define_fn, Literal}; /// /// define_fn! { diff --git a/src/foreign_macros/write_fn_step.rs b/src/foreign_macros/write_fn_step.rs index a2f4fea..897ca51 100644 --- a/src/foreign_macros/write_fn_step.rs +++ b/src/foreign_macros/write_fn_step.rs @@ -18,14 +18,14 @@ use crate::interpreted::ExprInst; /// discussed below. The newly bound names (here `s` and `i` before `=`) can /// also receive type annotations. /// -/// ```no_run +/// ``` /// // FIXME this is a very old example that wouldn't compile now /// use unicode_segmentation::UnicodeSegmentation; /// /// use orchidlang::{write_fn_step, Literal, Primitive}; /// use orchidlang::interpreted::Clause; -/// use orchidlang::stl::litconv::{with_str, with_uint}; -/// use orchidlang::stl::RuntimeError; +/// use orchidlang::systems::cast_exprinst::{with_str, with_uint}; +/// use orchidlang::systems::RuntimeError; /// /// // Initial state /// write_fn_step!(pub CharAt2 > CharAt1); @@ -40,7 +40,7 @@ use crate::interpreted::ExprInst; /// i = x => with_uint(x, Ok); /// { /// if let Some(c) = s.graphemes(true).nth(*i as usize) { -/// Ok(Literal::Char(c.to_string()).into()) +/// Ok(Literal::Str(c.to_string()).into()) /// } else { /// RuntimeError::fail( /// "Character index out of bounds".to_string(), diff --git a/src/interpreter/handler.rs b/src/interpreter/handler.rs index 82db766..4f9e4fe 100644 --- a/src/interpreter/handler.rs +++ b/src/interpreter/handler.rs @@ -1,5 +1,4 @@ use std::any::{Any, TypeId}; -use std::mem; use std::rc::Rc; use hashbrown::HashMap; @@ -8,7 +7,6 @@ use trait_set::trait_set; use super::{run, Context, Return, RuntimeError}; use crate::foreign::ExternError; use crate::interpreted::{Clause, ExprInst}; -use crate::utils::unwrap_or; use crate::Primitive; trait_set! { @@ -65,20 +63,15 @@ pub fn run_handler( ) -> Result { loop { let ret = run(expr.clone(), ctx.clone())?; - if ret.gas == Some(0) { - return Ok(ret); + if let Clause::P(Primitive::Atom(a)) = &ret.state.expr().clause { + if let Some(e) = handlers.dispatch(a.0.as_any()) { + expr = e?; + ctx.gas = ret.gas; + if ret.gas.map_or(true, |g| g > 0) { + continue; + } + } } - let state_ex = ret.state.expr(); - let a = if let Clause::P(Primitive::Atom(a)) = &state_ex.clause { - a - } else { - mem::drop(state_ex); - return Ok(ret); - }; - expr = unwrap_or!(handlers.dispatch(a.0.as_any()); { - mem::drop(state_ex); - return Ok(ret) - })?; - ctx.gas = ret.gas; + return Ok(ret); } } diff --git a/src/lib.rs b/src/lib.rs index 66a9e0f..c35ca6d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,4 +34,4 @@ pub use representations::{ ast, from_const_tree, interpreted, sourcefile, tree, ConstTree, Literal, Location, PathSet, Primitive, }; -pub use utils::{Side, Substack, ThreadPool}; +pub use utils::{thread_pool, Side, Substack}; diff --git a/src/parse/errors.rs b/src/parse/errors.rs index ac7220f..ad7ad84 100644 --- a/src/parse/errors.rs +++ b/src/parse/errors.rs @@ -6,7 +6,6 @@ use itertools::Itertools; use super::{Entry, Lexeme}; use crate::error::{ErrorPosition, ProjectError}; use crate::interner::InternedDisplay; -use crate::utils::iter::box_once; use crate::utils::BoxedIter; use crate::{Interner, Location, Tok}; @@ -21,8 +20,8 @@ impl ProjectError for LineNeedsPrefix { fn message(&self, i: &Interner) -> String { format!("{} cannot appear at the beginning of a line", self.entry.bundle(i)) } - fn positions(&self, _i: &Interner) -> BoxedIter { - box_once(ErrorPosition { message: None, location: self.entry.location() }) + fn one_position(&self, _i: &Interner) -> Location { + self.entry.location() } } @@ -42,8 +41,8 @@ impl ProjectError for UnexpectedEOL { .to_string() } - fn positions(&self, _i: &Interner) -> BoxedIter { - box_once(ErrorPosition { message: None, location: self.entry.location() }) + fn one_position(&self, _i: &Interner) -> Location { + self.entry.location() } } @@ -54,8 +53,8 @@ impl ProjectError for ExpectedEOL { fn description(&self) -> &str { "Expected the end of the line" } - fn positions(&self, _i: &Interner) -> BoxedIter { - box_once(ErrorPosition { location: self.location.clone(), message: None }) + fn one_position(&self, _i: &Interner) -> Location { + self.location.clone() } } @@ -87,8 +86,8 @@ impl ProjectError for ExpectedName { } } - fn positions(&self, _i: &Interner) -> BoxedIter { - box_once(ErrorPosition { location: self.entry.location(), message: None }) + fn one_position(&self, _i: &Interner) -> Location { + self.entry.location() } } @@ -126,8 +125,9 @@ impl ProjectError for Expected { let or_name = if self.or_name { " or a name" } else { "" }; format!("Expected {}{} but found {}", list, or_name, self.found.bundle(i)) } - fn positions(&self, _i: &Interner) -> BoxedIter { - box_once(ErrorPosition { location: self.found.location(), message: None }) + + fn one_position(&self, _i: &Interner) -> Location { + self.found.location() } } @@ -143,8 +143,8 @@ impl ProjectError for ReservedToken { format!("{} is a reserved token", self.entry.bundle(i)) } - fn positions(&self, _i: &Interner) -> BoxedIter { - box_once(ErrorPosition { location: self.entry.location(), message: None }) + fn one_position(&self, _i: &Interner) -> Location { + self.entry.location() } } @@ -161,8 +161,8 @@ impl ProjectError for BadTokenInRegion { format!("{} cannot appear in {}", self.entry.bundle(i), self.region) } - fn positions(&self, _i: &Interner) -> BoxedIter { - box_once(ErrorPosition { location: self.entry.location(), message: None }) + fn one_position(&self, _i: &Interner) -> Location { + self.entry.location() } } @@ -179,8 +179,8 @@ impl ProjectError for NotFound { format!("{} was expected", self.expected) } - fn positions(&self, _i: &Interner) -> BoxedIter { - box_once(ErrorPosition { location: self.location.clone(), message: None }) + fn one_position(&self, _i: &Interner) -> Location { + self.location.clone() } } @@ -191,8 +191,8 @@ impl ProjectError for LeadingNS { fn description(&self) -> &str { ":: can only follow a name token" } - fn positions(&self, _i: &Interner) -> BoxedIter { - box_once(ErrorPosition { location: self.location.clone(), message: None }) + fn one_position(&self, _i: &Interner) -> Location { + self.location.clone() } } @@ -206,8 +206,8 @@ impl ProjectError for MisalignedParen { fn message(&self, i: &Interner) -> String { format!("This {} has no pair", self.entry.bundle(i)) } - fn positions(&self, _i: &Interner) -> BoxedIter { - box_once(ErrorPosition { location: self.entry.location(), message: None }) + fn one_position(&self, _i: &Interner) -> Location { + self.entry.location() } } @@ -218,8 +218,20 @@ impl ProjectError for NamespacedExport { fn description(&self) -> &str { "Exports can only refer to unnamespaced names in the local namespace" } - fn positions(&self, _i: &Interner) -> BoxedIter { - box_once(ErrorPosition { location: self.location.clone(), message: None }) + fn one_position(&self, _i: &Interner) -> Location { + self.location.clone() + } +} + +pub struct GlobExport { + pub location: Location, +} +impl ProjectError for GlobExport { + fn description(&self) -> &str { + "Exports can only refer to concrete names, globstars are not allowed" + } + fn one_position(&self, _i: &Interner) -> Location { + self.location.clone() } } diff --git a/src/parse/multiname.rs b/src/parse/multiname.rs index 824c6f2..26dfe7a 100644 --- a/src/parse/multiname.rs +++ b/src/parse/multiname.rs @@ -1,18 +1,45 @@ use std::collections::VecDeque; -use std::iter; use super::context::Context; -use super::errors::{Expected, ExpectedName}; +use super::errors::Expected; use super::stream::Stream; use super::Lexeme; use crate::error::{ProjectError, ProjectResult}; -use crate::utils::iter::{box_chain, box_once, BoxedIterIter}; +use crate::sourcefile::Import; +use crate::utils::iter::{box_chain, box_once}; +use crate::utils::BoxedIter; use crate::Tok; +struct Subresult { + glob: bool, + deque: VecDeque>, +} +impl Subresult { + fn new_glob() -> Self { + Self { glob: true, deque: VecDeque::new() } + } + + fn new_named(name: Tok) -> Self { + Self { glob: false, deque: VecDeque::from([name]) } + } + + fn push_front(mut self, name: Tok) -> Self { + self.deque.push_front(name); + self + } + + fn finalize(self) -> Import { + let Self { mut deque, glob } = self; + debug_assert!(glob || !deque.is_empty(), "The constructors forbid this"); + let name = if glob { None } else { deque.pop_back() }; + Import { name, path: deque.into() } + } +} + fn parse_multiname_branch( cursor: Stream<'_>, ctx: impl Context, -) -> ProjectResult<(BoxedIterIter>, Stream<'_>)> { +) -> ProjectResult<(BoxedIter, Stream<'_>)> { let comma = ctx.interner().i(","); let (subnames, cursor) = parse_multiname_rec(cursor, ctx.clone())?; let (delim, cursor) = cursor.trim().pop()?; @@ -33,30 +60,46 @@ fn parse_multiname_branch( } } -pub fn parse_multiname_rec( +fn parse_multiname_rec( curosr: Stream<'_>, ctx: impl Context, -) -> ProjectResult<(BoxedIterIter>, Stream<'_>)> { +) -> ProjectResult<(BoxedIter, Stream<'_>)> { + let star = ctx.interner().i("*"); let comma = ctx.interner().i(","); - let (head, cursor) = curosr.trim().pop()?; + let (head, mut cursor) = curosr.trim().pop()?; match &head.lexeme { Lexeme::LP('(') => parse_multiname_branch(cursor, ctx), Lexeme::LP('[') => { - let (op_ent, cursor) = cursor.trim().pop()?; - let op = ExpectedName::expect(op_ent)?; - let (rp_ent, cursor) = cursor.trim().pop()?; - Expected::expect(Lexeme::RP('['), rp_ent)?; - Ok((box_once(box_once(op)), cursor)) + let mut names = Vec::new(); + loop { + let head; + (head, cursor) = cursor.trim().pop()?; + match head.lexeme { + Lexeme::Name(n) => names.push(n), + Lexeme::RP('[') => break, + _ => { + let err = Expected { + expected: vec![Lexeme::RP('[')], + or_name: true, + found: head.clone(), + }; + return Err(err.rc()); + }, + } + } + Ok((Box::new(names.into_iter().map(Subresult::new_named)), cursor)) }, - Lexeme::Name(n) if *n != comma => { + Lexeme::Name(n) if *n == star => + Ok((box_once(Subresult::new_glob()), cursor)), + Lexeme::Name(n) if ![comma, star].contains(n) => { let cursor = cursor.trim(); if cursor.get(0).ok().map(|e| &e.lexeme) == Some(&Lexeme::NS) { let cursor = cursor.step()?; let (out, cursor) = parse_multiname_rec(cursor, ctx)?; - let out = Box::new(out.map(|i| box_chain!(i, iter::once(*n)))); + let out = Box::new(out.map(|sr| sr.push_front(*n))); Ok((out, cursor)) } else { - Ok((box_once(box_once(*n)), cursor)) + Ok((box_once(Subresult::new_named(*n)), cursor)) } }, _ => Err( @@ -73,16 +116,7 @@ pub fn parse_multiname_rec( pub fn parse_multiname( cursor: Stream<'_>, ctx: impl Context, -) -> ProjectResult<(Vec>>, Stream<'_>)> { +) -> ProjectResult<(Vec, Stream<'_>)> { let (output, cont) = parse_multiname_rec(cursor, ctx)?; - let output = output - .map(|it| { - let mut deque = VecDeque::with_capacity(it.size_hint().0); - for item in it { - deque.push_front(item) - } - deque.into() - }) - .collect(); - Ok((output, cont)) + Ok((output.map(|sr| sr.finalize()).collect(), cont)) } diff --git a/src/parse/sourcefile.rs b/src/parse/sourcefile.rs index b66be47..b64d268 100644 --- a/src/parse/sourcefile.rs +++ b/src/parse/sourcefile.rs @@ -5,8 +5,8 @@ use itertools::Itertools; use super::context::Context; use super::errors::{ - BadTokenInRegion, Expected, ExpectedName, LeadingNS, MisalignedParen, - NamespacedExport, ReservedToken, UnexpectedEOL, + BadTokenInRegion, Expected, ExpectedName, GlobExport, LeadingNS, + MisalignedParen, NamespacedExport, ReservedToken, UnexpectedEOL, }; use super::lexer::Lexeme; use super::multiname::parse_multiname; @@ -71,18 +71,8 @@ pub fn parse_line( Lexeme::Const | Lexeme::Macro | Lexeme::Module => Ok(FileEntry::Internal(parse_member(cursor, ctx)?)), Lexeme::Import => { - let globstar = ctx.interner().i("*"); - let (names, cont) = parse_multiname(cursor.step()?, ctx.clone())?; + let (imports, cont) = parse_multiname(cursor.step()?, ctx)?; cont.expect_empty()?; - let imports = (names.into_iter()) - .map(|mut nsname| { - let name = nsname.pop().expect("multinames cannot be zero-length"); - Import { - path: ctx.interner().i(&nsname), - name: if name == globstar { None } else { Some(name) }, - } - }) - .collect(); Ok(FileEntry::Import(imports)) }, _ => { @@ -105,9 +95,12 @@ pub fn parse_export_line( let (names, cont) = parse_multiname(cursor.step()?, ctx)?; cont.expect_empty()?; let names = (names.into_iter()) - .map(|i| if i.len() == 1 { Some(i[0]) } else { None }) - .collect::>>() - .ok_or_else(|| NamespacedExport { location: cursor.location() }.rc())?; + .map(|Import { name, path }| match (name, &path[..]) { + (Some(n), []) => Ok(n), + (None, _) => Err(GlobExport { location: cursor.location() }.rc()), + _ => Err(NamespacedExport { location: cursor.location() }.rc()), + }) + .collect::, _>>()?; Ok(FileEntry::Export(names)) }, Lexeme::Const | Lexeme::Macro | Lexeme::Module => diff --git a/src/pipeline/file_loader.rs b/src/pipeline/file_loader.rs index f21542c..6530bdd 100644 --- a/src/pipeline/file_loader.rs +++ b/src/pipeline/file_loader.rs @@ -6,13 +6,12 @@ use std::{fs, io}; use hashbrown::{HashMap, HashSet}; use rust_embed::RustEmbed; -use crate::error::{ErrorPosition, ProjectError, ProjectResult}; +use crate::error::{ProjectError, ProjectResult}; #[allow(unused)] // for doc use crate::facade::System; use crate::interner::Interner; -use crate::utils::iter::box_once; -use crate::utils::{BoxedIter, Cache}; -use crate::{Stok, VName}; +use crate::utils::Cache; +use crate::{Location, Stok, VName}; /// All the data available about a failed source load call #[derive(Debug)] @@ -25,8 +24,8 @@ impl ProjectError for FileLoadingError { fn description(&self) -> &str { "Neither a file nor a directory could be read from the requested path" } - fn positions(&self, _i: &Interner) -> BoxedIter { - box_once(ErrorPosition::just_file(self.path.clone())) + fn one_position(&self, _i: &Interner) -> crate::Location { + Location::File(Rc::new(self.path.clone())) } fn message(&self, _i: &Interner) -> String { format!("File: {}\nDirectory: {}", self.file, self.dir) diff --git a/src/pipeline/import_resolution/collect_aliases.rs b/src/pipeline/import_resolution/collect_aliases.rs index fd8acd9..66e2f8c 100644 --- a/src/pipeline/import_resolution/collect_aliases.rs +++ b/src/pipeline/import_resolution/collect_aliases.rs @@ -14,7 +14,10 @@ fn assert_visible( target: &[Tok], // may point to a symbol or module of any kind project: &ProjectTree, ) -> ProjectResult<()> { - let (tgt_item, tgt_path) = unwrap_or!(target.split_last(); return Ok(())); + let (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); diff --git a/src/pipeline/project_tree/build_tree.rs b/src/pipeline/project_tree/build_tree.rs index 9640ca9..f7d8227 100644 --- a/src/pipeline/project_tree/build_tree.rs +++ b/src/pipeline/project_tree/build_tree.rs @@ -75,7 +75,7 @@ fn source_to_module( .collect::>(); let imports_from = (imports.iter()) .map(|imp| -> ProjectResult<_> { - let mut imp_path_v = i.r(imp.path).clone(); + let mut imp_path_v = imp.path.clone(); imp_path_v.push(imp.name.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"); diff --git a/src/pipeline/project_tree/collect_ops/exported_ops.rs b/src/pipeline/project_tree/collect_ops/exported_ops.rs index e2ece7c..5b08aae 100644 --- a/src/pipeline/project_tree/collect_ops/exported_ops.rs +++ b/src/pipeline/project_tree/collect_ops/exported_ops.rs @@ -7,7 +7,7 @@ use crate::error::{NotFound, ProjectError, ProjectResult}; use crate::interner::{Interner, Tok}; use crate::pipeline::source_loader::LoadedSourceTable; use crate::representations::tree::WalkErrorKind; -use crate::utils::{split_max_prefix, unwrap_or, Cache}; +use crate::utils::{split_max_prefix, Cache}; use crate::Sym; pub type OpsResult = ProjectResult>>>; @@ -33,40 +33,43 @@ pub fn collect_exported_ops( ) -> OpsResult { let injected = injected(path).unwrap_or_else(|| Rc::new(HashSet::new())); let path_s = &i.r(path)[..]; - let name_split = split_max_prefix(path_s, &|n| loaded.contains_key(n)); - let (fpath, subpath) = unwrap_or!(name_split; return Ok(Rc::new( - (loaded.keys()) - .filter_map(|modname| { - if path_s.len() == coprefix(path_s.iter(), modname.iter()) { - Some(modname[path_s.len()]) - } else { - None - } - }) - .chain(injected.iter().copied()) - .collect::>(), - ))); - 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") + match split_max_prefix(path_s, &|n| loaded.contains_key(n)) { + None => { + let ops = (loaded.keys()) + .filter_map(|modname| { + if path_s.len() == coprefix(path_s.iter(), modname.iter()) { + Some(modname[path_s.len()]) + } else { + None + } + }) + .chain(injected.iter().copied()) + .collect::>(); + Ok(Rc::new(ops)) + }, + Some((fpath, subpath)) => { + let preparsed = &loaded[fpath].preparsed; + let module = preparsed.0.walk_ref(subpath, false).map_err( + |walk_err| match walk_err.kind { + WalkErrorKind::Private => { + unreachable!("visibility is not being checked here") + }, + WalkErrorKind::Missing => NotFound { + source: None, + file: fpath.to_vec(), + subpath: subpath[..walk_err.pos].to_vec(), + } + .rc(), }, - 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) - .chain(injected.iter().copied()) - .collect::>(); - Ok(Rc::new(out)) + )?; + let out = (module.items.iter()) + .filter(|(_, v)| v.exported) + .map(|(k, _)| *k) + .chain(injected.iter().copied()) + .collect::>(); + Ok(Rc::new(out)) + }, + } } pub fn mk_cache<'a>( diff --git a/src/pipeline/project_tree/collect_ops/ops_for.rs b/src/pipeline/project_tree/collect_ops/ops_for.rs index c830258..49d5d72 100644 --- a/src/pipeline/project_tree/collect_ops/ops_for.rs +++ b/src/pipeline/project_tree/collect_ops/ops_for.rs @@ -44,7 +44,7 @@ pub fn collect_ops_for( ret.insert(n); } else { let path = i.expect( - import_abs_path(file, modpath, &i.r(import.path)[..], i), + import_abs_path(file, modpath, &import.path, i), "This error should have been caught during loading", ); ret.extend(ops_cache.find(&i.i(&path))?.iter().copied()); diff --git a/src/pipeline/project_tree/normalize_imports.rs b/src/pipeline/project_tree/normalize_imports.rs index 2d32da9..4a3d4b3 100644 --- a/src/pipeline/project_tree/normalize_imports.rs +++ b/src/pipeline/project_tree/normalize_imports.rs @@ -59,7 +59,7 @@ fn entv_rec( .flat_map(|import| { if let Import { name: None, path } = import { let p = i.expect( - import_abs_path(mod_path, mod_stack, &i.r(path)[..], i), + import_abs_path(mod_path, mod_stack, &path, i), "Should have emerged in preparsing", ); let names = i.expect( @@ -67,7 +67,7 @@ fn entv_rec( "Should have emerged in second parsing", ); let imports = (names.iter()) - .map(move |&n| Import { name: Some(n), path }) + .map(|&n| Import { name: Some(n), path: path.clone() }) .collect::>(); Box::new(imports.into_iter()) as BoxedIter } else { diff --git a/src/pipeline/source_loader/load_source.rs b/src/pipeline/source_loader/load_source.rs index 2c57558..8b59ef4 100644 --- a/src/pipeline/source_loader/load_source.rs +++ b/src/pipeline/source_loader/load_source.rs @@ -56,7 +56,7 @@ fn load_abs_path_rec( // recurse on all imported modules preparsed.0.visit_all_imports(&mut |modpath, _module, import| { let abs_pathv = - import_abs_path(filename, modpath, &import.nonglob_path(i), i)?; + import_abs_path(filename, modpath, &import.nonglob_path(), i)?; if abs_path.starts_with(&abs_pathv) { return Ok(()); } diff --git a/src/representations/ast_to_postmacro.rs b/src/representations/ast_to_postmacro.rs index 11b0853..16e47b1 100644 --- a/src/representations/ast_to_postmacro.rs +++ b/src/representations/ast_to_postmacro.rs @@ -2,9 +2,8 @@ use std::rc::Rc; use super::location::Location; use super::{ast, postmacro}; -use crate::error::{ErrorPosition, ProjectError}; -use crate::utils::iter::box_once; -use crate::utils::{BoxedIter, Substack}; +use crate::error::ProjectError; +use crate::utils::Substack; use crate::{Interner, Sym}; #[derive(Debug, Clone)] @@ -51,9 +50,8 @@ impl ProjectError for Error { _ => self.description().to_string(), } } - - fn positions(&self, _i: &Interner) -> BoxedIter { - box_once(ErrorPosition { location: self.location.clone(), message: None }) + fn one_position(&self, _i: &Interner) -> Location { + self.location.clone() } } diff --git a/src/representations/sourcefile.rs b/src/representations/sourcefile.rs index 3dff083..54629ca 100644 --- a/src/representations/sourcefile.rs +++ b/src/representations/sourcefile.rs @@ -7,11 +7,10 @@ use super::namelike::VName; use crate::ast::{Constant, Rule}; use crate::interner::{Interner, Tok}; use crate::utils::{unwrap_or, BoxedIter}; -use crate::Sym; /// An import pointing at another module, either specifying the symbol to be /// imported or importing all available symbols with a globstar (*) -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Import { /// Import path, a sequence of module names. Can either start with /// @@ -19,7 +18,7 @@ pub struct Import { /// - any number of `super` to reference the parent module of the implied /// `self` /// - a root name - pub path: Sym, + pub path: VName, /// If name is None, this is a wildcard import pub name: Option>, } @@ -29,8 +28,8 @@ impl Import { /// /// Returns the path if this is a glob import, or the path plus the /// name if this is a specific import - pub fn nonglob_path(&self, i: &Interner) -> Vec> { - let mut path_vec = i.r(self.path).clone(); + pub fn nonglob_path(&self) -> Vec> { + let mut path_vec = self.path.clone(); if let Some(n) = self.name { path_vec.push(n) } diff --git a/src/systems/io/facade.rs b/src/systems/io/facade.rs index eb349e2..6695658 100644 --- a/src/systems/io/facade.rs +++ b/src/systems/io/facade.rs @@ -145,7 +145,7 @@ impl<'a, P: MessagePort, ST: StreamTable + 'a> IntoSystem<'a> constants: io_bindings(i, streams).unwrap_tree(), code: embed_to_map::(".orc", i), prelude: vec![FileEntry::Import(vec![Import { - path: i.i(&vec![i.i("system"), i.i("io"), i.i("prelude")]), + path: vec![i.i("system"), i.i("io"), i.i("prelude")], name: None, }])], handlers, diff --git a/src/systems/io/flow.rs b/src/systems/io/flow.rs index 1a42fda..2c0ef7a 100644 --- a/src/systems/io/flow.rs +++ b/src/systems/io/flow.rs @@ -5,8 +5,8 @@ use hashbrown::HashMap; use crate::foreign::ExternError; use crate::systems::asynch::MessagePort; -use crate::utils::{take_with_output, Task}; -use crate::ThreadPool; +use crate::thread_pool::{Task, ThreadPool}; +use crate::utils::take_with_output; pub trait StreamHandle: Clone + Send { fn new(id: usize) -> Self; diff --git a/src/systems/stl/prelude.orc b/src/systems/stl/prelude.orc index 18b5c33..36d04b2 100644 --- a/src/systems/stl/prelude.orc +++ b/src/systems/stl/prelude.orc @@ -1,11 +1,11 @@ import std::num::* -export ::(+, -, *, /, %) +export ::[+ - * / %] import std::str::* export ::[++] import std::bool::* -export ::(==, if, then, else, true, false) +export ::([==], if, then, else, true, false) import std::fn::* -export ::($, |>, =>, identity, pass, pass2, return) +export ::([$ |> =>], identity, pass, pass2, return) import std::list import std::map import std::option diff --git a/src/systems/stl/stl_system.rs b/src/systems/stl/stl_system.rs index e4a6a24..3c00f9f 100644 --- a/src/systems/stl/stl_system.rs +++ b/src/systems/stl/stl_system.rs @@ -41,7 +41,7 @@ impl IntoSystem<'static> for StlConfig { constants: HashMap::from([(i.i("std"), fns)]), code: embed_to_map::(".orc", i), prelude: vec![FileEntry::Import(vec![Import { - path: i.i(&[i.i("std"), i.i("prelude")][..]), + path: vec![i.i("std"), i.i("prelude")], name: None, }])], handlers: HandlerTable::new(), diff --git a/src/utils/iter.rs b/src/utils/iter.rs index bae1c8e..cb0a630 100644 --- a/src/utils/iter.rs +++ b/src/utils/iter.rs @@ -5,8 +5,6 @@ use std::iter; /// A trait object of [Iterator] to be assigned to variables that may be /// initialized form multiple iterators of incompatible types pub type BoxedIter<'a, T> = Box + 'a>; -/// A [BoxedIter] of [BoxedIter]. -pub type BoxedIterIter<'a, T> = BoxedIter<'a, BoxedIter<'a, T>>; /// creates a [BoxedIter] of a single element pub fn box_once<'a, T: 'a>(t: T) -> BoxedIter<'a, T> { Box::new(iter::once(t)) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index b27d279..dc2bb45 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -11,7 +11,7 @@ mod split_max_prefix; mod string_from_charset; mod substack; mod take_with_output; -mod thread_pool; +pub mod thread_pool; mod unwrap_or; pub use cache::Cache; @@ -30,4 +30,3 @@ pub use iter::BoxedIter; pub use iter_find::iter_find; pub use string_from_charset::string_from_charset; pub use take_with_output::take_with_output; -pub use thread_pool::{Query, QueryTask, Task, ThreadPool}; diff --git a/src/utils/thread_pool.rs b/src/utils/thread_pool.rs index 2008b90..93db5ff 100644 --- a/src/utils/thread_pool.rs +++ b/src/utils/thread_pool.rs @@ -1,3 +1,7 @@ +//! A thread pool for executing tasks in parallel, spawning threads as workload +//! increases and terminating them as tasks finish. This is not terribly +//! efficient, its main design goal is to parallelize blocking I/O calls. + use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{sync_channel, SyncSender}; use std::sync::{Arc, Mutex}; @@ -6,6 +10,8 @@ use std::thread::spawn; /// A trait for a task dispatched on a [ThreadPool]. The task owns all relevant /// data, is safe to pass between threads and is executed only once. pub trait Task: Send + 'static { + /// Execute the task. At a minimum, this involves signaling some other thread, + /// otherwise the task has no effect. fn run(self); } @@ -15,10 +21,21 @@ impl Task for F { } } +/// An async unit of work that produces some result, see [Task]. This can be +/// wrapped in a generic reporter to create a task. pub trait Query: Send + 'static { + /// The value produced by the query type Result: Send + 'static; + /// Execute the query, producing some value which can then be sent to another + /// thread fn run(self) -> Self::Result; + + /// Associate the query with a reporter expressed in a plain function. + /// Note that because every lambda has a distinct type and every thread pool + /// runs exactly one type of task, this can appear only once in the code for + /// a given thread pool. It is practical in a narrow set of cases, most of the + /// time however you are better off defining an explicit reporter. fn then( self, callback: F, @@ -37,6 +54,8 @@ impl R + Send + 'static, R: Send + 'static> Query for F { } } +/// A reporter that calls a statically known function with the result of a +/// query. Constructed with [Query::then] pub struct QueryTask { query: Q, callback: F, @@ -68,16 +87,23 @@ struct ThreadPoolData { /// arrive. To get rid of the last waiting thread, drop the thread pool. /// /// ``` -/// use orchidlang::ThreadPool; +/// use orchidlang::thread_pool::{Task, ThreadPool}; /// -/// let pool = ThreadPool::new(|s: String, _| println!("{}", s)); +/// struct MyTask(&'static str); +/// impl Task for MyTask { +/// fn run(self) { +/// println!("{}", self.0) +/// } +/// } +/// +/// let pool = ThreadPool::new(); /// /// // spawns first thread -/// pool.submit("foo".to_string()); +/// pool.submit(MyTask("foo")); /// // probably spawns second thread -/// pool.submit("bar".to_string()); +/// pool.submit(MyTask("bar")); /// // either spawns third thread or reuses first -/// pool.submit("baz".to_string()); +/// pool.submit(MyTask("baz")); /// ``` pub struct ThreadPool { data: Arc>, diff --git a/src/utils/unwrap_or.rs b/src/utils/unwrap_or.rs index 944835f..6692981 100644 --- a/src/utils/unwrap_or.rs +++ b/src/utils/unwrap_or.rs @@ -8,10 +8,14 @@ /// It also supports unwrapping concrete variants of other enums /// /// ```ignore -/// use crate::representations::Literal; +/// use crate::Literal; /// /// crate::unwrap_or!(Literal::Usize(2) => Literal::Number; return) /// ``` +/// +/// Note: this macro influences the control flow of the surrounding code +/// without an `if`, which can be misleading. It should only be used for small, +/// straightforward jumps. macro_rules! unwrap_or { ($m:expr; $fail:expr) => {{ if let Some(res) = ($m) { res } else { $fail }