forked from Orchid/orchid
Massive feature drop
- pattern matching seems to be correct - dynamic dispatch works with the to_string example - template strings as a last-minute addition - interpreter revamp, virtual stack for abort safety
This commit is contained in:
@@ -1,24 +1,10 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::mem;
|
||||
|
||||
use never::Never;
|
||||
|
||||
use super::context::RunContext;
|
||||
use super::error::RunError;
|
||||
use super::nort::{Clause, ClauseInst, Expr};
|
||||
use super::path_set::{PathSet, Step};
|
||||
use super::run::run;
|
||||
use crate::location::CodeLocation;
|
||||
|
||||
/// Information about a function call presented to an external function
|
||||
pub struct CallData<'a> {
|
||||
/// Location of the function expression
|
||||
pub location: CodeLocation,
|
||||
/// The argument the function was called on. Functions are curried
|
||||
pub arg: Expr,
|
||||
/// Information relating to this interpreter run
|
||||
pub ctx: RunContext<'a>,
|
||||
}
|
||||
use crate::foreign::atom::CallData;
|
||||
|
||||
/// Process the clause at the end of the provided path. Note that paths always
|
||||
/// point to at least one target. Note also that this is not cached as a
|
||||
@@ -42,15 +28,12 @@ fn map_at<E>(
|
||||
_ => (),
|
||||
}
|
||||
Ok(match (source, path.next()) {
|
||||
(Clause::Lambda { .. } | Clause::Identity(_), _) =>
|
||||
unreachable!("Handled above"),
|
||||
(Clause::Lambda { .. } | Clause::Identity(_), _) => unreachable!("Handled above"),
|
||||
// If the path ends and this isn't a lambda, process it
|
||||
(val, None) => mapper(val)?,
|
||||
// If it's an Apply, execute the next step in the path
|
||||
(Clause::Apply { f, x }, Some(head)) => {
|
||||
let proc = |x: &Expr| {
|
||||
Ok(map_at(path, &x.clause.cls(), mapper)?.to_expr(x.location()))
|
||||
};
|
||||
let proc = |x: &Expr| Ok(map_at(path, &x.cls(), mapper)?.to_expr(x.location()));
|
||||
match head {
|
||||
None => Clause::Apply { f: proc(f)?, x: x.clone() },
|
||||
Some(n) => {
|
||||
@@ -69,7 +52,12 @@ fn map_at<E>(
|
||||
/// with the value in the body. Note that a path may point to multiple
|
||||
/// placeholders.
|
||||
#[must_use]
|
||||
fn substitute(paths: &PathSet, value: ClauseInst, body: &Clause) -> Clause {
|
||||
pub fn substitute(
|
||||
paths: &PathSet,
|
||||
value: ClauseInst,
|
||||
body: &Clause,
|
||||
on_sub: &mut impl FnMut(),
|
||||
) -> Clause {
|
||||
let PathSet { steps, next } = paths;
|
||||
map_at(steps.iter().cloned(), body, &mut |chkpt| -> Result<Clause, Never> {
|
||||
match (chkpt, next) {
|
||||
@@ -80,18 +68,20 @@ fn substitute(paths: &PathSet, value: ClauseInst, body: &Clause) -> Clause {
|
||||
let mut argv = x.clone();
|
||||
let f = match conts.get(&None) {
|
||||
None => f.clone(),
|
||||
Some(sp) => substitute(sp, value.clone(), &f.clause.cls())
|
||||
.to_expr(f.location()),
|
||||
Some(sp) => substitute(sp, value.clone(), &f.cls(), on_sub).to_expr(f.location()),
|
||||
};
|
||||
for (i, old) in argv.iter_mut().rev().enumerate() {
|
||||
if let Some(sp) = conts.get(&Some(i)) {
|
||||
let tmp = substitute(sp, value.clone(), &old.clause.cls());
|
||||
let tmp = substitute(sp, value.clone(), &old.cls(), on_sub);
|
||||
*old = tmp.to_expr(old.location());
|
||||
}
|
||||
}
|
||||
Ok(Clause::Apply { f, x: argv })
|
||||
},
|
||||
(Clause::LambdaArg, None) => Ok(Clause::Identity(value.clone())),
|
||||
},
|
||||
(Clause::LambdaArg, None) => {
|
||||
on_sub();
|
||||
Ok(Clause::Identity(value.clone()))
|
||||
},
|
||||
(_, None) => panic!("Argument path must point to LambdaArg"),
|
||||
(_, Some(_)) => panic!("Argument path can only fork at Apply"),
|
||||
}
|
||||
@@ -99,11 +89,7 @@ fn substitute(paths: &PathSet, value: ClauseInst, body: &Clause) -> Clause {
|
||||
.unwrap_or_else(|e| match e {})
|
||||
}
|
||||
|
||||
pub(super) fn apply_as_atom(
|
||||
f: Expr,
|
||||
arg: Expr,
|
||||
ctx: RunContext,
|
||||
) -> Result<Clause, RunError> {
|
||||
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 {
|
||||
@@ -116,73 +102,3 @@ pub(super) fn apply_as_atom(
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply a function-like expression to a parameter.
|
||||
pub(super) fn apply(
|
||||
mut f: Expr,
|
||||
mut argv: VecDeque<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::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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ pub struct RunContext<'a> {
|
||||
pub symbols: &'a HashMap<Sym, Expr>,
|
||||
/// The number of reduction steps the interpreter can take before returning
|
||||
pub gas: Option<usize>,
|
||||
/// The limit of recursion
|
||||
pub stack_size: usize,
|
||||
}
|
||||
impl<'a> RunContext<'a> {
|
||||
/// Consume some gas if it is being counted
|
||||
|
||||
@@ -1,34 +1,66 @@
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::sync::Arc;
|
||||
use std::fmt::{self, Debug, Display};
|
||||
|
||||
use crate::foreign::error::ExternError;
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::nort::Expr;
|
||||
use super::run::Interrupted;
|
||||
use crate::foreign::error::{ExternError, ExternErrorObj};
|
||||
use crate::location::CodeLocation;
|
||||
use crate::name::Sym;
|
||||
|
||||
use super::run::Interrupted;
|
||||
/// Print a stack trace
|
||||
pub fn strace(stack: &[Expr]) -> String {
|
||||
stack.iter().rev().map(|x| format!("{x}\n at {}", x.location)).join("\n")
|
||||
}
|
||||
|
||||
/// Problems in the process of execution
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RunError {
|
||||
/// A Rust function encountered an error
|
||||
Extern(Arc<dyn ExternError>),
|
||||
/// Symbol not in context
|
||||
MissingSymbol(Sym, CodeLocation),
|
||||
Extern(ExternErrorObj),
|
||||
/// Ran out of gas
|
||||
Interrupted(Interrupted)
|
||||
Interrupted(Interrupted),
|
||||
}
|
||||
|
||||
impl From<Arc<dyn ExternError>> for RunError {
|
||||
fn from(value: Arc<dyn ExternError>) -> Self { Self::Extern(value) }
|
||||
impl<T: ExternError + 'static> From<T> for RunError {
|
||||
fn from(value: T) -> Self { Self::Extern(value.rc()) }
|
||||
}
|
||||
|
||||
impl From<ExternErrorObj> for RunError {
|
||||
fn from(value: ExternErrorObj) -> Self { Self::Extern(value) }
|
||||
}
|
||||
|
||||
impl Display for RunError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Extern(e) => write!(f, "Error in external function: {e}"),
|
||||
Self::MissingSymbol(sym, loc) => {
|
||||
write!(f, "{sym}, called at {loc} is not loaded")
|
||||
Self::Interrupted(i) => {
|
||||
write!(f, "Ran out of gas:\n{}", strace(&i.stack))
|
||||
},
|
||||
Self::Extern(e) => write!(f, "Program fault: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct StackOverflow {
|
||||
pub stack: Vec<Expr>,
|
||||
}
|
||||
impl ExternError for StackOverflow {}
|
||||
impl Display for StackOverflow {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let limit = self.stack.len() - 2; // 1 for failed call, 1 for current
|
||||
write!(f, "Stack depth exceeded {limit}:\n{}", strace(&self.stack))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct MissingSymbol {
|
||||
pub sym: Sym,
|
||||
pub loc: CodeLocation,
|
||||
}
|
||||
impl ExternError for MissingSymbol {}
|
||||
impl Display for MissingSymbol {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}, called at {} is not loaded", self.sym, self.loc)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,12 +75,13 @@ impl<'a> HandlerTable<'a> {
|
||||
pub fn run_handler(
|
||||
mut state: Expr,
|
||||
handlers: &mut HandlerTable,
|
||||
RunContext { mut gas, symbols }: RunContext,
|
||||
mut ctx: RunContext,
|
||||
) -> Result<Halt, RunError> {
|
||||
loop {
|
||||
let inert;
|
||||
Halt { gas, inert, state } = run(state, RunContext { gas, symbols })?;
|
||||
let state_cls = state.clause.cls();
|
||||
let halt = run(state, ctx.clone())?;
|
||||
state = halt.state;
|
||||
ctx.use_gas(halt.gas.unwrap_or(0));
|
||||
let state_cls = state.cls();
|
||||
if let Clause::Atom(Atom(a)) = &*state_cls {
|
||||
if let Some(res) = handlers.dispatch(a.as_ref(), state.location()) {
|
||||
drop(state_cls);
|
||||
@@ -88,9 +89,9 @@ pub fn run_handler(
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if inert || gas == Some(0) {
|
||||
if halt.inert || ctx.no_gas() {
|
||||
drop(state_cls);
|
||||
break Ok(Halt { gas, inert, state });
|
||||
break Ok(Halt { gas: ctx.gas, inert: halt.inert, state });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,28 @@ impl Expr {
|
||||
| Clause::Bottom(_) => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Clone the refcounted [ClauseInst] out of the expression
|
||||
#[must_use]
|
||||
pub fn clsi(&self) -> ClauseInst { self.clause.clone() }
|
||||
|
||||
/// Readonly access to the [Clause]
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// if the clause is already borrowed
|
||||
#[must_use]
|
||||
pub fn cls(&self) -> impl Deref<Target = Clause> + '_ { self.clause.cls() }
|
||||
|
||||
/// Read-Write access to the [Clause]
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// if the clause is already borrowed
|
||||
#[must_use]
|
||||
pub fn cls_mut(&self) -> impl DerefMut<Target = Clause> + '_ {
|
||||
self.clause.cls_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Expr {
|
||||
@@ -160,15 +182,21 @@ impl ClauseInst {
|
||||
/// Call a normalization function on the expression. The expr is
|
||||
/// updated with the new clause which affects all copies of it
|
||||
/// across the tree.
|
||||
///
|
||||
/// This function bypasses and collapses identities, but calling it in a plain
|
||||
/// loop intermittently re-acquires the mutex, and looping inside of it breaks
|
||||
/// identity collapsing. [ClauseInst::try_normalize_trampoline] solves these
|
||||
/// problems.
|
||||
pub fn try_normalize<T>(
|
||||
&self,
|
||||
mapper: impl FnOnce(Clause) -> Result<(Clause, T), RunError>,
|
||||
) -> Result<(ClauseInst, T), RunError> {
|
||||
) -> Result<(Self, T), RunError> {
|
||||
enum Report<T> {
|
||||
Nested(ClauseInst, T),
|
||||
Plain(T),
|
||||
}
|
||||
let ret = take_with_output(&mut *self.cls_mut(), |clause| match &clause {
|
||||
// don't modify identities, instead update and return the nested clause
|
||||
Clause::Identity(alt) => match alt.try_normalize(mapper) {
|
||||
Ok((nested, t)) => (clause, Ok(Report::Nested(nested, t))),
|
||||
Err(e) => (Clause::Bottom(e.clone()), Err(e)),
|
||||
@@ -184,6 +212,32 @@ impl ClauseInst {
|
||||
})
|
||||
}
|
||||
|
||||
/// Repeatedly call a normalization function on the held clause, switching
|
||||
/// [ClauseInst] values as needed to ensure that
|
||||
pub fn try_normalize_trampoline<T>(
|
||||
mut self,
|
||||
mut mapper: impl FnMut(Clause) -> Result<(Clause, Option<T>), RunError>,
|
||||
) -> Result<(Self, T), RunError> {
|
||||
loop {
|
||||
let (next, exit) = self.try_normalize(|mut cls| {
|
||||
loop {
|
||||
if matches!(cls, Clause::Identity(_)) {
|
||||
break Ok((cls, None));
|
||||
}
|
||||
let (next, exit) = mapper(cls)?;
|
||||
if let Some(exit) = exit {
|
||||
break Ok((next, Some(exit)));
|
||||
}
|
||||
cls = next;
|
||||
}
|
||||
})?;
|
||||
if let Some(exit) = exit {
|
||||
break Ok((next, exit));
|
||||
}
|
||||
self = next
|
||||
}
|
||||
}
|
||||
|
||||
/// Call a predicate on the clause, returning whatever the
|
||||
/// predicate returns. This is a convenience function for reaching
|
||||
/// through the [Mutex]. The clause will never be [Clause::Identity].
|
||||
@@ -320,11 +374,11 @@ impl Display for Clause {
|
||||
Clause::Apply { f: fun, x } =>
|
||||
write!(f, "({fun} {})", x.iter().join(" ")),
|
||||
Clause::Lambda { args, body } => match args {
|
||||
Some(path) => write!(f, "\\{path:?}.{body}"),
|
||||
None => write!(f, "\\_.{body}"),
|
||||
Some(path) => write!(f, "[\\{path}.{body}]"),
|
||||
None => write!(f, "[\\_.{body}]"),
|
||||
},
|
||||
Clause::Constant(t) => write!(f, "{t}"),
|
||||
Clause::Identity(other) => write!(f, "({other})"),
|
||||
Clause::Identity(other) => write!(f, "{{{other}}}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,8 +67,7 @@ impl<'a, T: ?Sized, U: ?Sized> NortBuilder<'a, T, U> {
|
||||
}
|
||||
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))
|
||||
f(self.pop(1).push(IntGenData::AppF))
|
||||
} else {
|
||||
f(self)
|
||||
}
|
||||
@@ -80,14 +79,17 @@ impl<'a, T: ?Sized, U: ?Sized> NortBuilder<'a, T, U> {
|
||||
pub fn arg_logic(self, name: &'a U) {
|
||||
let mut lambda_chk = (self.lambda_picker)(name);
|
||||
self.non_app_step(|ctx| {
|
||||
let opt = ctx.stack.rfold(None, |path, item| match item {
|
||||
let res = ctx.stack.iter().try_fold(vec![], |path, item| match item {
|
||||
IntGenData::Apply(_) => panic!("This is removed after handling"),
|
||||
IntGenData::Lambda(n, rc) =>
|
||||
lambda_chk(n).then(|| (vec![], *rc)).or(path),
|
||||
IntGenData::AppArg(n) => path.map(|(p, rc)| (pushed(p, Some(*n)), rc)),
|
||||
IntGenData::AppF => path.map(|(p, rc)| (pushed(p, None), rc)),
|
||||
IntGenData::Lambda(n, rc) => match lambda_chk(n) {
|
||||
false => Ok(path),
|
||||
true => Err((path, *rc))
|
||||
},
|
||||
IntGenData::AppArg(n) => Ok(pushed(path, Some(*n))),
|
||||
IntGenData::AppF => Ok(pushed(path, None)),
|
||||
});
|
||||
let (path, slot) = opt.expect("Argument not wrapped in matching lambda");
|
||||
let (mut path, slot) = res.expect_err("Argument not wrapped in matching lambda");
|
||||
path.reverse();
|
||||
match &mut *slot.borrow_mut() {
|
||||
slot @ None => *slot = Some(PathSet::end(path)),
|
||||
Some(slot) => take_mut::take(slot, |p| p.overlay(PathSet::end(path))),
|
||||
|
||||
@@ -1,101 +1,129 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::mem;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use super::apply::apply;
|
||||
use super::context::{Halt, RunContext};
|
||||
use super::error::RunError;
|
||||
use super::nort::{Clause, Expr};
|
||||
use crate::foreign::atom::AtomicReturn;
|
||||
use crate::foreign::error::ExternResult;
|
||||
use crate::location::CodeLocation;
|
||||
use crate::name::Sym;
|
||||
use crate::foreign::atom::{AtomicReturn, RunData};
|
||||
use crate::foreign::error::ExternError;
|
||||
use crate::interpreter::apply::{apply_as_atom, substitute};
|
||||
use crate::interpreter::error::{strace, MissingSymbol, StackOverflow};
|
||||
use crate::utils::pure_seq::pushed;
|
||||
|
||||
/// Information about a normalization run presented to an atom
|
||||
#[derive(Clone)]
|
||||
pub struct RunData<'a> {
|
||||
/// Location of the atom
|
||||
pub location: CodeLocation,
|
||||
/// Information about the execution
|
||||
pub ctx: RunContext<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Interpreter state when processing was interrupted
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Interrupted {
|
||||
stack: Vec<Expr>,
|
||||
/// Cached soft stack to save the interpreter having to rebuild it from the
|
||||
/// bottom.
|
||||
pub stack: Vec<Expr>,
|
||||
}
|
||||
impl Interrupted {
|
||||
pub fn resume(self, ctx: RunContext) -> Result<Halt, RunError> {
|
||||
run_stack(self.stack, ctx)
|
||||
}
|
||||
/// Continue processing where it was 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(mut expr: Expr, mut ctx: RunContext) -> Result<Halt, RunError> {
|
||||
run_stack(vec![expr], ctx)
|
||||
pub fn run(expr: Expr, ctx: RunContext) -> Result<Halt, RunError> {
|
||||
let mut v = Vec::with_capacity(1000);
|
||||
v.push(expr);
|
||||
run_stack(v, ctx)
|
||||
}
|
||||
|
||||
fn run_stack(
|
||||
mut stack: Vec<Expr>,
|
||||
mut ctx: RunContext,
|
||||
) -> Result<Halt, RunError> {
|
||||
fn run_stack(mut stack: Vec<Expr>, mut ctx: RunContext) -> Result<Halt, RunError> {
|
||||
let mut expr = stack.pop().expect("Empty stack");
|
||||
let mut popped = false;
|
||||
loop {
|
||||
// print!("Now running {expr}");
|
||||
// let trace = strace(&stack);
|
||||
// if trace.is_empty() {
|
||||
// println!("\n")
|
||||
// } else {
|
||||
// println!("\n{trace}\n")
|
||||
// };
|
||||
if ctx.no_gas() {
|
||||
return Err(RunError::Interrupted(Interrupted {
|
||||
stack: pushed(stack, expr),
|
||||
}));
|
||||
return Err(RunError::Interrupted(Interrupted { stack: pushed(stack, expr) }));
|
||||
}
|
||||
let (next_clsi, inert) = expr.clause.try_normalize(|mut cls| {
|
||||
loop {
|
||||
if ctx.no_gas() {
|
||||
return Ok((cls, false));
|
||||
ctx.use_gas(1);
|
||||
enum Res {
|
||||
Inert,
|
||||
Cont,
|
||||
Push(Expr),
|
||||
}
|
||||
let (next_clsi, res) = expr.clause.try_normalize(|cls| match cls {
|
||||
Clause::Identity(_) => panic!("Passed by try_normalize"),
|
||||
Clause::LambdaArg => panic!("Unbound argument"),
|
||||
Clause::Lambda { .. } => Ok((cls, Res::Inert)),
|
||||
Clause::Bottom(b) => Err(b),
|
||||
Clause::Constant(n) => match ctx.symbols.get(&n) {
|
||||
Some(expr) => Ok((Clause::Identity(expr.clsi()), Res::Cont)),
|
||||
None => Err(RunError::Extern(MissingSymbol { sym: n.clone(), loc: expr.location() }.rc())),
|
||||
},
|
||||
Clause::Atom(mut a) => {
|
||||
if !popped {
|
||||
if let Some(delegate) = a.0.redirect() {
|
||||
let next = delegate.clone();
|
||||
return Ok((Clause::Atom(a), Res::Push(next)));
|
||||
}
|
||||
}
|
||||
match cls {
|
||||
cls @ Clause::Identity(_) => return Ok((cls, false)),
|
||||
// TODO:
|
||||
// - unfuck nested loop
|
||||
// - inline most of [apply] to eliminate recursion step
|
||||
Clause::Apply { f, x } => {
|
||||
if x.is_empty() {
|
||||
return Ok((f.clause.into_cls(), false));
|
||||
}
|
||||
let (gas, clause) = apply(f, x, ctx.clone())?;
|
||||
if ctx.gas.is_some() {
|
||||
ctx.gas = gas;
|
||||
}
|
||||
cls = clause;
|
||||
let rd = RunData { ctx: ctx.clone(), location: expr.location() };
|
||||
match a.run(rd)? {
|
||||
AtomicReturn::Inert(c) => Ok((c, Res::Inert)),
|
||||
AtomicReturn::Change(gas, c) => {
|
||||
ctx.use_gas(gas);
|
||||
Ok((c, Res::Cont))
|
||||
},
|
||||
Clause::Atom(data) => {
|
||||
let run = RunData { ctx: ctx.clone(), location: expr.location() };
|
||||
let atomic_ret = data.run(run)?;
|
||||
if ctx.gas.is_some() {
|
||||
ctx.gas = atomic_ret.gas;
|
||||
}
|
||||
},
|
||||
Clause::Apply { f, mut x } => {
|
||||
if x.is_empty() {
|
||||
return Ok((Clause::Identity(f.clsi()), Res::Cont));
|
||||
}
|
||||
match &*f.cls() {
|
||||
Clause::Identity(f2) =>
|
||||
return Ok((Clause::Apply { f: f2.clone().to_expr(f.location()), x }, Res::Cont)),
|
||||
Clause::Apply { f, x: x2 } => {
|
||||
for item in x2.iter().rev() {
|
||||
x.push_front(item.clone())
|
||||
}
|
||||
if atomic_ret.inert {
|
||||
return Ok((atomic_ret.clause, true));
|
||||
}
|
||||
cls = atomic_ret.clause;
|
||||
return Ok((Clause::Apply { f: f.clone(), x }, Res::Cont));
|
||||
},
|
||||
Clause::Constant(c) => {
|
||||
let symval = (ctx.symbols.get(&c)).ok_or_else(|| {
|
||||
RunError::MissingSymbol(c.clone(), expr.location())
|
||||
})?;
|
||||
ctx.gas = ctx.gas.map(|g| g - 1); // cost of lookup
|
||||
cls = Clause::Identity(symval.clause.clone());
|
||||
_ => (),
|
||||
}
|
||||
if !popped {
|
||||
return Ok((Clause::Apply { f: f.clone(), x }, Res::Push(f)));
|
||||
}
|
||||
let f_cls = f.cls();
|
||||
let arg = x.pop_front().expect("checked above");
|
||||
let loc = f.location();
|
||||
let f = match &*f_cls {
|
||||
Clause::Atom(_) => {
|
||||
mem::drop(f_cls);
|
||||
apply_as_atom(f, arg, ctx.clone())?
|
||||
},
|
||||
// non-reducible
|
||||
c => return Ok((c, true)),
|
||||
Clause::Lambda { args, body } => match args {
|
||||
None => body.clsi().into_cls(),
|
||||
Some(args) => substitute(args, arg.clsi(), &body.cls(), &mut || ctx.use_gas(1)),
|
||||
},
|
||||
c => panic!("Run should never settle on {c}"),
|
||||
};
|
||||
}
|
||||
Ok((Clause::Apply { f: f.to_expr(loc), x }, Res::Cont))
|
||||
},
|
||||
})?;
|
||||
expr.clause = next_clsi;
|
||||
if inert {
|
||||
match stack.pop() {
|
||||
Some(e) => expr = e,
|
||||
None => return Ok(Halt { state: expr, gas: ctx.gas, inert }),
|
||||
}
|
||||
popped = matches!(res, Res::Inert);
|
||||
match res {
|
||||
Res::Cont => continue,
|
||||
Res::Inert => match stack.pop() {
|
||||
None => return Ok(Halt { state: expr, gas: ctx.gas, inert: true }),
|
||||
Some(prev) => expr = prev,
|
||||
},
|
||||
Res::Push(next) => {
|
||||
if stack.len() == ctx.stack_size {
|
||||
stack.extend([expr, next]);
|
||||
return Err(RunError::Extern(StackOverflow { stack }.rc()));
|
||||
}
|
||||
stack.push(expr);
|
||||
expr = next;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user