From b9d47c3181cd41cf95a33c49725ef68fe6bebbb0 Mon Sep 17 00:00:00 2001 From: Lawrence Bethlenfalvy Date: Sun, 5 Mar 2023 19:55:38 +0000 Subject: [PATCH] Dead end with macros --- Cargo.lock | 54 +++ Cargo.toml | 3 + examples/lite/main.orc | 15 + src/external/assertion_error.rs | 30 ++ src/external/mod.rs | 4 + src/external/numbers/mod.rs | 38 ++ src/external/numbers/numeric.rs | 102 ++++++ src/foreign.rs | 91 ++--- src/foreign_macros.rs | 333 ++++++++++++++++++ src/main.rs | 59 ++-- src/parse/expression.rs | 5 +- src/representations/ast.rs | 21 +- src/representations/ast_to_postmacro.rs | 173 +++++++++ src/representations/ast_to_typed.rs | 171 --------- src/representations/interpreted.rs | 191 ++++++++++ src/representations/mod.rs | 10 +- src/representations/path_set.rs | 96 +++++ src/representations/postmacro.rs | 120 +++++++ .../postmacro_to_interpreted.rs | 74 ++++ src/representations/primitive.rs | 49 +++ src/representations/typed.rs | 67 ++-- src/rule/executor/execute.rs | 31 +- src/rule/executor/mod.rs | 1 + src/rule/executor/slice_matcher.rs | 2 +- src/rule/executor/update_first_seq_rec.rs | 53 +++ src/rule/repository.rs | 16 +- src/utils/for_loop.rs | 6 +- src/utils/mod.rs | 3 + src/utils/replace_first.rs | 14 + src/utils/visitor.rs | 18 + 30 files changed, 1518 insertions(+), 332 deletions(-) create mode 100644 examples/lite/main.orc create mode 100644 src/external/assertion_error.rs create mode 100644 src/external/mod.rs create mode 100644 src/external/numbers/mod.rs create mode 100644 src/external/numbers/numeric.rs create mode 100644 src/foreign_macros.rs create mode 100644 src/representations/ast_to_postmacro.rs delete mode 100644 src/representations/ast_to_typed.rs create mode 100644 src/representations/interpreted.rs create mode 100644 src/representations/path_set.rs create mode 100644 src/representations/postmacro.rs create mode 100644 src/representations/postmacro_to_interpreted.rs create mode 100644 src/representations/primitive.rs create mode 100644 src/rule/executor/update_first_seq_rec.rs create mode 100644 src/utils/replace_first.rs create mode 100644 src/utils/visitor.rs diff --git a/Cargo.lock b/Cargo.lock index 901ed74..6488968 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,18 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -82,12 +94,24 @@ dependencies = [ "syn", ] +[[package]] +name = "dyn-clone" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" + [[package]] name = "either" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "getrandom" version = "0.2.6" @@ -160,14 +184,17 @@ checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" name = "orchid" version = "0.1.0" dependencies = [ + "bitvec", "chumsky", "derivative", + "dyn-clone", "hashbrown", "implicit-clone", "itertools", "lazy_static", "mappable-rc", "ordered-float", + "paste", "smallvec", "thiserror", ] @@ -181,6 +208,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "paste" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" + [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -205,6 +238,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "smallvec" version = "1.10.0" @@ -222,6 +261,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "thiserror" version = "1.0.31" @@ -268,3 +313,12 @@ name = "wasi" version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] diff --git a/Cargo.toml b/Cargo.toml index 227c26d..b151ecd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,6 @@ itertools = "0.10" smallvec = { version = "1.10.0", features = ['const_generics'] } lazy_static = "1.4.0" implicit-clone = "0.3.5" +bitvec = "1.0.1" +dyn-clone = "1.0.11" +paste = "1.0.11" diff --git a/examples/lite/main.orc b/examples/lite/main.orc new file mode 100644 index 0000000..9511ac8 --- /dev/null +++ b/examples/lite/main.orc @@ -0,0 +1,15 @@ +TRUE := \t.\f.t +FALSE := \t.\f.f +NOT := \x.x FALSE TRUE +AND := \x.\y.x y FALSE +OR := \x.\y. x TRUE y +Y := \f.(\x.f (x x))(\x.f (x x)) + +(! ...$expr) =10=> (NOT ...$expr) +(...$lhs & ...$rhs) =10=> (AND (...$lhs) (...$rhs)) +(...$lhs | ...$rhs) =20=> (OR (...$lhs) (...$rhs)) + +main := (TRUE & TRUE | FALSE & FALSE) + +(start_token ...$rest) ==> (carriage(()) ...$rest) +(..$prefix carriage($state) $next ..$rest) ==> (..$prefix $out carriage(??) ..$rest) diff --git a/src/external/assertion_error.rs b/src/external/assertion_error.rs new file mode 100644 index 0000000..231f2fe --- /dev/null +++ b/src/external/assertion_error.rs @@ -0,0 +1,30 @@ +use std::rc::Rc; +use std::fmt::Display; + +use crate::foreign::ExternError; +use crate::representations::interpreted::Clause; + + +#[derive(Clone)] +pub struct AssertionError{ + pub value: Clause, + pub assertion: &'static str, +} + +impl AssertionError { + pub fn fail(value: Clause, assertion: &'static str) -> Result> { + return Err(Self { value, assertion }.into_extern()) + } + + pub fn into_extern(self) -> Rc { + Rc::new(self) + } +} + +impl Display for AssertionError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Error: {:?} is not {}", self.value, self.assertion) + } +} + +impl ExternError for AssertionError{} \ No newline at end of file diff --git a/src/external/mod.rs b/src/external/mod.rs new file mode 100644 index 0000000..c39d4bd --- /dev/null +++ b/src/external/mod.rs @@ -0,0 +1,4 @@ +mod numbers; +mod assertion_error; + +use numbers::Multiply2; diff --git a/src/external/numbers/mod.rs b/src/external/numbers/mod.rs new file mode 100644 index 0000000..cfe6b6c --- /dev/null +++ b/src/external/numbers/mod.rs @@ -0,0 +1,38 @@ +mod numeric; +use numeric::Numeric; + +use std::fmt::Debug; +use std::rc::Rc; +use std::hash::Hash; + +use crate::{atomic_impl, atomic_redirect, externfn_impl, xfn_initial, xfn_middle, xfn_last, xfn}; +use crate::foreign::{ExternError, ExternFn, Atom, Atomic}; +use crate::representations::Primitive; +use crate::representations::interpreted::{Clause, InternalError}; + +// xfn_initial!( +// /// Multiply function +// Multiply2, Multiply1 +// ); +// xfn_middle!( +// /// Partially applied multiply function +// Multiply2, Multiply1, Multiply0, ( +// a: Numeric: |c: &Clause| c.clone().try_into() +// ) +// ); +// xfn_last!( +// /// Fully applied Multiply function. +// Multiply1, Multiply0, ( +// b: Numeric: |c: &Clause| c.clone().try_into(), +// a: Numeric: |c: &Clause| c.clone().try_into() +// ), Ok((*a * b).into()) +// ); + +xfn!(( + /// Multiply function + a: Numeric: |c: &Clause| c.clone().try_into(), + /// Partially applied multiply function + b: Numeric: |c: &Clause| c.clone().try_into() +), { + Ok((*a * b).into()) +}); \ No newline at end of file diff --git a/src/external/numbers/numeric.rs b/src/external/numbers/numeric.rs new file mode 100644 index 0000000..6c763ab --- /dev/null +++ b/src/external/numbers/numeric.rs @@ -0,0 +1,102 @@ +use std::ops::{Add, Sub, Mul, Div, Rem}; +use std::rc::Rc; + +use ordered_float::NotNan; + +use crate::external::assertion_error::AssertionError; +use crate::foreign::ExternError; +use crate::representations::{Primitive, Literal}; +use crate::representations::interpreted::Clause; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Numeric { + Int(u64), + Num(NotNan) +} + +impl Add for Numeric { + type Output = Numeric; + + fn add(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (Numeric::Int(a), Numeric::Int(b)) => Numeric::Int(a + b), + (Numeric::Num(a), Numeric::Num(b)) => Numeric::Num(a + b), + (Numeric::Int(a), Numeric::Num(b)) | (Numeric::Num(b), Numeric::Int(a)) + => Numeric::Num(NotNan::new(a as f64).unwrap() + b) + } + } +} + +impl Sub for Numeric { + type Output = Numeric; + + fn sub(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (Numeric::Int(a), Numeric::Int(b)) if b < a => Numeric::Int(a - b), + (Numeric::Int(a), Numeric::Int(b)) + => Numeric::Num(NotNan::new(a as f64 - b as f64).unwrap()), + (Numeric::Num(a), Numeric::Num(b)) => Numeric::Num(a - b), + (Numeric::Int(a), Numeric::Num(b)) | (Numeric::Num(b), Numeric::Int(a)) + => Numeric::Num(NotNan::new(a as f64).unwrap() - b) + } + } +} + +impl Mul for Numeric { + type Output = Numeric; + + fn mul(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (Numeric::Int(a), Numeric::Int(b)) => Numeric::Int(a * b), + (Numeric::Num(a), Numeric::Num(b)) => Numeric::Num(a * b), + (Numeric::Int(a), Numeric::Num(b)) | (Numeric::Num(b), Numeric::Int(a)) + => Numeric::Num(NotNan::new(a as f64).unwrap() * b) + } + } +} + +impl Div for Numeric { + type Output = Numeric; + + fn div(self, rhs: Self) -> Self::Output { + let a = match self { Numeric::Int(i) => i as f64, Numeric::Num(f) => *f }; + let b = match rhs { Numeric::Int(i) => i as f64, Numeric::Num(f) => *f }; + Numeric::Num(NotNan::new(a / b).unwrap()) + } +} + +impl Rem for Numeric { + type Output = Numeric; + + fn rem(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (Numeric::Int(a), Numeric::Int(b)) => Numeric::Int(a % b), + (Numeric::Num(a), Numeric::Num(b)) => Numeric::Num(a % b), + (Numeric::Int(a), Numeric::Num(b)) | (Numeric::Num(b), Numeric::Int(a)) + => Numeric::Num(NotNan::new(a as f64).unwrap() % b) + } + } +} + +impl TryFrom for Numeric { + type Error = Rc; + fn try_from(value: Clause) -> Result { + let l = if let Clause::P(Primitive::Literal(l)) = value.clone() {l} else { + AssertionError::fail(value, "a literal value")? + }; + match l { + Literal::Int(i) => Ok(Numeric::Int(i)), + Literal::Num(n) => Ok(Numeric::Num(n)), + _ => AssertionError::fail(value, "an integer or number")? + } + } +} + +impl From for Clause { + fn from(value: Numeric) -> Self { + Clause::P(Primitive::Literal(match value { + Numeric::Int(i) => Literal::Int(i), + Numeric::Num(n) => Literal::Num(n) + })) + } +} \ No newline at end of file diff --git a/src/foreign.rs b/src/foreign.rs index fc84e46..c794c3e 100644 --- a/src/foreign.rs +++ b/src/foreign.rs @@ -1,75 +1,59 @@ use std::any::Any; use std::fmt::{Display, Debug}; use std::hash::Hash; +use std::rc::Rc; -use mappable_rc::Mrc; +use dyn_clone::DynClone; -use crate::representations::typed::{Expr, Clause}; +use crate::representations::interpreted::{Clause, RuntimeError, InternalError}; pub trait ExternError: Display {} /// Represents an externally defined function from the perspective of the executor /// Since Orchid lacks basic numerical operations, these are also external functions. -pub struct ExternFn { - name: String, param: Mrc, rttype: Mrc, - function: Mrc Result>> +pub trait ExternFn: DynClone { + fn name(&self) -> &str; + fn apply(&self, arg: Clause) -> Result>; + fn argstr(&self) -> &str { "clause" } + fn retstr(&self) -> &str { "clause" } + fn hash(&self, state: &mut dyn std::hash::Hasher) { state.write_str(self.name()) } } -impl ExternFn { - pub fn new Result>>( - name: String, param: Mrc, rttype: Mrc, f: F - ) -> Self { - Self { - name, param, rttype, - function: Mrc::map(Mrc::new(f), |f| { - f as &dyn Fn(Clause) -> Result> - }) - } - } - pub fn name(&self) -> &str {&self.name} - pub fn apply(&self, arg: Clause) -> Result> {(self.function)(arg)} -} - -impl Clone for ExternFn { fn clone(&self) -> Self { Self { - name: self.name.clone(), - param: Mrc::clone(&self.param), - rttype: Mrc::clone(&self.rttype), - function: Mrc::clone(&self.function) -}}} -impl Eq for ExternFn {} -impl PartialEq for ExternFn { +impl Eq for dyn ExternFn {} +impl PartialEq for dyn ExternFn { fn eq(&self, other: &Self) -> bool { self.name() == other.name() } } -impl Hash for ExternFn { - fn hash(&self, state: &mut H) { self.name.hash(state) } +impl Hash for dyn ExternFn { + fn hash(&self, state: &mut H) { self.name().hash(state) } } -impl Debug for ExternFn { +impl Debug for dyn ExternFn { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "##EXTERN[{}]:{:?} -> {:?}##", self.name(), self.param, self.rttype) + write!(f, "##EXTERN[{}]:{:?} -> {:?}##", self.name(), self.argstr(), self.retstr()) } } -pub trait Atomic: Any + Debug where Self: 'static { +pub trait Atomic: Any + Debug + DynClone where Self: 'static { fn as_any(&self) -> &dyn Any; fn definitely_eq(&self, _other: &dyn Any) -> bool; fn hash(&self, hasher: &mut dyn std::hash::Hasher); + fn run_once(&self) -> Result; + fn run_n_times(&self, n: usize) -> Result<(Clause, usize), RuntimeError>; + fn run_to_completion(&self) -> Result; + fn typestr(&self) -> &str { "clause" } } -/// Represents a unit of information from the perspective of the executor. This may be -/// something like a file descriptor which functions can operate on, but it can also be -/// information in the universe of types or kinds such as the type of signed integers or -/// the kind of types. Ad absurdum it can also be just a number, although Literal is -/// preferable for types it's defined on. -pub struct Atom { - typ: Mrc, - data: Mrc -} +/// Represents a black box unit of code with its own normalization steps. Typically [ExternFn] +/// will produce an [Atom] when applied to a [Clause], this [Atom] will then forward `run_*` calls +/// to the argument until it yields [InternalError::NonReducible] at which point the [Atom] will +/// validate and process the argument, returning a different [Atom] intended for processing by +/// external code, a new [ExternFn] to capture an additional argument, or an Orchid expression +/// to pass control back to the interpreter. +pub struct Atom(pub Box); impl Atom { - pub fn new(data: T, typ: Mrc) -> Self { Self{ - typ, - data: Mrc::map(Mrc::new(data), |d| d as &dyn Atomic) - } } - pub fn data(&self) -> &dyn Atomic { self.data.as_ref() as &dyn Atomic } + pub fn new(data: T) -> Self { + Self(Box::new(data) as Box) + } + pub fn data(&self) -> &dyn Atomic { self.0.as_ref() as &dyn Atomic } pub fn try_cast(&self) -> Result<&T, ()> { self.data().as_any().downcast_ref().ok_or(()) } @@ -80,20 +64,19 @@ impl Atom { } impl Clone for Atom { - fn clone(&self) -> Self { Self { - typ: Mrc::clone(&self.typ), - data: Mrc::clone(&self.data) - } } + fn clone(&self) -> Self { + Self(dyn_clone::clone_box(self.data())) + } } + impl Hash for Atom { fn hash(&self, state: &mut H) { - self.data.hash(state); - self.typ.hash(state) + self.0.hash(state) } } impl Debug for Atom { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "##ATOM[{:?}]:{:?}##", self.data(), self.typ) + write!(f, "##ATOM[{:?}]:{:?}##", self.data(), self.data().typestr()) } } impl Eq for Atom {} diff --git a/src/foreign_macros.rs b/src/foreign_macros.rs new file mode 100644 index 0000000..963349a --- /dev/null +++ b/src/foreign_macros.rs @@ -0,0 +1,333 @@ + +#[allow(unused)] // for the doc comments +use crate::representations::Primitive; +#[allow(unused)] // for the doc comments +use crate::foreign::{Atomic, ExternFn}; +#[allow(unused)] // for the doc comments +use std::any::Any; +#[allow(unused)] // for the doc comments +use std::hash::Hash; +#[allow(unused)] // for the doc comments +use dyn_clone::DynClone; +#[allow(unused)] // for the doc comments +use std::fmt::Debug; + +/// A macro that generates the straightforward, syntactically invariant part of implementing +/// [Atomic]. Implemented fns are [Atomic::as_any], [Atomic::definitely_eq] and [Atomic::hash]. +/// +/// It depends on [Eq] and [Hash] +#[macro_export] +macro_rules! atomic_defaults { + () => { + fn as_any(&self) -> &dyn std::any::Any { self } + fn definitely_eq(&self, _other: &dyn std::any::Any) -> bool { + _other.downcast_ref().map(|o| self == o).unwrap_or(false) + } + fn hash(&self, mut hasher: &mut dyn std::hash::Hasher) { ::hash(self, &mut hasher) } + }; +} + +/// A macro that generates implementations of [Atomic] to simplify the development of +/// external bindings for Orchid. +/// +/// The macro depends on implementations of [AsRef] and [From<(&Self, Clause)>] for +/// extracting the clause to be processed and then reconstructing the [Atomic]. Naturally, +/// supertraits of [Atomic] are also dependencies. These are [Any], [Debug] and [DynClone]. +/// +/// The simplest form just requires the typename to be specified. This additionally depends on an +/// implementation of [ExternFn] because after the clause is fully normalized it returns `Self` +/// wrapped in a [Primitive::ExternFn]. It is intended for intermediary +/// stages of the function where validation and the next state are defined in [ExternFn::apply]. +/// +/// ``` +/// atomic_impl!(Multiply1) +/// ``` +/// +/// The last stage of the function should use the extended form of the macro which takes an +/// additional closure to explicitly describe what happens when the argument is fully processed. +/// +/// ``` +/// // excerpt from the exact implementation of Multiply +/// atomic_impl!(Multiply0, |Self(a, cls): &Self| { +/// let b: Numeric = cls.clone().try_into().map_err(AssertionError::into_extern)?; +/// Ok(*a * b).into()) +/// }) +/// ``` +/// +#[macro_export] +macro_rules! atomic_impl { + ($typ:ident) => { + atomic_impl!{$typ, |this: &Self| Ok(Clause::P(Primitive::ExternFn(Box::new(this.clone()))))} + }; + ($typ:ident, $next_phase:expr) => { + impl Atomic for $typ { + $crate::atomic_defaults!{} + fn run_once(&self) -> Result { + match >::as_ref(self).run_once() { + Err(InternalError::NonReducible) => { + ($next_phase)(self) + .map_err($crate::representations::interpreted::RuntimeError::Extern) + .map_err(InternalError::Runtime) + } + Ok(arg) => Ok(Clause::P(Primitive::Atom(Atom::new( + >::from((self, arg)) + )))), + Err(e) => Err(e), + } + } + fn run_n_times(&self, n: usize) -> Result<(Clause, usize), $crate::representations::interpreted::RuntimeError> { + match >::as_ref(self).run_n_times(n) { + Ok((arg, k)) if k == n => Ok((Clause::P(Primitive::Atom(Atom::new( + >::from((self, arg))))), k)), + Ok((arg, k)) => { + let intermediate = >::from((self, arg)); + ($next_phase)(&intermediate) + .map(|cls| (cls, k)) + .map_err($crate::representations::interpreted::RuntimeError::Extern) + } + Err(e) => Err(e), + } + } + fn run_to_completion(&self) -> Result { + match >::as_ref(self).run_to_completion() { + Ok(arg) => { + let intermediate = >::from((self, arg)); + ($next_phase)(&intermediate) + .map_err($crate::representations::interpreted::RuntimeError::Extern) + }, + Err(e) => Err(e) + } + } + } + }; +} + +/// Implement the traits required by [atomic_impl] to redirect run_* functions to a field +/// with a particular name. +#[macro_export] +macro_rules! atomic_redirect { + ($typ:ident) => { + impl AsRef for $typ { + fn as_ref(&self) -> &Clause { &self.0 } + } + impl From<(&Self, Clause)> for $typ { + fn from((old, clause): (&Self, Clause)) -> Self { + Self{ 0: clause, ..old.clone() } + } + } + }; + ($typ:ident, $field:ident) => { + impl AsRef for $typ { + fn as_ref(&self) -> &Clause { &self.$field } + } + impl From<(&Self, Clause)> for $typ { + fn from((old, $field): (&Self, Clause)) -> Self { + Self{ $field, ..old.clone() } + } + } + }; +} + +/// Implement [ExternFn] with a closure that produces an [Atomic] from a reference to self +/// and a closure. This can be used in conjunction with [atomic_impl] and [atomic_redirect] +/// to normalize the argument automatically before using it. +#[macro_export] +macro_rules! externfn_impl { + ($typ:ident, $next_atomic:expr) => { + impl ExternFn for $typ { + fn name(&self) -> &str {stringify!($typ)} + fn apply(&self, c: Clause) -> Result> { + match ($next_atomic)(self, c) { // ? casts the result but we want to strictly forward it + Ok(r) => Ok(Clause::P(Primitive::Atom(Atom::new(r)))), + Err(e) => Err(e) + } + } + } + }; +} + +/// Define a new struct and implement [ExternFn] for it so that it combines with an argument into +/// the struct identified by `$next` with a single field called `clause`. +/// Also generates doc comment for the new struct +#[macro_export] +macro_rules! xfn_initial { + (#[$prefix:meta] $name:ident, $next:ident) => { + paste::paste!{ + #[$prefix] + #[doc = "\n\nNext state: [" [<$next:camel>] "]"] + #[derive(Clone)] + pub struct [<$name:camel>]; + externfn_impl!([<$name:camel>], |_:&Self, clause: Clause| Ok([<$next:camel>]{ clause })); + } + }; +} + +/// Define a struct with a field `clause: Clause` that forwards atomic reductions to that field and +/// then converts itself to an [ExternFn] which combines with an argument into the struct identified +/// by `$next` with all fields copied over, `$nname` converted via `$ntransform` and the +/// argument assigned to a field called `clause`. +#[macro_export] +macro_rules! xfn_middle { + (#[$prefix:meta] $prev:ident, $name:ident, $next:ident, ( + $nname:ident : $ntype:ty + $(, $fname:ident : $ftype:ty)* + ), $transform:expr) => { + paste::paste!{ + #[$prefix] + #[doc = "\n\nPrev state: [" [<$prev:camel>] "], Next state: [" [<$next:camel>] "]"] + #[derive(Debug, Clone, PartialEq, Hash)] + pub struct [<$name:camel>] { + clause: Clause, + $($fname: $ftype),* + } + atomic_redirect!([<$name:camel>], clause); + atomic_impl!([<$name:camel>]); + externfn_impl!([<$name:camel>], |this: &Self, clause: Clause| { + let $nname: $ntype = match ($transform)(&this.clause) { + Ok(a) => a, + Err(e) => return Err(e) + }; + Ok([<$next:camel>]{ + clause, + $nname, + $($fname: this.$fname.clone()),* + }) + }); + } + }; +} + +#[macro_export] +macro_rules! xfn_last { + (#[$prefix:meta] $prev:ident, $name:ident, ( + $nname:ident : $ntype:ty + $(, $fname:ident : $ftype:ty)* + ), $transform:expr, $operation:expr) => { + paste!{ + #[$prefix] + #[doc = "\n\nPrev state: [" [<$prev:camel>] "]" ] + #[derive(Debug, Clone, PartialEq, Hash)] + pub struct [<$name:camel>] { + clause: Clause, + $($fname: $ftype),* + } + } + atomic_redirect!([<$name:camel>], clause); + atomic_impl!([<$name:camel>], |this: &Self| { + let $nname: $ntype = match ($ntransform)(&this.clause) { + Ok(a) => a, + Err(e) => return Err(e) + }; + $( + let $fname = &this.$fname; + )* + $operation + }); + }; +} + +#[macro_export] +macro_rules! reverse_proplist { + ( + #[$nprefix:meta] $nname:ident : $ntype:ty : $ntransform:expr + $(, #[$fprefix:meta] $fname:ident : $ftype:ty : $ftransform:expr)* + ) => { + reverse_proplist!($($fname : $ftype : $ftransform),*) + $nname : $ntype : $ntranform + }; +} + +#[macro_export] +macro_rules! xfn_make_head { + ( + #[$cprefix:meta] $nname:ident : $ntype:ty : $ntransform:expr + , #[$nprefix:meta] $cname:ident : $ctype:ty : $ctransform:expr + $(, #[$fprefix:meta] $fname:ident : $ftype:ty : $ftransform:expr)+ + ) => { + xfn_make_head!( + #[$nprefix] $cname : $ctype : $ctransform + $(, #[$fprefix] $fname : $ftype : $ftransform)+ + ) + }; // skip through all intermediate rows + ( + #[$cprefix:meta] $nname:ident : $ntype:ty : $ntransform:expr + , #[$nprefix:meta] $cname:ident : $ctype:ty : $ctransform:expr + ) => { + paste!{ + xfn_initial!(#[$cprefix] $cname, $nname) + } + } // process first two rows +} + +#[macro_export] +macro_rules! xfn_make_middle { + ( + #[$nprefix:meta] $nname:ident : $ntype:ty : $ntransform:expr + , #[$cprefix:meta] $cname:ident : $ctype:ty : $ctransform:expr + , #[$pprefix:meta] $pname:ident : $ptype:ty : $ptransform:expr + $(, #[$fprefix:meta] $fname:ident : $ftype:ty : $ftransform:expr)* + ) => { + // repeat on tail + xfn_make_middle!( + #[$cprefix:meta] $cname:ident : $ctype:ty : $ctransform:expr + , #[$pprefix:meta] $pname:ident : $ptype:ty : $ptransform:expr + $(, #[$fprefix:meta] $fname:ident : $ftype:ty : $ftransform:expr)* + ) + xfn_middle!(#[$cprefix] $pname, $cname, $nname ( + $cname : $ctype + , $pname : $ptype + $(, $fname : $ftype )* + ), $ctransform) // note that the "next" row is not included + }; + ( + #[$nprefix:meta] $nname:ident : $ntype:ty : $ntransform:expr + , #[$cprefix:meta] $cname:ident : $ctype:ty : $ctransform:expr + ) => {}; // discard last two rows (xfn_make_head handles those) +} + +#[macro_export] +macro_rules! xfn_make_last { + (( + #[$cprefix:meta] $cname:ident : $ctype:ty : $ctransform:expr + , #[$pprefix:meta] $pname:ident : $ptype:ty : $ptransform:expr + $(, #[$fprefix:meta] $fname:ident : $ftype:ty : $ftransform:expr)* + ), $operation:expr) => { + xfn_last!( + #[$cprefix] $pname, $cname, ( + $cname : $ctype + , $pname : $ptype + $(, $fname : $ftype)* + ), $ctransform, $operation + ) + }; +} + +#[macro_export] +macro_rules! xfn_reversed { + ( + ( $(#[$fprefix:meta] $fname:ident : $ftype:ty : $ftransform:expr),* ), + $operation:expr + ) => { + xfn_make_head!($(#[$fprefix] $fname : $ftype : $ftransform),*) + xfn_make_middle!( + $(#[$fprefix] $fname : $ftype : $ftransform),* + ) + xfn_make_last( + ( $(#[$fprefix] $fname : $ftype : $ftransform),* ), + $operation + ) + }; +} + +#[macro_export] +macro_rules! xfn { + ( + ( $(#[$fprefix:meta] $fname:ident : $ftype:ty : $ftransform:expr),* ), + $operation:expr + ) => { + $crate::xfn_reversed!( + reverse_proplist!($(#[$fprefix] $fname : $ftype : $ftransform),*), + $operation + ); + }; +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 99ae0f1..a94b975 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,11 +3,15 @@ #![feature(adt_const_params)] #![feature(generic_const_exprs)] #![feature(generators, generator_trait)] +#![feature(never_type)] +#![feature(unwrap_infallible)] +#![feature(arc_unwrap_or_clone)] +#![feature(hasher_prefixfree_extras)] +#![feature(closure_lifetime_binder)] +use std::{env::current_dir, io}; -use std::env::current_dir; - -mod executor; +// mod executor; mod parse; mod project; mod utils; @@ -15,15 +19,19 @@ mod representations; mod rule; mod scheduler; pub(crate) mod foreign; +mod external; +mod foreign_macros; use file_loader::LoadingError; pub use representations::ast; use ast::{Expr, Clause}; -use representations::typed as t; +// use representations::typed as t; use mappable_rc::Mrc; use project::{rule_collector, Loaded, file_loader}; use rule::Repository; use utils::{to_mrc_slice, mrc_empty_slice, one_mrc_slice}; +use crate::representations::{ast_to_postmacro, postmacro_to_interpreted, interpreted}; + fn literal(orig: &[&str]) -> Mrc<[String]> { to_mrc_slice(vliteral(orig)) } @@ -50,23 +58,23 @@ fn initial_tree() -> Mrc<[Expr]> { } #[allow(unused)] -fn typed_notation_debug() { - let true_ex = t::Clause::Auto(0, mrc_empty_slice(), - t::Clause::Lambda(1, one_mrc_slice(t::Clause::AutoArg(0)), - t::Clause::Lambda(2, one_mrc_slice(t::Clause::AutoArg(0)), - t::Clause::LambdaArg(1).wrap_t(t::Clause::AutoArg(0)) - ).wrap() - ).wrap() - ).wrap(); - let false_ex = t::Clause::Auto(0, mrc_empty_slice(), - t::Clause::Lambda(1, one_mrc_slice(t::Clause::AutoArg(0)), - t::Clause::Lambda(2, one_mrc_slice(t::Clause::AutoArg(0)), - t::Clause::LambdaArg(2).wrap_t(t::Clause::AutoArg(0)) - ).wrap() - ).wrap() - ).wrap(); - println!("{:?}", t::Clause::Apply(t::Clause::Apply(Mrc::clone(&true_ex), true_ex).wrap(), false_ex)) -} +// fn typed_notation_debug() { +// let true_ex = t::Clause::Auto(0, mrc_empty_slice(), +// t::Clause::Lambda(1, one_mrc_slice(t::Clause::AutoArg(0)), +// t::Clause::Lambda(2, one_mrc_slice(t::Clause::AutoArg(0)), +// t::Clause::LambdaArg(1).wrap_t(t::Clause::AutoArg(0)) +// ).wrap() +// ).wrap() +// ).wrap(); +// let false_ex = t::Clause::Auto(0, mrc_empty_slice(), +// t::Clause::Lambda(1, one_mrc_slice(t::Clause::AutoArg(0)), +// t::Clause::Lambda(2, one_mrc_slice(t::Clause::AutoArg(0)), +// t::Clause::LambdaArg(2).wrap_t(t::Clause::AutoArg(0)) +// ).wrap() +// ).wrap() +// ).wrap(); +// println!("{:?}", t::Clause::Apply(t::Clause::Apply(Mrc::clone(&true_ex), true_ex).wrap(), false_ex)) +// } #[allow(unused)] fn load_project() { @@ -95,7 +103,14 @@ fn load_project() { }, Err(e) => panic!("Rule error: {e:?}") } - }; println!("Macro execution didn't halt")); + }; panic!("Macro execution didn't halt")); + let pmtree = ast_to_postmacro::exprv(tree.as_ref()) + .unwrap_or_else(|e| panic!("Postmacro conversion error: {e}")); + let runtree = postmacro_to_interpreted::expr_rec(&pmtree) + .unwrap_or_else(|e| panic!("Interpreted conversion error: {e}")); + let stable = runtree.run_to_completion() + .unwrap_or_else(|e| panic!("Runtime error {e}")); + println!("Settled at {stable:?}") } fn main() { diff --git a/src/parse/expression.rs b/src/parse/expression.rs index 1e031f0..93f9e0b 100644 --- a/src/parse/expression.rs +++ b/src/parse/expression.rs @@ -1,8 +1,9 @@ use chumsky::{self, prelude::*, Parser}; use mappable_rc::Mrc; use crate::enum_parser; +use crate::representations::Primitive; use crate::representations::{Literal, ast::{Clause, Expr}}; -use crate::utils::{to_mrc_slice, one_mrc_slice}; +use crate::utils::to_mrc_slice; use super::lexer::Lexeme; @@ -92,7 +93,7 @@ pub fn xpr_parser() -> impl Parser> { let clause = enum_parser!(Lexeme::Comment).repeated() .ignore_then(choice(( - enum_parser!(Lexeme >> Literal; Int, Num, Char, Str).map(Clause::Literal), + enum_parser!(Lexeme >> Literal; Int, Num, Char, Str).map(Primitive::Literal).map(Clause::P), placeholder_parser().map(|key| Clause::Placeh{key, vec: None}), just(Lexeme::name("...")).to(true) .or(just(Lexeme::name("..")).to(false)) diff --git a/src/representations/ast.rs b/src/representations/ast.rs index e611d89..8308795 100644 --- a/src/representations/ast.rs +++ b/src/representations/ast.rs @@ -4,9 +4,9 @@ use ordered_float::NotNan; use std::{hash::Hash, intrinsics::likely}; use std::fmt::Debug; use crate::utils::mrc_empty_slice; -use crate::{foreign::{ExternFn, Atom}, utils::one_mrc_slice}; +use crate::utils::one_mrc_slice; -use super::Literal; +use super::primitive::Primitive; /// An S-expression with a type #[derive(PartialEq, Eq, Hash)] @@ -38,8 +38,7 @@ impl Debug for Expr { /// An S-expression as read from a source file #[derive(PartialEq, Eq, Hash)] pub enum Clause { - /// A literal value, eg. `1`, `"hello"` - Literal(Literal), + P(Primitive), /// A c-style name or an operator, eg. `+`, `i`, `foo::bar` Name{ local: Option, @@ -53,12 +52,6 @@ pub enum Clause { Lambda(String, Mrc<[Expr]>, Mrc<[Expr]>), /// A parameterized expression with type inference, eg. `@T. T -> T` Auto(Option, Mrc<[Expr]>, Mrc<[Expr]>), - /// An opaque function, eg. an effectful function employing CPS. - /// Preferably wrap these in an Orchid monad. - ExternFn(ExternFn), - /// An opaque non-callable value, eg. a file handle. - /// Preferably wrap these in an Orchid structure. - Atom(Atom), /// A placeholder for macros, eg. `$name`, `...$body`, `...$lhs:1` Placeh{ key: String, @@ -111,9 +104,7 @@ impl Clone for Clause { n.clone(), Mrc::clone(t), Mrc::clone(b) ), Self::Placeh{key, vec} => Self::Placeh{key: key.clone(), vec: *vec}, - Self::Literal(l) => Self::Literal(l.clone()), - Self::ExternFn(nc) => Self::ExternFn(nc.clone()), - Self::Atom(a) => Self::Atom(a.clone()), + Self::P(p) => Self::P(p.clone()), Self::Explicit(expr) => Self::Explicit(Mrc::clone(expr)) } } @@ -130,7 +121,7 @@ fn fmt_expr_seq(it: &mut dyn Iterator, f: &mut std::fmt::Formatter impl Debug for Clause { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Literal(arg0) => write!(f, "{:?}", arg0), + Self::P(p) => write!(f, "{:?}", p), Self::Name{local, qualified} => if let Some(local) = local {write!(f, "{}`{}`", qualified.join("::"), local)} else {write!(f, "{}", qualified.join("::"))}, @@ -157,8 +148,6 @@ impl Debug for Clause { Self::Placeh{key, vec: None} => write!(f, "${key}"), Self::Placeh{key, vec: Some((prio, true))} => write!(f, "...${key}:{prio}"), Self::Placeh{key, vec: Some((prio, false))} => write!(f, "..${key}:{prio}"), - Self::ExternFn(nc) => write!(f, "{nc:?}"), - Self::Atom(a) => write!(f, "{a:?}"), Self::Explicit(expr) => write!(f, "@{:?}", expr.as_ref()) } } diff --git a/src/representations/ast_to_postmacro.rs b/src/representations/ast_to_postmacro.rs new file mode 100644 index 0000000..2b64161 --- /dev/null +++ b/src/representations/ast_to_postmacro.rs @@ -0,0 +1,173 @@ +use std::{rc::Rc, fmt::Display}; + +use crate::utils::Stackframe; + +use super::{ast, postmacro}; + +#[derive(Clone)] +pub enum Error { + /// `()` as a clause is meaningless in lambda calculus + EmptyS, + /// Only `(...)` may be converted to typed lambdas. `[...]` and `{...}` left in the code are + /// signs of incomplete macro execution + BadGroup(char), + /// `foo:bar:baz` will be parsed as `(foo:bar):baz`. Explicitly specifying `foo:(bar:baz)` + /// is forbidden and it's also meaningless since `baz` can only ever be the kind of types + ExplicitKindOfType, + /// Name never bound in an enclosing scope - indicates incomplete macro substitution + Unbound(String), + /// Namespaced names can never occur in the code, these are signs of incomplete macro execution + Symbol, + /// Placeholders shouldn't even occur in the code during macro execution. Something is clearly + /// terribly wrong + Placeholder, + /// It's possible to try and transform the clause `(foo:bar)` into a typed clause, + /// however the correct value of this ast clause is a typed expression (included in the error) + /// + /// [expr] handles this case, so it's only really possible to get this + /// error if you're calling [clause] directly + ExprToClause(postmacro::Expr), + /// @ tokens only ever occur between a function and a parameter + NonInfixAt +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::EmptyS => write!(f, "`()` as a clause is meaningless in lambda calculus"), + Error::BadGroup(c) => write!(f, "Only `(...)` may be converted to typed lambdas. `[...]` and `{{...}}` left in the code are signs of incomplete macro execution"), + Error::ExplicitKindOfType => write!(f, "`foo:bar:baz` will be parsed as `(foo:bar):baz`. Explicitly specifying `foo:(bar:baz)` is forbidden and meaningless since `baz` can only ever be the kind of types"), + Error::Unbound(name) => write!(f, "Name \"{name}\" never bound in an enclosing scope. This indicates incomplete macro substitution"), + Error::Symbol => write!(f, "Namespaced names not matching any macros found in the code."), + Error::Placeholder => write!(f, "Placeholders shouldn't even occur in the code during macro execution, this is likely a compiler bug"), + Error::ExprToClause(expr) => write!(f, "Attempted to transform the clause (foo:bar) into a typed clause. This is likely a compiler bug"), + Error::NonInfixAt => write!(f, "@ as a token can only ever occur between a generic and a type parameter.") + } + } +} + +/// Try to convert an expression from AST format to typed lambda +pub fn expr(expr: &ast::Expr) -> Result { + expr_rec(expr, Context::default()) +} + +/// Try and convert a single clause from AST format to typed lambda +pub fn clause(clause: &ast::Clause) -> Result { + clause_rec(clause, Context::default()) +} + +/// Try and convert a sequence of expressions from AST format to typed lambda +pub fn exprv(exprv: &[ast::Expr]) -> Result { + exprv_rec(exprv, Context::default()) +} + +#[derive(Clone, Copy)] +struct Context<'a> { + names: Stackframe<'a, (&'a str, bool)> +} + +impl<'a> Context<'a> { + fn w_name<'b>(&'b self, name: &'b str, is_auto: bool) -> Context<'b> where 'a: 'b { + Context { names: self.names.push((name, is_auto)) } + } +} + +impl Default for Context<'static> { + fn default() -> Self { + Self { names: Stackframe::new(("", false)) } + } +} + +/// Recursive state of [exprv] +fn exprv_rec<'a>( + v: &'a [ast::Expr], + ctx: Context<'a>, +) -> Result { + let (last, rest) = v.split_last().ok_or(Error::EmptyS)?; + if rest.len() == 0 {return expr_rec(&v[0], ctx)} + let clause = if let ast::Expr(ast::Clause::Explicit(inner), empty_slice) = last { + assert!(empty_slice.len() == 0, + "It is assumed that Explicit nodes can never have type annotations as the \ + wrapped expression node matches all trailing colons." + ); + let x = expr_rec(inner.as_ref(), ctx)?; + postmacro::Clause::Explicit(Rc::new(exprv_rec(rest, ctx)?), Rc::new(x)) + } else { + let f = exprv_rec(rest, ctx)?; + let x = expr_rec(last, ctx)?; + postmacro::Clause::Apply(Rc::new(f), Rc::new(x)) + }; + Ok(postmacro::Expr(clause, Rc::new(vec![]))) +} + +/// Recursive state of [expr] +fn expr_rec<'a>( + ast::Expr(val, typ): &'a ast::Expr, + ctx: Context<'a> +) -> Result { // (output, used_explicits) + let typ: Vec = typ.iter() + .map(|c| clause_rec(c, ctx)) + .collect::>()?; + if let ast::Clause::S(paren, body) = val { + if *paren != '(' {return Err(Error::BadGroup(*paren))} + let postmacro::Expr(inner, inner_t) = exprv_rec(body.as_ref(), ctx)?; + let new_t = + if typ.len() == 0 { inner_t } + else if inner_t.len() == 0 { Rc::new(typ) } + else { Rc::new(inner_t.iter().chain(typ.iter()).cloned().collect()) }; + Ok(postmacro::Expr(inner, new_t)) + } else { + let cls = clause_rec(&val, ctx)?; + Ok(postmacro::Expr(cls, Rc::new(typ))) + } +} + +// (\t:(@T. Pair T T). t \left.\right. left) @number -- this will fail +// (@T. \t:Pair T T. t \left.\right. left) @number -- this is the correct phrasing + +/// Recursive state of [clause] +fn clause_rec<'a>( + cls: &'a ast::Clause, + ctx: Context<'a> +) -> Result { + match cls { + ast::Clause::P(p) => Ok(postmacro::Clause::P(p.clone())), + ast::Clause::Auto(no, t, b) => { + let typ = if t.len() == 0 {Rc::new(vec![])} else { + let postmacro::Expr(c, t) = exprv_rec(t.as_ref(), ctx)?; + if t.len() > 0 {return Err(Error::ExplicitKindOfType)} + else {Rc::new(vec![c])} + }; + let body_ctx = if let Some(name) = no { + ctx.w_name(&&**name, true) + } else {ctx}; + let body = exprv_rec(b.as_ref(), body_ctx)?; + Ok(postmacro::Clause::Auto(typ, Rc::new(body))) + } + ast::Clause::Lambda(n, t, b) => { + let typ = if t.len() == 0 {Rc::new(vec![])} else { + let postmacro::Expr(c, t) = exprv_rec(t.as_ref(), ctx)?; + if t.len() > 0 {return Err(Error::ExplicitKindOfType)} + else {Rc::new(vec![c])} + }; + let body_ctx = ctx.w_name(&&**n, false); + let body = exprv_rec(b.as_ref(), body_ctx)?; + Ok(postmacro::Clause::Lambda(typ, Rc::new(body))) + } + ast::Clause::Name { local: Some(arg), .. } => { + let (level, (_, is_auto)) = ctx.names.iter().enumerate().find(|(_, (n, _))| n == arg) + .ok_or_else(|| Error::Unbound(arg.clone()))?; + let label = if *is_auto {postmacro::Clause::AutoArg} else {postmacro::Clause::LambdaArg}; + Ok(label(level)) + } + ast::Clause::S(paren, entries) => { + if *paren != '(' {return Err(Error::BadGroup(*paren))} + let postmacro::Expr(val, typ) = exprv_rec(entries.as_ref(), ctx)?; + if typ.len() == 0 {Ok(val)} + else {Err(Error::ExprToClause(postmacro::Expr(val, typ)))} + }, + ast::Clause::Name { local: None, .. } => Err(Error::Symbol), + ast::Clause::Placeh { .. } => Err(Error::Placeholder), + ast::Clause::Explicit(..) => Err(Error::NonInfixAt) + } +} \ No newline at end of file diff --git a/src/representations/ast_to_typed.rs b/src/representations/ast_to_typed.rs deleted file mode 100644 index 95c5649..0000000 --- a/src/representations/ast_to_typed.rs +++ /dev/null @@ -1,171 +0,0 @@ -use mappable_rc::Mrc; - -use crate::utils::{Stackframe, to_mrc_slice, mrc_empty_slice, ProtoMap, one_mrc_slice}; - -use super::{ast, typed, get_name::get_name}; - -#[derive(Clone)] -pub enum Error { - /// `()` as a clause is meaningless in lambda calculus - EmptyS, - /// Only `(...)` may be converted to typed lambdas. `[...]` and `{...}` left in the code are - /// signs of incomplete macro execution - BadGroup(char), - /// `foo:bar:baz` will be parsed as `(foo:bar):baz`, explicitly specifying `foo:(bar:baz)` - /// is forbidden and it's also meaningless since `baz` can only ever be the kind of types - ExplicitBottomKind, - /// Name never bound in an enclosing scope - indicates incomplete macro substitution - Unbound(String), - /// Namespaced names can never occur in the code, these are signs of incomplete macro execution - Symbol, - /// Placeholders shouldn't even occur in the code during macro execution. Something is clearly - /// terribly wrong - Placeholder, - /// It's possible to try and transform the clause `(foo:bar)` into a typed clause, - /// however the correct value of this ast clause is a typed expression (included in the error) - /// - /// [expr] handles this case, so it's only really possible to get this - /// error if you're calling [clause] directly - ExprToClause(typed::Expr), - /// @ tokens only ever occur between a function and a parameter - NonInfixAt -} - -/// Try to convert an expression from AST format to typed lambda -pub fn expr(expr: &ast::Expr) -> Result { - Ok(expr_rec(expr, ProtoMap::new(), None)?.0) -} - -/// Try and convert a single clause from AST format to typed lambda -pub fn clause(clause: &ast::Clause) -> Result { - Ok(clause_rec(clause, ProtoMap::new(), None)?.0) -} - -/// Try and convert a sequence of expressions from AST format to typed lambda -pub fn exprv(exprv: &[ast::Expr]) -> Result { - Ok(exprv_rec(exprv, ProtoMap::new(), None)?.0) -} - -const NAMES_INLINE_COUNT:usize = 3; - -/// Recursive state of [exprv] -fn exprv_rec<'a>( - v: &'a [ast::Expr], - names: ProtoMap<&'a str, (u64, bool), NAMES_INLINE_COUNT>, - explicits: Option<&Stackframe>>, -) -> Result<(typed::Expr, usize), Error> { - let (last, rest) = v.split_last().ok_or(Error::EmptyS)?; - if rest.len() == 0 {return expr_rec(&v[0], names, explicits)} - if let ast::Expr(ast::Clause::Explicit(inner), empty_slice) = last { - assert!(empty_slice.len() == 0, - "It is assumed that Explicit nodes can never have type annotations as the \ - wrapped expression node matches all trailing colons." - ); - let (x, _) = expr_rec(inner.as_ref(), names.clone(), None)?; - let new_explicits = Stackframe::opush(explicits, Mrc::new(x)); - let (body, used_expls) = exprv_rec(rest, names, Some(&new_explicits))?; - Ok((body, used_expls.saturating_sub(1))) - } else { - let (f, f_used_expls) = exprv_rec(rest, names.clone(), explicits)?; - let x_explicits = Stackframe::opop(explicits, f_used_expls); - let (x, x_used_expls) = expr_rec(last, names, x_explicits)?; - Ok((typed::Expr( - typed::Clause::Apply(Mrc::new(f), Mrc::new(x)), - mrc_empty_slice() - ), x_used_expls + f_used_expls)) - } -} - -/// Recursive state of [expr] -fn expr_rec<'a>( - ast::Expr(val, typ): &'a ast::Expr, - names: ProtoMap<&'a str, (u64, bool), NAMES_INLINE_COUNT>, - explicits: Option<&Stackframe>> // known explicit values -) -> Result<(typed::Expr, usize), Error> { // (output, used_explicits) - let typ: Vec = typ.iter() - .map(|c| Ok(clause_rec(c, names.clone(), None)?.0)) - .collect::>()?; - if let ast::Clause::S(paren, body) = val { - if *paren != '(' {return Err(Error::BadGroup(*paren))} - let (typed::Expr(inner, inner_t), used_expls) = exprv_rec(body.as_ref(), names, explicits)?; - let new_t = if typ.len() == 0 { inner_t } else { - to_mrc_slice(if inner_t.len() == 0 { typ } else { - inner_t.iter().chain(typ.iter()).cloned().collect() - }) - }; - Ok((typed::Expr(inner, new_t), used_expls)) - } else { - let (cls, used_expls) = clause_rec(&val, names, explicits)?; - Ok((typed::Expr(cls, to_mrc_slice(typ)), used_expls)) - } -} - -/// Recursive state of [clause] -fn clause_rec<'a>( - cls: &'a ast::Clause, - mut names: ProtoMap<&'a str, (u64, bool), NAMES_INLINE_COUNT>, - mut explicits: Option<&Stackframe>> -) -> Result<(typed::Clause, usize), Error> { - match cls { // (\t:(@T. Pair T T). t \left.\right. left) @number -- this will fail - ast::Clause::ExternFn(e) => Ok((typed::Clause::ExternFn(e.clone()), 0)), - ast::Clause::Atom(a) => Ok((typed::Clause::Atom(a.clone()), 0)), - ast::Clause::Auto(no, t, b) => { - // Allocate id - let id = get_name(); - // Pop an explicit if available - let (value, rest_explicits) = explicits.map( - |Stackframe{ prev, item, .. }| { - (Some(item), *prev) - } - ).unwrap_or_default(); - explicits = rest_explicits; - // Convert the type - let typ = if t.len() == 0 {mrc_empty_slice()} else { - let (typed::Expr(c, t), _) = exprv_rec(t.as_ref(), names.clone(), None)?; - if t.len() > 0 {return Err(Error::ExplicitBottomKind)} - else {one_mrc_slice(c)} - }; - // Traverse body with extended context - if let Some(name) = no {names.set(&&**name, (id, true))} - let (body, used_expls) = exprv_rec(b.as_ref(), names, explicits)?; - // Produce a binding instead of an auto if explicit was available - if let Some(known_value) = value { - Ok((typed::Clause::Apply( - typed::Clause::Lambda(id, typ, Mrc::new(body)).wrap(), - Mrc::clone(known_value) - ), used_expls + 1)) - } else { - Ok((typed::Clause::Auto(id, typ, Mrc::new(body)), 0)) - } - } - ast::Clause::Lambda(n, t, b) => { - // Allocate id - let id = get_name(); - // Convert the type - let typ = if t.len() == 0 {mrc_empty_slice()} else { - let (typed::Expr(c, t), _) = exprv_rec(t.as_ref(), names.clone(), None)?; - if t.len() > 0 {return Err(Error::ExplicitBottomKind)} - else {one_mrc_slice(c)} - }; - names.set(&&**n, (id, false)); - let (body, used_expls) = exprv_rec(b.as_ref(), names, explicits)?; - Ok((typed::Clause::Lambda(id, typ, Mrc::new(body)), used_expls)) - } - ast::Clause::Literal(l) => Ok((typed::Clause::Literal(l.clone()), 0)), - ast::Clause::Name { local: Some(arg), .. } => { - let (uid, is_auto) = names.get(&&**arg) - .ok_or_else(|| Error::Unbound(arg.clone()))?; - let label = if *is_auto {typed::Clause::AutoArg} else {typed::Clause::LambdaArg}; - Ok((label(*uid), 0)) - } - ast::Clause::S(paren, entries) => { - if *paren != '(' {return Err(Error::BadGroup(*paren))} - let (typed::Expr(val, typ), used_expls) = exprv_rec(entries.as_ref(), names, explicits)?; - if typ.len() == 0 {Ok((val, used_expls))} - else {Err(Error::ExprToClause(typed::Expr(val, typ)))} - }, - ast::Clause::Name { local: None, .. } => Err(Error::Symbol), - ast::Clause::Placeh { .. } => Err(Error::Placeholder), - ast::Clause::Explicit(..) => Err(Error::NonInfixAt) - } -} \ No newline at end of file diff --git a/src/representations/interpreted.rs b/src/representations/interpreted.rs new file mode 100644 index 0000000..9cfced0 --- /dev/null +++ b/src/representations/interpreted.rs @@ -0,0 +1,191 @@ +use std::fmt::{Display, Debug}; +use std::rc::Rc; + +use crate::utils::Side; +use crate::foreign::{ExternError, Atom}; + +use super::path_set::PathSet; +use super::primitive::Primitive; + +#[derive(Clone, PartialEq, Eq, Hash)] +pub enum Clause { + P(Primitive), + Apply{ + f: Rc, + x: Rc, + id: usize + }, + Lambda{ + args: Option, + body: Rc + }, + LambdaArg, +} + +impl Debug for Clause { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Clause::P(p) => write!(f, "{p:?}"), + Clause::LambdaArg => write!(f, "arg"), + Clause::Apply { f: fun, x, id } => write!(f, "({:?} {:?})@{}", fun.as_ref(), x.as_ref(), id), + Clause::Lambda { args, body } => { + write!(f, "\\")?; + match args { + Some(path) => write!(f, "{path:?}")?, + None => write!(f, "_")?, + } + write!(f, ".")?; + write!(f, "{:?}", body.as_ref()) + } + } + } +} + +/// Problems in the process of execution +#[derive(Clone)] +pub enum RuntimeError { + Extern(Rc), + NonFunctionApplication(usize), +} + +impl Display for RuntimeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Extern(e) => write!(f, "Error in external function: {e}"), + Self::NonFunctionApplication(loc) => write!(f, "Primitive applied as function at {loc}") + } + } +} + +/// Various reasons why a new clause might not have been produced +#[derive(Clone)] +pub enum InternalError { + Runtime(RuntimeError), + NonReducible +} + +fn map_at Result>( + path: &[Side], source: &Clause, mapper: F +) -> Result { + // Pass right through lambdas + if let Clause::Lambda { args, body } = source { + return Ok(Clause::Lambda { + args: args.clone(), + body: Rc::new(map_at(path, body, mapper)?) + }) + } + // If the path ends here, process the next (non-lambda) node + let (head, tail) = if let Some(sf) = path.split_first() {sf} else { + return mapper(source) + }; + // If it's an Apply, execute the next step in the path + if let Clause::Apply { f, x, id } = source { + return Ok(match head { + Side::Left => Clause::Apply { + f: Rc::new(map_at(tail, f, mapper)?), + x: x.clone(), + id: *id + }, + Side::Right => Clause::Apply { + f: f.clone(), + x: Rc::new(map_at(tail, x, mapper)?), + id: *id + } + }) + } + panic!("Invalid path") +} + +fn substitute(PathSet { steps, next }: &PathSet, value: &Clause, body: &Clause) -> Clause { + map_at(&steps, body, |checkpoint| -> Result { + match (checkpoint, next) { + (Clause::Lambda{..}, _) => unreachable!("Handled by map_at"), + (Clause::Apply { f, x, id }, Some((left, right))) => Ok(Clause::Apply { + f: Rc::new(substitute(left, value, f)), + x: Rc::new(substitute(right, value, x)), + id: *id + }), + (Clause::LambdaArg, None) => Ok(value.clone()), + (_, None) => panic!("Substitution path ends in something other than LambdaArg"), + (_, Some(_)) => panic!("Substitution path leads into something other than Apply"), + } + }).into_ok() +} + +fn apply(f: &Clause, x: Rc, id: usize) -> Result { + match f { + Clause::P(Primitive::ExternFn(f)) => f.apply(x.as_ref().clone()) + .map_err(|e| InternalError::Runtime(RuntimeError::Extern(e))), + fex@Clause::Apply{..} => Ok(Clause::Apply{ // Don't execute the pre-function expression + f: Rc::new(fex.run_once()?), // take a step in resolving it instead + x, id + }), + Clause::Lambda{args, body} => Ok(if let Some(args) = args { + substitute(args, x.as_ref(), body) + } else {body.as_ref().clone()}), + _ => Err(InternalError::Runtime(RuntimeError::NonFunctionApplication(id))) + } +} + +impl Clause { + pub fn run_once(&self) -> Result { + match self { + Clause::Apply{f, x, id} => apply(f.as_ref(), x.clone(), *id), + Clause::P(Primitive::Atom(Atom(data))) => data.run_once(), + _ => Err(InternalError::NonReducible) + } + } + + pub fn run_n_times(&self, n: usize) -> Result<(Self, usize), RuntimeError> { + let mut i = self.clone(); + let mut done = 0; + while done < n { + match match &i { + Clause::Apply{f, x, id} => match apply(f.as_ref(), x.clone(), *id) { + Err(e) => Err(e), + Ok(c) => { + i = c; + done += 1; + Ok(()) + } + }, + Clause::P(Primitive::Atom(Atom(data))) => match data.run_n_times(n - done) { + Err(e) => Err(InternalError::Runtime(e)), + Ok((c, n)) => { + i = c; + done += n; + Ok(()) + } + }, + _ => Err(InternalError::NonReducible) + } { + Err(InternalError::NonReducible) => return Ok((i, done)), + Err(InternalError::Runtime(e)) => return Err(e), + Ok(()) => () + } + } + return Ok((i, done)); + } + + pub fn run_to_completion(&self) -> Result { + let mut i = self.clone(); + loop { + match match &i { + Clause::Apply { f, x, id } => match apply(f.as_ref(), x.clone(), *id) { + Err(e) => Err(e), + Ok(c) => Ok(i = c) + }, + Clause::P(Primitive::Atom(Atom(data))) => match data.run_to_completion() { + Err(e) => Err(InternalError::Runtime(e)), + Ok(c) => Ok(i = c) + }, + _ => Err(InternalError::NonReducible) + } { + Err(InternalError::NonReducible) => break, + Err(InternalError::Runtime(e)) => return Err(e), + Ok(()) => () + } + }; + Ok(i) + } +} \ No newline at end of file diff --git a/src/representations/mod.rs b/src/representations/mod.rs index 78ea5a1..e6869c9 100644 --- a/src/representations/mod.rs +++ b/src/representations/mod.rs @@ -1,6 +1,12 @@ pub mod ast; -pub mod typed; +// pub mod typed; pub mod literal; -pub mod ast_to_typed; +pub mod ast_to_postmacro; pub mod get_name; +pub(crate) mod interpreted; +mod postmacro; +mod primitive; +mod path_set; +pub use primitive::Primitive; +pub mod postmacro_to_interpreted; pub use literal::Literal; diff --git a/src/representations/path_set.rs b/src/representations/path_set.rs new file mode 100644 index 0000000..70de485 --- /dev/null +++ b/src/representations/path_set.rs @@ -0,0 +1,96 @@ +use std::rc::Rc; +use std::ops::Add; +use std::fmt::Debug; + +use crate::utils::Side; + + +/// A set of paths into a Lambda expression +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct PathSet { + /// The definite steps + pub steps: Rc>, + /// if Some, it splits. If None, it ends. + pub next: Option<(Rc, Rc)> +} + +impl Debug for PathSet { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for s in self.steps.as_ref() { + match s { + Side::Left => write!(f, "L")?, + Side::Right => write!(f, "R")?, + } + } + match &self.next { + Some((l, r)) => write!(f, "({l:?}|{r:?})")?, + None => write!(f, "x")?, + } + Ok(()) + } +} + +impl Add for PathSet { + type Output = Self; + fn add(self, rhs: Self) -> Self::Output { + Self { + steps: Rc::new(vec![]), + next: Some((Rc::new(self), Rc::new(rhs))) + } + } +} + +impl Add for PathSet { + type Output = Self; + fn add(self, rhs: Side) -> Self::Output { + let PathSet{ steps, next } = self; + let mut new_steps = Rc::unwrap_or_clone(steps); + new_steps.insert(0, rhs); + Self { + steps: Rc::new(new_steps), + next, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_combine() -> Result<(), &'static str> { + let ps1 = PathSet { next: None, steps: Rc::new(vec![Side::Left, Side::Left]) }; + let ps2 = PathSet { next: None, steps: Rc::new(vec![Side::Left, Side::Right]) }; + let sum = ps1.clone() + ps2.clone(); + assert_eq!(sum.steps.as_ref(), &[]); + let nexts = sum.next.ok_or("nexts not set")?; + assert_eq!(nexts.0.as_ref(), &ps1); + assert_eq!(nexts.1.as_ref(), &ps2); + Ok(()) + } + + fn extend_scaffold() -> PathSet { + PathSet { + next: Some(( + Rc::new(PathSet { next: None, steps: Rc::new(vec![Side::Left, Side::Left]) }), + Rc::new(PathSet { next: None, steps: Rc::new(vec![Side::Left, Side::Right]) }) + )), + steps: Rc::new(vec![Side::Left, Side::Right, Side::Left]) + } + } + + #[test] + fn test_extend_noclone() { + let ps = extend_scaffold(); + let new = ps + Side::Left; + assert_eq!(new.steps.as_ref().as_slice(), &[Side::Left, Side::Left, Side::Right, Side::Left]) + } + + #[test] + fn test_extend_clone() { + let ps = extend_scaffold(); + let _anchor = ps.clone(); + let new = ps + Side::Left; + assert_eq!(new.steps.len(), 4); + } +} \ No newline at end of file diff --git a/src/representations/postmacro.rs b/src/representations/postmacro.rs new file mode 100644 index 0000000..ee046df --- /dev/null +++ b/src/representations/postmacro.rs @@ -0,0 +1,120 @@ +use crate::utils::string_from_charset; + +use super::primitive::Primitive; +use super::ast_to_postmacro; +use super::ast; + +use std::fmt::{Debug, Write}; +use std::rc::Rc; + +/// Indicates whether either side needs to be wrapped. Syntax whose end is ambiguous on that side +/// must use parentheses, or forward the flag +#[derive(PartialEq, Eq, Clone, Copy)] +struct Wrap(bool, bool); + +#[derive(PartialEq, Eq, Hash, Clone)] +pub struct Expr(pub Clause, pub Rc>); +impl Expr { + fn deep_fmt(&self, f: &mut std::fmt::Formatter<'_>, depth: usize, tr: Wrap) -> std::fmt::Result { + let Expr(val, typ) = self; + if typ.len() > 0 { + val.deep_fmt(f, depth, Wrap(true, true))?; + for typterm in typ.as_ref() { + f.write_char(':')?; + typterm.deep_fmt(f, depth, Wrap(true, true))?; + } + } else { + val.deep_fmt(f, depth, tr)?; + } + Ok(()) + } +} + +impl Debug for Expr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.deep_fmt(f, 0, Wrap(false, false)) + } +} + +#[derive(PartialEq, Eq, Hash, Clone)] +pub enum Clause { + Apply(Rc, Rc), + Explicit(Rc, Rc), + Lambda(Rc>, Rc), + Auto(Rc>, Rc), + LambdaArg(usize), + AutoArg(usize), + P(Primitive), +} + +const ARGNAME_CHARSET: &str = "abcdefghijklmnopqrstuvwxyz"; + +fn parametric_fmt( + f: &mut std::fmt::Formatter<'_>, depth: usize, + prefix: &str, argtyp: &[Clause], body: &Expr, wrap_right: bool +) -> std::fmt::Result { + if wrap_right { f.write_char('(')?; } + f.write_str(prefix)?; + f.write_str(&string_from_charset(depth as u64, ARGNAME_CHARSET))?; + for typ in argtyp.iter() { + f.write_str(":")?; + typ.deep_fmt(f, depth, Wrap(false, false))?; + } + f.write_str(".")?; + body.deep_fmt(f, depth + 1, Wrap(false, false))?; + if wrap_right { f.write_char(')')?; } + Ok(()) +} + +impl Clause { + fn deep_fmt(&self, f: &mut std::fmt::Formatter<'_>, depth: usize, Wrap(wl, wr): Wrap) + -> std::fmt::Result { + match self { + Self::P(p) => write!(f, "{p:?}"), + Self::Lambda(argtyp, body) => parametric_fmt(f, depth, "\\", argtyp, body, wr), + Self::Auto(argtyp, body) => parametric_fmt(f, depth, "@", argtyp, body, wr), + Self::LambdaArg(skip) | Self::AutoArg(skip) => { + let lambda_depth = (depth - skip - 1).try_into().unwrap(); + f.write_str(&string_from_charset(lambda_depth, ARGNAME_CHARSET)) + }, + Self::Apply(func, x) => { + 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 { f.write_char(')')?; } + Ok(()) + } + Self::Explicit(gen, t) => { + if wl { f.write_char('(')?; } + gen.deep_fmt(f, depth, Wrap(false, true))?; + f.write_str(" @")?; + t.deep_fmt(f, depth, Wrap(true, wr && !wl))?; + if wl { f.write_char(')'); } + Ok(()) + } + } + } + pub fn wrap(self) -> Box { Box::new(Expr(self, Rc::new(vec![]))) } + pub fn wrap_t(self, t: Clause) -> Box { Box::new(Expr(self, Rc::new(vec![t]))) } +} + +impl Debug for Clause { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.deep_fmt(f, 0, Wrap(false, false)) + } +} + +impl TryFrom<&ast::Expr> for Expr { + type Error = ast_to_postmacro::Error; + fn try_from(value: &ast::Expr) -> Result { + ast_to_postmacro::expr(value) + } +} + +impl TryFrom<&ast::Clause> for Clause { + type Error = ast_to_postmacro::Error; + fn try_from(value: &ast::Clause) -> Result { + ast_to_postmacro::clause(value) + } +} \ No newline at end of file diff --git a/src/representations/postmacro_to_interpreted.rs b/src/representations/postmacro_to_interpreted.rs new file mode 100644 index 0000000..5f4aaea --- /dev/null +++ b/src/representations/postmacro_to_interpreted.rs @@ -0,0 +1,74 @@ +use std::{rc::Rc, fmt::Display}; + +use crate::utils::Side; + +use super::{postmacro, interpreted, path_set::PathSet}; + +fn collect_paths_expr_rec(expr: &postmacro::Expr, depth: usize) -> Option { + collect_paths_cls_rec(&expr.0, depth) +} + +fn collect_paths_cls_rec(cls: &postmacro::Clause, depth: usize) -> Option { + match cls { + postmacro::Clause::P(_) | postmacro::Clause::Auto(..) | postmacro::Clause::AutoArg(_) + | postmacro::Clause::Explicit(..) => None, + postmacro::Clause::LambdaArg(h) => if *h != depth {None} else { + Some(PathSet{ next: None, steps: Rc::new(vec![]) }) + } + postmacro::Clause::Lambda(_, b) => collect_paths_expr_rec(b, depth + 1), + postmacro::Clause::Apply(f, x) => { + let f_opt = collect_paths_expr_rec(f, depth); + let x_opt = collect_paths_expr_rec(x, depth); + match (f_opt, x_opt) { + (Some(f_refs), Some(x_refs)) => Some(f_refs + x_refs), + (Some(f_refs), None) => Some(f_refs + Side::Left), + (None, Some(x_refs)) => Some(x_refs + Side::Right), + (None, None) => None + } + } + } +} + +#[derive(Clone)] +pub enum Error { + /// Auto, Explicit and AutoArg are unsupported + GenericMention, + /// Type annotations are unsupported + ExplicitType +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::ExplicitType => write!(f, "Type annotations are unsupported in the interpreter"), + Self::GenericMention + => write!(f, "The interpreter is typeless and therefore can't resolve generics") + } + } +} + +pub fn clause_rec(cls: &postmacro::Clause) -> Result { + match cls { + postmacro::Clause::P(p) => Ok(interpreted::Clause::P(p.clone())), + postmacro::Clause::Explicit(..) | postmacro::Clause::AutoArg(..) | postmacro::Clause::Auto(..) + => Err(Error::GenericMention), + postmacro::Clause::Apply(f, x) => Ok(interpreted::Clause::Apply { + f: Rc::new(expr_rec(f.as_ref())?), + x: Rc::new(expr_rec(x.as_ref())?), + id: 0 + }), + postmacro::Clause::Lambda(typ, body) => if typ.len() != 0 {Err(Error::ExplicitType)} else { + Ok(interpreted::Clause::Lambda { + args: collect_paths_expr_rec(body, 0), + body: Rc::new(expr_rec(body)?) + }) + }, + postmacro::Clause::LambdaArg(_) => Ok(interpreted::Clause::LambdaArg) + } +} + +pub fn expr_rec(expr: &postmacro::Expr) -> Result { + let postmacro::Expr(c, t) = expr; + if t.len() != 0 {Err(Error::ExplicitType)} + else {clause_rec(c)} +} \ No newline at end of file diff --git a/src/representations/primitive.rs b/src/representations/primitive.rs new file mode 100644 index 0000000..02e35c0 --- /dev/null +++ b/src/representations/primitive.rs @@ -0,0 +1,49 @@ +use std::fmt::Debug; + +use crate::foreign::{ExternFn, Atom}; + +use super::Literal; + +#[derive(Eq, Hash)] +pub enum Primitive { + /// A literal value, eg. `1`, `"hello"` + Literal(Literal), + /// An opaque function, eg. an effectful function employing CPS. + ExternFn(Box), + /// An opaque non-callable value, eg. a file handle. + Atom(Atom) +} + +impl PartialEq for Primitive { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Literal(l1), Self::Literal(l2)) => l1 == l2, + (Self::Atom(a1), Self::Atom(a2)) => a1 == a2, + (Self::ExternFn(efb1), Self::ExternFn(efb2)) => efb1 == efb2, + _ => false + } + } +} + +impl Clone for Primitive { + fn clone(&self) -> Self { + match self { + Primitive::Literal(l) => Primitive::Literal(l.clone()), + Primitive::Atom(a) => Primitive::Atom(a.clone()), + Primitive::ExternFn(ef) => Primitive::ExternFn( + dyn_clone::clone_box(ef.as_ref()) + ) + } + } +} + +impl Debug for Primitive { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Atom(a) => write!(f, "{a:?}"), + Self::ExternFn(ef) => write!(f, "{ef:?}"), + Self::Literal(l) => write!(f, "{l:?}"), + } + } +} + diff --git a/src/representations/typed.rs b/src/representations/typed.rs index 7ccc3ce..463c8b1 100644 --- a/src/representations/typed.rs +++ b/src/representations/typed.rs @@ -5,10 +5,12 @@ use crate::utils::{to_mrc_slice, one_mrc_slice}; use crate::utils::string_from_charset; use super::get_name::get_name; -use super::{Literal, ast_to_typed, get_name}; +use super::primitive::Primitive; +use super::{Literal, ast_to_postmacro, get_name}; use super::ast; use std::fmt::{Debug, Write}; +use std::rc::Rc; /// Indicates whether either side needs to be wrapped. Syntax whose end is ambiguous on that side /// must use parentheses, or forward the flag @@ -18,16 +20,16 @@ struct Wrap(bool, bool); #[derive(PartialEq, Eq, Hash, Clone)] pub struct Expr(pub Clause, pub Vec); impl Expr { - fn deep_fmt(&self, f: &mut std::fmt::Formatter<'_>, tr: Wrap) -> std::fmt::Result { + fn deep_fmt(&self, f: &mut std::fmt::Formatter<'_>, depth: usize, tr: Wrap) -> std::fmt::Result { let Expr(val, typ) = self; if typ.len() > 0 { - val.deep_fmt(f, Wrap(true, true))?; + val.deep_fmt(f, depth, Wrap(true, true))?; for typterm in typ { f.write_char(':')?; - typterm.deep_fmt(f, Wrap(true, true))?; + typterm.deep_fmt(f, depth, Wrap(true, true))?; } } else { - val.deep_fmt(f, tr)?; + val.deep_fmt(f, depth, tr)?; } Ok(()) } @@ -35,57 +37,54 @@ impl Expr { impl Debug for Expr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.deep_fmt(f, Wrap(false, false)) + self.deep_fmt(f, 0, Wrap(false, false)) } } #[derive(PartialEq, Eq, Hash)] pub enum Clause { - Literal(Literal), - Apply(Box, Box), - Lambda(u64, Box<[Clause]>, Box), - Auto(u64, Box<[Clause]>, Box), - LambdaArg(u64), AutoArg(u64), - ExternFn(ExternFn), - Atom(Atom) + P(Primitive), + Apply(Rc, Rc), + Lambda(Rc<[Clause]>, Rc), + Auto(Rc<[Clause]>, Rc), + LambdaArg(usize), AutoArg(usize), } const ARGNAME_CHARSET: &str = "abcdefghijklmnopqrstuvwxyz"; fn parametric_fmt( - f: &mut std::fmt::Formatter<'_>, - prefix: &str, argtyp: &[Clause], body: &Expr, uid: u64, wrap_right: bool + f: &mut std::fmt::Formatter<'_>, depth: usize, + prefix: &str, argtyp: &[Clause], body: &Expr, wrap_right: bool ) -> std::fmt::Result { if wrap_right { f.write_char('(')?; } f.write_str(prefix)?; - f.write_str(&string_from_charset(uid, ARGNAME_CHARSET))?; + f.write_str(&string_from_charset(depth as u64, ARGNAME_CHARSET))?; for typ in argtyp.iter() { f.write_str(":")?; - typ.deep_fmt(f, Wrap(false, false))?; + typ.deep_fmt(f, depth, Wrap(false, false))?; } f.write_str(".")?; - body.deep_fmt(f, Wrap(false, false))?; + body.deep_fmt(f, depth + 1, Wrap(false, false))?; if wrap_right { f.write_char(')')?; } Ok(()) } impl Clause { - fn deep_fmt(&self, f: &mut std::fmt::Formatter<'_>, Wrap(wl, wr): Wrap) + fn deep_fmt(&self, f: &mut std::fmt::Formatter<'_>, depth: usize, Wrap(wl, wr): Wrap) -> std::fmt::Result { match self { - Self::Literal(arg0) => write!(f, "{arg0:?}"), - Self::ExternFn(nc) => write!(f, "{nc:?}"), - Self::Atom(a) => write!(f, "{a:?}"), - Self::Lambda(uid, argtyp, body) => parametric_fmt(f, "\\", argtyp, body, *uid, wr), - Self::Auto(uid, argtyp, body) => parametric_fmt(f, "@", argtyp, body, *uid, wr), - Self::LambdaArg(uid) | Self::AutoArg(uid) => f.write_str(& - string_from_charset(*uid, ARGNAME_CHARSET) - ), + Self::P(p) => write!(f, "{p:?}"), + Self::Lambda(argtyp, body) => parametric_fmt(f, depth, "\\", argtyp, body, wr), + Self::Auto(argtyp, body) => parametric_fmt(f, depth, "@", argtyp, body, wr), + Self::LambdaArg(skip) | Self::AutoArg(skip) => { + let lambda_depth = (depth - skip - 1).try_into().unwrap(); + f.write_str(&string_from_charset(lambda_depth, ARGNAME_CHARSET)) + }, Self::Apply(func, x) => { if wl { f.write_char('(')?; } - func.deep_fmt(f, Wrap(false, true) )?; + func.deep_fmt(f, depth, Wrap(false, true) )?; f.write_char(' ')?; - x.deep_fmt(f, Wrap(true, wr && !wl) )?; + x.deep_fmt(f, depth, Wrap(true, wr && !wl) )?; if wl { f.write_char(')')?; } Ok(()) } @@ -98,7 +97,7 @@ impl Clause { impl Clone for Clause { fn clone(&self) -> Self { match self { - Clause::Auto(uid, t, b) => { + Clause::Auto(t, b) => { let new_id = get_name(); let new_body = apply_lambda(*uid, Clause::AutoArg(new_id).wrap(), b.clone()); Clause::Auto(new_id, t.clone(), new_body) @@ -125,16 +124,16 @@ impl Debug for Clause { } impl TryFrom<&ast::Expr> for Expr { - type Error = ast_to_typed::Error; + type Error = ast_to_postmacro::Error; fn try_from(value: &ast::Expr) -> Result { - ast_to_typed::expr(value) + ast_to_postmacro::expr(value) } } impl TryFrom<&ast::Clause> for Clause { - type Error = ast_to_typed::Error; + type Error = ast_to_postmacro::Error; fn try_from(value: &ast::Clause) -> Result { - ast_to_typed::clause(value) + ast_to_postmacro::clause(value) } } diff --git a/src/rule/executor/execute.rs b/src/rule/executor/execute.rs index 1adf6c0..eb94f53 100644 --- a/src/rule/executor/execute.rs +++ b/src/rule/executor/execute.rs @@ -1,13 +1,16 @@ +use std::iter; + use hashbrown::HashMap; use mappable_rc::Mrc; use crate::unwrap_or; -use crate::utils::{to_mrc_slice, one_mrc_slice, mrc_empty_slice}; +use crate::utils::{to_mrc_slice, one_mrc_slice, mrc_empty_slice, replace_first}; use crate::utils::iter::{box_once, into_boxed_iter}; use crate::ast::{Expr, Clause}; use super::slice_matcher::SliceMatcherDnC; use super::state::{State, Entry}; use super::super::RuleError; +use super::update_first_seq_rec; fn verify_scalar_vec(pattern: &Expr, is_vec: &mut HashMap) -> Result<(), String> { @@ -62,30 +65,12 @@ fn slice_to_vec(src: &mut Mrc<[Expr]>, tgt: &mut Mrc<[Expr]>) { *tgt = to_mrc_slice(prefix_vec.iter().chain(tgt.iter()).chain(postfix_vec.iter()).cloned().collect()); } -/// Traverse the tree, calling pred on every sibling list until it returns some vec -/// then replace the sibling list with that vec and return true -/// return false if pred never returned some -fn update_first_seq_rec(input: Mrc<[Expr]>, pred: &mut F) -> Option> -where F: FnMut(Mrc<[Expr]>) -> Option> { - if let o@Some(_) = pred(Mrc::clone(&input)) {o} else { - for Expr(cls, _) in input.iter() { - if let Some(t) = cls.typ() { - if let o@Some(_) = update_first_seq_rec(t, pred) {return o} - } - if let Some(b) = cls.body() { - if let o@Some(_) = update_first_seq_rec(b, pred) {return o} - } - } - None - } -} - /// keep re-probing the input with pred until it stops matching fn update_all_seqs(input: Mrc<[Expr]>, pred: &mut F) -> Option> where F: FnMut(Mrc<[Expr]>) -> Option> { - let mut tmp = update_first_seq_rec(input, pred); + let mut tmp = update_first_seq_rec::exprv(input, pred); while let Some(xv) = tmp { - tmp = update_first_seq_rec(Mrc::clone(&xv), pred); + tmp = update_first_seq_rec::exprv(Mrc::clone(&xv), pred); if tmp.is_none() {return Some(xv)} } None @@ -151,14 +136,12 @@ fn write_expr_rec(state: &State, Expr(tpl_clause, tpl_typ): &Expr) -> Box - box_once(Expr(c.to_owned(), out_typ.to_owned())) + c@Clause::P(_) | c@Clause::Name { .. } => box_once(Expr(c.to_owned(), out_typ.to_owned())) } } /// Fill in a template from a state as produced by a pattern fn write_slice_rec(state: &State, tpl: &Mrc<[Expr]>) -> Mrc<[Expr]> { - eprintln!("Writing {tpl:?} with state {state:?}"); tpl.iter().flat_map(|xpr| write_expr_rec(state, xpr)).collect() } diff --git a/src/rule/executor/mod.rs b/src/rule/executor/mod.rs index 551036a..ba84a09 100644 --- a/src/rule/executor/mod.rs +++ b/src/rule/executor/mod.rs @@ -2,6 +2,7 @@ mod slice_matcher; mod state; mod execute; mod split_at_max_vec; +mod update_first_seq_rec; use state::State; diff --git a/src/rule/executor/slice_matcher.rs b/src/rule/executor/slice_matcher.rs index b922e90..2282512 100644 --- a/src/rule/executor/slice_matcher.rs +++ b/src/rule/executor/slice_matcher.rs @@ -211,7 +211,7 @@ impl SliceMatcherDnC { + self.apply_side_with_cache(Side::Right, mrc_derive(&target, |t| &t[pos+1..]), cache) )?; match (self.clause.as_ref(), &target.as_ref()[pos].0) { - (Clause::Literal(val), Clause::Literal(tgt)) => { + (Clause::P(val), Clause::P(tgt)) => { if val == tgt {Some(own_state)} else {None} } (Clause::Placeh{key, vec: None}, tgt_clause) => { diff --git a/src/rule/executor/update_first_seq_rec.rs b/src/rule/executor/update_first_seq_rec.rs new file mode 100644 index 0000000..2ef062a --- /dev/null +++ b/src/rule/executor/update_first_seq_rec.rs @@ -0,0 +1,53 @@ +use mappable_rc::Mrc; + +use crate::{ast::{Expr, Clause}, utils::{replace_first, to_mrc_slice}}; + +/// Traverse the tree, calling pred on every sibling list until it returns some vec +/// then replace the sibling list with that vec and return true +/// return false if pred never returned some +pub fn exprv(input: Mrc<[Expr]>, pred: &mut F) -> Option> +where F: FnMut(Mrc<[Expr]>) -> Option> { + if let o@Some(_) = pred(Mrc::clone(&input)) {return o} + replace_first(input.as_ref(), |ex| expr(ex, pred)) + .map(|i| to_mrc_slice(i.collect())) +} + +pub fn expr(Expr(cls, typ): &Expr, pred: &mut F) -> Option +where F: FnMut(Mrc<[Expr]>) -> Option> { + if let Some(t) = clausev(Mrc::clone(typ), pred) {return Some(Expr(cls.clone(), t))} + if let Some(c) = clause(cls, pred) {return Some(Expr(c, Mrc::clone(typ)))} + None +} + +pub fn clausev(input: Mrc<[Clause]>, pred: &mut F) -> Option> +where F: FnMut(Mrc<[Expr]>) -> Option> { + replace_first(input.as_ref(), |c| clause(c, pred)) + .map(|i| to_mrc_slice(i.collect())) +} + +pub fn clause(c: &Clause, pred: &mut F) -> Option +where F: FnMut(Mrc<[Expr]>) -> Option> { + match c { + Clause::P(_) | Clause::Placeh {..} | Clause::Name {..} => None, + Clause::Lambda(n, typ, body) => { + if let Some(b) = exprv(Mrc::clone(body), pred) { + return Some(Clause::Lambda(n.clone(), Mrc::clone(typ), b)) + } + if let Some(t) = exprv(Mrc::clone(typ), pred) { + return Some(Clause::Lambda(n.clone(), t, Mrc::clone(body))) + } + None + } + Clause::Auto(n, typ, body) => { + if let Some(b) = exprv(Mrc::clone(body), pred) { + return Some(Clause::Auto(n.clone(), Mrc::clone(typ), b)) + } + if let Some(t) = exprv(Mrc::clone(typ), pred) { + return Some(Clause::Auto(n.clone(), t, Mrc::clone(body))) + } + None + } + Clause::S(c, body) => Some(Clause::S(*c, exprv(Mrc::clone(body), pred)?)), + Clause::Explicit(t) => Some(Clause::Explicit(Mrc::new(expr(t, pred)?))) + } +} \ No newline at end of file diff --git a/src/rule/repository.rs b/src/rule/repository.rs index 8dfe746..efc7df1 100644 --- a/src/rule/repository.rs +++ b/src/rule/repository.rs @@ -10,12 +10,22 @@ use super::{super::ast::Rule, executor::execute, RuleError}; pub struct Repository(Vec); impl Repository { pub fn new(mut rules: Vec) -> Self { - rules.sort_by_key(|r| r.prio); + rules.sort_by_key(|r| -r.prio); Self(rules) } + pub fn step(&self, code: Mrc<[Expr]>) -> Result>, RuleError> { + for rule in self.0.iter() { + if let Some(out) = execute( + Mrc::clone(&rule.source), Mrc::clone(&rule.target), + Mrc::clone(&code) + )? {return Ok(Some(out))} + } + Ok(None) + } + /// Attempt to run each rule in priority order once - pub fn step(&self, mut code: Mrc<[Expr]>) -> Result>, RuleError> { + pub fn pass(&self, mut code: Mrc<[Expr]>) -> Result>, RuleError> { let mut ran_once = false; for rule in self.0.iter() { if let Some(tmp) = execute( @@ -33,7 +43,7 @@ impl Repository { /// tree and the number of iterations left to the limit. pub fn long_step(&self, mut code: Mrc<[Expr]>, mut limit: usize) -> Result<(Mrc<[Expr]>, usize), RuleError> { - while let Some(tmp) = self.step(Mrc::clone(&code))? { + while let Some(tmp) = self.pass(Mrc::clone(&code))? { if 0 >= limit {break} limit -= 1; code = tmp diff --git a/src/utils/for_loop.rs b/src/utils/for_loop.rs index f9d0492..ae51f85 100644 --- a/src/utils/for_loop.rs +++ b/src/utils/for_loop.rs @@ -54,10 +54,10 @@ macro_rules! xloop { (for $p:pat in $it:expr; $body:stmt) => { xloop!(for $p in $it; $body; ()) }; - (for $p:pat in $it:expr; $body:stmt; $exit:stmt) => { + (for $p:pat in $iit:expr; $body:stmt; $exit:stmt) => { { - let mut __xloop__ = $it.into_iter(); - xloop!(let Some($p) = __xloop__.next(); $body; $exit) + let mut i = $iit.into_iter(); + xloop!(let Some($p) = i.next(); $body; $exit) } }; (let $p:pat = $e:expr; $body:stmt) => { diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 1caefae..2703855 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,5 +1,8 @@ mod cache; pub mod translate; +mod replace_first; +// mod visitor; +pub use replace_first::replace_first; pub use cache::Cache; mod substack; pub use substack::Stackframe; diff --git a/src/utils/replace_first.rs b/src/utils/replace_first.rs new file mode 100644 index 0000000..d7e642e --- /dev/null +++ b/src/utils/replace_first.rs @@ -0,0 +1,14 @@ +use std::iter; + +pub fn replace_first<'a, T, F>(slice: &'a [T], mut f: F) -> Option + 'a> +where T: Clone, F: FnMut(&T) -> Option { + for i in 0..slice.len() { + if let Some(new) = f(&slice[i]) { + let subbed_iter = slice[0..i].iter().cloned() + .chain(iter::once(new)) + .chain(slice[i+1..].iter().cloned()); + return Some(subbed_iter) + } + } + None +} \ No newline at end of file diff --git a/src/utils/visitor.rs b/src/utils/visitor.rs new file mode 100644 index 0000000..c7c931b --- /dev/null +++ b/src/utils/visitor.rs @@ -0,0 +1,18 @@ +pub trait Visit { + type Return; + fn visit(&self, target: T) -> Return; +} + +pub trait ImpureVisit { + type Shard; + type Return; + fn impure_visit(&self, target: T) -> (Shard, Return); + fn merge(&mut self, s: Shard); +} + +pub struct OverlayVisitor(VBase, VOver); + +impl Visitor for OverlayVisitor +where VBase: Visitor>, VOver: Visitor> { + +} \ No newline at end of file