in midst of refactor

This commit is contained in:
2024-04-29 21:46:42 +02:00
parent ed0d64d52e
commit aa3f7e99ab
221 changed files with 5431 additions and 685 deletions

View File

@@ -0,0 +1,109 @@
use never::Never;
use super::context::{RunEnv, RunParams};
use super::nort::{Clause, ClauseInst, Expr};
use super::path_set::{PathSet, Step};
use crate::foreign::atom::CallData;
use crate::foreign::error::RTResult;
/// 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>(
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_mut(), 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_mut(), mapper)?.into_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.cls_mut(), mapper)?.into_expr(x.location()));
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 }
},
}
},
(_, 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]
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) {
(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.cls_mut(), on_sub).into_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.cls_mut(), on_sub);
*old = tmp.into_expr(old.location());
}
}
Ok(Clause::Apply { f, x: argv })
},
(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"),
}
})
.unwrap_or_else(|e| match e {})
}
pub(super) fn apply_as_atom(
f: Expr,
arg: Expr,
env: &RunEnv,
params: &mut RunParams,
) -> RTResult<Clause> {
let call = CallData { location: f.location(), arg, env, params };
match f.clause.try_unwrap() {
Ok(clause) => match clause {
Clause::Atom(atom) => Ok(atom.apply(call)?),
_ => panic!("Not an atom"),
},
Err(clsi) => match &mut *clsi.cls_mut() {
Clause::Atom(atom) => Ok(atom.apply_mut(call)?),
_ => panic!("Not an atom"),
},
}
}

View File

@@ -0,0 +1,33 @@
//! Error produced by the interpreter.
use std::fmt;
use super::run::State;
use crate::foreign::error::{RTError, RTErrorObj};
/// Error produced by the interpreter. This could be because the code is faulty,
/// but equally because gas was being counted and it ran out.
#[derive(Debug)]
pub enum RunError<'a> {
/// A Rust function encountered an error
Extern(RTErrorObj),
/// Ran out of gas
Interrupted(State<'a>),
}
impl<'a, T: RTError + 'static> From<T> for RunError<'a> {
fn from(value: T) -> Self { Self::Extern(value.pack()) }
}
impl<'a> From<RTErrorObj> for RunError<'a> {
fn from(value: RTErrorObj) -> Self { Self::Extern(value) }
}
impl<'a> fmt::Display for RunError<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Interrupted(i) => write!(f, "Ran out of gas:\n{i}"),
Self::Extern(e) => write!(f, "Program fault: {e}"),
}
}
}

View File

@@ -0,0 +1,94 @@
//! 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))))
.into_expr(ctx.0.clone())
}
fn arg(ctx: Self::Ctx<'_>, name: &str) -> Self {
Clause::arg(ctx.clone(), name).into_expr(ctx.0.clone())
}
fn atom(ctx: Self::Ctx<'_>, a: Atom) -> Self {
Clause::atom(ctx.clone(), a).into_expr(ctx.0.clone())
}
fn constant<'a>(ctx: Self::Ctx<'_>, name: impl IntoIterator<Item = &'a str>) -> Self {
Clause::constant(ctx.clone(), name).into_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)))).into_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).into_inst() }
fn atom(ctx: Self::Ctx<'_>, a: Atom) -> Self { Clause::atom(ctx, a).into_inst() }
fn constant<'a>(ctx: Self::Ctx<'_>, name: impl IntoIterator<Item = &'a str>) -> Self {
Clause::constant(ctx, name).into_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)).into_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)).into_expr(ctx.0.clone()),
|c| x((ctx.0.clone(), c)).into_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)).into_expr(ctx.0.clone()),
|c| x((ctx.0.clone(), c)).into_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)).into_expr(ctx.0.clone()))
}
}

View File

@@ -0,0 +1,10 @@
//! functions to execute Orchid code
mod apply;
pub mod context;
pub mod error;
pub mod gen_nort;
pub mod handler;
pub mod nort;
pub mod nort_builder;
pub(crate) mod path_set;
pub mod run;

View File

@@ -0,0 +1,148 @@
//! Helper for generating the interpreter's internal representation
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() {
f(self.pop(1).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 res = ctx.stack.iter().try_fold(vec![], |path, item| match item {
IntGenData::Apply(_) => panic!("This is removed after handling"),
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 (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))),
}
})
}
/// 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 }
}

