diff --git a/examples/hello-world/main.orc b/examples/hello-world/main.orc index 3e0df95..d01a002 100644 --- a/examples/hello-world/main.orc +++ b/examples/hello-world/main.orc @@ -1,9 +1,10 @@ import std::exit_status import std::conv +import std::number +import std::tuple +import std::list -const main2 := ( - println "Hello, world!" - exit_status::success -) - -const main := conv::to_string t[1, 2, 3] +const main := match t["set", "foo", 1] { + t[= "set", key, val] => + $"Setting ${ key ++ $"${1 + 1}" } to ${val}" +} diff --git a/orchid.code-workspace b/orchid.code-workspace index 544c0f4..b91a55d 100644 --- a/orchid.code-workspace +++ b/orchid.code-workspace @@ -19,12 +19,14 @@ "editor.lineNumbers": "off", "editor.glyphMargin": false, "editor.rulers": [], - "editor.guides.indentation": false, + "editor.guides.indentation": false, "editor.formatOnSave": true, "editor.formatOnType": true, }, "[rust]": { - "editor.rulers": [80], + "editor.rulers": [ + 100 + ], }, "rust-analyzer.showUnlinkedFileNotification": false, "rust-analyzer.checkOnSave": true, diff --git a/rustfmt.toml b/rustfmt.toml index 7746353..d091543 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -5,7 +5,7 @@ version = "Two" # space tab_spaces = 2 -max_width = 80 +max_width = 100 error_on_line_overflow = true format_macro_matchers = true newline_style = "Unix" diff --git a/src/bin/orcx.rs b/src/bin/orcx.rs index 5fb49d7..b82f919 100644 --- a/src/bin/orcx.rs +++ b/src/bin/orcx.rs @@ -48,6 +48,7 @@ enum Command { }, #[command(arg_required_else_help = true)] MacroDebug { + #[arg(long, short)] symbol: String, }, ListMacros, diff --git a/src/facade/merge_trees.rs b/src/facade/merge_trees.rs index 632e773..c2ce32f 100644 --- a/src/facade/merge_trees.rs +++ b/src/facade/merge_trees.rs @@ -1,8 +1,6 @@ use std::sync::Arc; use hashbrown::HashMap; -use never::Never; -use substack::Substack; use super::system::System; use crate::error::ProjectResult; @@ -10,9 +8,10 @@ use crate::intermediate::ast_to_ir::ast_to_ir; use crate::intermediate::ir_to_nort::ir_to_nort; use crate::interpreter::nort; use crate::location::{CodeGenInfo, CodeLocation}; -use crate::name::{Sym, VPath}; +use crate::name::Sym; use crate::pipeline::project::ConstReport; -use crate::tree::{ModMember, ModMemberRef, TreeTransforms}; +use crate::tree::{ModMemberRef, TreeTransforms}; +use crate::utils::unwrap_or::unwrap_or; /// Equivalent of [crate::pipeline::project::ConstReport] for the interpreter's /// representation, [crate::interpreter::nort]. @@ -33,28 +32,27 @@ pub fn merge_trees<'a: 'b, 'b>( ) -> ProjectResult + 'static> { let mut out = HashMap::new(); for (name, rep) in source { + let ir = ast_to_ir(rep.value, name.clone())?; + // if name == Sym::literal("tree::main::main") { + // panic!("{ir:?}"); + // } out.insert(name.clone(), NortConst { - value: ir_to_nort(&ast_to_ir(rep.value, name)?), + value: ir_to_nort(&ir), location: CodeLocation::Source(rep.range), comments: rep.comments, }); } - for sys in systems { - let const_module = sys.constants.unwrap_mod_ref(); - const_module.search_all((), |path, node, ()| { - let m = if let ModMemberRef::Mod(m) = node { m } else { return }; - for (key, ent) in &m.entries { - if let ModMember::Item(c) = &ent.member { - let path = VPath::new(path.unreverse()).as_prefix_of(key.clone()); - let location = CodeLocation::Gen(CodeGenInfo::details( - "constant from", - format!("system.name={}", sys.name), - )); - let value = c.gen_nort(location.clone()); - let crep = NortConst { value, comments: vec![], location }; - out.insert(path.to_sym(), crep); - } - } + for system in systems { + let const_module = system.constants.unwrap_mod_ref(); + const_module.search_all((), |stack, node, ()| { + let c = unwrap_or!(node => ModMemberRef::Item; return); + let location = CodeLocation::Gen(CodeGenInfo::details( + "constant from", + format!("system.name={}", system.name), + )); + let value = c.clone().gen_nort(stack.clone(), location.clone()); + let crep = NortConst { value, comments: vec![], location }; + out.insert(Sym::new(stack.unreverse()).expect("root item is forbidden"), crep); }); } Ok(out) diff --git a/src/facade/process.rs b/src/facade/process.rs index 26e0c8a..9ee2707 100644 --- a/src/facade/process.rs +++ b/src/facade/process.rs @@ -9,7 +9,6 @@ use crate::interpreter::handler::{run_handler, HandlerTable}; use crate::interpreter::nort::{Clause, Expr}; use crate::location::CodeLocation; use crate::name::Sym; -use crate::utils::boxed_iter::BoxedIter; /// This struct ties the state of systems to loaded code, and allows to call /// Orchid-defined functions @@ -37,7 +36,7 @@ impl<'a> Process<'a> { prompt: Expr, gas: Option, ) -> Result { - let ctx = RunContext { gas, symbols: &self.symbols }; + let ctx = RunContext { gas, symbols: &self.symbols, stack_size: 1000 }; run_handler(prompt, &mut self.handlers, ctx) } @@ -48,7 +47,7 @@ impl<'a> Process<'a> { let mut errors = Vec::new(); let sym = self.symbols.get(&key).expect("symbol must exist"); sym.search_all(&mut |s: &Expr| { - if let Clause::Constant(sym) = &*s.clause.cls() { + if let Clause::Constant(sym) = &*s.cls() { if !self.symbols.contains_key(sym) { errors.push((sym.clone(), s.location())) } diff --git a/src/foreign/atom.rs b/src/foreign/atom.rs index 1f6ef98..5fba064 100644 --- a/src/foreign/atom.rs +++ b/src/foreign/atom.rs @@ -5,35 +5,27 @@ use std::sync::{Arc, Mutex}; use never::Never; use super::error::{ExternError, ExternResult}; -use crate::interpreter::apply::CallData; use crate::interpreter::context::RunContext; use crate::interpreter::error::RunError; use crate::interpreter::nort; -use crate::interpreter::run::RunData; use crate::location::{CodeLocation, SourceRange}; use crate::name::NameLike; use crate::parse::parsed; use crate::utils::ddispatch::{request, Request, Responder}; -/// Information returned by [Atomic::run]. This mirrors -/// [crate::interpreter::Return] but with a clause instead of an Expr. -pub struct AtomicReturn { - /// The next form of the expression - pub clause: nort::Clause, - /// Remaining gas - pub gas: Option, - /// Whether further normalization is possible by repeated calls to - /// [Atomic::run] - pub inert: bool, +/// Information returned by [Atomic::run]. +pub enum AtomicReturn { + /// No work was done. If the atom takes an argument, it can be provided now + Inert(nort::Clause), + /// Work was done, returns new clause and consumed gas. 1 gas is already + /// consumed by the virtual call, so nonzero values indicate expensive + /// operations. + Change(usize, nort::Clause), } impl AtomicReturn { /// Report indicating that the value is inert - pub fn inert(this: T, ctx: RunContext) -> Result { - Ok(Self { clause: this.atom_cls(), gas: ctx.gas, inert: true }) - } - /// Report indicating that the value has been processed - pub fn run(clause: nort::Clause, run: RunData) -> Result { - Ok(Self { clause, gas: run.ctx.gas, inert: false }) + pub fn inert(this: T) -> Result { + Ok(Self::Inert(this.atom_cls())) } } @@ -51,6 +43,25 @@ impl Display for NotAFunction { } } +/// Information about a function call presented to an external function +pub struct CallData<'a> { + /// Location of the function expression + pub location: CodeLocation, + /// The argument the function was called on. Functions are curried + pub arg: nort::Expr, + /// Information relating to this interpreter run + pub ctx: RunContext<'a>, +} + +/// Information about a normalization run presented to an atom +#[derive(Clone)] +pub struct RunData<'a> { + /// Location of the atom + pub location: CodeLocation, + /// Information about the execution + pub ctx: RunContext<'a>, +} + /// Functionality the interpreter needs to handle a value /// /// # Lifecycle methods @@ -91,7 +102,7 @@ where Self: 'static /// Returns a reference to a possible expression held inside the atom which /// can be reduced. For an overview of the lifecycle see [Atomic] - fn redirect(&mut self) -> Option<&mut nort::ClauseInst>; + fn redirect(&mut self) -> Option<&mut nort::Expr>; /// Attempt to normalize this value. If it wraps a value, this should report /// inert. If it wraps a computation, it should execute one logical step of @@ -172,7 +183,7 @@ impl AtomGenerator { } impl Debug for AtomGenerator { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("AtomGenerator").finish_non_exhaustive() + write!(f, "{:?}", self.run()) } } @@ -239,7 +250,7 @@ impl Responder for Never { impl Atomic for Never { fn as_any(self: Box) -> Box { match *self {} } fn as_any_ref(&self) -> &dyn Any { match *self {} } - fn redirect(&mut self) -> Option<&mut nort::ClauseInst> { match *self {} } + fn redirect(&mut self) -> Option<&mut nort::Expr> { match *self {} } fn run(self: Box, _: RunData) -> AtomicResult { match *self {} } fn apply_ref(&self, _: CallData) -> ExternResult { match *self {} diff --git a/src/foreign/cps_box.rs b/src/foreign/cps_box.rs index bbb97c5..07e426b 100644 --- a/src/foreign/cps_box.rs +++ b/src/foreign/cps_box.rs @@ -4,11 +4,11 @@ use std::fmt::Debug; use trait_set::trait_set; -use super::atom::{Atomic, AtomicResult, AtomicReturn, NotAFunction}; +use super::atom::{ + Atomic, AtomicResult, AtomicReturn, CallData, NotAFunction, RunData, +}; use super::error::{ExternError, ExternResult}; -use crate::interpreter::apply::CallData; -use crate::interpreter::nort::{Clause, ClauseInst, Expr}; -use crate::interpreter::run::RunData; +use crate::interpreter::nort::{Clause, Expr}; use crate::location::CodeLocation; use crate::utils::ddispatch::{Request, Responder}; use crate::utils::pure_seq::pushed_ref; @@ -78,9 +78,9 @@ impl Atomic for CPSBox { fn as_any(self: Box) -> Box { self } fn as_any_ref(&self) -> &dyn std::any::Any { self } fn parser_eq(&self, _: &dyn std::any::Any) -> bool { false } - fn redirect(&mut self) -> Option<&mut ClauseInst> { None } - fn run(self: Box, run: RunData) -> AtomicResult { - AtomicReturn::inert(*self, run.ctx) + fn redirect(&mut self) -> Option<&mut Expr> { None } + fn run(self: Box, _: RunData) -> AtomicResult { + AtomicReturn::inert(*self) } fn apply(mut self: Box, call: CallData) -> ExternResult { self.assert_applicable(&call.location)?; diff --git a/src/foreign/error.rs b/src/foreign/error.rs index f9cf981..c902db6 100644 --- a/src/foreign/error.rs +++ b/src/foreign/error.rs @@ -10,7 +10,7 @@ use crate::location::CodeLocation; pub trait ExternError: Display + Send + Sync + DynClone { /// Convert into trait object #[must_use] - fn rc(self) -> Arc + fn rc(self) -> ExternErrorObj where Self: 'static + Sized { Arc::new(self) } @@ -25,7 +25,10 @@ impl Debug for dyn ExternError { impl Error for dyn ExternError {} /// An error produced by Rust code called form Orchid. The error is type-erased. -pub type ExternResult = Result>; +pub type ExternErrorObj = Arc; + +/// A result produced by Rust code called from Orchid. +pub type ExternResult = Result; /// Some expectation (usually about the argument types of a function) did not /// hold. @@ -52,7 +55,7 @@ impl AssertionError { location: CodeLocation, message: &'static str, details: String, - ) -> Arc { + ) -> ExternErrorObj { Self { location, message, details }.rc() } } diff --git a/src/foreign/fn_bridge.rs b/src/foreign/fn_bridge.rs index f7cad49..e53c01c 100644 --- a/src/foreign/fn_bridge.rs +++ b/src/foreign/fn_bridge.rs @@ -1,15 +1,14 @@ use std::any::{Any, TypeId}; -use std::fmt::Debug; +use std::fmt::{Debug, Display}; use std::marker::PhantomData; -use super::atom::{Atomic, AtomicResult, AtomicReturn}; +use intern_all::{i, Tok}; + +use super::atom::{Atomic, AtomicResult, AtomicReturn, CallData, RunData}; use super::error::ExternResult; use super::to_clause::ToClause; use super::try_from_expr::TryFromExpr; -use crate::interpreter::apply::CallData; -use crate::interpreter::context::Halt; -use crate::interpreter::nort::{Clause, ClauseInst, Expr}; -use crate::interpreter::run::{run, RunData}; +use crate::interpreter::nort::{Clause, Expr}; use crate::utils::ddispatch::Responder; /// Return a unary lambda wrapped in this struct to take an additional argument @@ -27,22 +26,31 @@ use crate::utils::ddispatch::Responder; /// type's [TryFromExpr] impl. pub struct Param { data: F, + name: Tok, _t: PhantomData, _u: PhantomData, } unsafe impl Send for Param {} impl Param { /// Wrap a new function in a parametric struct - pub fn new(f: F) -> Self + pub fn new(name: Tok, f: F) -> Self where F: FnOnce(T) -> U { - Self { data: f, _t: PhantomData, _u: PhantomData } + Self { name, data: f, _t: PhantomData, _u: PhantomData } } /// Take out the function pub fn get(self) -> F { self.data } } impl Clone for Param { fn clone(&self) -> Self { - Self { data: self.data.clone(), _t: PhantomData, _u: PhantomData } + Self { name: self.name.clone(), data: self.data.clone(), _t: PhantomData, _u: PhantomData } + } +} +impl Display for Param { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.name) } +} +impl Debug for Param { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Param").field(&*self.name).finish() } } @@ -65,9 +73,7 @@ impl Clone for FnMiddleStage { } impl Debug for FnMiddleStage { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("FnMiddleStage") - .field("argument", &self.arg) - .finish_non_exhaustive() + write!(f, "FnMiddleStage({} {})", self.f, self.arg) } } impl Responder for FnMiddleStage {} @@ -79,26 +85,18 @@ impl< { fn as_any(self: Box) -> Box { self } fn as_any_ref(&self) -> &dyn std::any::Any { self } - fn redirect(&mut self) -> Option<&mut ClauseInst> { + fn redirect(&mut self) -> Option<&mut Expr> { // this should be ctfe'd - (TypeId::of::() != TypeId::of::()).then(|| &mut self.arg.clause) + (TypeId::of::() != TypeId::of::()).then_some(&mut self.arg) } fn run(self: Box, r: RunData) -> AtomicResult { let Self { arg, f: Param { data: f, .. } } = *self; - let clause = f(arg.downcast()?).to_clause(r.location); - Ok(AtomicReturn { gas: r.ctx.gas, inert: false, clause }) - } - fn apply_ref(&self, _: CallData) -> ExternResult { - panic!("Atom should have decayed") + Ok(AtomicReturn::Change(0, f(arg.downcast()?).to_clause(r.location))) } + fn apply_ref(&self, _: CallData) -> ExternResult { panic!("Atom should have decayed") } } impl Responder for Param {} -impl Debug for Param { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Param") - } -} impl< T: 'static + TryFromExpr + Clone, @@ -108,10 +106,8 @@ impl< { fn as_any(self: Box) -> Box { self } fn as_any_ref(&self) -> &dyn std::any::Any { self } - fn redirect(&mut self) -> Option<&mut ClauseInst> { None } - fn run(self: Box, r: RunData) -> AtomicResult { - AtomicReturn::inert(*self, r.ctx) - } + fn redirect(&mut self) -> Option<&mut Expr> { None } + fn run(self: Box, _: RunData) -> AtomicResult { AtomicReturn::inert(*self) } fn apply_ref(&self, call: CallData) -> ExternResult { Ok(FnMiddleStage { arg: call.arg, f: self.clone() }.atom_cls()) } @@ -120,17 +116,42 @@ impl< } } +/// Convert a Rust function to Orchid. If you can, register your Rust functions +/// statically with functions in [crate::gen::tree]. +pub fn xfn( + name: &str, + x: impl Xfn, +) -> impl Atomic + Clone { + x.to_atomic(i(name)) +} + +/// Trait for functions that can be directly passed to Orchid. Constraints in a +/// nutshell: +/// +/// - the function must live as long as ['static] +/// - All arguments must implement [TryFromExpr] +/// - all but the last argument must implement [Clone] and [Send] +/// - the return type must implement [ToClause] +/// +/// Take [Thunk] to consume the argument as-is, without normalization. +pub trait Xfn: Clone + 'static { + /// Convert Rust type to Orchid function, given a name for logging + fn to_atomic(self, name: Tok) -> impl Atomic + Clone; +} + /// Conversion functions from [Fn] traits into [Atomic]. Since Rust's type /// system allows overloaded [Fn] implementations, we must specify the arity and /// argument types for this process. Arities are only defined up to 9, but the /// function can always return another call to `xfn_`N`ary` to consume more /// arguments. -pub mod constructors { +pub mod xfn_impls { + use intern_all::{i, Tok}; + use super::super::atom::Atomic; use super::super::try_from_expr::TryFromExpr; #[allow(unused)] // for doc use super::Thunk; - use super::{Param, ToClause}; + use super::{Param, ToClause, Xfn}; macro_rules! xfn_variant { ( @@ -139,42 +160,40 @@ pub mod constructors { ($($alt:expr)*) ) => { paste::paste!{ - #[doc = "Convert a function of " $number " argument(s) into a curried" - " Orchid function. See also Constraints summarized:\n\n" - "- the callback must live as long as `'static`\n" - "- All arguments must implement [TryFromExpr]\n" - "- all but the last argument must implement [Clone] and [Send]\n" - "- the return type must implement [ToClause].\n\n" - ] - #[doc = "Take [Lazy] to take the argument as-is,\n" - "without normalization\n\n" - ] - #[doc = "Other arities: " $( "[xfn_" $alt "ary], " )+ ] - pub fn [< xfn_ $number ary >] < + impl< $( $t : TryFromExpr + Clone + Send + 'static, )* TLast: TryFromExpr + Clone + 'static, TReturn: ToClause + Send + 'static, TFunction: FnOnce( $( $t , )* TLast ) -> TReturn + Clone + Send + 'static - >(function: TFunction) -> impl Atomic + Clone { - xfn_variant!(@BODY_LOOP function - ( $( ( $t [< $t:lower >] ) )* ) - ( $( [< $t:lower >] )* ) - ) + > Xfn<$number, ($($t,)* TLast,), TReturn> for TFunction { + fn to_atomic(self, name: Tok) -> impl Atomic + Clone { + #[allow(unused_variables)] + let argc = 0; + let stage_n = name.clone(); + xfn_variant!(@BODY_LOOP self name stage_n argc + ( $( ( $t [< $t:lower >] ) )* ) + ( $( [< $t:lower >] )* ) + ) + } } } }; - (@BODY_LOOP $function:ident ( + (@BODY_LOOP $function:ident $name:ident $stage_n:ident $argc:ident ( ( $Next:ident $next:ident ) $( ( $T:ident $t:ident ) )* - ) $full:tt) => { - Param::new(|$next : $Next| { - xfn_variant!(@BODY_LOOP $function ( $( ( $T $t ) )* ) $full) + ) $full:tt) => {{ + Param::new($stage_n, move |$next : $Next| { + let $argc = $argc + 1; + let $stage_n = i(&format!("{}/{}", $name, $argc)); + xfn_variant!(@BODY_LOOP $function $name $stage_n $argc ( $( ( $T $t ) )* ) $full) }) - }; - (@BODY_LOOP $function:ident () ( $( $t:ident )* )) => { - Param::new(|last: TLast| $function ( $( $t , )* last )) - }; + }}; + (@BODY_LOOP $function:ident $name:ident $stage_n:ident $argc:ident ( + + ) ( $( $t:ident )* )) => {{ + Param::new($stage_n, |last: TLast| $function ( $( $t , )* last )) + }}; } xfn_variant!(1, () (2 3 4 5 6 7 8 9 10 11 12 13 14 15 16)); diff --git a/src/foreign/implementations.rs b/src/foreign/implementations.rs index 09790b5..1d2dc46 100644 --- a/src/foreign/implementations.rs +++ b/src/foreign/implementations.rs @@ -1,18 +1,15 @@ use std::any::Any; use std::fmt::Debug; -use std::sync::Arc; -use super::atom::{Atom, Atomic, AtomicResult}; -use super::error::{ExternError, ExternResult}; +use super::atom::{Atom, Atomic, AtomicResult, CallData, RunData}; +use super::error::{ExternErrorObj, ExternResult}; use super::process::Unstable; use super::to_clause::ToClause; use crate::gen::tpl; use crate::gen::traits::Gen; -use crate::interpreter::apply::CallData; use crate::interpreter::error::RunError; use crate::interpreter::gen_nort::nort_gen; -use crate::interpreter::nort::{Clause, ClauseInst}; -use crate::interpreter::run::RunData; +use crate::interpreter::nort::{Clause, Expr}; use crate::location::CodeLocation; use crate::utils::clonable_iter::Clonable; use crate::utils::ddispatch::Responder; @@ -40,7 +37,7 @@ impl ToClause for Result { } } -struct PendingError(Arc); +struct PendingError(ExternErrorObj); impl Responder for PendingError {} impl Debug for PendingError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -50,7 +47,7 @@ impl Debug for PendingError { impl Atomic for PendingError { fn as_any(self: Box) -> Box { self } fn as_any_ref(&self) -> &dyn Any { self } - fn redirect(&mut self) -> Option<&mut ClauseInst> { None } + fn redirect(&mut self) -> Option<&mut Expr> { None } fn run(self: Box, _: RunData) -> AtomicResult { Err(RunError::Extern(self.0)) } diff --git a/src/foreign/inert.rs b/src/foreign/inert.rs index d6ef8db..f76414b 100644 --- a/src/foreign/inert.rs +++ b/src/foreign/inert.rs @@ -4,13 +4,13 @@ use std::ops::{Deref, DerefMut}; use ordered_float::NotNan; -use super::atom::{Atom, Atomic, AtomicResult, AtomicReturn, NotAFunction}; +use super::atom::{ + Atom, Atomic, AtomicResult, AtomicReturn, CallData, NotAFunction, RunData, +}; use super::error::{ExternError, ExternResult}; use super::try_from_expr::TryFromExpr; use crate::foreign::error::AssertionError; -use crate::interpreter::apply::CallData; -use crate::interpreter::nort::{Clause, ClauseInst, Expr}; -use crate::interpreter::run::RunData; +use crate::interpreter::nort::{Clause, Expr}; use crate::libs::std::number::Numeric; use crate::libs::std::string::OrcString; use crate::utils::ddispatch::{Request, Responder}; @@ -72,9 +72,9 @@ impl Atomic for Inert { fn as_any(self: Box) -> Box { self } fn as_any_ref(&self) -> &dyn Any { self } - fn redirect(&mut self) -> Option<&mut ClauseInst> { None } - fn run(self: Box, run: RunData) -> AtomicResult { - AtomicReturn::inert(*self, run.ctx) + fn redirect(&mut self) -> Option<&mut Expr> { None } + fn run(self: Box, _: RunData) -> AtomicResult { + AtomicReturn::inert(*self) } fn apply_ref(&self, call: CallData) -> ExternResult { Err(NotAFunction(self.clone().atom_expr(call.location)).rc()) diff --git a/src/foreign/process.rs b/src/foreign/process.rs index 2db90d0..6116b91 100644 --- a/src/foreign/process.rs +++ b/src/foreign/process.rs @@ -1,11 +1,9 @@ use std::fmt::Debug; -use super::atom::{Atomic, AtomicReturn}; +use super::atom::{Atomic, AtomicReturn, CallData, RunData}; use super::error::ExternResult; use super::to_clause::ToClause; -use crate::interpreter::apply::CallData; -use crate::interpreter::nort::{Clause, ClauseInst}; -use crate::interpreter::run::RunData; +use crate::interpreter::nort::{Clause, Expr}; use crate::utils::ddispatch::Responder; /// An atom that immediately decays to the result of the function when @@ -33,7 +31,7 @@ impl R + Send + 'static, R: ToClause> Atomic } fn run(self: Box, run: RunData) -> super::atom::AtomicResult { let clause = self.0(run.clone()).to_clause(run.location.clone()); - AtomicReturn::run(clause, run) + Ok(AtomicReturn::Change(0, clause)) } - fn redirect(&mut self) -> Option<&mut ClauseInst> { None } + fn redirect(&mut self) -> Option<&mut Expr> { None } } diff --git a/src/foreign/try_from_expr.rs b/src/foreign/try_from_expr.rs index 0874358..f6ab809 100644 --- a/src/foreign/try_from_expr.rs +++ b/src/foreign/try_from_expr.rs @@ -14,7 +14,7 @@ impl TryFromExpr for Expr { } impl TryFromExpr for ClauseInst { - fn from_expr(expr: Expr) -> ExternResult { Ok(expr.clause.clone()) } + fn from_expr(expr: Expr) -> ExternResult { Ok(expr.clsi()) } } /// Request a value of a particular type and also return its location for diff --git a/src/gen/tree.rs b/src/gen/tree.rs index 0b372bc..32bbf69 100644 --- a/src/gen/tree.rs +++ b/src/gen/tree.rs @@ -4,11 +4,15 @@ use std::fmt::Debug; use dyn_clone::{clone_box, DynClone}; +use intern_all::Tok; +use itertools::Itertools; +use substack::Substack; use trait_set::trait_set; use super::tpl; use super::traits::{Gen, GenClause}; -use crate::foreign::atom::Atomic; +use crate::foreign::atom::{AtomGenerator, Atomic}; +use crate::foreign::fn_bridge::{xfn, Xfn}; use crate::interpreter::gen_nort::nort_gen; use crate::interpreter::nort::Expr; use crate::location::CodeLocation; @@ -17,22 +21,46 @@ use crate::utils::combine::Combine; trait_set! { trait TreeLeaf = Gen + DynClone; + trait XfnCB = FnOnce(Substack>) -> AtomGenerator + DynClone; +} + +enum GCKind { + Const(Box), + Xfn(usize, Box), } /// A leaf in the [ConstTree] -#[derive(Debug)] -pub struct GenConst(Box); +pub struct GenConst(GCKind); impl GenConst { - fn new(data: impl GenClause + Clone + 'static) -> Self { - Self(Box::new(data)) + fn c(data: impl GenClause + Clone + 'static) -> Self { Self(GCKind::Const(Box::new(data))) } + fn f(f: impl Xfn) -> Self { + Self(GCKind::Xfn(N, Box::new(move |stck| { + AtomGenerator::cloner(xfn(&stck.unreverse().iter().join("::"), f)) + }))) } - /// Instantiate template as [crate::interpreter::nort] - pub fn gen_nort(&self, location: CodeLocation) -> Expr { - self.0.template(nort_gen(location), []) + /// Instantiate as [crate::interpreter::nort] + pub fn gen_nort(self, stck: Substack>, location: CodeLocation) -> Expr { + match self.0 { + GCKind::Const(c) => c.template(nort_gen(location), []), + GCKind::Xfn(_, cb) => tpl::AnyAtom(cb(stck)).template(nort_gen(location), []), + } + } +} +impl Debug for GenConst { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.0 { + GCKind::Const(c) => write!(f, "{c:?}"), + GCKind::Xfn(n, _) => write!(f, "xfn/{n}"), + } } } impl Clone for GenConst { - fn clone(&self) -> Self { Self(clone_box(&*self.0)) } + fn clone(&self) -> Self { + match &self.0 { + GCKind::Const(c) => Self(GCKind::Const(clone_box(&**c))), + GCKind::Xfn(n, cb) => Self(GCKind::Xfn(*n, clone_box(&**cb))), + } + } } /// Error condition when constant trees that define the the same constant are @@ -43,9 +71,7 @@ pub struct ConflictingConsts; impl Combine for GenConst { type Error = ConflictingConsts; - fn combine(self, _: Self) -> Result { - Err(ConflictingConsts) - } + fn combine(self, _: Self) -> Result { Err(ConflictingConsts) } } /// A lightweight module tree that can be built declaratively by hand to @@ -56,16 +82,14 @@ pub type ConstTree = ModEntry; /// Describe a constant #[must_use] pub fn leaf(value: impl GenClause + Clone + 'static) -> ConstTree { - ModEntry { x: (), member: ModMember::Item(GenConst::new(value)) } + ModEntry::wrap(ModMember::Item(GenConst::c(value))) } /// Describe an [Atomic] #[must_use] -pub fn atom_leaf(atom: impl Atomic + Clone + 'static) -> ConstTree { - leaf(tpl::V(atom)) -} +pub fn atom_leaf(atom: impl Atomic + Clone + 'static) -> ConstTree { leaf(tpl::V(atom)) } -/// Describe an [Atomic] which appears as an entry in a [ConstTree#tree] +/// Describe an [Atomic] which appears as an entry in a [ConstTree::tree] /// /// The unarray is used to trick rustfmt into breaking the atom into a block /// without breaking this call into a block @@ -77,5 +101,22 @@ pub fn atom_ent>( (key, atom_leaf(atom)) } +/// Describe a function +pub fn xfn_leaf(f: impl Xfn) -> ConstTree { + ModEntry::wrap(ModMember::Item(GenConst::f(f))) +} + +/// Describe a function which appears as an entry in a [ConstTree::tree] +/// +/// The unarray is used to trick rustfmt into breaking the atom into a block +/// without breaking this call into a block +#[must_use] +pub fn xfn_ent>( + key: K, + [f]: [impl Xfn; 1], +) -> (K, ConstTree) { + (key, xfn_leaf(f)) +} + /// Errors produced duriung the merger of constant trees pub type ConstCombineErr = TreeConflict; diff --git a/src/intermediate/ir.rs b/src/intermediate/ir.rs index 258c965..36bb859 100644 --- a/src/intermediate/ir.rs +++ b/src/intermediate/ir.rs @@ -64,16 +64,16 @@ fn parametric_fmt( body: &Expr, wrap_right: bool, ) -> std::fmt::Result { - if wrap_right { + // if wrap_right { f.write_char('(')?; - } + // } f.write_str(prefix)?; f.write_str(&string_from_charset(depth as u64, ARGNAME_CHARSET))?; f.write_str(".")?; body.deep_fmt(f, depth + 1, Wrap(false, false))?; - if wrap_right { + // if wrap_right { f.write_char(')')?; - } + // } Ok(()) } @@ -92,18 +92,18 @@ impl Clause { f.write_str(&string_from_charset(lambda_depth, ARGNAME_CHARSET)) }, Self::Apply(func, x) => { - if wl { + // if wl { f.write_char('(')?; - } + // } func.deep_fmt(f, depth, Wrap(false, true))?; f.write_char(' ')?; x.deep_fmt(f, depth, Wrap(true, wr && !wl))?; - if wl { + // if wl { f.write_char(')')?; - } + // } Ok(()) }, - Self::Constant(token) => write!(f, "{:?}", token), + Self::Constant(token) => write!(f, "{token}"), } } } diff --git a/src/intermediate/ir_to_nort.rs b/src/intermediate/ir_to_nort.rs index 6150611..c4bebfe 100644 --- a/src/intermediate/ir_to_nort.rs +++ b/src/intermediate/ir_to_nort.rs @@ -24,7 +24,13 @@ fn clause(cls: &ir::Clause, ctx: NortBuilder<(), usize>) -> nort::Clause { pub fn ir_to_nort(expr: &ir::Expr) -> nort::Expr { let c = NortBuilder::new(&|count| { let mut count: usize = *count; - Box::new(move |()| count.checked_sub(1).map(|v| count = v).is_none()) + Box::new(move |()| match count { + 0 => true, + _ => { + count -= 1; + false + }, + }) }); nort::ClauseInst::new(clause(&expr.value, c)).to_expr(expr.location.clone()) } diff --git a/src/interpreter/apply.rs b/src/interpreter/apply.rs index b3ad50e..f9aa86c 100644 --- a/src/interpreter/apply.rs +++ b/src/interpreter/apply.rs @@ -1,24 +1,10 @@ -use std::collections::VecDeque; -use std::mem; - use never::Never; use super::context::RunContext; use super::error::RunError; use super::nort::{Clause, ClauseInst, Expr}; use super::path_set::{PathSet, Step}; -use super::run::run; -use crate::location::CodeLocation; - -/// Information about a function call presented to an external function -pub struct CallData<'a> { - /// Location of the function expression - pub location: CodeLocation, - /// The argument the function was called on. Functions are curried - pub arg: Expr, - /// Information relating to this interpreter run - pub ctx: RunContext<'a>, -} +use crate::foreign::atom::CallData; /// Process the clause at the end of the provided path. Note that paths always /// point to at least one target. Note also that this is not cached as a @@ -42,15 +28,12 @@ fn map_at( _ => (), } Ok(match (source, path.next()) { - (Clause::Lambda { .. } | Clause::Identity(_), _) => - unreachable!("Handled above"), + (Clause::Lambda { .. } | Clause::Identity(_), _) => unreachable!("Handled above"), // If the path ends and this isn't a lambda, process it (val, None) => mapper(val)?, // If it's an Apply, execute the next step in the path (Clause::Apply { f, x }, Some(head)) => { - let proc = |x: &Expr| { - Ok(map_at(path, &x.clause.cls(), mapper)?.to_expr(x.location())) - }; + let proc = |x: &Expr| Ok(map_at(path, &x.cls(), mapper)?.to_expr(x.location())); match head { None => Clause::Apply { f: proc(f)?, x: x.clone() }, Some(n) => { @@ -69,7 +52,12 @@ fn map_at( /// with the value in the body. Note that a path may point to multiple /// placeholders. #[must_use] -fn substitute(paths: &PathSet, value: ClauseInst, body: &Clause) -> Clause { +pub fn substitute( + paths: &PathSet, + value: ClauseInst, + body: &Clause, + on_sub: &mut impl FnMut(), +) -> Clause { let PathSet { steps, next } = paths; map_at(steps.iter().cloned(), body, &mut |chkpt| -> Result { match (chkpt, next) { @@ -80,18 +68,20 @@ fn substitute(paths: &PathSet, value: ClauseInst, body: &Clause) -> Clause { let mut argv = x.clone(); let f = match conts.get(&None) { None => f.clone(), - Some(sp) => substitute(sp, value.clone(), &f.clause.cls()) - .to_expr(f.location()), + Some(sp) => substitute(sp, value.clone(), &f.cls(), on_sub).to_expr(f.location()), }; for (i, old) in argv.iter_mut().rev().enumerate() { if let Some(sp) = conts.get(&Some(i)) { - let tmp = substitute(sp, value.clone(), &old.clause.cls()); + let tmp = substitute(sp, value.clone(), &old.cls(), on_sub); *old = tmp.to_expr(old.location()); } } Ok(Clause::Apply { f, x: argv }) - }, - (Clause::LambdaArg, None) => Ok(Clause::Identity(value.clone())), + }, + (Clause::LambdaArg, None) => { + on_sub(); + Ok(Clause::Identity(value.clone())) + }, (_, None) => panic!("Argument path must point to LambdaArg"), (_, Some(_)) => panic!("Argument path can only fork at Apply"), } @@ -99,11 +89,7 @@ fn substitute(paths: &PathSet, value: ClauseInst, body: &Clause) -> Clause { .unwrap_or_else(|e| match e {}) } -pub(super) fn apply_as_atom( - f: Expr, - arg: Expr, - ctx: RunContext, -) -> Result { +pub(super) fn apply_as_atom(f: Expr, arg: Expr, ctx: RunContext) -> Result { let call = CallData { location: f.location(), arg, ctx }; match f.clause.try_unwrap() { Ok(clause) => match clause { @@ -116,73 +102,3 @@ pub(super) fn apply_as_atom( }, } } - -/// Apply a function-like expression to a parameter. -pub(super) fn apply( - mut f: Expr, - mut argv: VecDeque, - mut ctx: RunContext, -) -> Result<(Option, Clause), RunError> { - // allow looping but break on the main path so that `continue` functions as a - // trampoline - loop { - if argv.is_empty() { - return Ok((ctx.gas, f.clause.into_cls())); - } else if ctx.gas == Some(0) { - return Ok((Some(0), Clause::Apply { f, x: argv })); - } - let mut f_cls = f.clause.cls_mut(); - match &mut *f_cls { - // apply an ExternFn or an internal function - Clause::Atom(_) => { - mem::drop(f_cls); - // take a step in expanding atom - let halt = run(f, ctx.clone())?; - ctx.gas = halt.gas; - if halt.inert && halt.state.clause.is_atom() { - let arg = argv.pop_front().expect("checked above"); - let loc = halt.state.location(); - f = apply_as_atom(halt.state, arg, ctx.clone())?.to_expr(loc) - } else { - f = halt.state - } - }, - Clause::Lambda { args, body } => { - match args { - None => *f_cls = body.clause.clone().into_cls(), - Some(args) => { - let arg = argv.pop_front().expect("checked above").clause.clone(); - let cls = substitute(args, arg, &body.clause.cls()); - // cost of substitution - // XXX: should this be the number of occurrences instead? - ctx.use_gas(1); - mem::drop(f_cls); - f = cls.to_expr(f.location()); - }, - } - }, - Clause::Constant(name) => { - let name = name.clone(); - mem::drop(f_cls); - f = (ctx.symbols.get(&name).cloned()) - .ok_or_else(|| RunError::MissingSymbol(name, f.location()))?; - ctx.use_gas(1); - }, - Clause::Apply { f: fun, x } => { - for item in x.drain(..).rev() { - argv.push_front(item) - } - let tmp = fun.clone(); - mem::drop(f_cls); - f = tmp; - }, - Clause::Identity(f2) => { - let tmp = f2.clone(); - mem::drop(f_cls); - f.clause = tmp - }, - Clause::Bottom(bottom) => return Err(bottom.clone()), - Clause::LambdaArg => panic!("Leftover argument marker"), - } - } -} diff --git a/src/interpreter/context.rs b/src/interpreter/context.rs index 094b2a5..bf29c38 100644 --- a/src/interpreter/context.rs +++ b/src/interpreter/context.rs @@ -10,6 +10,8 @@ pub struct RunContext<'a> { pub symbols: &'a HashMap, /// The number of reduction steps the interpreter can take before returning pub gas: Option, + /// The limit of recursion + pub stack_size: usize, } impl<'a> RunContext<'a> { /// Consume some gas if it is being counted diff --git a/src/interpreter/error.rs b/src/interpreter/error.rs index e24de70..d03454f 100644 --- a/src/interpreter/error.rs +++ b/src/interpreter/error.rs @@ -1,34 +1,66 @@ -use std::fmt::{Debug, Display}; -use std::sync::Arc; +use std::fmt::{self, Debug, Display}; -use crate::foreign::error::ExternError; +use itertools::Itertools; + +use super::nort::Expr; +use super::run::Interrupted; +use crate::foreign::error::{ExternError, ExternErrorObj}; use crate::location::CodeLocation; use crate::name::Sym; -use super::run::Interrupted; +/// Print a stack trace +pub fn strace(stack: &[Expr]) -> String { + stack.iter().rev().map(|x| format!("{x}\n at {}", x.location)).join("\n") +} /// Problems in the process of execution #[derive(Debug, Clone)] pub enum RunError { /// A Rust function encountered an error - Extern(Arc), - /// Symbol not in context - MissingSymbol(Sym, CodeLocation), + Extern(ExternErrorObj), /// Ran out of gas - Interrupted(Interrupted) + Interrupted(Interrupted), } -impl From> for RunError { - fn from(value: Arc) -> Self { Self::Extern(value) } +impl From for RunError { + fn from(value: T) -> Self { Self::Extern(value.rc()) } +} + +impl From for RunError { + fn from(value: ExternErrorObj) -> Self { Self::Extern(value) } } impl Display for RunError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Extern(e) => write!(f, "Error in external function: {e}"), - Self::MissingSymbol(sym, loc) => { - write!(f, "{sym}, called at {loc} is not loaded") + Self::Interrupted(i) => { + write!(f, "Ran out of gas:\n{}", strace(&i.stack)) }, + Self::Extern(e) => write!(f, "Program fault: {e}"), } } } + +#[derive(Clone)] +pub(crate) struct StackOverflow { + pub stack: Vec, +} +impl ExternError for StackOverflow {} +impl Display for StackOverflow { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let limit = self.stack.len() - 2; // 1 for failed call, 1 for current + write!(f, "Stack depth exceeded {limit}:\n{}", strace(&self.stack)) + } +} + +#[derive(Clone)] +pub(crate) struct MissingSymbol { + pub sym: Sym, + pub loc: CodeLocation, +} +impl ExternError for MissingSymbol {} +impl Display for MissingSymbol { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}, called at {} is not loaded", self.sym, self.loc) + } +} diff --git a/src/interpreter/handler.rs b/src/interpreter/handler.rs index 62cae3a..709694a 100644 --- a/src/interpreter/handler.rs +++ b/src/interpreter/handler.rs @@ -75,12 +75,13 @@ impl<'a> HandlerTable<'a> { pub fn run_handler( mut state: Expr, handlers: &mut HandlerTable, - RunContext { mut gas, symbols }: RunContext, + mut ctx: RunContext, ) -> Result { loop { - let inert; - Halt { gas, inert, state } = run(state, RunContext { gas, symbols })?; - let state_cls = state.clause.cls(); + let halt = run(state, ctx.clone())?; + state = halt.state; + ctx.use_gas(halt.gas.unwrap_or(0)); + let state_cls = state.cls(); if let Clause::Atom(Atom(a)) = &*state_cls { if let Some(res) = handlers.dispatch(a.as_ref(), state.location()) { drop(state_cls); @@ -88,9 +89,9 @@ pub fn run_handler( continue; } } - if inert || gas == Some(0) { + if halt.inert || ctx.no_gas() { drop(state_cls); - break Ok(Halt { gas, inert, state }); + break Ok(Halt { gas: ctx.gas, inert: halt.inert, state }); } } } diff --git a/src/interpreter/nort.rs b/src/interpreter/nort.rs index 55ff40a..b3ae06e 100644 --- a/src/interpreter/nort.rs +++ b/src/interpreter/nort.rs @@ -97,6 +97,28 @@ impl Expr { | Clause::Bottom(_) => None, }) } + + /// Clone the refcounted [ClauseInst] out of the expression + #[must_use] + pub fn clsi(&self) -> ClauseInst { self.clause.clone() } + + /// Readonly access to the [Clause] + /// + /// # Panics + /// + /// if the clause is already borrowed + #[must_use] + pub fn cls(&self) -> impl Deref + '_ { self.clause.cls() } + + /// Read-Write access to the [Clause] + /// + /// # Panics + /// + /// if the clause is already borrowed + #[must_use] + pub fn cls_mut(&self) -> impl DerefMut + '_ { + self.clause.cls_mut() + } } impl Debug for Expr { @@ -160,15 +182,21 @@ impl ClauseInst { /// Call a normalization function on the expression. The expr is /// updated with the new clause which affects all copies of it /// across the tree. + /// + /// This function bypasses and collapses identities, but calling it in a plain + /// loop intermittently re-acquires the mutex, and looping inside of it breaks + /// identity collapsing. [ClauseInst::try_normalize_trampoline] solves these + /// problems. pub fn try_normalize( &self, mapper: impl FnOnce(Clause) -> Result<(Clause, T), RunError>, - ) -> Result<(ClauseInst, T), RunError> { + ) -> Result<(Self, T), RunError> { enum Report { Nested(ClauseInst, T), Plain(T), } let ret = take_with_output(&mut *self.cls_mut(), |clause| match &clause { + // don't modify identities, instead update and return the nested clause Clause::Identity(alt) => match alt.try_normalize(mapper) { Ok((nested, t)) => (clause, Ok(Report::Nested(nested, t))), Err(e) => (Clause::Bottom(e.clone()), Err(e)), @@ -184,6 +212,32 @@ impl ClauseInst { }) } + /// Repeatedly call a normalization function on the held clause, switching + /// [ClauseInst] values as needed to ensure that + pub fn try_normalize_trampoline( + mut self, + mut mapper: impl FnMut(Clause) -> Result<(Clause, Option), RunError>, + ) -> Result<(Self, T), RunError> { + loop { + let (next, exit) = self.try_normalize(|mut cls| { + loop { + if matches!(cls, Clause::Identity(_)) { + break Ok((cls, None)); + } + let (next, exit) = mapper(cls)?; + if let Some(exit) = exit { + break Ok((next, Some(exit))); + } + cls = next; + } + })?; + if let Some(exit) = exit { + break Ok((next, exit)); + } + self = next + } + } + /// Call a predicate on the clause, returning whatever the /// predicate returns. This is a convenience function for reaching /// through the [Mutex]. The clause will never be [Clause::Identity]. @@ -320,11 +374,11 @@ impl Display for Clause { Clause::Apply { f: fun, x } => write!(f, "({fun} {})", x.iter().join(" ")), Clause::Lambda { args, body } => match args { - Some(path) => write!(f, "\\{path:?}.{body}"), - None => write!(f, "\\_.{body}"), + Some(path) => write!(f, "[\\{path}.{body}]"), + None => write!(f, "[\\_.{body}]"), }, Clause::Constant(t) => write!(f, "{t}"), - Clause::Identity(other) => write!(f, "({other})"), + Clause::Identity(other) => write!(f, "{{{other}}}"), } } } diff --git a/src/interpreter/nort_builder.rs b/src/interpreter/nort_builder.rs index daadf19..7a61da2 100644 --- a/src/interpreter/nort_builder.rs +++ b/src/interpreter/nort_builder.rs @@ -67,8 +67,7 @@ impl<'a, T: ?Sized, U: ?Sized> NortBuilder<'a, T, U> { } fn non_app_step(self, f: impl FnOnce(NortBuilder) -> V) -> V { if let Some(IntGenData::Apply(_)) = self.stack.value() { - let prev = self.pop(1); - f(prev.push(IntGenData::AppF)) + f(self.pop(1).push(IntGenData::AppF)) } else { f(self) } @@ -80,14 +79,17 @@ impl<'a, T: ?Sized, U: ?Sized> NortBuilder<'a, T, U> { pub fn arg_logic(self, name: &'a U) { let mut lambda_chk = (self.lambda_picker)(name); self.non_app_step(|ctx| { - let opt = ctx.stack.rfold(None, |path, item| match item { + let res = ctx.stack.iter().try_fold(vec![], |path, item| match item { IntGenData::Apply(_) => panic!("This is removed after handling"), - IntGenData::Lambda(n, rc) => - lambda_chk(n).then(|| (vec![], *rc)).or(path), - IntGenData::AppArg(n) => path.map(|(p, rc)| (pushed(p, Some(*n)), rc)), - IntGenData::AppF => path.map(|(p, rc)| (pushed(p, None), rc)), + IntGenData::Lambda(n, rc) => match lambda_chk(n) { + false => Ok(path), + true => Err((path, *rc)) + }, + IntGenData::AppArg(n) => Ok(pushed(path, Some(*n))), + IntGenData::AppF => Ok(pushed(path, None)), }); - let (path, slot) = opt.expect("Argument not wrapped in matching lambda"); + let (mut path, slot) = res.expect_err("Argument not wrapped in matching lambda"); + path.reverse(); match &mut *slot.borrow_mut() { slot @ None => *slot = Some(PathSet::end(path)), Some(slot) => take_mut::take(slot, |p| p.overlay(PathSet::end(path))), diff --git a/src/interpreter/run.rs b/src/interpreter/run.rs index 371cfe7..0913885 100644 --- a/src/interpreter/run.rs +++ b/src/interpreter/run.rs @@ -1,101 +1,129 @@ -use std::collections::VecDeque; +use std::mem; -use hashbrown::HashMap; - -use super::apply::apply; use super::context::{Halt, RunContext}; use super::error::RunError; use super::nort::{Clause, Expr}; -use crate::foreign::atom::AtomicReturn; -use crate::foreign::error::ExternResult; -use crate::location::CodeLocation; -use crate::name::Sym; +use crate::foreign::atom::{AtomicReturn, RunData}; +use crate::foreign::error::ExternError; +use crate::interpreter::apply::{apply_as_atom, substitute}; +use crate::interpreter::error::{strace, MissingSymbol, StackOverflow}; use crate::utils::pure_seq::pushed; -/// Information about a normalization run presented to an atom -#[derive(Clone)] -pub struct RunData<'a> { - /// Location of the atom - pub location: CodeLocation, - /// Information about the execution - pub ctx: RunContext<'a>, -} - -#[derive(Debug)] +/// Interpreter state when processing was interrupted +#[derive(Debug, Clone)] pub struct Interrupted { - stack: Vec, + /// Cached soft stack to save the interpreter having to rebuild it from the + /// bottom. + pub stack: Vec, } impl Interrupted { - pub fn resume(self, ctx: RunContext) -> Result { - run_stack(self.stack, ctx) - } + /// Continue processing where it was interrupted + pub fn resume(self, ctx: RunContext) -> Result { run_stack(self.stack, ctx) } } /// Normalize an expression using beta reduction with memoization -pub fn run(mut expr: Expr, mut ctx: RunContext) -> Result { - run_stack(vec![expr], ctx) +pub fn run(expr: Expr, ctx: RunContext) -> Result { + let mut v = Vec::with_capacity(1000); + v.push(expr); + run_stack(v, ctx) } -fn run_stack( - mut stack: Vec, - mut ctx: RunContext, -) -> Result { +fn run_stack(mut stack: Vec, mut ctx: RunContext) -> Result { let mut expr = stack.pop().expect("Empty stack"); + let mut popped = false; loop { + // print!("Now running {expr}"); + // let trace = strace(&stack); + // if trace.is_empty() { + // println!("\n") + // } else { + // println!("\n{trace}\n") + // }; if ctx.no_gas() { - return Err(RunError::Interrupted(Interrupted { - stack: pushed(stack, expr), - })); + return Err(RunError::Interrupted(Interrupted { stack: pushed(stack, expr) })); } - let (next_clsi, inert) = expr.clause.try_normalize(|mut cls| { - loop { - if ctx.no_gas() { - return Ok((cls, false)); + ctx.use_gas(1); + enum Res { + Inert, + Cont, + Push(Expr), + } + let (next_clsi, res) = expr.clause.try_normalize(|cls| match cls { + Clause::Identity(_) => panic!("Passed by try_normalize"), + Clause::LambdaArg => panic!("Unbound argument"), + Clause::Lambda { .. } => Ok((cls, Res::Inert)), + Clause::Bottom(b) => Err(b), + Clause::Constant(n) => match ctx.symbols.get(&n) { + Some(expr) => Ok((Clause::Identity(expr.clsi()), Res::Cont)), + None => Err(RunError::Extern(MissingSymbol { sym: n.clone(), loc: expr.location() }.rc())), + }, + Clause::Atom(mut a) => { + if !popped { + if let Some(delegate) = a.0.redirect() { + let next = delegate.clone(); + return Ok((Clause::Atom(a), Res::Push(next))); + } } - match cls { - cls @ Clause::Identity(_) => return Ok((cls, false)), - // TODO: - // - unfuck nested loop - // - inline most of [apply] to eliminate recursion step - Clause::Apply { f, x } => { - if x.is_empty() { - return Ok((f.clause.into_cls(), false)); - } - let (gas, clause) = apply(f, x, ctx.clone())?; - if ctx.gas.is_some() { - ctx.gas = gas; - } - cls = clause; + let rd = RunData { ctx: ctx.clone(), location: expr.location() }; + match a.run(rd)? { + AtomicReturn::Inert(c) => Ok((c, Res::Inert)), + AtomicReturn::Change(gas, c) => { + ctx.use_gas(gas); + Ok((c, Res::Cont)) }, - Clause::Atom(data) => { - let run = RunData { ctx: ctx.clone(), location: expr.location() }; - let atomic_ret = data.run(run)?; - if ctx.gas.is_some() { - ctx.gas = atomic_ret.gas; - } - if atomic_ret.inert { - return Ok((atomic_ret.clause, true)); + } + }, + Clause::Apply { f, mut x } => { + if x.is_empty() { + return Ok((Clause::Identity(f.clsi()), Res::Cont)); + } + match &*f.cls() { + Clause::Identity(f2) => + return Ok((Clause::Apply { f: f2.clone().to_expr(f.location()), x }, Res::Cont)), + Clause::Apply { f, x: x2 } => { + for item in x2.iter().rev() { + x.push_front(item.clone()) } - cls = atomic_ret.clause; + return Ok((Clause::Apply { f: f.clone(), x }, Res::Cont)); }, - Clause::Constant(c) => { - let symval = (ctx.symbols.get(&c)).ok_or_else(|| { - RunError::MissingSymbol(c.clone(), expr.location()) - })?; - ctx.gas = ctx.gas.map(|g| g - 1); // cost of lookup - cls = Clause::Identity(symval.clause.clone()); + _ => (), + } + if !popped { + return Ok((Clause::Apply { f: f.clone(), x }, Res::Push(f))); + } + let f_cls = f.cls(); + let arg = x.pop_front().expect("checked above"); + let loc = f.location(); + let f = match &*f_cls { + Clause::Atom(_) => { + mem::drop(f_cls); + apply_as_atom(f, arg, ctx.clone())? + }, + Clause::Lambda { args, body } => match args { + None => body.clsi().into_cls(), + Some(args) => substitute(args, arg.clsi(), &body.cls(), &mut || ctx.use_gas(1)), }, - // non-reducible - c => return Ok((c, true)), + c => panic!("Run should never settle on {c}"), }; - } + Ok((Clause::Apply { f: f.to_expr(loc), x }, Res::Cont)) + }, })?; expr.clause = next_clsi; - if inert { - match stack.pop() { - Some(e) => expr = e, - None => return Ok(Halt { state: expr, gas: ctx.gas, inert }), - } + popped = matches!(res, Res::Inert); + match res { + Res::Cont => continue, + Res::Inert => match stack.pop() { + None => return Ok(Halt { state: expr, gas: ctx.gas, inert: true }), + Some(prev) => expr = prev, + }, + Res::Push(next) => { + if stack.len() == ctx.stack_size { + stack.extend([expr, next]); + return Err(RunError::Extern(StackOverflow { stack }.rc())); + } + stack.push(expr); + expr = next; + }, } } } diff --git a/src/libs/asynch/system.rs b/src/libs/asynch/system.rs index 437b7f6..60d729c 100644 --- a/src/libs/asynch/system.rs +++ b/src/libs/asynch/system.rs @@ -20,11 +20,10 @@ use crate::facade::system::{IntoSystem, System}; use crate::foreign::atom::Atomic; use crate::foreign::cps_box::CPSBox; use crate::foreign::error::ExternError; -use crate::foreign::fn_bridge::constructors::xfn_2ary; use crate::foreign::inert::{Inert, InertPayload}; use crate::gen::tpl; use crate::gen::traits::Gen; -use crate::gen::tree::{atom_leaf, ConstTree}; +use crate::gen::tree::{atom_ent, xfn_ent, ConstTree}; use crate::interpreter::gen_nort::nort_gen; use crate::interpreter::handler::HandlerTable; use crate::interpreter::nort::Expr; @@ -81,9 +80,7 @@ impl Display for InfiniteBlock { pub struct MessagePort(Sender>); impl MessagePort { /// Send an event. Any type is accepted, handlers are dispatched by type ID - pub fn send(&mut self, message: T) { - let _ = self.0.send(Box::new(message)); - } + pub fn send(&mut self, message: T) { let _ = self.0.send(Box::new(message)); } } fn gen() -> CodeGenInfo { CodeGenInfo::no_details("asynch") } @@ -124,17 +121,10 @@ impl<'a> AsynchSystem<'a> { /// # Panics /// /// if the given type is already handled. - pub fn register( - &mut self, - mut f: impl FnMut(Box) -> Vec + 'a, - ) { + pub fn register(&mut self, mut f: impl FnMut(Box) -> Vec + 'a) { let cb = move |a: Box| f(a.downcast().expect("keyed by TypeId")); let prev = self.handlers.insert(TypeId::of::(), Box::new(cb)); - assert!( - prev.is_none(), - "Duplicate handlers for async event {}", - type_name::() - ) + assert!(prev.is_none(), "Duplicate handlers for async event {}", type_name::()) } /// Obtain a message port for sending messages to the main thread. If an @@ -189,16 +179,13 @@ impl<'a> IntoSystem<'a> for AsynchSystem<'a> { PollEvent::Recurring(expr) => return Ok(expr), PollEvent::Event(ev) => { let handler = (handlers.get_mut(&ev.as_ref().type_id())) - .unwrap_or_else(|| { - panic!("Unhandled messgae type: {:?}", (*ev).type_id()) - }); + .unwrap_or_else(|| panic!("Unhandled messgae type: {:?}", (*ev).type_id())); let events = handler(ev); // we got new microtasks if !events.is_empty() { microtasks = VecDeque::from(events); // trampoline - let loc = - CodeLocation::Gen(CodeGenInfo::no_details("system::asynch")); + let loc = CodeLocation::Gen(CodeGenInfo::no_details("system::asynch")); return Ok(Inert(Yield).atom_expr(loc)); } }, @@ -211,8 +198,8 @@ impl<'a> IntoSystem<'a> for AsynchSystem<'a> { lexer_plugins: vec![], line_parsers: vec![], constants: ConstTree::ns("system::async", [ConstTree::tree([ - ("set_timer", atom_leaf(xfn_2ary(set_timer))), - ("yield", atom_leaf(Inert(Yield))), + xfn_ent("set_timer", [set_timer]), + atom_ent("yield", [Inert(Yield)]), ])]), code: code(), prelude: Vec::new(), diff --git a/src/libs/directfs/commands.rs b/src/libs/directfs/commands.rs index 933433e..383de1f 100644 --- a/src/libs/directfs/commands.rs +++ b/src/libs/directfs/commands.rs @@ -7,13 +7,12 @@ use crate::facade::system::{IntoSystem, System}; use crate::foreign::atom::Atomic; use crate::foreign::cps_box::CPSBox; use crate::foreign::error::ExternResult; -use crate::foreign::fn_bridge::constructors::{xfn_1ary, xfn_2ary}; use crate::foreign::inert::{Inert, InertPayload}; use crate::foreign::process::Unstable; use crate::foreign::to_clause::ToClause; use crate::gen::tpl; use crate::gen::traits::Gen; -use crate::gen::tree::{atom_ent, atom_leaf, ConstTree}; +use crate::gen::tree::{atom_ent, xfn_ent, ConstTree}; use crate::interpreter::gen_nort::nort_gen; use crate::interpreter::handler::HandlerTable; use crate::interpreter::nort::{Clause, Expr}; @@ -80,10 +79,7 @@ fn read_dir(sched: &SeqScheduler, cmd: &CPSBox) -> Expr { Ok(os_namev) => { let converted = (os_namev.into_iter()) .map(|(n, d)| { - Ok(( - Inert(n).atom_expr(succ.location()), - Inert(d).atom_expr(succ.location()), - )) + Ok((Inert(n).atom_expr(succ.location()), Inert(d).atom_expr(succ.location()))) }) .collect::, Clause>>(); match converted { @@ -124,13 +120,9 @@ fn write_file(sched: &SeqScheduler, cmd: &CPSBox) -> Expr { tpl.template(nort_gen(cont.location()), [cont]) } -fn open_file_read_cmd(name: OsString) -> CPSBox { - CPSBox::new(3, ReadFileCmd(name)) -} +fn open_file_read_cmd(name: OsString) -> CPSBox { CPSBox::new(3, ReadFileCmd(name)) } -fn read_dir_cmd(name: OsString) -> CPSBox { - CPSBox::new(3, ReadDirCmd(name)) -} +fn read_dir_cmd(name: OsString) -> CPSBox { CPSBox::new(3, ReadDirCmd(name)) } fn open_file_write_cmd(name: OsString) -> CPSBox { CPSBox::new(3, WriteFile { name, append: false }) @@ -146,9 +138,7 @@ fn join_paths(root: OsString, sub: OsString) -> OsString { path.into_os_string() } -fn pop_path( - path: Inert, -) -> Option<(Inert, Inert)> { +fn pop_path(path: Inert) -> Option<(Inert, Inert)> { let mut path = PathBuf::from(path.0); let sub = path.file_name()?.to_owned(); debug_assert!(path.pop(), "file_name above returned Some"); @@ -181,15 +171,15 @@ impl IntoSystem<'static> for DirectFS { lexer_plugins: vec![], line_parsers: vec![], constants: ConstTree::ns("system::fs", [ConstTree::tree([ - ("read_file", atom_leaf(xfn_1ary(open_file_read_cmd))), - ("read_dir", atom_leaf(xfn_1ary(read_dir_cmd))), - ("write_file", atom_leaf(xfn_1ary(open_file_write_cmd))), - ("append_file", atom_leaf(xfn_1ary(open_file_append_cmd))), - ("join_paths", atom_leaf(xfn_2ary(join_paths))), - ("pop_path", atom_leaf(xfn_1ary(pop_path))), + xfn_ent("read_file", [open_file_read_cmd]), + xfn_ent("read_dir", [read_dir_cmd]), + xfn_ent("write_file", [open_file_write_cmd]), + xfn_ent("append_file", [open_file_append_cmd]), + xfn_ent("join_paths", [join_paths]), + xfn_ent("pop_path", [pop_path]), atom_ent("cwd", [Unstable::new(|_| -> ExternResult<_> { - let path = std::env::current_dir() - .map_err(|e| RuntimeError::ext(e.to_string(), "reading CWD"))?; + let path = + std::env::current_dir().map_err(|e| RuntimeError::ext(e.to_string(), "reading CWD"))?; Ok(Inert(path.into_os_string())) })]), ])]) diff --git a/src/libs/directfs/osstring.rs b/src/libs/directfs/osstring.rs index 3b4d753..4812426 100644 --- a/src/libs/directfs/osstring.rs +++ b/src/libs/directfs/osstring.rs @@ -2,11 +2,10 @@ use std::ffi::OsString; use crate::foreign::atom::Atomic; use crate::foreign::error::ExternResult; -use crate::foreign::fn_bridge::constructors::xfn_1ary; use crate::foreign::inert::{Inert, InertPayload}; use crate::foreign::to_clause::ToClause; use crate::foreign::try_from_expr::TryFromExpr; -use crate::gen::tree::{atom_leaf, ConstTree}; +use crate::gen::tree::{xfn_ent, ConstTree}; use crate::interpreter::nort::{Clause, Expr}; use crate::libs::std::string::OrcString; use crate::location::CodeLocation; @@ -21,24 +20,20 @@ impl ToClause for OsString { fn to_clause(self, _: CodeLocation) -> Clause { Inert(self).atom_cls() } } -pub fn os_to_string( - os: Inert, -) -> Result, Inert> { +pub fn os_to_string(os: Inert) -> Result, Inert> { os.0.into_string().map(|s| Inert(s.into())).map_err(Inert) } -pub fn string_to_os(str: Inert) -> Inert { - Inert(str.0.get_string().into()) -} +pub fn string_to_os(str: Inert) -> Inert { Inert(str.0.get_string().into()) } pub fn os_print(os: Inert) -> Inert { Inert(os.0.to_string_lossy().to_string().into()) } pub fn os_string_lib() -> ConstTree { - ConstTree::tree([ - ("os_to_string", atom_leaf(xfn_1ary(os_to_string))), - ("string_to_os", atom_leaf(xfn_1ary(string_to_os))), - ("os_print", atom_leaf(xfn_1ary(os_print))), - ]) + ConstTree::ns("system::fs", [ConstTree::tree([ + xfn_ent("os_to_string", [os_to_string]), + xfn_ent("string_to_os", [string_to_os]), + xfn_ent("os_print", [os_print]), + ])]) } diff --git a/src/libs/io/bindings.rs b/src/libs/io/bindings.rs index 5f4f8d0..50cab49 100644 --- a/src/libs/io/bindings.rs +++ b/src/libs/io/bindings.rs @@ -3,9 +3,8 @@ use super::instances::{BRead, ReadCmd, SRead, WriteCmd}; use super::service::{Sink, Source}; use crate::foreign::cps_box::CPSBox; use crate::foreign::error::ExternResult; -use crate::foreign::fn_bridge::constructors::{xfn_1ary, xfn_2ary}; use crate::foreign::inert::Inert; -use crate::gen::tree::{atom_leaf, ConstTree}; +use crate::gen::tree::{xfn_ent, ConstTree}; use crate::libs::scheduler::system::SharedHandle; use crate::libs::std::binary::Binary; use crate::libs::std::runtime_error::RuntimeError; @@ -45,35 +44,27 @@ pub fn read_until( let cmd = ReadCmd::RBytes(BRead::Until(pattern)); Ok(CPSBox::new(3, IOCmdHandlePack { handle, cmd })) } -pub fn write_str( - Inert(handle): WriteHandle, - string: Inert, -) -> WriteCmdPack { +pub fn write_str(Inert(handle): WriteHandle, string: Inert) -> WriteCmdPack { let cmd = WriteCmd::WStr(string.0.get_string()); CPSBox::new(3, IOCmdHandlePack { handle, cmd }) } -pub fn write_bin( - Inert(handle): WriteHandle, - bytes: Inert, -) -> WriteCmdPack { +pub fn write_bin(Inert(handle): WriteHandle, bytes: Inert) -> WriteCmdPack { CPSBox::new(3, IOCmdHandlePack { handle, cmd: WriteCmd::WBytes(bytes.0) }) } pub fn flush(Inert(handle): WriteHandle) -> WriteCmdPack { CPSBox::new(3, IOCmdHandlePack { handle, cmd: WriteCmd::Flush }) } -pub fn io_bindings<'a>( - std_streams: impl IntoIterator, -) -> ConstTree { +pub fn io_bindings<'a>(std_streams: impl IntoIterator) -> ConstTree { ConstTree::ns("system::io", [ConstTree::tree([ - ("read_string", atom_leaf(xfn_1ary(read_string))), - ("read_line", atom_leaf(xfn_1ary(read_line))), - ("read_bin", atom_leaf(xfn_1ary(read_bin))), - ("read_n_bytes", atom_leaf(xfn_2ary(read_bytes))), - ("read_until", atom_leaf(xfn_2ary(read_until))), - ("write_str", atom_leaf(xfn_2ary(write_str))), - ("write_bin", atom_leaf(xfn_2ary(write_bin))), - ("flush", atom_leaf(xfn_1ary(flush))), + xfn_ent("read_string", [read_string]), + xfn_ent("read_line", [read_line]), + xfn_ent("read_bin", [read_bin]), + xfn_ent("read_n_bytes", [read_bytes]), + xfn_ent("read_until", [read_until]), + xfn_ent("write_str", [write_str]), + xfn_ent("write_bin", [write_bin]), + xfn_ent("flush", [flush]), ]) .combine(ConstTree::tree(std_streams)) .expect("std_stream name clashing with io functions")]) diff --git a/src/libs/scheduler/system.rs b/src/libs/scheduler/system.rs index 98ba09a..fac8f34 100644 --- a/src/libs/scheduler/system.rs +++ b/src/libs/scheduler/system.rs @@ -1,11 +1,11 @@ //! Object to pass to [crate::facade::loader::Loader::add_system] to enable the //! scheduling subsystem. Other systems also take clones as dependencies. -//! +//! //! ``` +//! use orchidlang::facade::loader::Loader; //! use orchidlang::libs::asynch::system::AsynchSystem; //! use orchidlang::libs::scheduler::system::SeqScheduler; //! use orchidlang::libs::std::std_system::StdConfig; -//! use orchidlang::facade::loader::Loader; //! //! let mut asynch = AsynchSystem::new(); //! let scheduler = SeqScheduler::new(&mut asynch); @@ -30,9 +30,8 @@ use super::thread_pool::ThreadPool; use crate::facade::system::{IntoSystem, System}; use crate::foreign::cps_box::CPSBox; use crate::foreign::error::{AssertionError, ExternResult}; -use crate::foreign::fn_bridge::constructors::xfn_1ary; use crate::foreign::inert::{Inert, InertPayload}; -use crate::gen::tree::{atom_leaf, ConstTree}; +use crate::gen::tree::{xfn_ent, ConstTree}; use crate::interpreter::handler::HandlerTable; use crate::interpreter::nort::Expr; use crate::libs::asynch::system::{AsynchSystem, MessagePort}; @@ -70,9 +69,7 @@ pub struct SharedHandle(pub(super) Arc>>); impl SharedHandle { /// Wrap a value to be accessible to a [SeqScheduler]. - pub fn wrap(t: T) -> Self { - Self(Arc::new(Mutex::new(SharedResource::Free(t)))) - } + pub fn wrap(t: T) -> Self { Self(Arc::new(Mutex::new(SharedResource::Free(t)))) } /// Check the state of the handle pub fn state(&self) -> SharedState { @@ -151,9 +148,7 @@ fn take_and_drop(x: Expr) -> ExternResult> { } } -fn is_taken_error(x: Expr) -> Inert { - Inert(x.downcast::>().is_ok()) -} +fn is_taken_e(x: Expr) -> Inert { Inert(x.downcast::>().is_ok()) } trait_set! { /// The part of processing a blocking I/O task that cannot be done on a remote @@ -221,11 +216,9 @@ impl SeqScheduler { |state| { match state { SharedResource::Taken => (SharedResource::Taken, Err(SealedOrTaken)), - SharedResource::Busy(mut b) => { - match b.enqueue(operation, handler, early_cancel) { - Some(cancelled) => (SharedResource::Busy(b), Ok(cancelled)), - None => (SharedResource::Busy(b), Err(SealedOrTaken)), - } + SharedResource::Busy(mut b) => match b.enqueue(operation, handler, early_cancel) { + Some(cancelled) => (SharedResource::Busy(b), Ok(cancelled)), + None => (SharedResource::Busy(b), Err(SealedOrTaken)), }, SharedResource::Free(t) => { let cancelled = CancelFlag::new(); @@ -251,11 +244,9 @@ impl SeqScheduler { ) -> CancelFlag { let cancelled = CancelFlag::new(); let canc1 = cancelled.clone(); - let opid = self.0.pending.borrow_mut().insert(Box::new( - |data: Box, _| { - handler(*data.downcast().expect("This is associated by ID"), canc1) - }, - )); + let opid = self.0.pending.borrow_mut().insert(Box::new(|data: Box, _| { + handler(*data.downcast().expect("This is associated by ID"), canc1) + })); let canc1 = cancelled.clone(); let mut port = self.0.port.clone(); self.0.pool.submit(Box::new(move || { @@ -298,8 +289,7 @@ impl SeqScheduler { let opid = self.0.pending.borrow_mut().insert(Box::new({ let cancelled = cancelled.clone(); move |data: Box, this: SeqScheduler| { - let (t, u): (T, Box) = - *data.downcast().expect("This is associated by ID"); + let (t, u): (T, Box) = *data.downcast().expect("This is associated by ID"); let handle2 = handle.clone(); take_with_output(&mut *handle.0.lock().unwrap(), |state| { let busy = unwrap_or! { state => SharedResource::Busy; @@ -307,15 +297,9 @@ impl SeqScheduler { }; let report = busy.rotate(t, u, cancelled); match report.kind { - NextItemReportKind::Free(t) => - (SharedResource::Free(t), report.events), + NextItemReportKind::Free(t) => (SharedResource::Free(t), report.events), NextItemReportKind::Taken => (SharedResource::Taken, report.events), - NextItemReportKind::Next { - instance, - cancelled, - operation, - rest, - } => { + NextItemReportKind::Next { instance, cancelled, operation, rest } => { this.submit(instance, handle2, cancelled, operation); (SharedResource::Busy(rest), report.events) }, @@ -352,8 +336,8 @@ impl IntoSystem<'static> for SeqScheduler { lexer_plugins: vec![], line_parsers: vec![], constants: ConstTree::ns("system::scheduler", [ConstTree::tree([ - ("is_taken_error", atom_leaf(xfn_1ary(is_taken_error))), - ("take_and_drop", atom_leaf(xfn_1ary(take_and_drop))), + xfn_ent("is_taken_e", [is_taken_e]), + xfn_ent("take_and_drop", [take_and_drop]), ])]), } } diff --git a/src/libs/std/binary.rs b/src/libs/std/binary.rs index 0da5532..3adcdea 100644 --- a/src/libs/std/binary.rs +++ b/src/libs/std/binary.rs @@ -9,11 +9,8 @@ use itertools::Itertools; use super::runtime_error::RuntimeError; use crate::foreign::atom::Atomic; use crate::foreign::error::ExternResult; -use crate::foreign::fn_bridge::constructors::{ - xfn_1ary, xfn_2ary, xfn_3ary, xfn_4ary, -}; use crate::foreign::inert::{Inert, InertPayload}; -use crate::gen::tree::{atom_leaf, ConstTree}; +use crate::gen::tree::{atom_ent, xfn_ent, ConstTree}; use crate::interpreter::nort::Clause; use crate::utils::iter_find::iter_find; use crate::utils::unwrap_or::unwrap_or; @@ -40,8 +37,7 @@ impl Debug for Binary { let a = chunk.next().expect("Chunks cannot be empty"); let b = unwrap_or!(chunk.next(); return write!(f, "{a:02x}")); let c = unwrap_or!(chunk.next(); return write!(f, "{a:02x}{b:02x}")); - let d = - unwrap_or!(chunk.next(); return write!(f, "{a:02x}{b:02x}{c:02x}")); + let d = unwrap_or!(chunk.next(); return write!(f, "{a:02x}{b:02x}{c:02x}")); write!(f, "{a:02x}{b:02x}{c:02x}{d:02x}")? } if iter.next().is_some() { write!(f, "...") } else { Ok(()) } @@ -55,16 +51,9 @@ pub fn concatenate(a: Inert, b: Inert) -> Inert { } /// Extract a subsection of the binary data -pub fn slice( - s: Inert, - i: Inert, - len: Inert, -) -> ExternResult> { +pub fn slice(s: Inert, i: Inert, len: Inert) -> ExternResult> { if i.0 + len.0 < s.0.0.len() { - RuntimeError::fail( - "Byte index out of bounds".to_string(), - "indexing binary", - )? + RuntimeError::fail("Byte index out of bounds".to_string(), "indexing binary")? } Ok(Inert(Binary(Arc::new(s.0.0[i.0..i.0 + len.0].to_vec())))) } @@ -76,21 +65,12 @@ pub fn find(haystack: Inert, needle: Inert) -> Option { } /// Split binary data block into two smaller blocks -pub fn split( - bin: Inert, - i: Inert, -) -> ExternResult<(Inert, Inert)> { +pub fn split(bin: Inert, i: Inert) -> ExternResult<(Inert, Inert)> { if bin.0.0.len() < i.0 { - RuntimeError::fail( - "Byte index out of bounds".to_string(), - "splitting binary", - )? + RuntimeError::fail("Byte index out of bounds".to_string(), "splitting binary")? } let (asl, bsl) = bin.0.0.split_at(i.0); - Ok(( - Inert(Binary(Arc::new(asl.to_vec()))), - Inert(Binary(Arc::new(bsl.to_vec()))), - )) + Ok((Inert(Binary(Arc::new(asl.to_vec()))), Inert(Binary(Arc::new(bsl.to_vec()))))) } /// Read a number from a binary blob @@ -101,10 +81,7 @@ pub fn get_num( is_le: Inert, ) -> ExternResult> { if buf.0.0.len() < (loc.0 + size.0) { - RuntimeError::fail( - "section out of range".to_string(), - "reading number from binary data", - )? + RuntimeError::fail("section out of range".to_string(), "reading number from binary data")? } if INT_BYTES < size.0 { RuntimeError::fail( @@ -148,13 +125,13 @@ pub fn size(b: Inert) -> Inert { Inert(b.0.len()) } pub(super) fn bin_lib() -> ConstTree { ConstTree::ns("std::binary", [ConstTree::tree([ - ("concat", atom_leaf(xfn_2ary(concatenate))), - ("slice", atom_leaf(xfn_3ary(slice))), - ("find", atom_leaf(xfn_2ary(find))), - ("split", atom_leaf(xfn_2ary(split))), - ("get_num", atom_leaf(xfn_4ary(get_num))), - ("from_num", atom_leaf(xfn_3ary(from_num))), - ("size", atom_leaf(xfn_1ary(size))), - ("int_bytes", atom_leaf(Inert(INT_BYTES))), + xfn_ent("concat", [concatenate]), + xfn_ent("slice", [slice]), + xfn_ent("find", [find]), + xfn_ent("split", [split]), + xfn_ent("get_num", [get_num]), + xfn_ent("from_num", [from_num]), + xfn_ent("size", [size]), + atom_ent("int_bytes", [Inert(INT_BYTES)]), ])]) } diff --git a/src/libs/std/bool.orc b/src/libs/std/bool.orc index 92c9a55..c538c54 100644 --- a/src/libs/std/bool.orc +++ b/src/libs/std/bool.orc @@ -1,4 +1,5 @@ import std::(pmatch, inspect) +import std::known::(=) export ::(!=, ==) @@ -12,7 +13,7 @@ export macro if ...$cond then ...$true else ...$false:1 =0x1p84=> ( ) ( - macro pmatch::request (== ...$other) + macro pmatch::request (= ...$other) =0x1p230=> pmatch::response ( if pmatch::value == (...$other) then pmatch::pass diff --git a/src/libs/std/bool.rs b/src/libs/std/bool.rs index 53c3ca1..2171f48 100644 --- a/src/libs/std/bool.rs +++ b/src/libs/std/bool.rs @@ -1,14 +1,12 @@ use super::number::Numeric; use super::string::OrcString; use crate::foreign::error::{AssertionError, ExternResult}; -use crate::foreign::fn_bridge::constructors::{xfn_1ary, xfn_2ary}; use crate::foreign::inert::Inert; use crate::foreign::try_from_expr::WithLoc; use crate::gen::tpl; use crate::gen::traits::{Gen, GenClause}; -use crate::gen::tree::{atom_leaf, ConstTree}; +use crate::gen::tree::{atom_ent, xfn_ent, ConstTree}; use crate::interpreter::gen_nort::nort_gen; -use crate::interpreter::nort_builder::NortBuilder; use crate::interpreter::nort::Expr; const fn left() -> impl GenClause { tpl::L("l", tpl::L("_", tpl::P("l"))) } @@ -28,10 +26,7 @@ pub fn if_then_else(WithLoc(loc, b): WithLoc>) -> Expr { /// - both are string, /// - both are bool, /// - both are either uint or num -pub fn equals( - WithLoc(loc, a): WithLoc, - b: Expr, -) -> ExternResult> { +pub fn equals(WithLoc(loc, a): WithLoc, b: Expr) -> ExternResult> { Ok(Inert(if let Ok(l) = a.clone().downcast::>() { b.downcast::>().is_ok_and(|r| *l == *r) } else if let Ok(l) = a.clone().downcast::>() { @@ -45,9 +40,9 @@ pub fn equals( pub fn bool_lib() -> ConstTree { ConstTree::ns("std::bool", [ConstTree::tree([ - ("ifthenelse", atom_leaf(xfn_1ary(if_then_else))), - ("equals", atom_leaf(xfn_2ary(equals))), - ("true", atom_leaf(Inert(true))), - ("false", atom_leaf(Inert(false))), + xfn_ent("ifthenelse", [if_then_else]), + xfn_ent("equals", [equals]), + atom_ent("true", [Inert(true)]), + atom_ent("false", [Inert(false)]), ])]) } diff --git a/src/libs/std/conv.rs b/src/libs/std/conv.rs index 441103c..5383235 100644 --- a/src/libs/std/conv.rs +++ b/src/libs/std/conv.rs @@ -6,14 +6,12 @@ use super::protocol::{gen_resolv, Protocol}; use super::string::OrcString; use crate::foreign::atom::Atomic; use crate::foreign::error::{AssertionError, ExternResult}; -use crate::foreign::fn_bridge::constructors::xfn_1ary; use crate::foreign::inert::Inert; use crate::foreign::try_from_expr::WithLoc; use crate::gen::tpl; use crate::gen::traits::Gen; -use crate::gen::tree::{atom_leaf, ConstTree}; +use crate::gen::tree::{xfn_ent, ConstTree}; use crate::interpreter::gen_nort::nort_gen; -use crate::interpreter::nort_builder::NortBuilder; use crate::interpreter::nort::{ClauseInst, Expr}; use crate::parse::numeric::parse_num; @@ -63,9 +61,9 @@ pub fn conv_lib() -> ConstTree { ConstTree::ns("std", [ConstTree::tree([ TO_STRING.as_tree_ent([]), ConstTree::tree_ent("conv", [ - ("to_float", atom_leaf(xfn_1ary(to_float))), - ("to_uint", atom_leaf(xfn_1ary(to_uint))), - ("to_string", atom_leaf(xfn_1ary(to_string))), + xfn_ent("to_float", [to_float]), + xfn_ent("to_uint", [to_uint]), + xfn_ent("to_string", [to_string]), ]), ])]) } diff --git a/src/libs/std/cross_pipeline.rs b/src/libs/std/cross_pipeline.rs index 94fcc64..03cf915 100644 --- a/src/libs/std/cross_pipeline.rs +++ b/src/libs/std/cross_pipeline.rs @@ -3,7 +3,7 @@ use std::iter; use std::rc::Rc; use crate::foreign::atom::Atomic; -use crate::foreign::fn_bridge::constructors::xfn_1ary; +use crate::foreign::fn_bridge::xfn; use crate::foreign::process::Unstable; use crate::foreign::to_clause::ToClause; use crate::foreign::try_from_expr::TryFromExpr; @@ -37,12 +37,11 @@ fn table_receiver_rec< callback: impl DeferredRuntimeCallback, ) -> impl Atomic { let t = remaining_keys.pop_front().expect("empty handled elsewhere"); - xfn_1ary(move |u: U| { + xfn("__table_receiver__", move |u: U| { let results = pushed(results, (t, u)); match remaining_keys.is_empty() { true => callback(results).to_clause(CodeLocation::Source(range)), - false => - table_receiver_rec(range, results, remaining_keys, callback).atom_cls(), + false => table_receiver_rec(range, results, remaining_keys, callback).atom_cls(), } }) } @@ -59,10 +58,8 @@ fn table_receiver< if keys.is_empty() { Unstable::new(move |_| callback(Vec::new())).ast_cls() } else { - Unstable::new(move |_| { - table_receiver_rec(range, Vec::new(), keys, callback).atom_cls() - }) - .ast_cls() + Unstable::new(move |_| table_receiver_rec(range, Vec::new(), keys, callback).atom_cls()) + .ast_cls() } } @@ -77,10 +74,8 @@ pub fn defer_to_runtime< pairs: impl IntoIterator)>, callback: impl DeferredRuntimeCallback, ) -> parsed::Clause { - let (keys, ast_values) = - pairs.into_iter().unzip::<_, _, VecDeque<_>, Vec<_>>(); - let items = iter::once(table_receiver(range.clone(), keys, callback)).chain( - ast_values.into_iter().map(|v| parsed::Clause::S(PType::Par, Rc::new(v))), - ); + let (keys, ast_values) = pairs.into_iter().unzip::<_, _, VecDeque<_>, Vec<_>>(); + let items = iter::once(table_receiver(range.clone(), keys, callback)) + .chain(ast_values.into_iter().map(|v| parsed::Clause::S(PType::Par, Rc::new(v)))); parsed::Clause::s('(', items, range) } diff --git a/src/libs/std/exit_status.rs b/src/libs/std/exit_status.rs index d2b4e3f..b8e9af8 100644 --- a/src/libs/std/exit_status.rs +++ b/src/libs/std/exit_status.rs @@ -5,9 +5,8 @@ use std::process::ExitCode; -use crate::foreign::fn_bridge::constructors::xfn_1ary; use crate::foreign::inert::{Inert, InertPayload}; -use crate::gen::tree::{atom_leaf, ConstTree}; +use crate::gen::tree::{atom_ent, xfn_ent, ConstTree}; /// An Orchid equivalent to Rust's binary exit status model #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -34,8 +33,8 @@ impl InertPayload for ExitStatus { pub(super) fn exit_status_lib() -> ConstTree { let is_success = |es: Inert| Inert(es.0 == ExitStatus::Success); ConstTree::ns("std::exit_status", [ConstTree::tree([ - ("success", atom_leaf(Inert(ExitStatus::Success))), - ("failure", atom_leaf(Inert(ExitStatus::Failure))), - ("is_success", atom_leaf(xfn_1ary(is_success))), + atom_ent("success", [Inert(ExitStatus::Success)]), + atom_ent("failure", [Inert(ExitStatus::Failure)]), + xfn_ent("is_success", [is_success]), ])]) } diff --git a/src/libs/std/functional.orc b/src/libs/std/functional.orc index c68ef0a..c60be0b 100644 --- a/src/libs/std/functional.orc +++ b/src/libs/std/functional.orc @@ -1,5 +1,6 @@ import super::known::* -import super::pmatch::* +import super::pmatch +import super::pmatch::(match, =>) import super::macro --[ Do nothing. Especially useful as a passive cps operation ]-- @@ -24,10 +25,11 @@ export macro ...$prefix $ ...$suffix:1 =0x1p38=> ...$prefix (...$suffix) export macro ...$prefix |> $fn ..$suffix:1 =0x2p32=> $fn (...$prefix) ..$suffix ( macro (..$argv) => ...$body - =0x2p127=> lambda_walker macro::comma_list (..$argv) (...$body) + =0x3p127=> lambda_walker macro::comma_list (..$argv) (...$body) ) ( macro $_arg => ...$body - =0x2p127=> \$_arg. ...$body) + =0x2p127=> \$_arg. ...$body +) ( macro lambda_walker ( macro::list_item ($_argname) $tail ) $body =0x2p254=> \$_argname. lambda_walker $tail $body ) diff --git a/src/libs/std/inspect.rs b/src/libs/std/inspect.rs index f547300..f9e010e 100644 --- a/src/libs/std/inspect.rs +++ b/src/libs/std/inspect.rs @@ -1,39 +1,32 @@ use std::fmt::Debug; -use crate::foreign::atom::{Atomic, AtomicResult, AtomicReturn}; +use crate::foreign::atom::{Atomic, AtomicResult, AtomicReturn, CallData, RunData}; use crate::foreign::error::ExternResult; -use crate::foreign::fn_bridge::constructors::xfn_1ary; use crate::foreign::to_clause::ToClause; -use crate::gen::tree::{atom_leaf, ConstTree}; -use crate::interpreter::apply::CallData; -use crate::interpreter::nort::{Clause, ClauseInst, Expr}; -use crate::interpreter::run::RunData; +use crate::gen::tree::{atom_ent, xfn_ent, ConstTree}; +use crate::interpreter::nort::{Clause, Expr}; use crate::utils::ddispatch::Responder; #[derive(Debug, Clone)] struct Inspect; -impl Responder for Inspect {} +impl Responder for Inspect {} impl Atomic for Inspect { fn as_any(self: Box) -> Box { self } fn as_any_ref(&self) -> &dyn std::any::Any { self } - fn redirect(&mut self) -> Option<&mut ClauseInst> { None } - fn run(self: Box, run: RunData) -> AtomicResult { - AtomicReturn::inert(*self, run.ctx) - } + fn redirect(&mut self) -> Option<&mut Expr> { None } + fn run(self: Box, _: RunData) -> AtomicResult { AtomicReturn::inert(*self) } fn apply_ref(&self, call: CallData) -> ExternResult { eprintln!("{}", call.arg); Ok(call.arg.to_clause(call.location)) } -} - -fn tee(x: Expr) -> Expr { - eprintln!("{x}"); - x -} +} pub fn inspect_lib() -> ConstTree { ConstTree::ns("std", [ConstTree::tree([ - ("inspect", atom_leaf(Inspect)), - ("tee", atom_leaf(xfn_1ary(tee))), + atom_ent("inspect", [Inspect]), + xfn_ent("tee", [|x: Expr| { + eprintln!("{x}"); + x + }]), ])]) } diff --git a/src/libs/std/list.orc b/src/libs/std/list.orc index c7ac8c7..7d8b97f 100644 --- a/src/libs/std/list.orc +++ b/src/libs/std/list.orc @@ -94,6 +94,8 @@ export const enumerate := \list. ( cons t[n, head] $ r tail $ n + 1 ) +export const count := \list. fold list 0 \a. \n. a + 1 + --[ Turn a list of CPS commands into a sequence. This is achieved by calling every element on the return value of the next element with the tail passed to it. @@ -128,10 +130,10 @@ export ::(new) (pmatch::take_binds $h_binds ( (\pmatch::pass. (\pmatch::value. $t_expr) tail) (pmatch::take_binds $t_binds ( - pmatch::give_binds pmatch::chain_binds $h_binds $t_binds pmatch::pass + pmatch::give_binds (pmatch::chain_binds $h_binds $t_binds) pmatch::pass )) )) ) ) - (pmatch::chain_binds $h_binds $t_binds) + ( (pmatch::chain_binds $h_binds $t_binds) ) ) diff --git a/src/libs/std/map.orc b/src/libs/std/map.orc index 461fceb..184cca6 100644 --- a/src/libs/std/map.orc +++ b/src/libs/std/map.orc @@ -84,11 +84,11 @@ export ::having pmatch::take_binds $binds ( (\pmatch::pass. $t_expr) ( pmatch::take_binds $t_binds ( - pmatch::give_binds pmatch::chain_binds $binds $t_binds pmatch::pass + pmatch::give_binds (pmatch::chain_binds $binds $t_binds) pmatch::pass ) ) ) ) ) - ( pmatch::chain_binds $binds $t_binds ) + ( (pmatch::chain_binds $binds $t_binds) ) ) diff --git a/src/libs/std/mod.rs b/src/libs/std/mod.rs index 0d89ffe..07751ce 100644 --- a/src/libs/std/mod.rs +++ b/src/libs/std/mod.rs @@ -16,3 +16,4 @@ mod state; pub mod std_system; pub mod string; pub mod tuple; +mod tstring; diff --git a/src/libs/std/number.rs b/src/libs/std/number.rs index 0d428e0..f624f28 100644 --- a/src/libs/std/number.rs +++ b/src/libs/std/number.rs @@ -5,11 +5,10 @@ use ordered_float::NotNan; use super::arithmetic_error::ArithmeticError; use crate::foreign::atom::Atomic; use crate::foreign::error::{AssertionError, ExternError, ExternResult}; -use crate::foreign::fn_bridge::constructors::xfn_2ary; use crate::foreign::inert::Inert; use crate::foreign::to_clause::ToClause; use crate::foreign::try_from_expr::TryFromExpr; -use crate::gen::tree::{atom_leaf, ConstTree}; +use crate::gen::tree::{xfn_ent, ConstTree}; use crate::interpreter::nort::{Clause, Expr}; use crate::location::CodeLocation; @@ -140,11 +139,11 @@ pub fn less_than(a: Numeric, b: Numeric) -> Inert { pub(super) fn num_lib() -> ConstTree { ConstTree::ns("std::number", [ConstTree::tree([ - ("add", atom_leaf(xfn_2ary(add))), - ("subtract", atom_leaf(xfn_2ary(subtract))), - ("multiply", atom_leaf(xfn_2ary(multiply))), - ("divide", atom_leaf(xfn_2ary(divide))), - ("remainder", atom_leaf(xfn_2ary(remainder))), - ("less_than", atom_leaf(xfn_2ary(less_than))), + xfn_ent("add", [add]), + xfn_ent("subtract", [subtract]), + xfn_ent("multiply", [multiply]), + xfn_ent("divide", [divide]), + xfn_ent("remainder", [remainder]), + xfn_ent("less_than", [less_than]), ])]) } diff --git a/src/libs/std/panic.rs b/src/libs/std/panic.rs index c50826f..9755310 100644 --- a/src/libs/std/panic.rs +++ b/src/libs/std/panic.rs @@ -5,9 +5,8 @@ use never::Never; use super::string::OrcString; use crate::foreign::error::{ExternError, ExternResult}; -use crate::foreign::fn_bridge::constructors::xfn_1ary; use crate::foreign::inert::Inert; -use crate::gen::tree::{atom_leaf, ConstTree}; +use crate::gen::tree::{xfn_leaf, ConstTree}; /// An unrecoverable error in Orchid land. Because Orchid is lazy, this only /// invalidates expressions that reference the one that generated it. @@ -28,6 +27,4 @@ pub fn orc_panic(msg: Inert) -> ExternResult { Err(OrchidPanic(Arc::new(msg.0.get_string())).rc()) } -pub fn panic_lib() -> ConstTree { - ConstTree::ns("std::panic", [atom_leaf(xfn_1ary(orc_panic))]) -} +pub fn panic_lib() -> ConstTree { ConstTree::ns("std::panic", [xfn_leaf(orc_panic)]) } diff --git a/src/libs/std/pmatch.orc b/src/libs/std/pmatch.orc index 8a3a0f3..5b51e56 100644 --- a/src/libs/std/pmatch.orc +++ b/src/libs/std/pmatch.orc @@ -11,6 +11,8 @@ import std::panic Response contains an expression and the list of names ]-- +export ::(match, value, pass, fail, request, response, =>) + ( macro ..$prefix:1 match ...$argument:0 { ..$body } ..$suffix:1 =0x1p130=> ..$prefix ( @@ -33,21 +35,21 @@ macro request (( ..$pattern )) =0x1p254=> request ( ..$pattern ) export ::(no_binds, add_bind, chain_binds, give_binds, take_binds) -macro add_bind $_new no_binds =0x1p254=> ( binds_list $_new no_binds ) -( macro add_bind $_new ( binds_list ...$tail ) - =0x1p254=> ( binds_list $_new ( binds_list ...$tail ) ) +macro ( add_bind $_new no_binds ) =0x1p254=> ( binds_list $_new no_binds ) +( macro ( add_bind $_new (binds_list ...$tail) ) + =0x1p254=> ( binds_list $_new (binds_list ...$tail) ) ) -macro give_binds no_binds $cont =0x1p254=> $cont -( macro give_binds ( binds_list $_name $tail ) $cont - =0x1p254=> (give_binds $tail $cont $_name) +macro ( give_binds no_binds $cont ) =0x1p254=> $cont +( macro ( give_binds ( binds_list $_name $tail ) $cont ) + =0x1p254=> (( give_binds $tail $cont ) $_name ) ) -macro take_binds no_binds $cont =0x1p254=> $cont -( macro take_binds ( binds_list $_name $tail ) $cont - =0x1p254=> \$_name. take_binds $tail $cont +macro ( take_binds no_binds $cont ) =0x1p254=> $cont +( macro ( take_binds ( binds_list $_name $tail ) $cont ) + =0x1p254=> ( take_binds $tail \$_name. $cont ) ) -macro chain_binds no_binds $second =0x1p254=> $second -( macro chain_binds ( binds_list $_head $tail ) $second - =0x1p254=> add_bind $_head chain_binds $tail $second +macro ( chain_binds no_binds $second ) =0x1p254=> $second +( macro ( chain_binds ( binds_list $_head $tail ) $second ) + =0x1p254=> ( add_bind $_head ( chain_binds $tail $second )) ) --[ primitive pattern ( _ ) ]-- @@ -61,7 +63,7 @@ macro chain_binds no_binds $second =0x1p254=> $second ( macro request ( $_name ) - =0x1p226=> response ( pass value ) ( add_bind $_name no_binds ) + =0x1p226=> response ( pass value ) ( ( add_bind $_name no_binds ) ) ) --[ primitive pattern ( and ) ]-- @@ -74,11 +76,11 @@ macro chain_binds no_binds $second =0x1p254=> $second =0x1p254=> response ( (\pass. $lh_expr) (take_binds $lh_binds ( (\pass. $rh_expr) (take_binds $rh_binds ( - give_binds chain_binds $lh_binds $rh_binds pass + give_binds (chain_binds $lh_binds $rh_binds) pass )) )) ) - ( chain_binds $lh_binds $rh_binds ) + ( (chain_binds $lh_binds $rh_binds) ) ) --[ primitive pattern ( or ) ]-- @@ -93,7 +95,7 @@ macro chain_binds no_binds $second =0x1p254=> $second ( -- for this to work, lh and rh must produce the same bindings macro await_or_subpatterns ( response $lh_expr ( $lh_binds) ) ( response $rh_expr ( $rh_binds ) ) =0x1p254=> response ( - (\cancel. $lh_expr) -- lh works with pass directly because its bindings are reported up + (\fail. $lh_expr) -- lh works with pass directly because its bindings are reported up ($rh_expr (take_binds $rh_binds -- rh runs if lh cancels (give_binds $lh_binds pass) -- translate rh binds to lh binds )) @@ -101,4 +103,3 @@ macro chain_binds no_binds $second =0x1p254=> $second ( $lh_binds ) -- report lh bindings ) -export ::(match, cancel, argument, request, response, =>) diff --git a/src/libs/std/prelude.orc b/src/libs/std/prelude.orc index 1df70c8..1d15a79 100644 --- a/src/libs/std/prelude.orc +++ b/src/libs/std/prelude.orc @@ -22,3 +22,6 @@ export ::(loop_over, recursive, while) import std::known::* export ::[, _ ; . =] + +import std +export ::(std) diff --git a/src/libs/std/protocol.rs b/src/libs/std/protocol.rs index ffa7c29..218d56d 100644 --- a/src/libs/std/protocol.rs +++ b/src/libs/std/protocol.rs @@ -13,13 +13,12 @@ use super::runtime_error::RuntimeError; use crate::error::ProjectResult; use crate::foreign::atom::Atomic; use crate::foreign::error::ExternResult; -use crate::foreign::fn_bridge::constructors::xfn_2ary; use crate::foreign::inert::{Inert, InertPayload}; use crate::foreign::process::Unstable; use crate::foreign::to_clause::ToClause; use crate::gen::tpl; use crate::gen::traits::GenClause; -use crate::gen::tree::{atom_leaf, ConstTree}; +use crate::gen::tree::{atom_leaf, xfn_ent, ConstTree}; use crate::interpreter::nort as int; use crate::interpreter::nort::ClauseInst; use crate::libs::parse_custom_line::custom_line; @@ -33,7 +32,6 @@ use crate::parse::parsed::{ SourceLineKind, }; use crate::utils::ddispatch::Request; -use crate::utils::pure_seq::pushed; pub struct TypeData { pub id: RefEqual, @@ -439,9 +437,9 @@ pub const fn gen_resolv(name: &'static str) -> impl GenClause { pub fn protocol_lib() -> ConstTree { ConstTree::ns("std::protocol", [ConstTree::tree([ - ("unwrap", atom_leaf(xfn_2ary(unwrap))), - ("wrap", atom_leaf(xfn_2ary(wrap))), - ("get_impl", atom_leaf(xfn_2ary(get_impl))), - ("resolve", atom_leaf(xfn_2ary(resolve))), + xfn_ent("unwrap", [unwrap]), + xfn_ent("wrap", [wrap]), + xfn_ent("get_impl", [get_impl]), + xfn_ent("resolve", [resolve]), ])]) } diff --git a/src/libs/std/reflect.rs b/src/libs/std/reflect.rs index 1bcc6c0..9baeffc 100644 --- a/src/libs/std/reflect.rs +++ b/src/libs/std/reflect.rs @@ -8,9 +8,8 @@ use std::sync::atomic::{self, AtomicUsize}; use intern_all::i; -use crate::foreign::fn_bridge::constructors::{xfn_1ary, xfn_2ary}; use crate::foreign::inert::{Inert, InertPayload}; -use crate::gen::tree::{atom_ent, ConstTree}; +use crate::gen::tree::{xfn_ent, ConstTree}; use crate::interpreter::nort::Clause; use crate::name::Sym; @@ -57,9 +56,7 @@ impl Ord for RefEqual { fn cmp(&self, other: &Self) -> cmp::Ordering { self.id().cmp(&other.id()) } } impl PartialOrd for RefEqual { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Hash for RefEqual { fn hash(&self, state: &mut H) { self.id().hash(state) } @@ -67,11 +64,6 @@ impl Hash for RefEqual { pub(super) fn reflect_lib() -> ConstTree { ConstTree::ns("std::reflect", [ConstTree::tree([ - atom_ent("auto_tuple_test", [xfn_1ary(|_: Inert| { - (Inert(1usize), Inert(2usize), Inert(3usize)) - })]), - atom_ent("ref_equal", [xfn_2ary( - |l: Inert, r: Inert| Inert(l.0.id() == r.0.id()), - )]), + xfn_ent("ref_equal", [|l: Inert, r: Inert| Inert(l.0.id() == r.0.id())]), ])]) } diff --git a/src/libs/std/runtime_error.rs b/src/libs/std/runtime_error.rs index f3d4e03..79b08d1 100644 --- a/src/libs/std/runtime_error.rs +++ b/src/libs/std/runtime_error.rs @@ -2,9 +2,8 @@ //! for runtime errors such as missing files. use std::fmt::Display; -use std::sync::Arc; -use crate::foreign::error::{ExternError, ExternResult}; +use crate::foreign::error::{ExternError, ExternErrorObj, ExternResult}; /// Some external event prevented the operation from succeeding #[derive(Clone)] @@ -21,7 +20,7 @@ impl RuntimeError { } /// Construct and upcast to [ExternError] - pub fn ext(message: String, operation: &'static str) -> Arc { + pub fn ext(message: String, operation: &'static str) -> ExternErrorObj { Self { message, operation }.rc() } } diff --git a/src/libs/std/state.rs b/src/libs/std/state.rs index 7afdf91..299fa3b 100644 --- a/src/libs/std/state.rs +++ b/src/libs/std/state.rs @@ -1,13 +1,11 @@ use std::sync::{Arc, Mutex}; -use crate::foreign::fn_bridge::constructors::{xfn_2ary, xfn_3ary}; use crate::foreign::fn_bridge::Thunk; use crate::foreign::inert::{Inert, InertPayload}; use crate::gen::tpl; use crate::gen::traits::Gen; -use crate::gen::tree::{atom_leaf, ConstTree}; +use crate::gen::tree::{xfn_ent, ConstTree}; use crate::interpreter::gen_nort::nort_gen; -use crate::interpreter::nort_builder::NortBuilder; use crate::interpreter::handler::HandlerTable; use crate::interpreter::nort::Expr; @@ -77,8 +75,8 @@ pub fn state_handlers() -> HandlerTable<'static> { pub fn state_lib() -> ConstTree { ConstTree::ns("std::state", [ConstTree::tree([ - ("new_state", atom_leaf(xfn_2ary(new_state))), - ("get_state", atom_leaf(xfn_2ary(get_state))), - ("set_state", atom_leaf(xfn_3ary(set_state))), + xfn_ent("new_state", [new_state]), + xfn_ent("get_state", [get_state]), + xfn_ent("set_state", [set_state]), ])]) } diff --git a/src/libs/std/std_system.rs b/src/libs/std/std_system.rs index bb16105..d11934a 100644 --- a/src/libs/std/std_system.rs +++ b/src/libs/std/std_system.rs @@ -15,6 +15,7 @@ use super::protocol::{parsers, protocol_lib}; use super::reflect::reflect_lib; use super::state::{state_handlers, state_lib}; use super::string::str_lib; +use super::tstring::TStringLexer; use super::tuple::tuple_lib; use crate::facade::system::{IntoSystem, System}; use crate::gen::tree::{ConstCombineErr, ConstTree}; @@ -66,20 +67,19 @@ impl StdConfig { impl IntoSystem<'static> for StdConfig { fn into_system(self) -> System<'static> { - let gen = CodeGenInfo::no_details("std"); System { name: "stdlib", constants: self.stdlib().expect("stdlib tree is malformed"), code: ModEntry::ns("std", [ModEntry::leaf( - EmbeddedFS::new::(".orc", gen.clone()).rc(), + EmbeddedFS::new::(".orc", CodeGenInfo::no_details("std::fs")).rc(), )]), prelude: vec![Prelude { target: VName::literal("std::prelude"), exclude: VName::literal("std"), - owner: gen.clone(), + owner: CodeGenInfo::no_details("std::prelude"), }], handlers: state_handlers(), - lexer_plugins: vec![], + lexer_plugins: vec![Box::new(TStringLexer)], line_parsers: parsers(), } } diff --git a/src/libs/std/string.rs b/src/libs/std/string.rs index 9ef46af..ec4b120 100644 --- a/src/libs/std/string.rs +++ b/src/libs/std/string.rs @@ -11,11 +11,10 @@ use unicode_segmentation::UnicodeSegmentation; use super::runtime_error::RuntimeError; use crate::foreign::atom::Atomic; use crate::foreign::error::ExternResult; -use crate::foreign::fn_bridge::constructors::{xfn_1ary, xfn_2ary, xfn_3ary}; use crate::foreign::inert::{Inert, InertPayload}; use crate::foreign::to_clause::ToClause; use crate::foreign::try_from_expr::TryFromExpr; -use crate::gen::tree::{atom_ent, ConstTree}; +use crate::gen::tree::{xfn_ent, ConstTree}; use crate::interpreter::nort::{Clause, Expr}; use crate::location::CodeLocation; use crate::utils::iter_find::iter_find; @@ -50,8 +49,7 @@ impl OrcString { pub fn get_string(self) -> String { match self { Self::Interned(s) => s.as_str().to_owned(), - Self::Runtime(rc) => - Arc::try_unwrap(rc).unwrap_or_else(|rc| (*rc).clone()), + Self::Runtime(rc) => Arc::try_unwrap(rc).unwrap_or_else(|rc| (*rc).clone()), } } } @@ -68,9 +66,7 @@ impl Deref for OrcString { } impl Hash for OrcString { - fn hash(&self, state: &mut H) { - self.as_str().hash(state) - } + fn hash(&self, state: &mut H) { self.as_str().hash(state) } } impl From for OrcString { @@ -96,9 +92,7 @@ impl InertPayload for OrcString { } impl ToClause for String { - fn to_clause(self, _: CodeLocation) -> Clause { - Inert(OrcString::from(self)).atom_cls() - } + fn to_clause(self, _: CodeLocation) -> Clause { Inert(OrcString::from(self)).atom_cls() } } impl TryFromExpr for String { @@ -109,62 +103,47 @@ impl TryFromExpr for String { pub(super) fn str_lib() -> ConstTree { ConstTree::ns("std::string", [ConstTree::tree([ - atom_ent("slice", [xfn_3ary( - |s: Inert, i: Inert, len: Inert| { - let graphs = s.0.as_str().graphemes(true); - if i.0 == 0 { - return Ok(graphs.take(len.0).collect::()); - } - let mut prefix = graphs.skip(i.0 - 1); - if prefix.next().is_none() { - return Err(RuntimeError::ext( - "Character index out of bounds".to_string(), - "indexing string", - )); - } - let mut count = 0; - let ret = (prefix.take(len.0)) - .map(|x| { - count += 1; - x - }) - .collect::(); - if count == len.0 { - Ok(ret) - } else { - RuntimeError::fail( - "Character index out of bounds".to_string(), - "indexing string", - ) - } - }, - )]), - atom_ent("concat", [xfn_2ary(|a: String, b: Inert| { - a + b.0.as_str() - })]), - atom_ent("find", [xfn_2ary( - |haystack: Inert, needle: Inert| { - let haystack_graphs = haystack.0.as_str().graphemes(true); - iter_find(haystack_graphs, needle.0.as_str().graphemes(true)).map(Inert) - }, - )]), - atom_ent("split", [xfn_2ary( - |s: String, i: Inert| -> (String, String) { - let mut graphs = s.as_str().graphemes(true); - (graphs.by_ref().take(i.0).collect(), graphs.collect()) - }, - )]), - atom_ent("len", [xfn_1ary(|s: Inert| { - Inert(s.0.graphemes(true).count()) - })]), - atom_ent("size", [xfn_1ary(|s: Inert| { - Inert(s.0.as_bytes().len()) - })]), - atom_ent("intern", [xfn_1ary(|s: Inert| { + xfn_ent("slice", [|s: Inert, i: Inert, len: Inert| { + let graphs = s.0.as_str().graphemes(true); + if i.0 == 0 { + return Ok(graphs.take(len.0).collect::()); + } + let mut prefix = graphs.skip(i.0 - 1); + if prefix.next().is_none() { + return Err(RuntimeError::ext( + "Character index out of bounds".to_string(), + "indexing string", + )); + } + let mut count = 0; + let ret = (prefix.take(len.0)) + .map(|x| { + count += 1; + x + }) + .collect::(); + if count == len.0 { + Ok(ret) + } else { + RuntimeError::fail("Character index out of bounds".to_string(), "indexing string") + } + }]), + xfn_ent("concat", [|a: String, b: Inert| a + b.0.as_str()]), + xfn_ent("find", [|haystack: Inert, needle: Inert| { + let haystack_graphs = haystack.0.as_str().graphemes(true); + iter_find(haystack_graphs, needle.0.as_str().graphemes(true)).map(Inert) + }]), + xfn_ent("split", [|s: String, i: Inert| -> (String, String) { + let mut graphs = s.as_str().graphemes(true); + (graphs.by_ref().take(i.0).collect(), graphs.collect()) + }]), + xfn_ent("len", [|s: Inert| Inert(s.0.graphemes(true).count())]), + xfn_ent("size", [|s: Inert| Inert(s.0.as_bytes().len())]), + xfn_ent("intern", [|s: Inert| { Inert(match s.0 { OrcString::Runtime(s) => OrcString::Interned(i(&*s)), x => x, }) - })]), + }]), ])]) } diff --git a/src/libs/std/tstring.rs b/src/libs/std/tstring.rs new file mode 100644 index 0000000..758a338 --- /dev/null +++ b/src/libs/std/tstring.rs @@ -0,0 +1,81 @@ +use std::fmt::Write; + +use intern_all::i; + +use crate::error::ProjectResult; +use crate::foreign::atom::AtomGenerator; +use crate::foreign::inert::Inert; +use crate::libs::std::string::OrcString; +use crate::parse::errors::ParseErrorKind; +use crate::parse::lex_plugin::{LexPluginRecur, LexPluginReq, LexerPlugin}; +use crate::parse::lexer::{Entry, LexRes, Lexeme}; +use crate::parse::parsed::PType; +use crate::parse::string::parse_string; + +pub struct TStringLexer; +impl LexerPlugin for TStringLexer { + fn lex<'a>(&self, req: &'_ dyn LexPluginReq<'a>) -> Option>> { + req.tail().strip_prefix("$\"").map(|mut txt| { + let ctx = req.ctx(); + let mut parts = vec![Entry::new(ctx.range(0, txt), Lexeme::LP(PType::Par))]; + let mut str = String::new(); + let commit_str = |str: &mut String, tail: &str, parts: &mut Vec| -> ProjectResult<_> { + let str_val = parse_string(str).map_err(|e| e.to_proj(ctx, ctx.pos(txt)))?; + let ag = AtomGenerator::cloner(Inert(OrcString::from(i(&str_val)))); + parts.push(Entry::new(ctx.range(str.len(), tail), Lexeme::Atom(ag))); + *str = String::new(); + Ok(()) + }; + loop { + if let Some(rest) = txt.strip_prefix('"') { + commit_str(&mut str, txt, &mut parts)?; + parts.push(Entry::new(ctx.range(0, rest), Lexeme::RP(PType::Par))); + return Ok(LexRes { tail: rest, tokens: parts }); + } + if let Some(rest) = txt.strip_prefix("${") { + let mut depth = 0; + commit_str(&mut str, rest, &mut parts)?; + parts.extend(req.insert("++ std::conv::to_string (", ctx.source_range(0, rest))); + let res = req.recurse(LexPluginRecur { + tail: rest, + exit: &mut |c| { + match c.chars().next() { + None => return Err(UnclosedInterpolation.pack(ctx.source_range(2, rest))), + Some('{') => depth += 1, + Some('}') if depth == 0 => return Ok(true), + Some('}') => depth -= 1, + _ => (), + } + Ok(false) + }, + })?; + txt = &res.tail[1..]; // account for final } + parts.extend(res.tokens); + parts.extend(req.insert(") ++", ctx.source_range(0, txt))); + } else { + let mut chars = txt.chars(); + match chars.next() { + None => return Err(NoTStringEnd.pack(ctx.source_range(req.tail().len(), ""))), + Some('\\') => match chars.next() { + None => write!(str, "\\").expect("writing \\ into string"), + Some(next) => write!(str, "\\{next}").expect("writing \\ and char into string"), + }, + Some(c) => write!(str, "{c}").expect("writing char into string"), + } + txt = chars.as_str(); + } + } + }) + } +} + +pub struct UnclosedInterpolation; +impl ParseErrorKind for UnclosedInterpolation { + const DESCRIPTION: &'static str = "A ${ block within a $-string wasn't closed"; +} + +/// String literal never ends +pub(super) struct NoTStringEnd; +impl ParseErrorKind for NoTStringEnd { + const DESCRIPTION: &'static str = "A $-string literal was not closed with `\"`"; +} diff --git a/src/libs/std/tuple.orc b/src/libs/std/tuple.orc index f3ea26a..830109e 100644 --- a/src/libs/std/tuple.orc +++ b/src/libs/std/tuple.orc @@ -37,41 +37,40 @@ export ::(t, size) ( macro::length macro::comma_list ( ..$items ) ) ( pattern_walker - (0) -- index of next item macro::comma_list ( ..$items ) -- leftover items ) ) ( macro tuple_pattern $length ( pattern_result $expr ( $binds ) ) =0x1p254=> pmatch::response ( if length pmatch::value == $length - then $expr + then ((\tuple_idx. $expr ) 0) else pmatch::fail ) ( $binds ) ) -( macro pattern_walker $length macro::list_end +( macro pattern_walker macro::list_end =0x1p254=> pattern_result pmatch::pass ( pmatch::no_binds ) ) -( macro pattern_walker (...$length) ( macro::list_item $next $tail ) +( macro pattern_walker ( macro::list_item $next $tail ) =0x1p254=> pattern_await - (...$length) ( pmatch::request $next ) - ( pattern_walker (...$length + 1) $tail ) + ( pattern_walker $tail ) ) -( macro pattern_await $length +( macro pattern_await ( pmatch::response $expr ( $binds ) ) ( pattern_result $tail_expr ( $tail_binds ) ) =0x1p254=> pattern_result ( - (\pmatch::pass. (\pmatch::value. $expr) (pick pmatch::value $length)) ( + (\pmatch::pass. (\pmatch::value. $expr) (pick pmatch::value tuple_idx)) ( pmatch::take_binds $binds ( - (\pmatch::pass. $tail_expr) ( pmatch::take_binds $tail_binds ( + (\pmatch::pass. (\tuple_idx. $tail_expr) (tuple_idx + 1)) + ( pmatch::take_binds $tail_binds ( pmatch::give_binds - pmatch::chain_binds $binds $tail_binds + (pmatch::chain_binds $binds $tail_binds) pmatch::pass )) ) ) ) - ( pmatch::chain_binds $binds $tail_binds ) + ( ( pmatch::chain_binds $binds $tail_binds ) ) ) diff --git a/src/libs/std/tuple.rs b/src/libs/std/tuple.rs index 6e5d708..defec8b 100644 --- a/src/libs/std/tuple.rs +++ b/src/libs/std/tuple.rs @@ -9,24 +9,18 @@ use super::conv::TO_STRING; use super::protocol::Tag; use super::reflect::refer; use crate::foreign::error::{AssertionError, ExternResult}; -use crate::foreign::fn_bridge::constructors::{xfn_1ary, xfn_2ary}; use crate::foreign::fn_bridge::Thunk; use crate::foreign::inert::{Inert, InertPayload}; use crate::foreign::try_from_expr::WithLoc; -use crate::gen::tpl; -use crate::gen::tree::{atom_leaf, leaf, ConstTree}; +use crate::gen::tree::{atom_ent, xfn_ent, ConstTree}; use crate::interpreter::nort::Expr; use crate::location::{CodeGenInfo, CodeLocation}; use crate::utils::ddispatch::Request; use crate::utils::pure_seq::pushed; static TUPLE_TAG: Lazy = Lazy::new(|| { - let location = - CodeLocation::Gen(CodeGenInfo::no_details("stdlib::tuple::tag")); - Tag::new("tuple", [( - TO_STRING.id(), - refer("std::tuple::to_string_impl").to_expr(location), - )]) + let location = CodeLocation::Gen(CodeGenInfo::no_details("stdlib::tuple::tag")); + Tag::new("tuple", [(TO_STRING.id(), refer("std::tuple::to_string_impl").to_expr(location))]) }); /// A short contiquous random access sequence of Orchid values. @@ -34,27 +28,22 @@ static TUPLE_TAG: Lazy = Lazy::new(|| { pub struct Tuple(pub Arc>); impl InertPayload for Tuple { const TYPE_STR: &'static str = "tuple"; - fn respond(&self, mut request: Request) { - request.serve_with(|| TUPLE_TAG.clone()) - } + fn respond(&self, mut request: Request) { request.serve_with(|| TUPLE_TAG.clone()) } } impl Debug for Tuple { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Tuple")?; - f.debug_list().entries(self.0.iter()).finish() + f.debug_list().entries(self.0.iter().map(|e| &e.clause)).finish() } } fn length(tuple: Inert) -> Inert { Inert(tuple.0.0.len()) } -fn pick( - WithLoc(loc, tuple): WithLoc>, - idx: Inert, -) -> ExternResult { +fn pick(WithLoc(loc, tuple): WithLoc>, idx: Inert) -> ExternResult { (tuple.0.0.get(idx.0).cloned()).ok_or_else(|| { let msg = format!("{} <= {idx}", tuple.0.0.len()); AssertionError::ext(loc, "Tuple index out of bounds", msg) - }) + }) } fn push(Inert(tuple): Inert, item: Thunk) -> Inert { @@ -64,9 +53,9 @@ fn push(Inert(tuple): Inert, item: Thunk) -> Inert { pub(super) fn tuple_lib() -> ConstTree { ConstTree::ns("std", [ConstTree::tree([TUPLE_TAG.as_tree_ent([ - ("empty", leaf(tpl::V(Inert(Tuple(Arc::new(Vec::new())))))), - ("length", atom_leaf(xfn_1ary(length))), - ("pick", atom_leaf(xfn_2ary(pick))), - ("push", atom_leaf(xfn_2ary(push))), + atom_ent("empty", [Inert(Tuple(Arc::new(Vec::new())))]), + xfn_ent("length", [length]), + xfn_ent("pick", [pick]), + xfn_ent("push", [push]), ])])]) } diff --git a/src/name.rs b/src/name.rs index 7ff39c9..0dbf043 100644 --- a/src/name.rs +++ b/src/name.rs @@ -280,7 +280,7 @@ impl Sym { } impl Debug for Sym { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Sym{self})") + write!(f, "Sym({self})") } } impl Display for Sym { diff --git a/src/parse/context.rs b/src/parse/context.rs index 76001cb..b69db03 100644 --- a/src/parse/context.rs +++ b/src/parse/context.rs @@ -39,7 +39,7 @@ pub trait ParseCtx { } /// Create a contextful location for error reporting #[must_use] - fn code_range(&self, len: usize, tl: &str) -> SourceRange { + fn source_range(&self, len: usize, tl: &str) -> SourceRange { self.range_loc(&self.range(len, tl)) } /// Create a contentful location from a range directly. diff --git a/src/parse/facade.rs b/src/parse/facade.rs index f0e85bd..3b8ef1f 100644 --- a/src/parse/facade.rs +++ b/src/parse/facade.rs @@ -11,7 +11,7 @@ use crate::parse::sourcefile::{parse_line, split_lines}; /// Parse a file pub fn parse_file(ctx: &impl ParseCtx) -> ProjectResult> { - let tokens = lex(vec![], ctx.source().as_str(), ctx, |_| false)?.tokens; + let tokens = lex(vec![], ctx.source().as_str(), ctx, |_| Ok(false))?.tokens; if tokens.is_empty() { Ok(Vec::new()) } else { @@ -30,7 +30,7 @@ pub fn parse_entries( range: SourceRange, ) -> Vec { let ctx = FlatLocContext::new(ctx, &range); - let res = lex(vec![], text, &ctx, |_| false).expect("pre-specified source"); + let res = lex(vec![], text, &ctx, |_| Ok(false)).expect("pre-specified source"); split_lines(Frag::from_slice(&res.tokens), &ctx) .flat_map(|tokens| parse_line(tokens, &ctx).expect("pre-specified source")) .map(|kind| kind.wrap(range.clone())) diff --git a/src/parse/lex_plugin.rs b/src/parse/lex_plugin.rs index 051607e..7862eda 100644 --- a/src/parse/lex_plugin.rs +++ b/src/parse/lex_plugin.rs @@ -1,8 +1,9 @@ //! Abstractions for dynamic extensions to the lexer to parse custom literals -use super::context::ParseCtx; -use super::lexer::{lex, LexRes}; +use super::context::{FlatLocContext, ParseCtx}; +use super::lexer::{lex, Entry, LexRes}; use crate::error::ProjectResult; +use crate::location::SourceRange; /// Data passed to the recursive sub-lexer pub struct LexPluginRecur<'a, 'b> { @@ -11,7 +12,7 @@ pub struct LexPluginRecur<'a, 'b> { /// Callback that will be called between lexemes on the leftover text. /// When it returns true, the lexer exits and leaves the remaining text for /// you. - pub exit: &'b dyn for<'c> Fn(&'c str) -> bool, + pub exit: &'b mut dyn for<'c> FnMut(&'c str) -> ProjectResult, } /// Data and actions available to a lexer plugin @@ -25,16 +26,20 @@ pub trait LexPluginReq<'a> { /// expressions in your literals like the template strings of most languages /// other than Rust. fn recurse(&self, req: LexPluginRecur<'a, '_>) -> ProjectResult>; + /// Lex an inserted piece of text, especially when translating custom syntax + /// into multiple lexemes. + /// + /// # Panics + /// + /// If tokenization fails + fn insert(&self, data: &str, range: SourceRange) -> Vec; } /// External plugin that parses a literal into recognized Orchid lexemes, most /// likely atoms. pub trait LexerPlugin: Send + Sync { /// Run the lexer - fn lex<'a>( - &self, - req: &'_ dyn LexPluginReq<'a>, - ) -> Option>>; + fn lex<'a>(&self, req: &'_ dyn LexPluginReq<'a>) -> Option>>; } pub(super) struct LexPlugReqImpl<'a, 'b, TCtx: ParseCtx> { @@ -47,4 +52,10 @@ impl<'a, 'b, TCtx: ParseCtx> LexPluginReq<'a> for LexPlugReqImpl<'a, 'b, TCtx> { fn recurse(&self, req: LexPluginRecur<'a, '_>) -> ProjectResult> { lex(Vec::new(), req.tail, self.ctx, |s| (req.exit)(s)) } + fn insert(&self, data: &str, range: SourceRange) -> Vec { + let ctx = FlatLocContext::new(self.ctx as &dyn ParseCtx, &range); + lex(Vec::new(), data, &ctx, |_| Ok(false)) + .expect("Insert failed to lex") + .tokens + } } diff --git a/src/parse/lexer.rs b/src/parse/lexer.rs index b2cb066..448471a 100644 --- a/src/parse/lexer.rs +++ b/src/parse/lexer.rs @@ -40,7 +40,9 @@ impl Entry { matches!(self.lexeme, Lexeme::Comment(_) | Lexeme::BR) } - fn new(range: Range, lexeme: Lexeme) -> Self { Self { lexeme, range } } + /// Create a new entry + #[must_use] + pub fn new(range: Range, lexeme: Lexeme) -> Self { Self { lexeme, range } } } impl Display for Entry { @@ -139,7 +141,7 @@ pub fn namechar(c: char) -> bool { c.is_alphanumeric() | (c == '_') } pub fn namestart(c: char) -> bool { c.is_alphabetic() | (c == '_') } /// Character filter that can appear in operators. pub fn opchar(c: char) -> bool { - !namestart(c) && !numstart(c) && !c.is_whitespace() && !"()[]{},".contains(c) + !namestart(c) && !numstart(c) && !c.is_whitespace() && !"()[]{},'\"\\".contains(c) } /// Split off all characters from the beginning that match a filter @@ -176,18 +178,18 @@ pub fn lex<'a>( mut tokens: Vec, mut data: &'a str, ctx: &'_ impl ParseCtx, - bail: impl Fn(&str) -> bool, + mut bail: impl FnMut(&str) -> ProjectResult, ) -> ProjectResult> { let mut prev_len = data.len() + 1; 'tail: loop { - if bail(data) { - return Ok(LexRes { tokens, tail: data }); - } if prev_len == data.len() { panic!("got stuck at {data:?}, parsed {:?}", tokens.last().unwrap()); } prev_len = data.len(); data = data.trim_start_matches(|c: char| c.is_whitespace() && c != '\n'); + if bail(data)? { + return Ok(LexRes { tokens, tail: data }); + } let mut chars = data.chars(); let head = match chars.next() { None => return Ok(LexRes { tokens, tail: data }), @@ -221,7 +223,7 @@ pub fn lex<'a>( } if let Some(tail) = data.strip_prefix("--[") { let (note, tail) = (tail.split_once("]--")) - .ok_or_else(|| NoCommentEnd.pack(ctx.code_range(tail.len(), "")))?; + .ok_or_else(|| NoCommentEnd.pack(ctx.source_range(tail.len(), "")))?; let lexeme = Lexeme::Comment(Arc::new(note.to_string())); tokens.push(Entry::new(ctx.range(note.len() + 3, tail), lexeme)); data = tail; @@ -276,7 +278,7 @@ pub fn lex<'a>( .and_then(|num| match num { Numeric::Uint(usize) => Ok(usize), Numeric::Float(_) => Err( - FloatPlacehPrio.pack(ctx.code_range(num_str.len(), tail)), + FloatPlacehPrio.pack(ctx.source_range(num_str.len(), tail)), ), }) .map(|p| (p, num_str.len() + 1, tail)) diff --git a/src/parse/string.rs b/src/parse/string.rs index c609139..746aff2 100644 --- a/src/parse/string.rs +++ b/src/parse/string.rs @@ -3,13 +3,12 @@ use intern_all::i; use itertools::Itertools; -use super::errors::{ - BadCodePoint, BadEscapeSequence, NoStringEnd, NotHex, ParseErrorKind, -}; +use super::context::ParseCtx; +use super::errors::{BadCodePoint, BadEscapeSequence, NoStringEnd, NotHex, ParseErrorKind}; #[allow(unused)] // for doc use super::lex_plugin::LexerPlugin; use super::lexer::{Entry, LexRes, Lexeme}; -use crate::error::ProjectResult; +use crate::error::{ProjectErrorObj, ProjectResult}; use crate::foreign::atom::AtomGenerator; use crate::foreign::inert::Inert; use crate::libs::std::string::OrcString; @@ -32,6 +31,19 @@ pub struct StringError { kind: StringErrorKind, } +impl StringError { + /// Convert into project error for reporting + pub fn to_proj(self, ctx: &dyn ParseCtx, pos: usize) -> ProjectErrorObj { + let start = pos + self.pos; + let location = ctx.range_loc(&(start..start + 1)); + match self.kind { + StringErrorKind::NotHex => NotHex.pack(location), + StringErrorKind::BadCodePoint => BadCodePoint.pack(location), + StringErrorKind::BadEscSeq => BadEscapeSequence.pack(location), + } + } +} + /// Process escape sequences in a string literal pub fn parse_string(str: &str) -> Result { let mut target = String::new(); @@ -61,18 +73,14 @@ pub fn parse_string(str: &str) -> Result { 'u' => { let acc = ((0..4).rev()) .map(|radical| { - let (j, c) = (iter.next()) - .ok_or(StringError { pos, kind: StringErrorKind::NotHex })?; + let (j, c) = (iter.next()).ok_or(StringError { pos, kind: StringErrorKind::NotHex })?; pos = j; - let b = - u32::from_str_radix(&String::from(c), 16).map_err(|_| { - StringError { pos, kind: StringErrorKind::NotHex } - })?; + let b = u32::from_str_radix(&String::from(c), 16) + .map_err(|_| StringError { pos, kind: StringErrorKind::NotHex })?; Ok(16u32.pow(radical) + b) }) .fold_ok(0, u32::wrapping_add)?; - char::from_u32(acc) - .ok_or(StringError { pos, kind: StringErrorKind::BadCodePoint })? + char::from_u32(acc).ok_or(StringError { pos, kind: StringErrorKind::BadCodePoint })? }, _ => return Err(StringError { pos, kind: StringErrorKind::BadEscSeq }), }; @@ -91,26 +99,15 @@ impl LexerPlugin for StringLexer { req.tail().strip_prefix('"').map(|data| { let mut leftover = data; return loop { - let (inside, outside) = - (leftover.split_once('"')).ok_or_else(|| { - NoStringEnd.pack(req.ctx().code_range(data.len(), "")) - })?; - let backslashes = - inside.chars().rev().take_while(|c| *c == '\\').count(); + let (inside, outside) = (leftover.split_once('"')) + .ok_or_else(|| NoStringEnd.pack(req.ctx().source_range(data.len(), "")))?; + let backslashes = inside.chars().rev().take_while(|c| *c == '\\').count(); if backslashes % 2 == 0 { // cut form tail to recoup what string_content doesn't have - let (string_data, tail) = - data.split_at(data.len() - outside.len() - 1); + let (string_data, tail) = data.split_at(data.len() - outside.len() - 1); let tail = &tail[1..]; // push the tail past the end quote - let string = parse_string(string_data).map_err(|e| { - let start = req.ctx().pos(data) + e.pos; - let location = req.ctx().range_loc(&(start..start + 1)); - match e.kind { - StringErrorKind::NotHex => NotHex.pack(location), - StringErrorKind::BadCodePoint => BadCodePoint.pack(location), - StringErrorKind::BadEscSeq => BadEscapeSequence.pack(location), - } - })?; + let string = + parse_string(string_data).map_err(|e| e.to_proj(req.ctx(), req.ctx().pos(data)))?; let output = Inert(OrcString::from(i(&string))); let ag = AtomGenerator::cloner(output); let range = req.ctx().range(string_data.len(), tail); @@ -144,8 +141,7 @@ mod test { [Entry { lexeme: Lexeme::Atom(ag), .. }] => ag, _ => panic!("Expected a single atom"), }; - let atom = - ag.run().try_downcast::>().expect("Lexed to inert"); + let atom = ag.run().try_downcast::>().expect("Lexed to inert"); assert_eq!(atom.0.as_str(), "hello world!"); assert_eq!(res.tail, " - says the programmer"); } diff --git a/src/pipeline/load_solution.rs b/src/pipeline/load_solution.rs index a30fa21..2dffe87 100644 --- a/src/pipeline/load_solution.rs +++ b/src/pipeline/load_solution.rs @@ -10,9 +10,7 @@ use intern_all::{sweep_t, Tok}; use super::dealias::resolve_aliases::resolve_aliases; use super::process_source::{process_ns, resolve_globs, GlobImports}; use super::project::{ItemKind, ProjItem, ProjXEnt, ProjectMod, ProjectTree}; -use crate::error::{ - bundle_location, ErrorPosition, ProjectError, ProjectResult, -}; +use crate::error::{bundle_location, ErrorPosition, ProjectError, ProjectResult}; use crate::location::{CodeGenInfo, CodeLocation, SourceCode, SourceRange}; use crate::name::{PathSlice, Sym, VName, VPath}; use crate::parse::context::ParseCtxImpl; @@ -42,10 +40,7 @@ fn split_max_prefix<'a, T>( path: &'a [T], is_valid: &impl Fn(&[T]) -> bool, ) -> Option<(&'a [T], &'a [T])> { - (0..=path.len()) - .rev() - .map(|i| path.split_at(i)) - .find(|(file, _)| is_valid(file)) + (0..=path.len()).rev().map(|i| path.split_at(i)).find(|(file, _)| is_valid(file)) } /// Represents a prelude / implicit import requested by a library. @@ -94,10 +89,8 @@ pub fn load_solution( ) -> ProjectResult { let mut target_queue = VecDeque::<(Sym, CodeLocation)>::new(); target_queue.extend(targets.into_iter()); - target_queue.extend( - (ctx.preludes.iter()) - .map(|p| (p.target.to_sym(), CodeLocation::Gen(p.owner.clone()))), - ); + target_queue + .extend((ctx.preludes.iter()).map(|p| (p.target.to_sym(), CodeLocation::Gen(p.owner.clone())))); let mut known_files = HashSet::new(); let mut tree_acc: ProjectMod = Module::wrap([]); let mut glob_acc: GlobImports = Module::wrap([]); @@ -111,21 +104,16 @@ pub fn load_solution( } known_files.insert(filename.to_vec()); let path = VPath(filename.to_vec()); - let loaded = fs - .read(PathSlice(filename)) - .map_err(|e| bundle_location(&referrer, &*e))?; + let loaded = fs.read(PathSlice(filename)).map_err(|e| bundle_location(&referrer, &*e))?; let code = match loaded { - Loaded::Collection(_) => - return Err(UnexpectedDirectory { path }.pack()), + Loaded::Collection(_) => return Err(UnexpectedDirectory { path }.pack()), Loaded::Code(source) => SourceCode { source, path: Arc::new(path) }, }; - let full_range = - SourceRange { range: 0..code.source.len(), code: code.clone() }; + let full_range = SourceRange { range: 0..code.source.len(), code: code.clone() }; let lines = parse_file(&ctx.parsing(code.clone()))?; let report = process_ns(code.path, lines, full_range)?; target_queue.extend( - (report.external_references.into_iter()) - .map(|(k, v)| (k, CodeLocation::Source(v))), + (report.external_references.into_iter()).map(|(k, v)| (k, CodeLocation::Source(v))), ); if !report.comments.is_empty() && filename.is_empty() { todo!("panic - module op comments on root are lost") @@ -137,23 +125,22 @@ pub fn load_solution( // i over valid indices of filename let key = filename[i].clone(); // last segment let comments = comments.take().into_iter().flatten().collect(); - glob = - Module::wrap([(key.clone(), ModEntry::wrap(ModMember::Sub(glob)))]); + glob = Module::wrap([(key.clone(), ModEntry::wrap(ModMember::Sub(glob)))]); module = Module::wrap([(key, ModEntry { member: ModMember::Sub(module), x: ProjXEnt { comments, ..Default::default() }, })]); } - glob_acc = (glob_acc.combine(glob)) - .expect("source code loaded for two nested paths"); - tree_acc = (tree_acc.combine(module)) - .expect("source code loaded for two nested paths"); + glob_acc = (glob_acc.combine(glob)).expect("source code loaded for two nested paths"); + tree_acc = (tree_acc.combine(module)).expect("source code loaded for two nested paths"); } else { known_files.insert(target[..].to_vec()); // If the path is not within a file, load it as directory match fs.read(target.as_path_slice()) { - Ok(Loaded::Collection(c)) => target_queue - .extend(c.iter().map(|e| (Sym::parse(e).unwrap(), referrer.clone()))), + Ok(Loaded::Collection(c)) => target_queue.extend(c.iter().map(|e| { + let name = VPath::new(target.iter()).as_prefix_of(e.clone()).to_sym(); + (name, referrer.clone()) + })), Ok(Loaded::Code(_)) => unreachable!("Should have split to self and []"), // Ignore error if the path is walkable in the const tree Err(_) if env.walk1_ref(&[], &target[..], |_| true).is_ok() => (), @@ -172,12 +159,10 @@ pub fn load_solution( )?; let ret = resolve_aliases(tree_acc, env)?; for ((glob, original), locations) in contention { - let (glob_val, _) = ret - .walk1_ref(&[], &glob[..], |_| true) - .expect("Should've emerged in dealias"); - let (original_val, _) = ret - .walk1_ref(&[], &original[..], |_| true) - .expect("Should've emerged in dealias"); + let (glob_val, _) = + ret.walk1_ref(&[], &glob[..], |_| true).expect("Should've emerged in dealias"); + let (original_val, _) = + ret.walk1_ref(&[], &original[..], |_| true).expect("Should've emerged in dealias"); let glob_real = match &glob_val.member { ModMember::Item(ProjItem { kind: ItemKind::Alias(glob_tgt) }) => glob_tgt, _ => &glob, @@ -208,9 +193,7 @@ struct UnexpectedDirectory { impl ProjectError for UnexpectedDirectory { const DESCRIPTION: &'static str = "A stage that deals specifically with code \ encountered a path that refers to a directory"; - fn message(&self) -> String { - format!("{} was expected to be a file", self.path) - } + fn message(&self) -> String { format!("{} was expected to be a file", self.path) } fn positions(&self) -> impl IntoIterator { [] } } @@ -223,8 +206,7 @@ struct ConflictingGlobs { locations: Vec, } impl ProjectError for ConflictingGlobs { - const DESCRIPTION: &'static str = - "A symbol from a glob import conflicts with an existing name"; + const DESCRIPTION: &'static str = "A symbol from a glob import conflicts with an existing name"; fn message(&self) -> String { let Self { glob, glob_real, original, real, .. } = self; format!( @@ -233,7 +215,6 @@ impl ProjectError for ConflictingGlobs { ) } fn positions(&self) -> impl IntoIterator { - (self.locations.iter()) - .map(|l| ErrorPosition { location: l.clone(), message: None }) + (self.locations.iter()).map(|l| ErrorPosition { location: l.clone(), message: None }) } } diff --git a/src/pipeline/process_source.rs b/src/pipeline/process_source.rs index 0fa144c..9af4d54 100644 --- a/src/pipeline/process_source.rs +++ b/src/pipeline/process_source.rs @@ -9,12 +9,9 @@ use never::Never; use super::load_solution::Prelude; use super::path::absolute_path; use super::project::{ - ItemKind, ProjItem, ProjRule, ProjXEnt, ProjXMod, ProjectEntry, ProjectMod, - SourceModule, -}; -use crate::error::{ - ErrorPosition, ProjectError, ProjectErrorObj, ProjectResult, + ItemKind, ProjItem, ProjRule, ProjXEnt, ProjXMod, ProjectEntry, ProjectMod, SourceModule, }; +use crate::error::{ErrorPosition, ProjectError, ProjectErrorObj, ProjectResult}; use crate::location::{CodeLocation, SourceRange}; use crate::name::{Sym, VName, VPath}; use crate::parse::parsed::{ @@ -112,8 +109,7 @@ pub(super) fn process_ns( external_references.insert(abs.to_sym(), import.range.clone()); } match import.name { - None => (glob_imports.x.0) - .push(GlobImpReport { target: abs, location: import.range }), + None => (glob_imports.x.0).push(GlobImpReport { target: abs, location: import.range }), Some(key) => { let entry = get_or_make(&mut entries, &key, default_entry); entry.x.locations.push(CodeLocation::Source(import.range)); @@ -151,8 +147,7 @@ pub(super) fn process_ns( entry.x.comments.append(&mut new_comments); }, MemberKind::Rule(Rule { pattern, prio, template }) => { - let prule = - ProjRule { pattern, prio, template, comments: new_comments }; + let prule = ProjRule { pattern, prio, template, comments: new_comments }; new_comments = Vec::new(); for name in prule.collect_root_names() { let entry = get_or_make(&mut entries, &name, default_entry); @@ -171,10 +166,7 @@ pub(super) fn process_ns( MemberKind::Module(ModuleBlock { name, body }) => { let entry = get_or_make(&mut entries, &name, default_entry); entry.x.locations.push(CodeLocation::Source(range.clone())); - if !matches!( - entry.member, - ModMember::Item(ProjItem { kind: ItemKind::None }) - ) { + if !matches!(entry.member, ModMember::Item(ProjItem { kind: ItemKind::None })) { let err = MultipleDefinitions { path: (*path).clone().as_prefix_of(name).to_sym(), locations: entry.x.locations.clone(), @@ -196,14 +188,12 @@ pub(super) fn process_ns( entry.member = ModMember::Sub(report.module); // record new external references external_references.extend( - (report.external_references.into_iter()) - .filter(|(r, _)| !r[..].starts_with(&path.0)), + (report.external_references.into_iter()).filter(|(r, _)| !r[..].starts_with(&path.0)), ); // add glob_imports subtree to own tree - glob_imports.entries.insert(name, ModEntry { - x: (), - member: ModMember::Sub(report.glob_imports), - }); + glob_imports + .entries + .insert(name, ModEntry { x: (), member: ModMember::Sub(report.glob_imports) }); }, }, } @@ -219,15 +209,10 @@ pub(super) fn process_ns( }) } -fn walk_at_path( - e: WalkError, - root: &ProjectMod, - path: &[Tok], -) -> ProjectErrorObj { - let submod = (root.walk_ref(&[], path, |_| true)) - .expect("Invalid source path in walk error populator"); - let src = - submod.x.src.as_ref().expect("Import cannot appear in implied module"); +fn walk_at_path(e: WalkError, root: &ProjectMod, path: &[Tok]) -> ProjectErrorObj { + let submod = + (root.walk_ref(&[], path, |_| true)).expect("Invalid source path in walk error populator"); + let src = submod.x.src.as_ref().expect("Import cannot appear in implied module"); e.at(&CodeLocation::Source(src.range.clone())) } @@ -244,15 +229,12 @@ pub fn resolve_globs( contention: &mut HashMap<(Sym, Sym), Vec>, ) -> ProjectResult<()> { // All glob imports in this module - let all = (globtree.x.0.into_iter()) - .map(|gir| (gir.target, CodeLocation::Source(gir.location))) - .chain( + let all = + (globtree.x.0.into_iter()).map(|gir| (gir.target, CodeLocation::Source(gir.location))).chain( preludes .iter() .filter(|&pre| !globtree_prefix.0.starts_with(&pre.exclude[..])) - .map(|Prelude { target, owner, .. }| { - (target.clone(), CodeLocation::Gen(owner.clone())) - }), + .map(|Prelude { target, owner, .. }| (target.clone(), CodeLocation::Gen(owner.clone()))), ); for (target, location) in all { let (tgt, parent) = project_root @@ -272,13 +254,12 @@ pub fn resolve_globs( .chain(module.keys(|e| e.x.exported)) .collect_vec(); // Reference to the module to be modified - let mut_mod = - globtree_prefix.0.iter().fold(&mut *project_root, |m, k| { - let entry = m.entries.get_mut(k).expect("this is a source path"); - unwrap_or!(&mut entry.member => ModMember::Sub; { - panic!("This is also a source path") - }) - }); + let mut_mod = globtree_prefix.0.iter().fold(&mut *project_root, |m, k| { + let entry = m.entries.get_mut(k).expect("this is a source path"); + unwrap_or!(&mut entry.member => ModMember::Sub; { + panic!("This is also a source path") + }) + }); // Walk errors for the environment are suppressed because leaf-node // conflicts will emerge when merging modules, and walking off the tree // is valid. @@ -286,23 +267,12 @@ pub fn resolve_globs( let entry = get_or_make(&mut mut_mod.entries, &key, default_entry); entry.x.locations.push(location.clone()); let alias_tgt = target.clone().suffix([key.clone()]).to_sym(); - match &mut entry.member { - ModMember::Item(ProjItem { kind: kref @ ItemKind::None }) => - *kref = ItemKind::Alias(alias_tgt), - ModMember::Item(ProjItem { kind: ItemKind::Alias(prev_alias) }) => - if prev_alias != &alias_tgt { - let local_name = - globtree_prefix.clone().as_prefix_of(key.clone()).to_sym(); - let locs = pushed_ref(&entry.x.locations, location.clone()); - contention.insert((alias_tgt, local_name), locs); - }, - _ => { - let err = MultipleDefinitions { - locations: entry.x.locations.clone(), - path: globtree_prefix.as_prefix_of(key).to_sym(), - }; - return Err(err.pack()); - }, + if let ModMember::Item(ProjItem { kind: kref @ ItemKind::None }) = &mut entry.member { + *kref = ItemKind::Alias(alias_tgt) + } else { + let local_name = globtree_prefix.clone().as_prefix_of(key.clone()).to_sym(); + let locs = pushed_ref(&entry.x.locations, location.clone()); + contention.insert((alias_tgt, local_name), locs); } } }, @@ -313,14 +283,7 @@ pub fn resolve_globs( ModMember::Item(n) => match n {}, ModMember::Sub(module) => { let subpath = VPath(pushed_ref(&globtree_prefix.0, key)); - resolve_globs( - subpath, - module, - preludes.clone(), - project_root, - env, - contention, - )?; + resolve_globs(subpath, module, preludes.clone(), project_root, env, contention)?; }, } } @@ -333,12 +296,9 @@ struct MultipleExports { } impl ProjectError for MultipleExports { const DESCRIPTION: &'static str = "A symbol was exported in multiple places"; - fn message(&self) -> String { - format!("{} exported multiple times", self.path) - } + fn message(&self) -> String { format!("{} exported multiple times", self.path) } fn positions(&self) -> impl IntoIterator { - (self.locations.iter()) - .map(|l| ErrorPosition { location: l.clone(), message: None }) + (self.locations.iter()).map(|l| ErrorPosition { location: l.clone(), message: None }) } } @@ -348,11 +308,8 @@ pub(super) struct MultipleDefinitions { } impl ProjectError for MultipleDefinitions { const DESCRIPTION: &'static str = "Symbol defined twice"; - fn message(&self) -> String { - format!("{} refers to multiple conflicting items", self.path) - } + fn message(&self) -> String { format!("{} refers to multiple conflicting items", self.path) } fn positions(&self) -> impl IntoIterator { - (self.locations.iter()) - .map(|l| ErrorPosition { location: l.clone(), message: None }) + (self.locations.iter()).map(|l| ErrorPosition { location: l.clone(), message: None }) } } diff --git a/src/pipeline/project.rs b/src/pipeline/project.rs index fa55658..74fbd8f 100644 --- a/src/pipeline/project.rs +++ b/src/pipeline/project.rs @@ -198,6 +198,7 @@ impl Display for ProjXMod { pub type ProjectEntry = ModEntry; /// A node in the tree describing the project pub type ProjectMod = Module; +/// A reference to an item or module in the project pub type ProjectMemberRef<'a> = ModMemberRef<'a, ProjItem, ProjXMod, ProjXEnt>; fn collect_rules_rec(bag: &mut Vec, module: &ProjectMod) { @@ -267,5 +268,6 @@ pub struct ConstReport { pub comments: Vec>, /// Value assigned to the constant pub value: Expr, + /// Source location this constant was parsed from pub range: SourceRange, } diff --git a/src/rule/matcher_vectree/mod.rs b/src/rule/matcher_vectree/mod.rs index d6f9442..8b054ea 100644 --- a/src/rule/matcher_vectree/mod.rs +++ b/src/rule/matcher_vectree/mod.rs @@ -1,14 +1,18 @@ -// Construction: -// convert pattern into hierarchy of plain, scan, middle -// - plain: accept any sequence or any non-empty sequence -// - scan: a single scalar pattern moves LTR or RTL, submatchers on either -// side -// - middle: two scalar patterns walk over all permutations of matches -// while getting progressively closer to each other -// -// Application: -// walk over the current matcher's valid options and poll the submatchers -// for each of them +//! Optimized form of macro pattern that can be quickly tested against the AST. +//! +//! # Construction +//! +//! convert pattern into hierarchy of plain, scan, middle +//! - plain: accept any sequence or any non-empty sequence +//! - scan: a single scalar pattern moves LTR or RTL, submatchers on either +//! side +//! - middle: two scalar patterns walk over all permutations of matches +//! while getting progressively closer to each other +//! +//! # Application +//! +//! walk over the current matcher's valid options and poll the submatchers +//! for each of them mod any_match; mod build; diff --git a/src/rule/matcher_vectree/shared.rs b/src/rule/matcher_vectree/shared.rs index 6ac6b97..ef84824 100644 --- a/src/rule/matcher_vectree/shared.rs +++ b/src/rule/matcher_vectree/shared.rs @@ -1,3 +1,5 @@ +//! Datastructures for cached pattern + use std::fmt::{Display, Write}; use std::rc::Rc; diff --git a/src/rule/rule_error.rs b/src/rule/rule_error.rs index 4322f27..f22a3a0 100644 --- a/src/rule/rule_error.rs +++ b/src/rule/rule_error.rs @@ -7,7 +7,6 @@ use crate::error::{ErrorPosition, ProjectError, ProjectErrorObj}; use crate::location::{CodeLocation, SourceRange}; use crate::parse::parsed::{search_all_slcs, Clause, PHClass, Placeholder}; use crate::pipeline::project::ProjRule; -use crate::utils::boxed_iter::BoxedIter; /// Various reasons why a substitution rule may be invalid #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/src/utils/join.rs b/src/utils/join.rs index c86d1ec..a145414 100644 --- a/src/utils/join.rs +++ b/src/utils/join.rs @@ -1,3 +1,5 @@ +//! Join hashmaps with a callback for merging or failing on conflicting keys. + use std::hash::Hash; use hashbrown::HashMap; @@ -10,8 +12,7 @@ pub fn join_maps( right: HashMap, mut merge: impl FnMut(&K, V, V) -> V, ) -> HashMap { - try_join_maps(left, right, |k, l, r| Ok(merge(k, l, r))) - .unwrap_or_else(|e: Never| match e {}) + try_join_maps(left, right, |k, l, r| Ok(merge(k, l, r))).unwrap_or_else(|e: Never| match e {}) } /// Combine two hashmaps via a fallible value merger. See also [join_maps] @@ -23,11 +24,11 @@ pub fn try_join_maps( let mut mixed = HashMap::with_capacity(left.len() + right.len()); for (key, lval) in left { let val = match right.remove(&key) { - None => lval, + None => lval, Some(rval) => merge(&key, lval, rval)?, }; mixed.insert(key, val); } - mixed.extend(right.into_iter()); + mixed.extend(right); Ok(mixed) } diff --git a/src/virt_fs/common.rs b/src/virt_fs/common.rs index 68a207a..0256b5a 100644 --- a/src/virt_fs/common.rs +++ b/src/virt_fs/common.rs @@ -1,7 +1,8 @@ use std::rc::Rc; use std::sync::Arc; -use intern_all::Tok; +use intern_all::{i, Tok}; +use itertools::Itertools; use crate::error::{ErrorSansLocation, ErrorSansLocationObj}; use crate::name::{PathSlice, VPath}; @@ -31,6 +32,11 @@ pub type FSResult = Result; /// Distinguished error for missing code #[derive(Clone, PartialEq, Eq)] pub struct CodeNotFound(pub VPath); +impl CodeNotFound { + pub fn new(path: VPath) -> Self { + Self(path) + } +} impl ErrorSansLocation for CodeNotFound { const DESCRIPTION: &'static str = "No source code for path"; fn message(&self) -> String { format!("{} not found", self.0) } @@ -60,7 +66,5 @@ impl VirtFS for &dyn VirtFS { fn get(&self, path: &[Tok], full_path: PathSlice) -> FSResult { (*self).get(path, full_path) } - fn display(&self, path: &[Tok]) -> Option { - (*self).display(path) - } + fn display(&self, path: &[Tok]) -> Option { (*self).display(path) } } diff --git a/src/virt_fs/decl.rs b/src/virt_fs/decl.rs index 8d16bb8..fd3f5ac 100644 --- a/src/virt_fs/decl.rs +++ b/src/virt_fs/decl.rs @@ -30,7 +30,7 @@ impl VirtFS for DeclTree { ModMember::Sub(module) => match path.split_first() { None => Ok(Loaded::collection(module.keys(|_| true))), Some((head, tail)) => (module.entries.get(head)) - .ok_or_else(|| CodeNotFound(full_path.to_vpath()).pack()) + .ok_or_else(|| CodeNotFound::new(full_path.to_vpath()).pack()) .and_then(|ent| ent.get(tail, full_path)), }, } diff --git a/src/virt_fs/dir.rs b/src/virt_fs/dir.rs index cc27531..d5b67f1 100644 --- a/src/virt_fs/dir.rs +++ b/src/virt_fs/dir.rs @@ -89,7 +89,7 @@ impl DirNode { let mut file = File::open(&fpath).map_err(|file_e| { match (dir_e.kind(), file_e.kind()) { (ErrorKind::NotFound, ErrorKind::NotFound) => - CodeNotFound(orig_path.to_vpath()).pack(), + CodeNotFound::new(orig_path.to_vpath()).pack(), _ => OpenError::wrap(file_e, dir_e), } })?; diff --git a/src/virt_fs/embed.rs b/src/virt_fs/embed.rs index c8c2a9b..270ef9d 100644 --- a/src/virt_fs/embed.rs +++ b/src/virt_fs/embed.rs @@ -61,7 +61,7 @@ impl VirtFS for EmbeddedFS { return Ok(Loaded::collection(self.tree.keys(|_| true))); } let entry = (self.tree.walk1_ref(&[], path, |_| true)) - .map_err(|_| CodeNotFound(full_path.to_vpath()).pack())?; + .map_err(|_| CodeNotFound::new(full_path.to_vpath()).pack())?; Ok(match &entry.0.member { ModMember::Item(text) => Loaded::Code(text.clone()), ModMember::Sub(sub) => Loaded::collection(sub.keys(|_| true)), diff --git a/src/virt_fs/prefix.rs b/src/virt_fs/prefix.rs index 79db969..843e3de 100644 --- a/src/virt_fs/prefix.rs +++ b/src/virt_fs/prefix.rs @@ -33,7 +33,7 @@ impl PrefixFS { impl VirtFS for PrefixFS { fn get(&self, path: &[Tok], full_path: PathSlice) -> super::FSResult { let path = (self.proc_path(path)) - .ok_or_else(|| CodeNotFound(full_path.to_vpath()).pack())?; + .ok_or_else(|| CodeNotFound::new(full_path.to_vpath()).pack())?; self.wrapped.get(&path, full_path) } fn display(&self, path: &[Tok]) -> Option {