Backup commit

My backspace key started ghosting. Nothing works atm.
This commit is contained in:
2024-01-27 14:50:33 +00:00
parent f77e4fd90a
commit a8887227e5
236 changed files with 10946 additions and 8977 deletions

View File

@@ -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"),
}
}
}

View File

@@ -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

View File

@@ -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
View 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()))
}
}

View File

@@ -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;
}
}

View File

@@ -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
View 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 }
}

View 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
View 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)");
}
}

View File

@@ -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 }),
}
}
}
}