View File

@@ -0,0 +1,160 @@
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())
.sorted_unstable_by_key(|(k, _)| k.map_or(0, |n| n + 1))
.map(|(h, t)| format!("{}>{t}", print_step(*h)))
.join("|");
if !step_s.is_empty() {
write!(f, "{step_s}>")?;
}
write!(f, "({opts})")
},
None => write!(f, "{step_s}"),
}
}
}
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|3>1)");
}
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)");
}
}

View File

@@ -0,0 +1,249 @@
//! Executes Orchid code
use std::ops::{Deref, DerefMut};
use std::sync::{MutexGuard, TryLockError};
use std::{fmt, mem};
use bound::Bound;
use itertools::Itertools;
use super::context::{Halt, RunEnv, RunParams};
use super::error::RunError;
use super::nort::{Clause, Expr};
use crate::foreign::atom::{AtomicReturn, RunData};
use crate::foreign::error::{RTError, RTErrorObj};
use crate::interpreter::apply::{apply_as_atom, substitute};
use crate::location::CodeLocation;
use crate::utils::take_with_output::take_with_output;
#[derive(Debug)]
struct Stackframe {
expr: Expr,
cls: Bound<MutexGuard<'static, Clause>, Expr>,
}
impl Stackframe {
pub fn new(expr: Expr) -> Option<Self> {
match Bound::try_new(expr.clone(), |e| e.clause.0.try_lock()) {
Ok(cls) => Some(Stackframe { cls, expr }),
Err(bound_e) if matches!(bound_e.wrapped(), TryLockError::WouldBlock) => None,
Err(bound_e) => panic!("{:?}", bound_e.wrapped()),
}
}
pub fn wait_new(expr: Expr) -> Self {
Self { cls: Bound::new(expr.clone(), |e| e.clause.0.lock().unwrap()), expr }
}
pub fn record_cycle(&mut self) -> RTErrorObj {
let err = CyclicalExpression(self.expr.clone()).pack();
*self.cls = Clause::Bottom(err.clone());
err
}
}
impl Deref for Stackframe {
type Target = Clause;
fn deref(&self) -> &Self::Target { &self.cls }
}
impl DerefMut for Stackframe {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.cls }
}
impl fmt::Display for Stackframe {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}\n at {}", *self.cls, self.expr.location)
}
}
/// Interpreter state when processing was interrupted
pub struct State<'a> {
stack: Vec<Stackframe>,
popped: Option<Expr>,
env: &'a RunEnv<'a>,
}
impl<'a> State<'a> {
/// Create a new trivial state with a specified stack size and a single
/// element on the stack
fn new(base: Expr, env: &'a RunEnv<'a>) -> Self {
let stack = vec![Stackframe::new(base).expect("Initial state should not be locked")];
State { stack, popped: None, env }
}
/// Try to push an expression on the stack, raise appropriate errors if the
/// expression is already on the stack (and thus references itself), or if the
/// stack now exceeds the pre-defined height
fn push_expr(&'_ mut self, expr: Expr, params: &RunParams) -> Result<(), RunError<'a>> {
let sf = match Stackframe::new(expr.clone()) {
Some(sf) => sf,
None => match self.stack.iter_mut().rev().find(|sf| sf.expr.clause.is_same(&expr.clause)) {
None => Stackframe::wait_new(expr),
Some(sf) => return Err(RunError::Extern(sf.record_cycle())),
},
};
self.stack.push(sf);
if params.stack < self.stack.len() {
let so = StackOverflow(self.stack.iter().map(|sf| sf.expr.clone()).collect());
return Err(RunError::Extern(so.pack()));
}
Ok(())
}
/// Process this state until it either completes, runs out of gas as specified
/// in the context, or produces an error.
pub fn run(mut self, params: &mut RunParams) -> Result<Halt, RunError<'a>> {
loop {
if params.no_gas() {
return Err(RunError::Interrupted(self));
}
params.use_gas(1);
let top = self.stack.last_mut().expect("Stack never empty");
let location = top.expr.location();
let op = take_with_output(&mut *top.cls, |c| {
match step(c, self.popped, location, self.env, params) {
Err(e) => (Clause::Bottom(e.clone()), Err(RunError::Extern(e))),
Ok((cls, cmd)) => (cls, Ok(cmd)),
}
})?;
self.popped = None;
match op {
StackOp::Nop => continue,
StackOp::Push(ex) => self.push_expr(ex, params)?,
StackOp::Swap(ex) => {
self.stack.pop().expect("Stack never empty");
self.push_expr(ex, params)?
},
StackOp::Pop => {
let ret = self.stack.pop().expect("last_mut called above");
if self.stack.is_empty() {
if let Some(alt) = self.env.dispatch(&ret.cls, ret.expr.location()) {
self.push_expr(alt, params)?;
params.use_gas(1);
continue;
}
return Ok(ret.expr);
} else {
self.popped = Some(ret.expr);
}
},
}
}
}
}
impl<'a> fmt::Display for State<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.stack.iter().rev().join("\n"))
}
}
impl<'a> fmt::Debug for State<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "State({self})") }
}
/// Process an expression with specific resource limits
pub fn run<'a>(
base: Expr,
env: &'a RunEnv<'a>,
params: &mut RunParams,
) -> Result<Halt, RunError<'a>> {
State::new(base, env).run(params)
}
enum StackOp {
Pop,
Nop,
Swap(Expr),
Push(Expr),
}
fn step(
top: Clause,
popped: Option<Expr>,
location: CodeLocation,
env: &RunEnv,
params: &mut RunParams,
) -> Result<(Clause, StackOp), RTErrorObj> {
match top {
Clause::Bottom(err) => Err(err),
Clause::LambdaArg => Ok((Clause::Bottom(UnboundArg(location).pack()), StackOp::Nop)),
l @ Clause::Lambda { .. } => Ok((l, StackOp::Pop)),
Clause::Identity(other) =>
Ok((Clause::Identity(other.clone()), StackOp::Swap(other.into_expr(location)))),
Clause::Constant(name) => {
let expr = env.load(name, location)?;
Ok((Clause::Identity(expr.clsi()), StackOp::Swap(expr.clone())))
},
Clause::Atom(mut at) => {
if let Some(delegate) = at.0.redirect() {
match popped {
Some(popped) => *delegate = popped,
None => {
let tmp = delegate.clone();
return Ok((Clause::Atom(at), StackOp::Push(tmp)));
},
}
}
match at.run(RunData { params, env, location })? {
AtomicReturn::Inert(at) => Ok((Clause::Atom(at), StackOp::Pop)),
AtomicReturn::Change(gas, c) => {
params.use_gas(gas);
Ok((c, StackOp::Nop))
},
}
},
Clause::Apply { mut f, mut x } => {
if x.is_empty() {
return Ok((Clause::Identity(f.clsi()), StackOp::Swap(f)));
}
f = match popped {
None => return Ok((Clause::Apply { f: f.clone(), x }, StackOp::Push(f))),
Some(ex) => ex,
};
let val = x.pop_front().expect("Empty args handled above");
let f_mut = f.clause.cls_mut();
let mut cls = match &*f_mut {
Clause::Lambda { args, body } => match args {
None => Clause::Identity(body.clsi()),
Some(args) => substitute(args, val.clsi(), &body.cls_mut(), &mut || params.use_gas(1)),
},
Clause::Atom(_) => {
mem::drop(f_mut);
apply_as_atom(f, val, env, params)?
},
c => unreachable!("Run should never settle on {c}"),
};
if !x.is_empty() {
cls = Clause::Apply { f: cls.into_expr(location), x };
}
Ok((cls, StackOp::Nop))
},
}
}
#[derive(Clone)]
pub(crate) struct StackOverflow(Vec<Expr>);
impl RTError for StackOverflow {}
impl fmt::Display for StackOverflow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Stack depth exceeded {}:", self.0.len() - 1)?; // 1 for failed call, 1 for current
for item in self.0.iter().rev() {
match Stackframe::new(item.clone()) {
Some(sf) => writeln!(f, "{sf}")?,
None => writeln!(f, "Locked frame at {}", item.location)?,
}
}
Ok(())
}
}
#[derive(Clone)]
pub(crate) struct UnboundArg(CodeLocation);
impl RTError for UnboundArg {}
impl fmt::Display for UnboundArg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Unbound argument found at {}. This is likely a codegen error", self.0)
}
}
#[derive(Clone)]
pub(crate) struct CyclicalExpression(Expr);
impl RTError for CyclicalExpression {}
impl fmt::Display for CyclicalExpression {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "The expression {} contains itself", self.0)
}
}