From 6900d1213af7295c7776c44e08a7178a38629334 Mon Sep 17 00:00:00 2001 From: Lawrence Bethlenfalvy Date: Mon, 7 Nov 2022 15:15:38 +0000 Subject: [PATCH] executor, mostly --- Cargo.lock | 1 + Cargo.toml | 1 + notes/type_system/definitions.md | 33 +++++++-- notes/type_system/impls.md | 6 ++ src/executor/apply_lambda.rs | 67 ++++++++++++++++++ src/executor/foreign.rs | 12 ++-- src/executor/mod.rs | 4 ++ src/executor/normalize.rs | 30 ++++++++ src/executor/partial_hash.rs | 38 +++++++++++ src/executor/reduction_tree.rs | 102 ++++++++++++++++++++++++++++ src/executor/syntax_eq.rs | 41 +++++++++++ src/main.rs | 1 + src/parse/expression.rs | 6 +- src/project/rule_collector.rs | 9 ++- src/representations/ast.rs | 51 ++++++++++---- src/representations/ast_to_typed.rs | 41 ++++++++--- src/rule/executor/execute.rs | 58 +++++++++++----- src/utils/mod.rs | 4 ++ 18 files changed, 444 insertions(+), 61 deletions(-) create mode 100644 src/executor/apply_lambda.rs create mode 100644 src/executor/normalize.rs create mode 100644 src/executor/partial_hash.rs create mode 100644 src/executor/reduction_tree.rs create mode 100644 src/executor/syntax_eq.rs diff --git a/Cargo.lock b/Cargo.lock index 8a9dcd1..5d87fdb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -158,6 +158,7 @@ dependencies = [ "derivative", "hashbrown", "itertools", + "lazy_static", "mappable-rc", "ordered-float", "smallvec", diff --git a/Cargo.toml b/Cargo.toml index 5467f31..974e76a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,4 @@ mappable-rc = "0.1" ordered-float = "3.0" itertools = "0.10" smallvec = "1.10.0" +lazy_static = "1.4.0" diff --git a/notes/type_system/definitions.md b/notes/type_system/definitions.md index 60144fe..cd018e3 100644 --- a/notes/type_system/definitions.md +++ b/notes/type_system/definitions.md @@ -1,11 +1,20 @@ ## Type definitions +A new type can be created with the define expression, which associates a templated expression of +type `type` with a name and a template. The name allocated in this fashion is always representedas +an Atom of type `type` or some function that eventually returns `type`. The kind of the template +parameters is always inferred to be `type` rather than deduced from context. + +The following type definition + ```orc -define Cons as \T:type. loop \r. Option (Pair T r) +define Cons $T as loop \r. Option (Pair $T r) ``` -Results in -- (Cons Int) is not assignable to @T. Option T +results in these conditions: + +- (Cons Int) is not assignable to @T. Option T, or any other type expression that its + definitions would be assignable to, and vice versa. - An instance of (Cons Int) can be constructed with `categorise @(Cons Int) (some (pair 1 none))` but the type parameter can also be inferred from the expected return type - An instance of (Cons Int) can be deconstructed with `generalise @(Cons Int) numbers` @@ -28,8 +37,18 @@ The following must unify: Mult Int (Cons Int) (Cons Int) ``` -### Impls for types +## Typeclasses -Impls for types are generally not a good idea as autos with types like Int can -often be used in dependent typing to represent eg. an index into a type-level conslist to be -deduced by the compiler, and impls take precedence over resolution by unification. +Typeclasses and types use the same define syntax. In fact, much like a type is nothing but a +distinguished instance of the underlying type with added meaning and constraints, a typeclass is +nothing but a distinguished instance of the underlying function (or collection of functions) with +added meaning and constraints. A typeclass definition is therefore perfectly identical to a type +definition: + +``` +define Add $T $U $R as $T -> $U -> $R +``` + +It is clear that the definition of this type would match many, many functions, including +multiplication, so functions that should be considered addition are [impls](./impls.md) of the +typeclass Add. diff --git a/notes/type_system/impls.md b/notes/type_system/impls.md index 4b1e037..08aaff9 100644 --- a/notes/type_system/impls.md +++ b/notes/type_system/impls.md @@ -11,6 +11,12 @@ An impl candidate can be used to resolve an auto if - it is not present in any other matching impl's override tree - all other candidates are present in its override tree +### Impls for types + +Impls for types are generally not a good idea as autos with types like Int can +often be used in dependent typing to represent eg. an index into a type-level conslist to be +deduced by the compiler, and impls take precedence over resolution by unification. + In Rust impls can be placed in one of two modules; the trait owner, and the type owner. In orchid that is not the case, so two additional possibilities arise that Rust's orphan rules prevent. diff --git a/src/executor/apply_lambda.rs b/src/executor/apply_lambda.rs new file mode 100644 index 0000000..7f9e5c9 --- /dev/null +++ b/src/executor/apply_lambda.rs @@ -0,0 +1,67 @@ +use mappable_rc::Mrc; + +use super::super::representations::typed::{Clause, Expr}; + +pub fn apply_lambda(body: Mrc, arg: Mrc) -> Mrc { + apply_lambda_expr_rec(Mrc::clone(&body), arg, 0) + .unwrap_or(body) +} + +fn apply_lambda_expr_rec( + item: Mrc, arg: Mrc, depth: usize +) -> Option> { + let Expr(clause, typ) = item.as_ref(); + apply_lambda_clause_rec(clause.clone(), arg, depth) + .map(|c| Mrc::new(Expr(c, Mrc::clone(typ)))) +} + +fn apply_lambda_clause_rec( + clause: Clause, arg: Mrc, depth: usize +) -> Option { + match clause { + // Only element actually manipulated + Clause::Argument(d) => { + if d == depth {Some(arg.0.clone())} // Resolve reference + // Application eliminates a layer of indirection + else if d > depth {Some(Clause::Argument(d - 1))} + else {None} // Undisturbed ancestry + } + // Traverse, yield Some if either had changed. + Clause::Apply(f, x) => apply_lambda__traverse_call(arg, depth, f, x, Clause::Apply), + Clause::Explicit(f, t) => apply_lambda__traverse_call(arg, depth, f, t, Clause::Explicit), + Clause::Lambda(t, b) => apply_lambda__traverse_param(arg, depth, t, b, Clause::Lambda), + Clause::Auto(t, b) => apply_lambda__traverse_param(arg, depth, t, b, Clause::Auto), + // Leaf nodes + Clause::Atom(_) | Clause::ExternFn(_) | Clause::Literal(_) => None + } +} + +fn apply_lambda__traverse_call( + arg: Mrc, depth: usize, f: Mrc, x: Mrc, + wrap: impl Fn(Mrc, Mrc) -> Clause +) -> Option { + let new_f = apply_lambda_expr_rec(Mrc::clone(&f), Mrc::clone(&arg), depth); + let new_x = apply_lambda_expr_rec(Mrc::clone(&x), arg, depth); + match (new_f, new_x) { + (None, None) => None, + (None, Some(x)) => Some(wrap(f, x)), + (Some(f), None) => Some(wrap(f, x)), + (Some(f), Some(x)) => Some(wrap(f, x)) + } +} + +fn apply_lambda__traverse_param( + arg: Mrc, depth: usize, t: Option>, b: Mrc, + wrap: impl Fn(Option>, Mrc) -> Clause +) -> Option { + let new_t = t.as_ref().and_then(|t| { + apply_lambda_clause_rec(t.as_ref().clone(), Mrc::clone(&arg), depth) + }); + let new_b = apply_lambda_expr_rec(Mrc::clone(&b), arg, depth + 1); + match (new_t, new_b) { + (None, None) => None, + (None, Some(b)) => Some(Clause::Lambda(t, b)), + (Some(t), None) => Some(Clause::Lambda(Some(Mrc::new(t)), b)), + (Some(t), Some(b)) => Some(Clause::Lambda(Some(Mrc::new(t)), b)) + } +} \ No newline at end of file diff --git a/src/executor/foreign.rs b/src/executor/foreign.rs index 8cb83fd..4bbf0e1 100644 --- a/src/executor/foreign.rs +++ b/src/executor/foreign.rs @@ -10,7 +10,6 @@ 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. -#[derive(Eq)] pub struct ExternFn { name: String, param: Mrc, rttype: Mrc, function: Mrc Result>> @@ -27,8 +26,8 @@ impl ExternFn { }) } } - fn name(&self) -> &str {&self.name} - fn apply(&self, arg: Clause) -> Result> {(self.function)(arg)} + 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 { @@ -37,7 +36,10 @@ impl Clone for ExternFn { fn clone(&self) -> Self { Self { rttype: Mrc::clone(&self.rttype), function: Mrc::clone(&self.function) }}} -impl PartialEq for ExternFn { fn eq(&self, other: &Self) -> bool { self.name() == other.name() }} +impl Eq for ExternFn {} +impl PartialEq for 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) } } @@ -58,7 +60,6 @@ pub trait Atomic: Any + Debug where Self: 'static { /// 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. -#[derive(Eq)] pub struct Atom { typ: Mrc, data: Mrc @@ -95,6 +96,7 @@ impl Debug for Atom { write!(f, "##ATOM[{:?}]:{:?}##", self.data(), self.typ) } } +impl Eq for Atom {} impl PartialEq for Atom { fn eq(&self, other: &Self) -> bool { self.data().definitely_eq(other.data().as_any()) diff --git a/src/executor/mod.rs b/src/executor/mod.rs index 2631413..8ddabe2 100644 --- a/src/executor/mod.rs +++ b/src/executor/mod.rs @@ -1,3 +1,7 @@ mod foreign; +// mod normalize; +mod partial_hash; +mod reduction_tree; +mod apply_lambda; pub use foreign::ExternFn; pub use foreign::Atom; \ No newline at end of file diff --git a/src/executor/normalize.rs b/src/executor/normalize.rs new file mode 100644 index 0000000..c7ec3dd --- /dev/null +++ b/src/executor/normalize.rs @@ -0,0 +1,30 @@ +use mappable_rc::Mrc; + +use crate::utils::collect_to_mrc; + +use super::super::representations::typed::{Clause, Expr}; + +fn normalize(Expr(clause, typ): Expr) -> Expr { + todo!() +} + +fn collect_autos( + Expr(clause, typ): Expr, + arg_types: Vec>, + indirect_argt_trees: Vec>, + sunk_types: &mut dyn Iterator +) -> (Vec>, Expr) { + if let Clause::Auto(argt, body) = clause { + + } + else {( + arg_types, + Expr( + clause, + collect_to_mrc( + typ.iter().cloned() + .chain(sunk_types) + ) + ) + )} +} \ No newline at end of file diff --git a/src/executor/partial_hash.rs b/src/executor/partial_hash.rs new file mode 100644 index 0000000..03170c6 --- /dev/null +++ b/src/executor/partial_hash.rs @@ -0,0 +1,38 @@ +use std::hash::{Hasher, Hash}; + +use super::super::representations::typed::{Clause, Expr}; +use super::super::utils::Stackframe; + +/// Hash the parts of an expression that are required to be equal for syntactic equality. +pub fn partial_hash_rec(Expr(clause, _): &Expr, state: &mut H, is_auto: Stackframe) { + match clause { + // Skip autos and explicits + Clause::Auto(_, body) => partial_hash_rec(body, state, is_auto.push(true)), + Clause::Explicit(f, _) => partial_hash_rec(f, state, is_auto), + // Annotate everything else with a prefix + // - Recurse into the tree of lambdas and calls - classic lambda calc + Clause::Lambda(_, body) => { + state.write_u8(0); + partial_hash_rec(body, state, is_auto.push(false)) + } + Clause::Apply(f, x) => { + state.write_u8(1); + partial_hash_rec(f, state, is_auto); + partial_hash_rec(x, state, is_auto); + } + // - Only recognize the depth of an argument if it refers to a non-auto parameter + Clause::Argument(depth) => { + // If the argument references an auto, acknowledge its existence + if *is_auto.iter().nth(*depth).unwrap_or(&false) { + state.write_u8(2) + } else { + state.write_u8(3); + state.write_usize(*depth) + } + } + // - Hash leaves like normal + Clause::Literal(lit) => { state.write_u8(4); lit.hash(state) } + Clause::Atom(at) => { state.write_u8(5); at.hash(state) } + Clause::ExternFn(f) => { state.write_u8(6); f.hash(state) } + } +} \ No newline at end of file diff --git a/src/executor/reduction_tree.rs b/src/executor/reduction_tree.rs new file mode 100644 index 0000000..73cf328 --- /dev/null +++ b/src/executor/reduction_tree.rs @@ -0,0 +1,102 @@ +use mappable_rc::Mrc; + +use crate::box_chain; +use crate::utils::BoxedIter; +use crate::utils::iter::{box_once, box_empty}; + +use super::apply_lambda::apply_lambda; +use super::super::representations::typed::{Clause, Expr}; + +/// Call the function with the first Expression that isn't an Auto, +/// wrap all elements in the returned iterator back in the original sequence of Autos. +fn skip_autos<'a, + F: 'a + FnOnce(Mrc, usize) -> I, + I: Iterator> + 'static +>( + depth: usize, expr: Mrc, function: F +) -> BoxedIter<'static, Mrc> { + match expr.as_ref() { + Expr(Clause::Auto(arg, body), typ) => { + return Box::new(skip_autos(depth + 1, Mrc::clone(body), function).map({ + let arg = arg.as_ref().map(Mrc::clone); + let typ = Mrc::clone(typ); + move |body| { + Mrc::new(Expr(Clause::Auto( + arg.as_ref().map(Mrc::clone), + body + ), Mrc::clone(&typ))) + } + })) as BoxedIter<'static, Mrc> + } + Expr(Clause::Explicit(expr, targ), typ) => { + return Box::new(skip_autos(depth, Mrc::clone(expr), function).map({ + let (targ, typ) = (Mrc::clone(targ), Mrc::clone(typ)); + move |expr| { + Mrc::new(Expr(Clause::Explicit(expr, Mrc::clone(&targ)), Mrc::clone(&typ))) + } + })) as BoxedIter<'static, Mrc> + } + _ => () + } + Box::new(function(expr, depth)) +} + +/// Produces an iterator of every expression that can be produced from this one through B-reduction. +fn direct_reductions(ex: Mrc) -> impl Iterator> { + skip_autos(0, ex, |mexpr, _| { + let Expr(clause, typ_ref) = mexpr.as_ref(); + match clause { + Clause::Apply(f, x) => box_chain!( + skip_autos(0, Mrc::clone(f), |mexpr, _| { + let Expr(f, _) = mexpr.as_ref(); + match f { + Clause::Lambda(_, body) => box_once( + apply_lambda(Mrc::clone(body), Mrc::clone(x)) + ), + Clause::ExternFn(xfn) => { + let Expr(xval, xtyp) = x.as_ref(); + xfn.apply(xval.clone()) + .map(|ret| box_once(Mrc::new(Expr(ret, Mrc::clone(xtyp))))) + .unwrap_or(box_empty()) + }, + // Parametric newtypes are atoms of function type + Clause::Atom(..) | Clause::Argument(..) | Clause::Apply(..) => box_empty(), + Clause::Literal(lit) => + panic!("Literal expression {lit:?} can't be applied as function"), + Clause::Auto(..) | Clause::Explicit(..) => + unreachable!("skip_autos should have filtered these"), + } + }), + direct_reductions(Mrc::clone(f)).map({ + let typ = Mrc::clone(typ_ref); + let x = Mrc::clone(x); + move |f| Mrc::new(Expr(Clause::Apply( + f, + Mrc::clone(&x) + ), Mrc::clone(&typ))) + }), + direct_reductions(Mrc::clone(x)).map({ + let typ = Mrc::clone(typ_ref); + let f = Mrc::clone(f); + move |x| Mrc::new(Expr(Clause::Apply( + Mrc::clone(&f), + x + ), Mrc::clone(&typ))) + }) + ), + Clause::Lambda(argt, body) => Box::new(direct_reductions(Mrc::clone(body)).map({ + let typ = Mrc::clone(typ_ref); + let argt = argt.as_ref().map(Mrc::clone); + move |body| Mrc::new(Expr(Clause::Lambda( + argt.as_ref().map(Mrc::clone), + body + ), Mrc::clone(&typ))) + })), + Clause::Literal(..) | Clause::ExternFn(..) | Clause::Atom(..) | Clause::Argument(..) => + box_empty(), + Clause::Auto(..) | Clause::Explicit(..) => + unreachable!("skip_autos should have filtered these"), + } + }) +} + diff --git a/src/executor/syntax_eq.rs b/src/executor/syntax_eq.rs new file mode 100644 index 0000000..b65287d --- /dev/null +++ b/src/executor/syntax_eq.rs @@ -0,0 +1,41 @@ +use std::hash::{Hasher, Hash}; + +use super::super::representations::typed::{Clause, Expr}; +use super::super::utils::Stackframe; + +/// Hash the parts of an expression that are required to be equal for syntactic equality. +pub fn syntax_eq_rec( + ex1: &Expr, ex1_stack: Stackframe, + ex2: &Expr, ex2_stack: Stackframe +) -> bool { + match clause { + // Skip autos and explicits + Clause::Auto(_, body) => partial_hash_rec(body, state, is_auto.push(true)), + Clause::Explicit(f, _) => partial_hash_rec(f, state, is_auto), + // Annotate everything else with a prefix + // - Recurse into the tree of lambdas and calls - classic lambda calc + Clause::Lambda(_, body) => { + state.write_u8(0); + partial_hash_rec(body, state, is_auto.push(false)) + } + Clause::Apply(f, x) => { + state.write_u8(1); + partial_hash_rec(f, state, is_auto); + partial_hash_rec(x, state, is_auto); + } + // - Only recognize the depth of an argument if it refers to a non-auto parameter + Clause::Argument(depth) => { + // If the argument references an auto, acknowledge its existence + if *is_auto.iter().nth(*depth).unwrap_or(&false) { + state.write_u8(2) + } else { + state.write_u8(3); + state.write_usize(*depth) + } + } + // - Hash leaves like normal + Clause::Literal(lit) => { state.write_u8(4); lit.hash(state) } + Clause::Atom(at) => { state.write_u8(5); at.hash(state) } + Clause::ExternFn(f) => { state.write_u8(6); f.hash(state) } + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index bc71a69..0e5231a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ #![feature(specialization)] +#![feature(core_intrinsics)] use std::env::current_dir; diff --git a/src/parse/expression.rs b/src/parse/expression.rs index 57cb228..34e1b53 100644 --- a/src/parse/expression.rs +++ b/src/parse/expression.rs @@ -1,4 +1,5 @@ use chumsky::{self, prelude::*, Parser}; +use mappable_rc::Mrc; use crate::enum_parser; use crate::representations::{Literal, ast::{Clause, Expr}}; use crate::utils::{to_mrc_slice, one_mrc_slice}; @@ -112,9 +113,8 @@ pub fn xpr_parser() -> impl Parser> { sexpr_parser(expr.clone()), lambda_parser(expr.clone()), auto_parser(expr.clone()), - just(Lexeme::At).to(Clause::Name { - local: Some("@".to_string()), - qualified: one_mrc_slice("@".to_string()) + just(Lexeme::At).ignore_then(expr.clone()).map(|arg| { + Clause::Explicit(Mrc::new(arg)) }) ))).then_ignore(enum_parser!(Lexeme::Comment).repeated()); clause.clone().then( diff --git a/src/project/rule_collector.rs b/src/project/rule_collector.rs index 472db1f..918ea4b 100644 --- a/src/project/rule_collector.rs +++ b/src/project/rule_collector.rs @@ -157,8 +157,13 @@ where .filter_map(|ent| { if let FileEntry::Rule(Rule{source, prio, target}, _) = ent { Some(Rule { - source: source.iter().map(|ex| prefix_expr(ex, Mrc::clone(&path))).collect(), - target: target.iter().map(|ex| prefix_expr(ex, Mrc::clone(&path))).collect(), + source: source.iter() + .map(|ex| { + prefix_expr(ex, Mrc::clone(&path)) + }).collect(), + target: target.iter().map(|ex| { + prefix_expr(ex, Mrc::clone(&path)) + }).collect(), prio: *prio, }) } else { None } diff --git a/src/representations/ast.rs b/src/representations/ast.rs index 0ddd4f5..0eaef53 100644 --- a/src/representations/ast.rs +++ b/src/representations/ast.rs @@ -1,15 +1,22 @@ use mappable_rc::Mrc; use itertools::Itertools; use ordered_float::NotNan; -use std::hash::Hash; +use std::{hash::Hash, intrinsics::likely}; use std::fmt::Debug; -use crate::executor::{ExternFn, Atom}; +use crate::utils::mrc_empty_slice; +use crate::{executor::{ExternFn, Atom}, utils::one_mrc_slice}; use super::Literal; /// An S-expression with a type #[derive(PartialEq, Eq, Hash)] pub struct Expr(pub Clause, pub Mrc<[Clause]>); +impl Expr { + pub fn into_clause(self) -> Clause { + if likely(self.1.len() == 0) { self.0 } + else { Clause::S('(', one_mrc_slice(self)) } + } +} impl Clone for Expr { fn clone(&self) -> Self { @@ -37,6 +44,7 @@ pub enum Clause { qualified: Mrc<[String]> }, S(char, Mrc<[Expr]>), + Explicit(Mrc), Lambda(String, Mrc<[Expr]>, Mrc<[Expr]>), Auto(Option, Mrc<[Expr]>, Mrc<[Expr]>), ExternFn(ExternFn), @@ -53,37 +61,49 @@ pub enum Clause { impl Clause { pub fn body(&self) -> Option> { match self { - Clause::Auto(_, _, body) | - Clause::Lambda(_, _, body) | - Clause::S(_, body) => Some(Mrc::clone(body)), + Self::Auto(_, _, body) | + Self::Lambda(_, _, body) | + Self::S(_, body) => Some(Mrc::clone(body)), _ => None } } pub fn typ(&self) -> Option> { match self { - Clause::Auto(_, typ, _) | Clause::Lambda(_, typ, _) => Some(Mrc::clone(typ)), + Self::Auto(_, typ, _) | Self::Lambda(_, typ, _) => Some(Mrc::clone(typ)), _ => None } } + pub fn into_expr(self) -> Expr { + if let Self::S('(', body) = &self { + if body.len() == 1 { body[0].clone() } + else { Expr(self, mrc_empty_slice()) } + } else { Expr(self, mrc_empty_slice()) } + } + pub fn from_exprv(exprv: Mrc<[Expr]>) -> Option { + if exprv.len() == 0 { None } + else if exprv.len() == 1 { Some(exprv[0].clone().into_clause()) } + else { Some(Self::S('(', exprv)) } + } } impl Clone for Clause { fn clone(&self) -> Self { match self { - Clause::S(c, b) => Clause::S(*c, Mrc::clone(b)), - Clause::Auto(n, t, b) => Clause::Auto( + Self::S(c, b) => Self::S(*c, Mrc::clone(b)), + Self::Auto(n, t, b) => Self::Auto( n.clone(), Mrc::clone(t), Mrc::clone(b) ), - Clause::Name { local: l, qualified: q } => Clause::Name { + Self::Name { local: l, qualified: q } => Self::Name { local: l.clone(), qualified: Mrc::clone(q) }, - Clause::Lambda(n, t, b) => Clause::Lambda( + Self::Lambda(n, t, b) => Self::Lambda( n.clone(), Mrc::clone(t), Mrc::clone(b) ), - Clause::Placeh{key, vec} => Clause::Placeh{key: key.clone(), vec: *vec}, - Clause::Literal(l) => Clause::Literal(l.clone()), - Clause::ExternFn(nc) => Clause::ExternFn(nc.clone()), - Clause::Atom(a) => Clause::Atom(a.clone()) + 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::Explicit(expr) => Self::Explicit(Mrc::clone(expr)) } } } @@ -127,7 +147,8 @@ impl Debug for Clause { 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::Atom(a) => write!(f, "{a:?}"), + Self::Explicit(expr) => write!(f, "@{:?}", expr.as_ref()) } } } diff --git a/src/representations/ast_to_typed.rs b/src/representations/ast_to_typed.rs index b61234c..6801cd8 100644 --- a/src/representations/ast_to_typed.rs +++ b/src/representations/ast_to_typed.rs @@ -1,6 +1,6 @@ use mappable_rc::Mrc; -use crate::utils::{Stackframe, to_mrc_slice}; +use crate::utils::{Stackframe, to_mrc_slice, mrc_empty_slice}; use super::{ast, typed}; @@ -26,7 +26,9 @@ pub enum Error { /// /// [expr] handles this case, so it's only really possible to get this /// error if you're calling [clause] directly - ExprToClause(typed::Expr) + 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 @@ -44,21 +46,37 @@ pub fn exprv(exprv: &[ast::Expr]) -> Result { exprv_rec(exprv, Stackframe::new(None)) } +fn apply_rec(f: typed::Expr, x: &ast::Expr, names: Stackframe>) +-> Result { + if let ast::Expr(ast::Clause::Explicit(inner), empty_slice) = x { + 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)?; + Ok(typed::Clause::Explicit(Mrc::new(f), Mrc::new(x))) + } else { + let x = expr_rec(x, names)?; + Ok(typed::Clause::Apply(Mrc::new(f), Mrc::new(x))) + } +} + /// Recursive state of [exprv] fn exprv_rec(v: &[ast::Expr], names: Stackframe>) -> Result { if v.len() == 0 {return Err(Error::EmptyS)} if v.len() == 1 {return expr_rec(&v[0], names)} let (head, tail) = v.split_at(2); let f = expr_rec(&head[0], names)?; - let x = expr_rec(&head[1], names)?; + let x = &head[1]; // TODO this could probably be normalized, it's a third copy. - tail.iter().map(|e| expr_rec(e, names)).fold( - Ok(typed::Clause::Apply(Mrc::new(f), Mrc::new(x))), - |acc, e| Ok(typed::Clause::Apply( - Mrc::new(typed::Expr(acc?, to_mrc_slice(vec![]))), - Mrc::new(e?) - )) - ).map(|cls| typed::Expr(cls, to_mrc_slice(vec![]))) + tail.iter().fold( + apply_rec(f, x, names), + |acc, e| apply_rec( + typed::Expr(acc?, mrc_empty_slice()), + e, + names + ) + ).map(|cls| typed::Expr(cls, mrc_empty_slice())) } /// Recursive state of [expr] @@ -115,6 +133,7 @@ fn clause_rec(cls: &ast::Clause, names: Stackframe>) else {Err(Error::ExprToClause(typed::Expr(val, typ)))} }, ast::Clause::Name { local: None, .. } => Err(Error::Symbol), - ast::Clause::Placeh { .. } => Err(Error::Placeholder) + ast::Clause::Placeh { .. } => Err(Error::Placeholder), + ast::Clause::Explicit(..) => Err(Error::NonInfixAt) } } \ No newline at end of file diff --git a/src/rule/executor/execute.rs b/src/rule/executor/execute.rs index e1dee7f..57a0c27 100644 --- a/src/rule/executor/execute.rs +++ b/src/rule/executor/execute.rs @@ -1,8 +1,13 @@ use hashbrown::HashMap; use mappable_rc::Mrc; -use crate::{ast::{Expr, Clause}, utils::{iter::{box_once, into_boxed_iter}, to_mrc_slice, one_mrc_slice}, unwrap_or}; -use super::{super::RuleError, state::{State, Entry}, slice_matcher::SliceMatcherDnC}; +use crate::unwrap_or; +use crate::utils::{to_mrc_slice, one_mrc_slice, mrc_empty_slice}; +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; fn verify_scalar_vec(pattern: &Expr, is_vec: &mut HashMap) -> Result<(), String> { @@ -86,10 +91,14 @@ where F: FnMut(Mrc<[Expr]>) -> Option> { None } -/// Fill in a template from a state as produced by a pattern -fn write_slice(state: &State, tpl: &Mrc<[Expr]>) -> Mrc<[Expr]> { - eprintln!("Writing {tpl:?} with state {state:?}"); - tpl.iter().flat_map(|Expr(clause, xpr_typ)| match clause { +// fn write_clause_rec(state: &State, clause: &Clause) -> + +fn write_expr_rec(state: &State, Expr(tpl_clause, tpl_typ): &Expr) -> Box> { + let out_typ = tpl_typ.iter() + .flat_map(|c| write_expr_rec(state, &c.clone().into_expr())) + .map(Expr::into_clause) + .collect::>(); + match tpl_clause { Clause::Auto(name_opt, typ, body) => box_once(Expr(Clause::Auto( name_opt.as_ref().and_then(|name| { if let Some(state_key) = name.strip_prefix('$') { @@ -102,9 +111,9 @@ fn write_slice(state: &State, tpl: &Mrc<[Expr]>) -> Mrc<[Expr]> { Some(name.to_owned()) } }), - write_slice(state, typ), - write_slice(state, body) - ), xpr_typ.to_owned())), + write_slice_rec(state, typ), + write_slice_rec(state, body) + ), out_typ.to_owned())), Clause::Lambda(name, typ, body) => box_once(Expr(Clause::Lambda( if let Some(state_key) = name.strip_prefix('$') { if let Entry::Name(name) = &state[state_key] { @@ -113,13 +122,13 @@ fn write_slice(state: &State, tpl: &Mrc<[Expr]>) -> Mrc<[Expr]> { } else { name.to_owned() }, - write_slice(state, typ), - write_slice(state, body) - ), xpr_typ.to_owned())), + write_slice_rec(state, typ), + write_slice_rec(state, body) + ), out_typ.to_owned())), Clause::S(c, body) => box_once(Expr(Clause::S( *c, - write_slice(state, body) - ), xpr_typ.to_owned())), + write_slice_rec(state, body) + ), out_typ.to_owned())), Clause::Placeh{key, vec: None} => { let real_key = unwrap_or!(key.strip_prefix('_'); key); match &state[real_key] { @@ -127,17 +136,30 @@ fn write_slice(state: &State, tpl: &Mrc<[Expr]>) -> Mrc<[Expr]> { Entry::Name(n) => box_once(Expr(Clause::Name { local: Some(n.as_ref().to_owned()), qualified: one_mrc_slice(n.as_ref().to_owned()) - }, to_mrc_slice(vec![]))), + }, mrc_empty_slice())), _ => panic!("Scalar template may only be derived from scalar placeholder"), } }, Clause::Placeh{key, vec: Some(_)} => if let Entry::Vec(v) = &state[key] { into_boxed_iter(v.as_ref().to_owned()) } else {panic!("Vectorial template may only be derived from vectorial placeholder")}, + Clause::Explicit(param) => { + assert!(out_typ.len() == 0, "Explicit should never have a type annotation"); + box_once(Clause::Explicit(Mrc::new( + Clause::from_exprv(write_expr_rec(state, param).collect()) + .expect("Result shorter than template").into_expr() + )).into_expr()) + }, // Explicit base case so that we get an error if Clause gets new values c@Clause::Literal(_) | c@Clause::Name { .. } | c@Clause::ExternFn(_) | c@Clause::Atom(_) => - box_once(Expr(c.to_owned(), xpr_typ.to_owned())) - }).collect() + 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() } /// Apply a rule (a pair of pattern and template) to an expression @@ -156,6 +178,6 @@ pub fn execute(mut src: Mrc<[Expr]>, mut tgt: Mrc<[Expr]>, input: Mrc<[Expr]>) let matcher_cache = SliceMatcherDnC::get_matcher_cache(); Ok(update_all_seqs(Mrc::clone(&input), &mut |p| { let state = matcher.match_range_cached(p, &matcher_cache)?; - Some(write_slice(&state, &tgt)) + Some(write_slice_rec(&state, &tgt)) })) } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index a97bd1f..35a1ca5 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -28,6 +28,10 @@ where P: for<'a> FnOnce(&'a T) -> Option<&'a U> { Mrc::try_map(Mrc::clone(m), p).ok() } +pub fn mrc_empty_slice() -> Mrc<[T]> { + mrc_derive_slice(&Mrc::new(Vec::new())) +} + pub fn to_mrc_slice(v: Vec) -> Mrc<[T]> { Mrc::map(Mrc::new(v), |v| v.as_slice()) }