forked from Orchid/orchid
Backup commit
My backspace key started ghosting. Nothing works atm.
This commit is contained in:
@@ -1,113 +1,188 @@
|
||||
use super::context::Context;
|
||||
use super::error::RuntimeError;
|
||||
use super::Return;
|
||||
use crate::foreign::AtomicReturn;
|
||||
use crate::representations::interpreted::{Clause, ExprInst};
|
||||
use crate::representations::PathSet;
|
||||
use crate::utils::never::{unwrap_always, Always};
|
||||
use crate::utils::Side;
|
||||
use std::collections::VecDeque;
|
||||
use std::mem;
|
||||
|
||||
use never::Never;
|
||||
|
||||
use super::context::RunContext;
|
||||
use super::error::RunError;
|
||||
use super::nort::{Clause, ClauseInst, Expr};
|
||||
use super::path_set::{PathSet, Step};
|
||||
use super::run::run;
|
||||
use crate::location::CodeLocation;
|
||||
|
||||
/// Information about a function call presented to an external function
|
||||
pub struct CallData<'a> {
|
||||
/// Location of the function expression
|
||||
pub location: CodeLocation,
|
||||
/// The argument the function was called on. Functions are curried
|
||||
pub arg: Expr,
|
||||
/// Information relating to this interpreter run
|
||||
pub ctx: RunContext<'a>,
|
||||
}
|
||||
|
||||
/// Process the clause at the end of the provided path. Note that paths always
|
||||
/// point to at least one target. Note also that this is not cached as a
|
||||
/// normalization step in the intermediate expressions.
|
||||
fn map_at<E>(
|
||||
path: &[Side],
|
||||
source: ExprInst,
|
||||
mapper: &mut impl FnMut(Clause) -> Result<Clause, E>,
|
||||
) -> Result<ExprInst, E> {
|
||||
source
|
||||
.try_update(|value, _loc| {
|
||||
// Pass right through lambdas
|
||||
if let Clause::Lambda { args, body } = value {
|
||||
return Ok((
|
||||
Clause::Lambda { args, body: 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 Ok((mapper(value)?, ()));
|
||||
mut path: impl Iterator<Item = Step>,
|
||||
source: &Clause,
|
||||
mapper: &mut impl FnMut(&Clause) -> Result<Clause, E>,
|
||||
) -> Result<Clause, E> {
|
||||
// Pass through some unambiguous wrapper clauses
|
||||
match source {
|
||||
Clause::Identity(alt) => return map_at(path, &alt.cls(), mapper),
|
||||
Clause::Lambda { args, body: Expr { location: b_loc, clause } } =>
|
||||
return Ok(Clause::Lambda {
|
||||
args: args.clone(),
|
||||
body: Expr {
|
||||
clause: map_at(path, &clause.cls(), mapper)?.to_inst(),
|
||||
location: b_loc.clone(),
|
||||
},
|
||||
}),
|
||||
_ => (),
|
||||
}
|
||||
Ok(match (source, path.next()) {
|
||||
(Clause::Lambda { .. } | Clause::Identity(_), _) =>
|
||||
unreachable!("Handled above"),
|
||||
// If the path ends and this isn't a lambda, process it
|
||||
(val, None) => mapper(val)?,
|
||||
// If it's an Apply, execute the next step in the path
|
||||
(Clause::Apply { f, x }, Some(head)) => {
|
||||
let proc = |x: &Expr| {
|
||||
Ok(map_at(path, &x.clause.cls(), mapper)?.to_expr(x.location()))
|
||||
};
|
||||
// If it's an Apply, execute the next step in the path
|
||||
if let Clause::Apply { f, x } = value {
|
||||
return Ok((
|
||||
match head {
|
||||
Side::Left => Clause::Apply { f: map_at(tail, f, mapper)?, x },
|
||||
Side::Right => Clause::Apply { f, x: map_at(tail, x, mapper)? },
|
||||
},
|
||||
(),
|
||||
));
|
||||
match head {
|
||||
None => Clause::Apply { f: proc(f)?, x: x.clone() },
|
||||
Some(n) => {
|
||||
let i = x.len() - n - 1;
|
||||
let mut argv = x.clone();
|
||||
argv[i] = proc(&x[i])?;
|
||||
Clause::Apply { f: f.clone(), x: argv }
|
||||
},
|
||||
}
|
||||
panic!("Invalid path")
|
||||
})
|
||||
.map(|p| p.0)
|
||||
},
|
||||
(_, Some(_)) => panic!("Path leads into node that isn't Apply or Lambda"),
|
||||
})
|
||||
}
|
||||
|
||||
/// Replace the [Clause::LambdaArg] placeholders at the ends of the [PathSet]
|
||||
/// with the value in the body. Note that a path may point to multiple
|
||||
/// placeholders.
|
||||
#[must_use]
|
||||
fn substitute(paths: &PathSet, value: Clause, body: ExprInst) -> ExprInst {
|
||||
fn substitute(paths: &PathSet, value: ClauseInst, body: &Clause) -> Clause {
|
||||
let PathSet { steps, next } = paths;
|
||||
unwrap_always(map_at(steps, body, &mut |checkpoint| -> Always<Clause> {
|
||||
match (checkpoint, next) {
|
||||
(Clause::Lambda { .. }, _) => unreachable!("Handled by map_at"),
|
||||
(Clause::Apply { f, x }, Some((left, right))) => Ok(Clause::Apply {
|
||||
f: substitute(left, value.clone(), f),
|
||||
x: substitute(right, value.clone(), x),
|
||||
}),
|
||||
(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")
|
||||
map_at(steps.iter().cloned(), body, &mut |chkpt| -> Result<Clause, Never> {
|
||||
match (chkpt, next) {
|
||||
(Clause::Lambda { .. } | Clause::Identity(_), _) => {
|
||||
unreachable!("Handled by map_at")
|
||||
},
|
||||
(Clause::Apply { f, x }, Some(conts)) => {
|
||||
let mut argv = x.clone();
|
||||
let f = match conts.get(&None) {
|
||||
None => f.clone(),
|
||||
Some(sp) => substitute(sp, value.clone(), &f.clause.cls())
|
||||
.to_expr(f.location()),
|
||||
};
|
||||
for (i, old) in argv.iter_mut().rev().enumerate() {
|
||||
if let Some(sp) = conts.get(&Some(i)) {
|
||||
let tmp = substitute(sp, value.clone(), &old.clause.cls());
|
||||
*old = tmp.to_expr(old.location());
|
||||
}
|
||||
}
|
||||
Ok(Clause::Apply { f, x: argv })
|
||||
},
|
||||
(Clause::LambdaArg, None) => Ok(Clause::Identity(value.clone())),
|
||||
(_, None) => panic!("Argument path must point to LambdaArg"),
|
||||
(_, Some(_)) => panic!("Argument path can only fork at Apply"),
|
||||
}
|
||||
}))
|
||||
})
|
||||
.unwrap_or_else(|e| match e {})
|
||||
}
|
||||
|
||||
pub(super) fn apply_as_atom(
|
||||
f: Expr,
|
||||
arg: Expr,
|
||||
ctx: RunContext,
|
||||
) -> Result<Clause, RunError> {
|
||||
let call = CallData { location: f.location(), arg, ctx };
|
||||
match f.clause.try_unwrap() {
|
||||
Ok(clause) => match clause {
|
||||
Clause::Atom(atom) => Ok(atom.apply(call)?),
|
||||
_ => panic!("Not an atom"),
|
||||
},
|
||||
Err(clsi) => match &*clsi.cls() {
|
||||
Clause::Atom(atom) => Ok(atom.apply_ref(call)?),
|
||||
_ => panic!("Not an atom"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply a function-like expression to a parameter.
|
||||
pub fn apply(
|
||||
f: ExprInst,
|
||||
x: ExprInst,
|
||||
ctx: Context,
|
||||
) -> Result<Return, RuntimeError> {
|
||||
let (state, (gas, inert)) = f.try_update(|clause, loc| match clause {
|
||||
// apply an ExternFn or an internal function
|
||||
Clause::ExternFn(f) => {
|
||||
let clause = f.apply(x, ctx.clone()).map_err(RuntimeError::Extern)?;
|
||||
Ok((clause, (ctx.gas.map(|g| g - 1), false)))
|
||||
},
|
||||
Clause::Lambda { args, body } => Ok(if let Some(args) = args {
|
||||
let x_cls = x.expr_val().clause;
|
||||
let result = substitute(&args, x_cls, body);
|
||||
// cost of substitution
|
||||
// XXX: should this be the number of occurrences instead?
|
||||
(result.expr_val().clause, (ctx.gas.map(|x| x - 1), false))
|
||||
} else {
|
||||
(body.expr_val().clause, (ctx.gas, false))
|
||||
}),
|
||||
Clause::Constant(name) =>
|
||||
if let Some(sym) = ctx.symbols.get(&name) {
|
||||
Ok((Clause::Apply { f: sym.clone(), x }, (ctx.gas, false)))
|
||||
} else {
|
||||
Err(RuntimeError::MissingSymbol(name.clone(), loc))
|
||||
pub(super) fn apply(
|
||||
mut f: Expr,
|
||||
mut argv: VecDeque<Expr>,
|
||||
mut ctx: RunContext,
|
||||
) -> Result<(Option<usize>, Clause), RunError> {
|
||||
// allow looping but break on the main path so that `continue` functions as a
|
||||
// trampoline
|
||||
loop {
|
||||
if argv.is_empty() {
|
||||
return Ok((ctx.gas, f.clause.into_cls()));
|
||||
} else if ctx.gas == Some(0) {
|
||||
return Ok((Some(0), Clause::Apply { f, x: argv }));
|
||||
}
|
||||
let mut f_cls = f.clause.cls_mut();
|
||||
match &mut *f_cls {
|
||||
// apply an ExternFn or an internal function
|
||||
Clause::Atom(_) => {
|
||||
mem::drop(f_cls);
|
||||
// take a step in expanding atom
|
||||
let halt = run(f, ctx.clone())?;
|
||||
ctx.gas = halt.gas;
|
||||
if halt.inert && halt.state.clause.is_atom() {
|
||||
let arg = argv.pop_front().expect("checked above");
|
||||
let loc = halt.state.location();
|
||||
f = apply_as_atom(halt.state, arg, ctx.clone())?.to_expr(loc)
|
||||
} else {
|
||||
f = halt.state
|
||||
}
|
||||
},
|
||||
Clause::Atom(atom) => {
|
||||
// take a step in expanding atom
|
||||
let AtomicReturn { clause, gas, inert } = atom.run(ctx.clone())?;
|
||||
Ok((Clause::Apply { f: clause.wrap(), x }, (gas, inert)))
|
||||
},
|
||||
Clause::Apply { f: fun, x: arg } => {
|
||||
// take a step in resolving pre-function
|
||||
let ret = apply(fun, arg, ctx.clone())?;
|
||||
let Return { state, inert, gas } = ret;
|
||||
Ok((Clause::Apply { f: state, x }, (gas, inert)))
|
||||
},
|
||||
_ => Err(RuntimeError::NonFunctionApplication(loc)),
|
||||
})?;
|
||||
Ok(Return { state, gas, inert })
|
||||
Clause::Lambda { args, body } => {
|
||||
match args {
|
||||
None => *f_cls = body.clause.clone().into_cls(),
|
||||
Some(args) => {
|
||||
let arg = argv.pop_front().expect("checked above").clause.clone();
|
||||
let cls = substitute(args, arg, &body.clause.cls());
|
||||
// cost of substitution
|
||||
// XXX: should this be the number of occurrences instead?
|
||||
ctx.use_gas(1);
|
||||
mem::drop(f_cls);
|
||||
f = cls.to_expr(f.location());
|
||||
},
|
||||
}
|
||||
},
|
||||
Clause::Constant(name) => {
|
||||
let name = name.clone();
|
||||
mem::drop(f_cls);
|
||||
f = (ctx.symbols.get(&name).cloned())
|
||||
.ok_or_else(|| RunError::MissingSymbol(name, f.location()))?;
|
||||
ctx.use_gas(1);
|
||||
},
|
||||
Clause::Apply { f: fun, x } => {
|
||||
for item in x.drain(..).rev() {
|
||||
argv.push_front(item)
|
||||
}
|
||||
let tmp = fun.clone();
|
||||
mem::drop(f_cls);
|
||||
f = tmp;
|
||||
},
|
||||
Clause::Identity(f2) => {
|
||||
let tmp = f2.clone();
|
||||
mem::drop(f_cls);
|
||||
f.clause = tmp
|
||||
},
|
||||
Clause::Bottom(bottom) => return Err(bottom.clone()),
|
||||
Clause::LambdaArg => panic!("Leftover argument marker"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,38 @@
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use crate::interner::Interner;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::Sym;
|
||||
use super::nort::Expr;
|
||||
use crate::name::Sym;
|
||||
|
||||
/// All the data associated with an interpreter run
|
||||
#[derive(Clone)]
|
||||
pub struct Context<'a> {
|
||||
pub struct RunContext<'a> {
|
||||
/// Table used to resolve constants
|
||||
pub symbols: &'a HashMap<Sym, ExprInst>,
|
||||
/// The interner used for strings internally, so external functions can
|
||||
/// deduce referenced constant names on the fly
|
||||
pub interner: &'a Interner,
|
||||
pub symbols: &'a HashMap<Sym, Expr>,
|
||||
/// The number of reduction steps the interpreter can take before returning
|
||||
pub gas: Option<usize>,
|
||||
}
|
||||
impl<'a> RunContext<'a> {
|
||||
/// Consume some gas if it is being counted
|
||||
pub fn use_gas(&mut self, amount: usize) {
|
||||
if let Some(g) = self.gas.as_mut() {
|
||||
*g = g.saturating_sub(amount)
|
||||
}
|
||||
}
|
||||
/// Gas is being counted and there is none left
|
||||
pub fn no_gas(&self) -> bool { self.gas == Some(0) }
|
||||
}
|
||||
|
||||
/// All the data produced by an interpreter run
|
||||
#[derive(Clone)]
|
||||
pub struct Return {
|
||||
pub struct Halt {
|
||||
/// The new expression tree
|
||||
pub state: ExprInst,
|
||||
pub state: Expr,
|
||||
/// Leftover [Context::gas] if counted
|
||||
pub gas: Option<usize>,
|
||||
/// If true, the next run would not modify the expression
|
||||
pub inert: bool,
|
||||
}
|
||||
impl Return {
|
||||
impl Halt {
|
||||
/// Check if gas has run out. Returns false if gas is not being used
|
||||
pub fn preempted(&self) -> bool { self.gas.map_or(false, |g| g == 0) }
|
||||
/// Returns a general report of the return
|
||||
|
||||
@@ -1,37 +1,33 @@
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::foreign::ExternError;
|
||||
use crate::{Location, Sym};
|
||||
use crate::foreign::error::ExternError;
|
||||
use crate::location::CodeLocation;
|
||||
use crate::name::Sym;
|
||||
|
||||
use super::run::Interrupted;
|
||||
|
||||
/// Problems in the process of execution
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RuntimeError {
|
||||
pub enum RunError {
|
||||
/// A Rust function encountered an error
|
||||
Extern(Arc<dyn ExternError>),
|
||||
/// Primitive applied as function
|
||||
NonFunctionApplication(Location),
|
||||
/// Symbol not in context
|
||||
MissingSymbol(Sym, Location),
|
||||
MissingSymbol(Sym, CodeLocation),
|
||||
/// Ran out of gas
|
||||
Interrupted(Interrupted)
|
||||
}
|
||||
|
||||
impl From<Arc<dyn ExternError>> for RuntimeError {
|
||||
impl From<Arc<dyn ExternError>> for RunError {
|
||||
fn from(value: Arc<dyn ExternError>) -> Self { Self::Extern(value) }
|
||||
}
|
||||
|
||||
impl Display for RuntimeError {
|
||||
impl Display for RunError {
|
||||
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(location) => {
|
||||
write!(f, "Primitive applied as function at {}", location)
|
||||
},
|
||||
Self::MissingSymbol(sym, loc) => {
|
||||
write!(
|
||||
f,
|
||||
"{}, called at {loc} is not loaded",
|
||||
sym.extern_vec().join("::")
|
||||
)
|
||||
write!(f, "{sym}, called at {loc} is not loaded")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
126
src/interpreter/gen_nort.rs
Normal file
126
src/interpreter/gen_nort.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
//! Implementations of [Generable] for [super::nort]
|
||||
|
||||
use intern_all::i;
|
||||
|
||||
use super::nort_builder::NortBuilder;
|
||||
use crate::foreign::atom::Atom;
|
||||
use crate::foreign::to_clause::ToClause;
|
||||
use crate::gen::traits::Generable;
|
||||
use crate::interpreter::nort::{Clause, ClauseInst, Expr};
|
||||
use crate::location::CodeLocation;
|
||||
use crate::name::Sym;
|
||||
|
||||
/// Context data for instantiating templated expressions as [super::nort].
|
||||
/// Instances of this type are created via [nort_gen]
|
||||
pub type NortGenCtx<'a> = (CodeLocation, NortBuilder<'a, str, str>);
|
||||
|
||||
/// Create [NortGenCtx] instances to generate interpreted expressions
|
||||
pub fn nort_gen<'a>(location: CodeLocation) -> NortGenCtx<'a> {
|
||||
(location, NortBuilder::new(&|l| Box::new(move |r| l == r)))
|
||||
}
|
||||
|
||||
impl Generable for Expr {
|
||||
type Ctx<'a> = NortGenCtx<'a>;
|
||||
fn apply(
|
||||
ctx: Self::Ctx<'_>,
|
||||
f_cb: impl FnOnce(Self::Ctx<'_>) -> Self,
|
||||
x_cb: impl FnOnce(Self::Ctx<'_>) -> Self,
|
||||
) -> Self {
|
||||
(ctx
|
||||
.1
|
||||
.apply_logic(|c| f_cb((ctx.0.clone(), c)), |c| x_cb((ctx.0.clone(), c))))
|
||||
.to_expr(ctx.0.clone())
|
||||
}
|
||||
fn arg(ctx: Self::Ctx<'_>, name: &str) -> Self {
|
||||
Clause::arg(ctx.clone(), name).to_expr(ctx.0.clone())
|
||||
}
|
||||
fn atom(ctx: Self::Ctx<'_>, a: Atom) -> Self {
|
||||
Clause::atom(ctx.clone(), a).to_expr(ctx.0.clone())
|
||||
}
|
||||
fn constant<'a>(
|
||||
ctx: Self::Ctx<'_>,
|
||||
name: impl IntoIterator<Item = &'a str>,
|
||||
) -> Self {
|
||||
Clause::constant(ctx.clone(), name).to_expr(ctx.0.clone())
|
||||
}
|
||||
fn lambda(
|
||||
ctx: Self::Ctx<'_>,
|
||||
name: &str,
|
||||
body: impl FnOnce(Self::Ctx<'_>) -> Self,
|
||||
) -> Self {
|
||||
(ctx.1.lambda_logic(name, |c| body((ctx.0.clone(), c))))
|
||||
.to_expr(ctx.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Generable for ClauseInst {
|
||||
type Ctx<'a> = NortGenCtx<'a>;
|
||||
fn arg(ctx: Self::Ctx<'_>, name: &str) -> Self {
|
||||
Clause::arg(ctx, name).to_inst()
|
||||
}
|
||||
fn atom(ctx: Self::Ctx<'_>, a: Atom) -> Self {
|
||||
Clause::atom(ctx, a).to_inst()
|
||||
}
|
||||
fn constant<'a>(
|
||||
ctx: Self::Ctx<'_>,
|
||||
name: impl IntoIterator<Item = &'a str>,
|
||||
) -> Self {
|
||||
Clause::constant(ctx, name).to_inst()
|
||||
}
|
||||
fn lambda(
|
||||
ctx: Self::Ctx<'_>,
|
||||
name: &str,
|
||||
body: impl FnOnce(Self::Ctx<'_>) -> Self,
|
||||
) -> Self {
|
||||
(ctx
|
||||
.1
|
||||
.lambda_logic(name, |c| body((ctx.0.clone(), c)).to_expr(ctx.0.clone())))
|
||||
.to_clsi(ctx.0.clone())
|
||||
}
|
||||
fn apply(
|
||||
ctx: Self::Ctx<'_>,
|
||||
f: impl FnOnce(Self::Ctx<'_>) -> Self,
|
||||
x: impl FnOnce(Self::Ctx<'_>) -> Self,
|
||||
) -> Self {
|
||||
(ctx.1.apply_logic(
|
||||
|c| f((ctx.0.clone(), c)).to_expr(ctx.0.clone()),
|
||||
|c| x((ctx.0.clone(), c)).to_expr(ctx.0.clone()),
|
||||
))
|
||||
.to_clsi(ctx.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Generable for Clause {
|
||||
type Ctx<'a> = NortGenCtx<'a>;
|
||||
fn atom(_: Self::Ctx<'_>, a: Atom) -> Self { Clause::Atom(a) }
|
||||
fn constant<'a>(
|
||||
_: Self::Ctx<'_>,
|
||||
name: impl IntoIterator<Item = &'a str>,
|
||||
) -> Self {
|
||||
let sym = Sym::new(name.into_iter().map(i)).expect("Empty constant");
|
||||
Clause::Constant(sym)
|
||||
}
|
||||
fn apply(
|
||||
ctx: Self::Ctx<'_>,
|
||||
f: impl FnOnce(Self::Ctx<'_>) -> Self,
|
||||
x: impl FnOnce(Self::Ctx<'_>) -> Self,
|
||||
) -> Self {
|
||||
ctx.1.apply_logic(
|
||||
|c| f((ctx.0.clone(), c)).to_expr(ctx.0.clone()),
|
||||
|c| x((ctx.0.clone(), c)).to_expr(ctx.0.clone()),
|
||||
)
|
||||
}
|
||||
fn arg(ctx: Self::Ctx<'_>, name: &str) -> Self {
|
||||
ctx.1.arg_logic(name);
|
||||
Clause::LambdaArg
|
||||
}
|
||||
fn lambda(
|
||||
ctx: Self::Ctx<'_>,
|
||||
name: &str,
|
||||
body: impl FnOnce(Self::Ctx<'_>) -> Self,
|
||||
) -> Self {
|
||||
ctx
|
||||
.1
|
||||
.lambda_logic(name, |c| body((ctx.0.clone(), c)).to_expr(ctx.0.clone()))
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,19 @@
|
||||
use std::any::{Any, TypeId};
|
||||
use std::sync::Arc;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use trait_set::trait_set;
|
||||
|
||||
use super::{run, Context, Return, RuntimeError};
|
||||
use crate::foreign::{Atom, Atomic, ExternError};
|
||||
use crate::interpreted::{Clause, Expr, ExprInst};
|
||||
use crate::utils::take_with_output;
|
||||
use super::context::{Halt, RunContext};
|
||||
use super::error::RunError;
|
||||
use super::nort::{Clause, Expr};
|
||||
use super::run::run;
|
||||
use crate::foreign::atom::{Atom, Atomic};
|
||||
use crate::foreign::error::ExternResult;
|
||||
use crate::foreign::to_clause::ToClause;
|
||||
use crate::location::CodeLocation;
|
||||
|
||||
trait_set! {
|
||||
trait Handler = FnMut(Box<dyn Any>) -> HandlerRes;
|
||||
trait Handler = for<'a> FnMut(&'a dyn Any, CodeLocation) -> Expr;
|
||||
}
|
||||
|
||||
/// A table of command handlers
|
||||
@@ -23,26 +26,37 @@ impl<'a> HandlerTable<'a> {
|
||||
#[must_use]
|
||||
pub fn new() -> Self { Self { handlers: HashMap::new() } }
|
||||
|
||||
/// Add a handler function to interpret a type of atom and decide what happens
|
||||
/// next. This function can be impure.
|
||||
pub fn register<T: 'static>(
|
||||
/// Add a handler function to interpret a command and select the continuation.
|
||||
/// See [HandlerTable#with] for a declarative option.
|
||||
pub fn register<T: 'static, R: ToClause>(
|
||||
&mut self,
|
||||
mut f: impl FnMut(Box<T>) -> HandlerRes + 'a,
|
||||
mut f: impl for<'b> FnMut(&'b T) -> R + 'a,
|
||||
) {
|
||||
let cb = move |a: Box<dyn Any>| f(a.downcast().expect("found by TypeId"));
|
||||
let cb = move |a: &dyn Any, loc: CodeLocation| {
|
||||
f(a.downcast_ref().expect("found by TypeId")).to_expr(loc)
|
||||
};
|
||||
let prev = self.handlers.insert(TypeId::of::<T>(), Box::new(cb));
|
||||
assert!(prev.is_none(), "A handler for this type is already registered");
|
||||
}
|
||||
|
||||
/// Add a handler function to interpret a command and select the continuation.
|
||||
/// See [HandlerTable#register] for a procedural option.
|
||||
pub fn with<T: 'static>(
|
||||
mut self,
|
||||
f: impl FnMut(&T) -> ExternResult<Expr> + 'a,
|
||||
) -> Self {
|
||||
self.register(f);
|
||||
self
|
||||
}
|
||||
|
||||
/// Find and execute the corresponding handler for this type
|
||||
pub fn dispatch(
|
||||
&mut self,
|
||||
arg: Box<dyn Atomic>,
|
||||
) -> Result<HandlerRes, Box<dyn Atomic>> {
|
||||
match self.handlers.get_mut(&arg.as_any_ref().type_id()) {
|
||||
Some(f) => Ok(f(arg.as_any())),
|
||||
None => Err(arg),
|
||||
}
|
||||
arg: &dyn Atomic,
|
||||
loc: CodeLocation,
|
||||
) -> Option<Expr> {
|
||||
(self.handlers.get_mut(&arg.as_any_ref().type_id()))
|
||||
.map(|f| f(arg.as_any_ref(), loc))
|
||||
}
|
||||
|
||||
/// Combine two non-overlapping handler sets
|
||||
@@ -56,33 +70,27 @@ impl<'a> HandlerTable<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Various possible outcomes of a [Handler] execution. Ok returns control to
|
||||
/// the interpreter. The meaning of Err is decided by the value in it.
|
||||
pub type HandlerRes = Result<ExprInst, Arc<dyn ExternError>>;
|
||||
|
||||
/// [run] orchid code, executing any commands it returns using the specified
|
||||
/// [Handler]s.
|
||||
pub fn run_handler(
|
||||
mut expr: ExprInst,
|
||||
mut state: Expr,
|
||||
handlers: &mut HandlerTable,
|
||||
mut ctx: Context,
|
||||
) -> Result<Return, RuntimeError> {
|
||||
RunContext { mut gas, symbols }: RunContext,
|
||||
) -> Result<Halt, RunError> {
|
||||
loop {
|
||||
let mut ret = run(expr, ctx.clone())?;
|
||||
let quit = take_with_output(&mut ret.state, |exi| match exi.expr_val() {
|
||||
Expr { clause: Clause::Atom(a), .. } => match handlers.dispatch(a.0) {
|
||||
Err(b) => (Clause::Atom(Atom(b)).wrap(), Ok(true)),
|
||||
Ok(e) => match e {
|
||||
Ok(expr) => (expr, Ok(false)),
|
||||
Err(e) => (Clause::Bottom.wrap(), Err(e)),
|
||||
},
|
||||
},
|
||||
expr => (ExprInst::new(expr), Ok(true)),
|
||||
})?;
|
||||
if quit | ret.gas.map_or(false, |g| g == 0) {
|
||||
return Ok(ret);
|
||||
let inert;
|
||||
Halt { gas, inert, state } = run(state, RunContext { gas, symbols })?;
|
||||
let state_cls = state.clause.cls();
|
||||
if let Clause::Atom(Atom(a)) = &*state_cls {
|
||||
if let Some(res) = handlers.dispatch(a.as_ref(), state.location()) {
|
||||
drop(state_cls);
|
||||
state = res;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if inert || gas == Some(0) {
|
||||
drop(state_cls);
|
||||
break Ok(Halt { gas, inert, state });
|
||||
}
|
||||
ctx.gas = ret.gas;
|
||||
expr = ret.state;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
//! functions to interact with Orchid code
|
||||
mod apply;
|
||||
mod context;
|
||||
mod error;
|
||||
mod handler;
|
||||
mod run;
|
||||
|
||||
pub use context::{Context, Return, ReturnStatus};
|
||||
pub use error::RuntimeError;
|
||||
pub use handler::{run_handler, HandlerRes, HandlerTable};
|
||||
pub use run::run;
|
||||
pub mod apply;
|
||||
pub mod context;
|
||||
pub mod error;
|
||||
pub mod gen_nort;
|
||||
pub mod handler;
|
||||
pub mod nort_builder;
|
||||
pub mod nort;
|
||||
pub(crate) mod path_set;
|
||||
pub mod run;
|
||||
|
||||
334
src/interpreter/nort.rs
Normal file
334
src/interpreter/nort.rs
Normal file
@@ -0,0 +1,334 @@
|
||||
//! The NORT (Normal Order Referencing Tree) is the interpreter's runtime
|
||||
//! representation of Orchid programs.
|
||||
//!
|
||||
//! It uses a locator tree to find bound variables in lambda functions, which
|
||||
//! necessitates a normal reduction order because modifying the body by reducing
|
||||
//! expressions would invalidate any locators in enclosing lambdas.
|
||||
//!
|
||||
//! Clauses are held in a mutable `Arc<Mutex<_>>`, so that after substitution
|
||||
//! the instances of the argument remain linked and a reduction step applied to
|
||||
//! any instance transforms all of them.
|
||||
//!
|
||||
//! To improve locality and make the tree less deep and locators shorter,
|
||||
//! function calls store multiple arguments in a deque.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::{Arc, Mutex, TryLockError};
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::error::RunError;
|
||||
use super::path_set::PathSet;
|
||||
use crate::foreign::atom::Atom;
|
||||
#[allow(unused)] // for doc
|
||||
use crate::foreign::atom::Atomic;
|
||||
use crate::foreign::error::ExternResult;
|
||||
use crate::foreign::try_from_expr::TryFromExpr;
|
||||
use crate::location::CodeLocation;
|
||||
use crate::name::Sym;
|
||||
#[allow(unused)] // for doc
|
||||
use crate::parse::parsed;
|
||||
use crate::utils::ddispatch::request;
|
||||
use crate::utils::take_with_output::take_with_output;
|
||||
|
||||
/// Kinda like [AsMut] except it supports a guard
|
||||
pub(crate) trait AsDerefMut<T> {
|
||||
fn as_deref_mut(&mut self) -> impl DerefMut<Target = T> + '_;
|
||||
}
|
||||
|
||||
/// An expression with metadata
|
||||
#[derive(Clone)]
|
||||
pub struct Expr {
|
||||
/// The actual value
|
||||
pub clause: ClauseInst,
|
||||
/// Information about the code that produced this value
|
||||
pub location: CodeLocation,
|
||||
}
|
||||
impl Expr {
|
||||
/// Constructor
|
||||
pub fn new(clause: ClauseInst, location: CodeLocation) -> Self {
|
||||
Self { clause, location }
|
||||
}
|
||||
/// Obtain the location of the expression
|
||||
pub fn location(&self) -> CodeLocation { self.location.clone() }
|
||||
|
||||
/// Convert into any type that implements [TryFromExpr]. Calls to this
|
||||
/// function are generated wherever a conversion is elided in an extern
|
||||
/// function.
|
||||
pub fn downcast<T: TryFromExpr>(self) -> ExternResult<T> {
|
||||
let Expr { mut clause, location } = self;
|
||||
loop {
|
||||
let cls_deref = clause.cls();
|
||||
match &*cls_deref {
|
||||
Clause::Identity(alt) => {
|
||||
let temp = alt.clone();
|
||||
drop(cls_deref);
|
||||
clause = temp;
|
||||
},
|
||||
_ => {
|
||||
drop(cls_deref);
|
||||
return T::from_expr(Expr { clause, location });
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Visit all expressions in the tree. The search can be exited early by
|
||||
/// returning [Some]
|
||||
///
|
||||
/// See also [parsed::Expr::search_all]
|
||||
pub fn search_all<T>(
|
||||
&self,
|
||||
predicate: &mut impl FnMut(&Self) -> Option<T>,
|
||||
) -> Option<T> {
|
||||
if let Some(t) = predicate(self) {
|
||||
return Some(t);
|
||||
}
|
||||
self.clause.inspect(|c| match c {
|
||||
Clause::Identity(_alt) => unreachable!("Handled by inspect"),
|
||||
Clause::Apply { f, x } => (f.search_all(predicate))
|
||||
.or_else(|| x.iter().find_map(|x| x.search_all(predicate))),
|
||||
Clause::Lambda { body, .. } => body.search_all(predicate),
|
||||
Clause::Constant(_)
|
||||
| Clause::LambdaArg
|
||||
| Clause::Atom(_)
|
||||
| Clause::Bottom(_) => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Expr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}@{}", self.clause, self.location)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Expr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.clause)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsDerefMut<Clause> for Expr {
|
||||
fn as_deref_mut(&mut self) -> impl DerefMut<Target = Clause> + '_ {
|
||||
self.clause.cls_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/// [ExprInst::with_literal] produces this marker unit to indicate that the
|
||||
/// expression is not a literal
|
||||
pub struct NotALiteral;
|
||||
|
||||
/// A wrapper around expressions to handle their multiple occurences in
|
||||
/// the tree together
|
||||
#[derive(Clone)]
|
||||
pub struct ClauseInst(pub Arc<Mutex<Clause>>);
|
||||
impl ClauseInst {
|
||||
/// Wrap a [Clause] in a shared container so that normalization steps are
|
||||
/// applied to all references
|
||||
#[must_use]
|
||||
pub fn new(cls: Clause) -> Self { Self(Arc::new(Mutex::new(cls))) }
|
||||
|
||||
/// Take the [Clause] out of this container if it's the last reference to it,
|
||||
/// or return self.
|
||||
pub fn try_unwrap(self) -> Result<Clause, ClauseInst> {
|
||||
Arc::try_unwrap(self.0).map(|c| c.into_inner().unwrap()).map_err(Self)
|
||||
}
|
||||
|
||||
/// Read-only access to the shared clause instance
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// if the clause is already borrowed in read-write mode
|
||||
#[must_use]
|
||||
pub fn cls(&self) -> impl Deref<Target = Clause> + '_ {
|
||||
self.0.lock().unwrap()
|
||||
}
|
||||
|
||||
/// Read-Write access to the shared clause instance
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// if the clause is already borrowed
|
||||
#[must_use]
|
||||
pub fn cls_mut(&self) -> impl DerefMut<Target = Clause> + '_ {
|
||||
self.0.lock().unwrap()
|
||||
}
|
||||
|
||||
/// Call a normalization function on the expression. The expr is
|
||||
/// updated with the new clause which affects all copies of it
|
||||
/// across the tree.
|
||||
pub fn try_normalize<T>(
|
||||
&self,
|
||||
mapper: impl FnOnce(Clause) -> Result<(Clause, T), RunError>,
|
||||
) -> Result<(ClauseInst, T), RunError> {
|
||||
enum Report<T> {
|
||||
Nested(ClauseInst, T),
|
||||
Plain(T),
|
||||
}
|
||||
let ret = take_with_output(&mut *self.cls_mut(), |clause| match &clause {
|
||||
Clause::Identity(alt) => match alt.try_normalize(mapper) {
|
||||
Ok((nested, t)) => (clause, Ok(Report::Nested(nested, t))),
|
||||
Err(e) => (Clause::Bottom(e.clone()), Err(e)),
|
||||
},
|
||||
_ => match mapper(clause) {
|
||||
Err(e) => (Clause::Bottom(e.clone()), Err(e)),
|
||||
Ok((clause, t)) => (clause, Ok(Report::Plain(t))),
|
||||
},
|
||||
})?;
|
||||
Ok(match ret {
|
||||
Report::Nested(nested, t) => (nested, t),
|
||||
Report::Plain(t) => (self.clone(), t),
|
||||
})
|
||||
}
|
||||
|
||||
/// Call a predicate on the clause, returning whatever the
|
||||
/// predicate returns. This is a convenience function for reaching
|
||||
/// through the [Mutex]. The clause will never be [Clause::Identity].
|
||||
#[must_use]
|
||||
pub fn inspect<T>(&self, predicate: impl FnOnce(&Clause) -> T) -> T {
|
||||
match &*self.cls() {
|
||||
Clause::Identity(sub) => sub.inspect(predicate),
|
||||
x => predicate(x),
|
||||
}
|
||||
}
|
||||
|
||||
/// If this expression is an [Atomic], request an object of the given type.
|
||||
/// If it's not an atomic, fail the request automatically.
|
||||
#[must_use = "your request might not have succeeded"]
|
||||
pub fn request<T: 'static>(&self) -> Option<T> {
|
||||
match &*self.cls() {
|
||||
Clause::Atom(a) => request(&*a.0),
|
||||
Clause::Identity(alt) => alt.request(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Associate a location with this clause
|
||||
pub fn to_expr(self, location: CodeLocation) -> Expr {
|
||||
Expr { clause: self.clone(), location: location.clone() }
|
||||
}
|
||||
/// Check ahead-of-time if this clause contains an atom. Calls
|
||||
/// [ClauseInst#cls] for read access.
|
||||
///
|
||||
/// Since atoms cannot become normalizable, if this is true and previous
|
||||
/// normalization failed, the atom is known to be in normal form.
|
||||
pub fn is_atom(&self) -> bool { matches!(&*self.cls(), Clause::Atom(_)) }
|
||||
|
||||
/// Tries to unwrap the [Arc]. If that fails, clones it field by field.
|
||||
/// If it's a [Clause::Atom] which cannot be cloned, wraps it in a
|
||||
/// [Clause::Identity].
|
||||
///
|
||||
/// Implementation of [crate::foreign::to_clause::ToClause::to_clause]. The
|
||||
/// trait is more general so it requires a location which this one doesn't.
|
||||
pub fn into_cls(self) -> Clause {
|
||||
self.try_unwrap().unwrap_or_else(|clsi| match &*clsi.cls() {
|
||||
Clause::Apply { f, x } => Clause::Apply { f: f.clone(), x: x.clone() },
|
||||
Clause::Atom(_) => Clause::Identity(clsi.clone()),
|
||||
Clause::Bottom(e) => Clause::Bottom(e.clone()),
|
||||
Clause::Constant(c) => Clause::Constant(c.clone()),
|
||||
Clause::Identity(sub) => Clause::Identity(sub.clone()),
|
||||
Clause::Lambda { args, body } =>
|
||||
Clause::Lambda { args: args.clone(), body: body.clone() },
|
||||
Clause::LambdaArg => Clause::LambdaArg,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ClauseInst {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.0.try_lock() {
|
||||
Ok(expr) => write!(f, "{expr:?}"),
|
||||
Err(TryLockError::Poisoned(_)) => write!(f, "<poisoned>"),
|
||||
Err(TryLockError::WouldBlock) => write!(f, "<locked>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ClauseInst {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.0.try_lock() {
|
||||
Ok(expr) => write!(f, "{expr}"),
|
||||
Err(TryLockError::Poisoned(_)) => write!(f, "<poisoned>"),
|
||||
Err(TryLockError::WouldBlock) => write!(f, "<locked>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsDerefMut<Clause> for ClauseInst {
|
||||
fn as_deref_mut(&mut self) -> impl DerefMut<Target = Clause> + '_ {
|
||||
self.cls_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/// Distinct types of expressions recognized by the interpreter
|
||||
#[derive(Debug)]
|
||||
pub enum Clause {
|
||||
/// An expression that causes an error
|
||||
Bottom(RunError),
|
||||
/// Indicates that this [ClauseInst] has the same value as the other
|
||||
/// [ClauseInst]. This has two benefits;
|
||||
///
|
||||
/// - [Clause] and therefore [Atomic] doesn't have to be [Clone] which saves
|
||||
/// many synchronization primitives and reference counters in usercode
|
||||
/// - it enforces on the type level that all copies are normalized together,
|
||||
/// so accidental inefficiency in the interpreter is rarer.
|
||||
///
|
||||
/// That being said, it's still arbitrary many indirections, so when possible
|
||||
/// APIs should be usable with a [ClauseInst] directly.
|
||||
Identity(ClauseInst),
|
||||
/// An opaque non-callable value, eg. a file handle
|
||||
Atom(Atom),
|
||||
/// A function application
|
||||
Apply {
|
||||
/// Function to be applied
|
||||
f: Expr,
|
||||
/// Argument to be substituted in the function
|
||||
x: VecDeque<Expr>,
|
||||
},
|
||||
/// A name to be looked up in the interpreter's symbol table
|
||||
Constant(Sym),
|
||||
/// A function
|
||||
Lambda {
|
||||
/// A collection of (zero or more) paths to placeholders belonging to this
|
||||
/// function
|
||||
args: Option<PathSet>,
|
||||
/// The tree produced by this function, with placeholders where the
|
||||
/// argument will go
|
||||
body: Expr,
|
||||
},
|
||||
/// A placeholder within a function that will be replaced upon application
|
||||
LambdaArg,
|
||||
}
|
||||
impl Clause {
|
||||
/// Wrap a clause in a refcounted lock
|
||||
pub fn to_inst(self) -> ClauseInst { ClauseInst::new(self) }
|
||||
/// Wrap a clause in an expression.
|
||||
pub fn to_expr(self, location: CodeLocation) -> Expr {
|
||||
self.to_inst().to_expr(location)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Clause {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Clause::Atom(a) => write!(f, "{a:?}"),
|
||||
Clause::Bottom(err) => write!(f, "bottom({err})"),
|
||||
Clause::LambdaArg => write!(f, "arg"),
|
||||
Clause::Apply { f: fun, x } =>
|
||||
write!(f, "({fun} {})", x.iter().join(" ")),
|
||||
Clause::Lambda { args, body } => match args {
|
||||
Some(path) => write!(f, "\\{path:?}.{body}"),
|
||||
None => write!(f, "\\_.{body}"),
|
||||
},
|
||||
Clause::Constant(t) => write!(f, "{t}"),
|
||||
Clause::Identity(other) => write!(f, "({other})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsDerefMut<Clause> for Clause {
|
||||
fn as_deref_mut(&mut self) -> impl DerefMut<Target = Clause> + '_ { self }
|
||||
}
|
||||
149
src/interpreter/nort_builder.rs
Normal file
149
src/interpreter/nort_builder.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
use std::cell::RefCell;
|
||||
use std::mem;
|
||||
|
||||
use substack::Substack;
|
||||
|
||||
use super::nort::{AsDerefMut, Clause, Expr};
|
||||
use super::path_set::PathSet;
|
||||
use crate::utils::pure_seq::pushed;
|
||||
|
||||
enum IntGenData<'a, T: ?Sized> {
|
||||
Lambda(&'a T, &'a RefCell<Option<PathSet>>),
|
||||
/// Counts left steps within a chain of [Clause::Apply] for collapsing.
|
||||
Apply(&'a RefCell<usize>),
|
||||
/// Replaces [IntGenData::Apply] when stepping left into non-apply to record
|
||||
/// a [None] [super::path_set::Step].
|
||||
AppF,
|
||||
/// Replaces [IntGenData::Apply] when stepping right to freeze the value.
|
||||
AppArg(usize),
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> Copy for IntGenData<'a, T> {}
|
||||
impl<'a, T: ?Sized> Clone for IntGenData<'a, T> {
|
||||
fn clone(&self) -> Self { *self }
|
||||
}
|
||||
|
||||
struct ArgCollector(RefCell<Option<PathSet>>);
|
||||
impl ArgCollector {
|
||||
pub fn new() -> Self { Self(RefCell::new(None)) }
|
||||
pub fn into_path(self) -> Option<PathSet> { self.0.into_inner() }
|
||||
}
|
||||
|
||||
/// Strategy used to find the lambda corresponding to a given argument in the
|
||||
/// stack. The function is called on the data associated with the argument, then
|
||||
/// the callback it returns is called on every lambda ancestor's associated
|
||||
/// data from closest to outermost ancestor. The first lambda where this
|
||||
/// callback returns true is considered to own the argument.
|
||||
pub type LambdaPicker<'a, T, U> =
|
||||
&'a dyn for<'b> Fn(&'b U) -> Box<dyn FnMut(&T) -> bool + 'b>;
|
||||
|
||||
/// Bundle of information passed down through recursive fnuctions to instantiate
|
||||
/// runtime [Expr], [super::nort::ClauseInst] or [Clause].
|
||||
///
|
||||
/// The context used by [crate::gen::traits::Gen] to convert templates is which
|
||||
/// includes this type is constructed with [super::gen_nort::nort_gen].
|
||||
pub struct NortBuilder<'a, T: ?Sized, U: ?Sized> {
|
||||
stack: Substack<'a, IntGenData<'a, T>>,
|
||||
lambda_picker: LambdaPicker<'a, T, U>,
|
||||
}
|
||||
impl<'a, T: ?Sized, U: ?Sized> NortBuilder<'a, T, U> {
|
||||
/// Create a new recursive [super::nort] builder from a location that will be
|
||||
pub fn new(lambda_picker: LambdaPicker<'a, T, U>) -> Self {
|
||||
Self { stack: Substack::Bottom, lambda_picker }
|
||||
}
|
||||
/// [Substack::pop] and clone the location
|
||||
fn pop<'b>(&'b self, count: usize) -> NortBuilder<'b, T, U>
|
||||
where 'a: 'b {
|
||||
let mut new = *self;
|
||||
new.stack = *self.stack.pop(count);
|
||||
new
|
||||
}
|
||||
/// [Substack::push] and clone the location
|
||||
fn push<'b>(&'b self, data: IntGenData<'a, T>) -> NortBuilder<'b, T, U>
|
||||
where 'a: 'b {
|
||||
let mut new = *self;
|
||||
new.stack = self.stack.push(data);
|
||||
new
|
||||
}
|
||||
fn non_app_step<V>(self, f: impl FnOnce(NortBuilder<T, U>) -> V) -> V {
|
||||
if let Some(IntGenData::Apply(_)) = self.stack.value() {
|
||||
let prev = self.pop(1);
|
||||
f(prev.push(IntGenData::AppF))
|
||||
} else {
|
||||
f(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Climb back through the stack and find a lambda associated with this
|
||||
/// argument, then record the path taken from the lambda to this argument in
|
||||
/// the lambda's mutable cell.
|
||||
pub fn arg_logic(self, name: &'a U) {
|
||||
let mut lambda_chk = (self.lambda_picker)(name);
|
||||
self.non_app_step(|ctx| {
|
||||
let opt = ctx.stack.rfold(None, |path, item| match item {
|
||||
IntGenData::Apply(_) => panic!("This is removed after handling"),
|
||||
IntGenData::Lambda(n, rc) =>
|
||||
lambda_chk(n).then(|| (vec![], *rc)).or(path),
|
||||
IntGenData::AppArg(n) => path.map(|(p, rc)| (pushed(p, Some(*n)), rc)),
|
||||
IntGenData::AppF => path.map(|(p, rc)| (pushed(p, None), rc)),
|
||||
});
|
||||
let (path, slot) = opt.expect("Argument not wrapped in matching lambda");
|
||||
match &mut *slot.borrow_mut() {
|
||||
slot @ None => *slot = Some(PathSet::end(path)),
|
||||
Some(slot) => take_mut::take(slot, |p| p.overlay(PathSet::end(path))),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Push a stackframe corresponding to a lambda expression, build the body,
|
||||
/// then record the path set collected by [NortBuilder::arg_logic] calls
|
||||
/// within the body.
|
||||
pub fn lambda_logic(
|
||||
self,
|
||||
name: &T,
|
||||
body: impl FnOnce(NortBuilder<T, U>) -> Expr,
|
||||
) -> Clause {
|
||||
let coll = ArgCollector::new();
|
||||
let frame = IntGenData::Lambda(name, &coll.0);
|
||||
let body = self.non_app_step(|ctx| body(ctx.push(frame)));
|
||||
let args = coll.into_path();
|
||||
Clause::Lambda { args, body }
|
||||
}
|
||||
|
||||
/// Logic for collapsing Apply clauses. Different steps of the logic
|
||||
/// communicate via mutable variables on the stack
|
||||
pub fn apply_logic(
|
||||
self,
|
||||
f: impl FnOnce(NortBuilder<T, U>) -> Expr,
|
||||
x: impl FnOnce(NortBuilder<T, U>) -> Expr,
|
||||
) -> Clause {
|
||||
let mut fun: Expr;
|
||||
let arg: Expr;
|
||||
if let Some(IntGenData::Apply(rc)) = self.stack.value() {
|
||||
// argument side commits backidx
|
||||
arg = x(self.pop(1).push(IntGenData::AppArg(*rc.borrow())));
|
||||
// function side increments backidx
|
||||
*rc.borrow_mut() += 1;
|
||||
fun = f(self);
|
||||
} else {
|
||||
// function side starts from backidx 1
|
||||
fun = f(self.push(IntGenData::Apply(&RefCell::new(1))));
|
||||
// argument side commits 0
|
||||
arg = x(self.push(IntGenData::AppArg(0)));
|
||||
};
|
||||
let mut cls_lk = fun.as_deref_mut();
|
||||
if let Clause::Apply { x, f: _ } = &mut *cls_lk {
|
||||
x.push_back(arg);
|
||||
mem::drop(cls_lk);
|
||||
fun.clause.into_cls()
|
||||
} else {
|
||||
mem::drop(cls_lk);
|
||||
Clause::Apply { f: fun, x: [arg].into() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized, U: ?Sized> Copy for NortBuilder<'a, T, U> {}
|
||||
impl<'a, T: ?Sized, U: ?Sized> Clone for NortBuilder<'a, T, U> {
|
||||
fn clone(&self) -> Self { *self }
|
||||
}
|
||||
165
src/interpreter/path_set.rs
Normal file
165
src/interpreter/path_set.rs
Normal file
@@ -0,0 +1,165 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::utils::join::join_maps;
|
||||
|
||||
/// A step into a [super::nort::Clause::Apply]. If [None], it steps to the
|
||||
/// function. If [Some(n)], it steps to the `n`th _last_ argument.
|
||||
pub type Step = Option<usize>;
|
||||
fn print_step(step: Step) -> String {
|
||||
if let Some(n) = step { format!("{n}>") } else { "f>".to_string() }
|
||||
}
|
||||
|
||||
/// A branching path selecting some placeholders (but at least one) in a Lambda
|
||||
/// expression
|
||||
#[derive(Clone)]
|
||||
pub struct PathSet {
|
||||
/// The single steps through [super::nort::Clause::Apply]
|
||||
pub steps: VecDeque<Step>,
|
||||
/// if Some, it splits at a [super::nort::Clause::Apply]. If None, it ends in
|
||||
/// a [super::nort::Clause::LambdaArg]
|
||||
pub next: Option<HashMap<Step, PathSet>>,
|
||||
}
|
||||
|
||||
impl PathSet {
|
||||
/// Create a path set for more than one target
|
||||
pub fn branch(
|
||||
steps: impl IntoIterator<Item = Step>,
|
||||
conts: impl IntoIterator<Item = (Step, Self)>,
|
||||
) -> Self {
|
||||
let conts = conts.into_iter().collect::<HashMap<_, _>>();
|
||||
assert!(1 < conts.len(), "Branching pathsets need multiple continuations");
|
||||
Self { steps: steps.into_iter().collect(), next: Some(conts) }
|
||||
}
|
||||
|
||||
/// Create a path set for one target
|
||||
pub fn end(steps: impl IntoIterator<Item = Step>) -> Self {
|
||||
Self { steps: steps.into_iter().collect(), next: None }
|
||||
}
|
||||
|
||||
/// Create a path set that points to a slot that is a direct
|
||||
/// child of the given lambda with no applications. In essence, this means
|
||||
/// that this argument will be picked as the value of the expression after an
|
||||
/// arbitrary amount of subsequent discarded parameters.
|
||||
pub fn pick() -> Self { Self { steps: VecDeque::new(), next: None } }
|
||||
|
||||
/// Merge two paths into one path that points to all targets of both. Only
|
||||
/// works if both paths select leaf nodes of the same partial tree.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// if either path selects a node the other path dissects
|
||||
pub fn overlay(self, other: Self) -> Self {
|
||||
let (mut short, mut long) = match self.steps.len() < other.steps.len() {
|
||||
true => (self, other),
|
||||
false => (other, self),
|
||||
};
|
||||
let short_len = short.steps.len();
|
||||
let long_len = long.steps.len();
|
||||
let match_len = (short.steps.iter())
|
||||
.zip(long.steps.iter())
|
||||
.take_while(|(a, b)| a == b)
|
||||
.count();
|
||||
// fact: match_len <= short_len <= long_len
|
||||
if short_len == match_len && match_len == long_len {
|
||||
// implies match_len == short_len == long_len
|
||||
match (short.next, long.next) {
|
||||
(None, None) => Self::end(short.steps.iter().cloned()),
|
||||
(Some(_), None) | (None, Some(_)) => {
|
||||
panic!("One of these paths is faulty")
|
||||
},
|
||||
(Some(s), Some(l)) => Self::branch(
|
||||
short.steps.iter().cloned(),
|
||||
join_maps(s, l, |_, l, r| l.overlay(r)),
|
||||
),
|
||||
}
|
||||
} else if short_len == match_len {
|
||||
// implies match_len == short_len < long_len
|
||||
// long.steps[0..match_len] is in steps
|
||||
// long.steps[match_len] becomes the choice of branch below
|
||||
// long.steps[match_len + 1..] is in tail
|
||||
let mut conts = short.next.expect("One path ends inside the other");
|
||||
let tail_steps = long.steps.split_off(match_len + 1);
|
||||
let tail = match long.next {
|
||||
Some(n) => Self::branch(tail_steps, n),
|
||||
None => Self::end(tail_steps),
|
||||
};
|
||||
let branch = long.steps[match_len];
|
||||
let prev_c = conts.remove(&branch);
|
||||
let new_c = if let Some(x) = prev_c { x.overlay(tail) } else { tail };
|
||||
conts.insert(branch, new_c);
|
||||
Self::branch(short.steps, conts)
|
||||
} else {
|
||||
// implies match_len < short_len <= long_len
|
||||
// steps[0..match_len] is in shared
|
||||
// steps[match_len] become the branches below
|
||||
// steps[match_len + 1..] is in new_long and new_short
|
||||
let new_short_steps = short.steps.split_off(match_len + 1);
|
||||
let short_last = short.steps.pop_back().expect("split at n + 1");
|
||||
let new_short = Self { next: short.next.clone(), steps: new_short_steps };
|
||||
let new_long_steps = long.steps.split_off(match_len + 1);
|
||||
let new_long = Self { next: long.next.clone(), steps: new_long_steps };
|
||||
Self::branch(short.steps, [
|
||||
(short_last, new_short),
|
||||
(long.steps[match_len], new_long),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepend a step to a path. If it had previously started at a node that is
|
||||
/// at the specified step within an Apply clause, it now starts at the Apply.
|
||||
///
|
||||
/// This is only valid if the new Apply is **separate** from the previous
|
||||
/// root.
|
||||
pub fn prepend(&mut self, step: Step) { self.steps.push_front(step); }
|
||||
}
|
||||
|
||||
impl fmt::Display for PathSet {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let step_s = self.steps.iter().copied().map(print_step).join("");
|
||||
match &self.next {
|
||||
Some(conts) => {
|
||||
let opts =
|
||||
conts.iter().map(|(h, t)| format!("{}{t}", print_step(*h))).join("|");
|
||||
write!(f, "{step_s}({opts})")
|
||||
},
|
||||
None => write!(f, "{step_s}x"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for PathSet {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "PathSet({self})")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_combine() {
|
||||
let ps1 = PathSet { next: None, steps: VecDeque::from([Some(2), None]) };
|
||||
let ps2 = PathSet { next: None, steps: VecDeque::from([Some(3), Some(1)]) };
|
||||
let sum = ps1.clone().overlay(ps2.clone());
|
||||
assert_eq!(format!("{sum}"), "(2>f>x|3>1>x)");
|
||||
}
|
||||
|
||||
fn extend_scaffold() -> PathSet {
|
||||
PathSet::branch([None, Some(1), None], [
|
||||
(None, PathSet::end([None, Some(1)])),
|
||||
(Some(1), PathSet::end([None, Some(2)])),
|
||||
])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extend_noclone() {
|
||||
let mut ps = extend_scaffold();
|
||||
ps.prepend(Some(0));
|
||||
assert_eq!(format!("{ps}"), "0>f>1>f>(f>f>1|1>f>2)");
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,101 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use super::apply::apply;
|
||||
use super::context::{Context, Return};
|
||||
use super::error::RuntimeError;
|
||||
use crate::foreign::AtomicReturn;
|
||||
use crate::representations::interpreted::{Clause, ExprInst};
|
||||
use super::context::{Halt, RunContext};
|
||||
use super::error::RunError;
|
||||
use super::nort::{Clause, Expr};
|
||||
use crate::foreign::atom::AtomicReturn;
|
||||
use crate::foreign::error::ExternResult;
|
||||
use crate::location::CodeLocation;
|
||||
use crate::name::Sym;
|
||||
use crate::utils::pure_seq::pushed;
|
||||
|
||||
/// Information about a normalization run presented to an atom
|
||||
#[derive(Clone)]
|
||||
pub struct RunData<'a> {
|
||||
/// Location of the atom
|
||||
pub location: CodeLocation,
|
||||
/// Information about the execution
|
||||
pub ctx: RunContext<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Interrupted {
|
||||
stack: Vec<Expr>,
|
||||
}
|
||||
impl Interrupted {
|
||||
pub fn resume(self, ctx: RunContext) -> Result<Halt, RunError> {
|
||||
run_stack(self.stack, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalize an expression using beta reduction with memoization
|
||||
pub fn run(expr: ExprInst, mut ctx: Context) -> Result<Return, RuntimeError> {
|
||||
let (state, (gas, inert)) = expr.try_normalize(
|
||||
|mut cls, loc| -> Result<(Clause, _), RuntimeError> {
|
||||
while ctx.gas.map(|g| g > 0).unwrap_or(true) {
|
||||
pub fn run(mut expr: Expr, mut ctx: RunContext) -> Result<Halt, RunError> {
|
||||
run_stack(vec![expr], ctx)
|
||||
}
|
||||
|
||||
fn run_stack(
|
||||
mut stack: Vec<Expr>,
|
||||
mut ctx: RunContext,
|
||||
) -> Result<Halt, RunError> {
|
||||
let mut expr = stack.pop().expect("Empty stack");
|
||||
loop {
|
||||
if ctx.no_gas() {
|
||||
return Err(RunError::Interrupted(Interrupted {
|
||||
stack: pushed(stack, expr),
|
||||
}));
|
||||
}
|
||||
let (next_clsi, inert) = expr.clause.try_normalize(|mut cls| {
|
||||
loop {
|
||||
if ctx.no_gas() {
|
||||
return Ok((cls, false));
|
||||
}
|
||||
match cls {
|
||||
cls @ Clause::Identity(_) => return Ok((cls, false)),
|
||||
// TODO:
|
||||
// - unfuck nested loop
|
||||
// - inline most of [apply] to eliminate recursion step
|
||||
Clause::Apply { f, x } => {
|
||||
let res = apply(f, x, ctx.clone())?;
|
||||
if res.inert {
|
||||
return Ok((res.state.expr_val().clause, (res.gas, true)));
|
||||
if x.is_empty() {
|
||||
return Ok((f.clause.into_cls(), false));
|
||||
}
|
||||
ctx.gas = res.gas;
|
||||
cls = res.state.expr().clause.clone();
|
||||
let (gas, clause) = apply(f, x, ctx.clone())?;
|
||||
if ctx.gas.is_some() {
|
||||
ctx.gas = gas;
|
||||
}
|
||||
cls = clause;
|
||||
},
|
||||
Clause::Atom(data) => {
|
||||
let AtomicReturn { clause, gas, inert } = data.run(ctx.clone())?;
|
||||
if inert {
|
||||
return Ok((clause, (gas, true)));
|
||||
let run = RunData { ctx: ctx.clone(), location: expr.location() };
|
||||
let atomic_ret = data.run(run)?;
|
||||
if ctx.gas.is_some() {
|
||||
ctx.gas = atomic_ret.gas;
|
||||
}
|
||||
ctx.gas = gas;
|
||||
cls = clause;
|
||||
if atomic_ret.inert {
|
||||
return Ok((atomic_ret.clause, true));
|
||||
}
|
||||
cls = atomic_ret.clause;
|
||||
},
|
||||
Clause::Constant(c) => {
|
||||
let symval = (ctx.symbols.get(&c)).ok_or_else(|| {
|
||||
RuntimeError::MissingSymbol(c.clone(), loc.clone())
|
||||
RunError::MissingSymbol(c.clone(), expr.location())
|
||||
})?;
|
||||
ctx.gas = ctx.gas.map(|g| g - 1); // cost of lookup
|
||||
cls = symval.expr().clause.clone();
|
||||
cls = Clause::Identity(symval.clause.clone());
|
||||
},
|
||||
// non-reducible
|
||||
_ => return Ok((cls, (ctx.gas, true))),
|
||||
}
|
||||
c => return Ok((c, true)),
|
||||
};
|
||||
}
|
||||
// out of gas
|
||||
Ok((cls, (ctx.gas, false)))
|
||||
},
|
||||
)?;
|
||||
Ok(Return { state, gas, inert })
|
||||
})?;
|
||||
expr.clause = next_clsi;
|
||||
if inert {
|
||||
match stack.pop() {
|
||||
Some(e) => expr = e,
|
||||
None => return Ok(Halt { state: expr, gas: ctx.gas, inert }),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user