forked from Orchid/orchid
Almost Alpha
Massive improvements across the board. One day I'll adopt incremental commits.
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
use never::Never;
|
||||
|
||||
use super::context::RunContext;
|
||||
use super::error::RunError;
|
||||
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
|
||||
@@ -16,12 +16,12 @@ fn map_at<E>(
|
||||
) -> Result<Clause, E> {
|
||||
// Pass through some unambiguous wrapper clauses
|
||||
match source {
|
||||
Clause::Identity(alt) => return map_at(path, &alt.cls(), mapper),
|
||||
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(), mapper)?.to_inst(),
|
||||
clause: map_at(path, &clause.cls_mut(), mapper)?.into_inst(),
|
||||
location: b_loc.clone(),
|
||||
},
|
||||
}),
|
||||
@@ -33,7 +33,7 @@ fn map_at<E>(
|
||||
(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(), mapper)?.to_expr(x.location()));
|
||||
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) => {
|
||||
@@ -68,12 +68,12 @@ pub fn substitute(
|
||||
let mut argv = x.clone();
|
||||
let f = match conts.get(&None) {
|
||||
None => f.clone(),
|
||||
Some(sp) => substitute(sp, value.clone(), &f.cls(), on_sub).to_expr(f.location()),
|
||||
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(), on_sub);
|
||||
*old = tmp.to_expr(old.location());
|
||||
let tmp = substitute(sp, value.clone(), &old.cls_mut(), on_sub);
|
||||
*old = tmp.into_expr(old.location());
|
||||
}
|
||||
}
|
||||
Ok(Clause::Apply { f, x: argv })
|
||||
@@ -89,15 +89,20 @@ pub fn substitute(
|
||||
.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 };
|
||||
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 &*clsi.cls() {
|
||||
Clause::Atom(atom) => Ok(atom.apply_ref(call)?),
|
||||
Err(clsi) => match &mut *clsi.cls_mut() {
|
||||
Clause::Atom(atom) => Ok(atom.apply_mut(call)?),
|
||||
_ => panic!("Not an atom"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,19 +1,67 @@
|
||||
//! Addiitional information passed to the interpreter
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use super::nort::Expr;
|
||||
use super::handler::HandlerTable;
|
||||
use super::nort::{Clause, Expr};
|
||||
use crate::foreign::error::{RTError, RTErrorObj, RTResult};
|
||||
use crate::location::CodeLocation;
|
||||
use crate::name::Sym;
|
||||
|
||||
/// All the data associated with an interpreter run
|
||||
#[derive(Clone)]
|
||||
pub struct RunContext<'a> {
|
||||
/// Table used to resolve constants
|
||||
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,
|
||||
/// Data that must not change except in well-defined ways while any data
|
||||
/// associated with this process persists
|
||||
pub struct RunEnv<'a> {
|
||||
/// Mutable callbacks the code can invoke with continuation passing
|
||||
pub handlers: HandlerTable<'a>,
|
||||
/// Constants referenced in the code in [super::nort::Clause::Constant] nodes
|
||||
pub symbols: RefCell<HashMap<Sym, RTResult<Expr>>>,
|
||||
/// Callback to invoke when a symbol is not found
|
||||
pub symbol_cb: Box<dyn Fn(Sym, CodeLocation) -> RTResult<Expr> + 'a>,
|
||||
}
|
||||
impl<'a> RunContext<'a> {
|
||||
|
||||
impl<'a> RunEnv<'a> {
|
||||
/// Create a new context. The return values of the symbol callback are cached
|
||||
pub fn new(
|
||||
handlers: HandlerTable<'a>,
|
||||
symbol_cb: impl Fn(Sym, CodeLocation) -> RTResult<Expr> + 'a,
|
||||
) -> Self {
|
||||
Self { handlers, symbols: RefCell::new(HashMap::new()), symbol_cb: Box::new(symbol_cb) }
|
||||
}
|
||||
|
||||
/// Produce an error indicating that a symbol was missing
|
||||
pub fn sym_not_found(sym: Sym, location: CodeLocation) -> RTErrorObj {
|
||||
MissingSymbol { location, sym }.pack()
|
||||
}
|
||||
|
||||
/// Load a symbol from cache or invoke the callback
|
||||
pub fn load(&self, sym: Sym, location: CodeLocation) -> RTResult<Expr> {
|
||||
let mut guard = self.symbols.borrow_mut();
|
||||
let (_, r) = (guard.raw_entry_mut().from_key(&sym))
|
||||
.or_insert_with(|| (sym.clone(), (self.symbol_cb)(sym, location)));
|
||||
r.clone()
|
||||
}
|
||||
|
||||
/// Attempt to resolve the command with the command handler table
|
||||
pub fn dispatch(&self, expr: &Clause, location: CodeLocation) -> Option<Expr> {
|
||||
match expr {
|
||||
Clause::Atom(at) => self.handlers.dispatch(&*at.0, location),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Limits and other context that is subject to change
|
||||
pub struct RunParams {
|
||||
/// Number of reduction steps permitted before the program is preempted
|
||||
pub gas: Option<usize>,
|
||||
/// Maximum recursion depth. Orchid uses a soft stack so this can be very
|
||||
/// large, but it must not be
|
||||
pub stack: usize,
|
||||
}
|
||||
impl RunParams {
|
||||
/// Consume some gas if it is being counted
|
||||
pub fn use_gas(&mut self, amount: usize) {
|
||||
if let Some(g) = self.gas.as_mut() {
|
||||
@@ -22,39 +70,26 @@ impl<'a> RunContext<'a> {
|
||||
}
|
||||
/// 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 Halt {
|
||||
/// The new expression tree
|
||||
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 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
|
||||
pub fn status(&self) -> ReturnStatus {
|
||||
if self.preempted() {
|
||||
ReturnStatus::Preempted
|
||||
} else if self.inert {
|
||||
ReturnStatus::Inert
|
||||
} else {
|
||||
ReturnStatus::Active
|
||||
/// Add gas to make execution longer, or to resume execution in a preempted
|
||||
/// expression
|
||||
pub fn add_gas(&mut self, amount: usize) {
|
||||
if let Some(g) = self.gas.as_mut() {
|
||||
*g = g.saturating_add(amount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Possible states of a [Return]
|
||||
pub enum ReturnStatus {
|
||||
/// The data is not normalizable any further
|
||||
Inert,
|
||||
/// Gas is being used and it ran out
|
||||
Preempted,
|
||||
/// Normalization stopped for a different reason and should continue.
|
||||
Active,
|
||||
/// The interpreter's sole output excluding error conditions is an expression
|
||||
pub type Halt = Expr;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct MissingSymbol {
|
||||
pub sym: Sym,
|
||||
pub location: CodeLocation,
|
||||
}
|
||||
impl RTError for MissingSymbol {}
|
||||
impl fmt::Display for MissingSymbol {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}, called at {} is not loaded", self.sym, self.location)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +1,33 @@
|
||||
use std::fmt::{self, Debug, Display};
|
||||
//! Error produced by the interpreter.
|
||||
|
||||
use itertools::Itertools;
|
||||
use std::fmt;
|
||||
|
||||
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::State;
|
||||
use crate::foreign::error::{RTError, RTErrorObj};
|
||||
|
||||
/// 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 {
|
||||
/// 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(ExternErrorObj),
|
||||
Extern(RTErrorObj),
|
||||
/// Ran out of gas
|
||||
Interrupted(Interrupted),
|
||||
Interrupted(State<'a>),
|
||||
}
|
||||
|
||||
impl<T: ExternError + 'static> From<T> for RunError {
|
||||
fn from(value: T) -> Self { Self::Extern(value.rc()) }
|
||||
impl<'a, T: RTError + 'static> From<T> for RunError<'a> {
|
||||
fn from(value: T) -> Self { Self::Extern(value.pack()) }
|
||||
}
|
||||
|
||||
impl From<ExternErrorObj> for RunError {
|
||||
fn from(value: ExternErrorObj) -> Self { Self::Extern(value) }
|
||||
impl<'a> From<RTErrorObj> for RunError<'a> {
|
||||
fn from(value: RTErrorObj) -> Self { Self::Extern(value) }
|
||||
}
|
||||
|
||||
impl Display for RunError {
|
||||
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{}", strace(&i.stack))
|
||||
},
|
||||
Self::Interrupted(i) => write!(f, "Ran out of gas:\n{i}"),
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,56 +26,33 @@ impl Generable for Expr {
|
||||
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())
|
||||
(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).to_expr(ctx.0.clone())
|
||||
Clause::arg(ctx.clone(), name).into_expr(ctx.0.clone())
|
||||
}
|
||||
fn atom(ctx: Self::Ctx<'_>, a: Atom) -> Self {
|
||||
Clause::atom(ctx.clone(), a).to_expr(ctx.0.clone())
|
||||
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).to_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))))
|
||||
.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)))).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).to_inst()
|
||||
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 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 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<'_>,
|
||||
@@ -83,8 +60,8 @@ impl Generable for ClauseInst {
|
||||
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()),
|
||||
|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())
|
||||
}
|
||||
@@ -93,10 +70,7 @@ impl Generable for ClauseInst {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
@@ -106,21 +80,15 @@ impl Generable for Clause {
|
||||
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()),
|
||||
|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)).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)).into_expr(ctx.0.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,39 @@
|
||||
//! Impure functions that can be triggered by Orchid code when a command
|
||||
//! evaluates to an atom representing a command
|
||||
|
||||
use std::any::{Any, TypeId};
|
||||
use std::cell::RefCell;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use trait_set::trait_set;
|
||||
|
||||
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 super::nort::Expr;
|
||||
use crate::foreign::atom::Atomic;
|
||||
use crate::foreign::error::RTResult;
|
||||
use crate::foreign::to_clause::ToClause;
|
||||
use crate::location::CodeLocation;
|
||||
|
||||
trait_set! {
|
||||
trait Handler = for<'a> FnMut(&'a dyn Any, CodeLocation) -> Expr;
|
||||
trait Handler = for<'a> Fn(&'a dyn Any, CodeLocation) -> Expr;
|
||||
}
|
||||
|
||||
/// A table of command handlers
|
||||
enum HTEntry<'a> {
|
||||
Handler(Box<dyn Handler + 'a>),
|
||||
Forward(&'a (dyn Handler + 'a)),
|
||||
}
|
||||
impl<'a> AsRef<dyn Handler + 'a> for HTEntry<'a> {
|
||||
fn as_ref(&self) -> &(dyn Handler + 'a) {
|
||||
match self {
|
||||
HTEntry::Handler(h) => &**h,
|
||||
HTEntry::Forward(h) => *h,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A table of impure command handlers exposed to Orchid
|
||||
#[derive(Default)]
|
||||
pub struct HandlerTable<'a> {
|
||||
handlers: HashMap<TypeId, Box<dyn Handler + 'a>>,
|
||||
handlers: HashMap<TypeId, HTEntry<'a>>,
|
||||
}
|
||||
impl<'a> HandlerTable<'a> {
|
||||
/// Create a new [HandlerTable]
|
||||
@@ -28,35 +42,25 @@ impl<'a> HandlerTable<'a> {
|
||||
|
||||
/// 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 for<'b> FnMut(&'b T) -> R + 'a,
|
||||
) {
|
||||
pub fn register<T: 'static, R: ToClause>(&mut self, f: impl for<'b> FnMut(&'b T) -> R + 'a) {
|
||||
let cell = RefCell::new(f);
|
||||
let cb = move |a: &dyn Any, loc: CodeLocation| {
|
||||
f(a.downcast_ref().expect("found by TypeId")).to_expr(loc)
|
||||
cell.borrow_mut()(a.downcast_ref().expect("found by TypeId")).to_expr(loc)
|
||||
};
|
||||
let prev = self.handlers.insert(TypeId::of::<T>(), Box::new(cb));
|
||||
let prev = self.handlers.insert(TypeId::of::<T>(), HTEntry::Handler(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 {
|
||||
pub fn with<T: 'static>(mut self, f: impl FnMut(&T) -> RTResult<Expr> + 'a) -> Self {
|
||||
self.register(f);
|
||||
self
|
||||
}
|
||||
|
||||
/// Find and execute the corresponding handler for this type
|
||||
pub fn dispatch(
|
||||
&mut self,
|
||||
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))
|
||||
pub fn dispatch(&self, arg: &dyn Atomic, loc: CodeLocation) -> Option<Expr> {
|
||||
(self.handlers.get(&arg.as_any_ref().type_id())).map(|ent| ent.as_ref()(arg.as_any_ref(), loc))
|
||||
}
|
||||
|
||||
/// Combine two non-overlapping handler sets
|
||||
@@ -68,30 +72,45 @@ impl<'a> HandlerTable<'a> {
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// [run] orchid code, executing any commands it returns using the specified
|
||||
/// [Handler]s.
|
||||
pub fn run_handler(
|
||||
mut state: Expr,
|
||||
handlers: &mut HandlerTable,
|
||||
mut ctx: RunContext,
|
||||
) -> Result<Halt, RunError> {
|
||||
loop {
|
||||
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);
|
||||
state = res;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if halt.inert || ctx.no_gas() {
|
||||
drop(state_cls);
|
||||
break Ok(Halt { gas: ctx.gas, inert: halt.inert, state });
|
||||
/// Add entries that forward requests to a borrowed non-overlapping handler
|
||||
/// set
|
||||
pub fn link<'b: 'a>(mut self, other: &'b HandlerTable<'b>) -> Self {
|
||||
for (key, value) in other.handlers.iter() {
|
||||
let prev = self.handlers.insert(*key, HTEntry::Forward(value.as_ref()));
|
||||
assert!(prev.is_none(), "Duplicate handlers")
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(unconditional_recursion)]
|
||||
#[allow(clippy::ptr_arg)]
|
||||
mod test {
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use super::HandlerTable;
|
||||
|
||||
/// Ensure that the method I use to verify covariance actually passes with
|
||||
/// covariant and fails with invariant
|
||||
///
|
||||
/// The failing case:
|
||||
/// ```
|
||||
/// struct Cov2<'a>(PhantomData<&'a mut &'a ()>);
|
||||
/// fn fail<'a>(_c: &Cov2<'a>, _s: &'a String) { fail(_c, &String::new()) }
|
||||
/// ```
|
||||
#[allow(unused)]
|
||||
fn covariant_control() {
|
||||
struct Cov<'a>(PhantomData<&'a ()>);
|
||||
fn pass<'a>(_c: &Cov<'a>, _s: &'a String) { pass(_c, &String::new()) }
|
||||
}
|
||||
|
||||
/// The &mut ensures that 'a in the two functions must be disjoint, and that
|
||||
/// ht must outlive both. For this to compile, Rust has to cast ht to the
|
||||
/// shorter lifetimes, ensuring covariance
|
||||
#[allow(unused)]
|
||||
fn assert_covariant() {
|
||||
fn pass<'a>(_ht: HandlerTable<'a>, _s: &'a String) { pass(_ht, &String::new()) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//! functions to interact with Orchid code
|
||||
pub mod apply;
|
||||
//! functions to execute Orchid code
|
||||
mod apply;
|
||||
pub mod context;
|
||||
pub mod error;
|
||||
pub mod gen_nort;
|
||||
pub mod handler;
|
||||
pub mod nort_builder;
|
||||
pub mod nort;
|
||||
pub mod nort_builder;
|
||||
pub(crate) mod path_set;
|
||||
pub mod run;
|
||||
|
||||
@@ -13,25 +13,23 @@
|
||||
//! 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 std::fmt;
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::{Arc, Mutex, MutexGuard, 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::error::{RTErrorObj, RTResult};
|
||||
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> {
|
||||
@@ -48,19 +46,17 @@ pub struct Expr {
|
||||
}
|
||||
impl Expr {
|
||||
/// Constructor
|
||||
pub fn new(clause: ClauseInst, location: CodeLocation) -> Self {
|
||||
Self { clause, location }
|
||||
}
|
||||
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> {
|
||||
pub fn downcast<T: TryFromExpr>(self) -> RTResult<T> {
|
||||
let Expr { mut clause, location } = self;
|
||||
loop {
|
||||
let cls_deref = clause.cls();
|
||||
let cls_deref = clause.cls_mut();
|
||||
match &*cls_deref {
|
||||
Clause::Identity(alt) => {
|
||||
let temp = alt.clone();
|
||||
@@ -79,22 +75,16 @@ impl Expr {
|
||||
/// returning [Some]
|
||||
///
|
||||
/// See also [parsed::Expr::search_all]
|
||||
pub fn search_all<T>(
|
||||
&self,
|
||||
predicate: &mut impl FnMut(&Self) -> Option<T>,
|
||||
) -> Option<T> {
|
||||
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::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,
|
||||
Clause::Constant(_) | Clause::LambdaArg | Clause::Atom(_) | Clause::Bottom(_) => None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -102,47 +92,28 @@ impl Expr {
|
||||
#[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()
|
||||
}
|
||||
pub fn cls_mut(&self) -> MutexGuard<'_, Clause> { self.clause.cls_mut() }
|
||||
}
|
||||
|
||||
impl Debug for Expr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
impl fmt::Debug for Expr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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 fmt::Display for Expr {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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()
|
||||
}
|
||||
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)]
|
||||
@@ -159,91 +130,17 @@ impl 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.
|
||||
///
|
||||
/// 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<(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)),
|
||||
},
|
||||
_ => 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),
|
||||
})
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
/// if the clause is already borrowed, this will block until it is released.
|
||||
pub fn cls_mut(&self) -> MutexGuard<'_, Clause> { self.0.lock().unwrap() }
|
||||
|
||||
/// 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() {
|
||||
match &*self.cls_mut() {
|
||||
Clause::Identity(sub) => sub.inspect(predicate),
|
||||
x => predicate(x),
|
||||
}
|
||||
@@ -253,7 +150,7 @@ impl ClauseInst {
|
||||
/// 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() {
|
||||
match &*self.cls_mut() {
|
||||
Clause::Atom(a) => request(&*a.0),
|
||||
Clause::Identity(alt) => alt.request(),
|
||||
_ => None,
|
||||
@@ -261,7 +158,7 @@ impl ClauseInst {
|
||||
}
|
||||
|
||||
/// Associate a location with this clause
|
||||
pub fn to_expr(self, location: CodeLocation) -> Expr {
|
||||
pub fn into_expr(self, location: CodeLocation) -> Expr {
|
||||
Expr { clause: self.clone(), location: location.clone() }
|
||||
}
|
||||
/// Check ahead-of-time if this clause contains an atom. Calls
|
||||
@@ -269,7 +166,7 @@ impl ClauseInst {
|
||||
///
|
||||
/// 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(_)) }
|
||||
pub fn is_atom(&self) -> bool { matches!(&*self.cls_mut(), 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
|
||||
@@ -278,21 +175,24 @@ impl ClauseInst {
|
||||
/// 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() {
|
||||
self.try_unwrap().unwrap_or_else(|clsi| match &*clsi.cls_mut() {
|
||||
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::Lambda { args, body } => Clause::Lambda { args: args.clone(), body: body.clone() },
|
||||
Clause::LambdaArg => Clause::LambdaArg,
|
||||
})
|
||||
}
|
||||
|
||||
/// Decides if this clause is the exact same instance as another. Most useful
|
||||
/// to detect potential deadlocks.
|
||||
pub fn is_same(&self, other: &Self) -> bool { Arc::ptr_eq(&self.0, &other.0) }
|
||||
}
|
||||
|
||||
impl Debug for ClauseInst {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
impl fmt::Debug for ClauseInst {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.0.try_lock() {
|
||||
Ok(expr) => write!(f, "{expr:?}"),
|
||||
Err(TryLockError::Poisoned(_)) => write!(f, "<poisoned>"),
|
||||
@@ -301,8 +201,8 @@ impl Debug for ClauseInst {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ClauseInst {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
impl fmt::Display for ClauseInst {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.0.try_lock() {
|
||||
Ok(expr) => write!(f, "{expr}"),
|
||||
Err(TryLockError::Poisoned(_)) => write!(f, "<poisoned>"),
|
||||
@@ -312,16 +212,14 @@ impl Display for ClauseInst {
|
||||
}
|
||||
|
||||
impl AsDerefMut<Clause> for ClauseInst {
|
||||
fn as_deref_mut(&mut self) -> impl DerefMut<Target = Clause> + '_ {
|
||||
self.cls_mut()
|
||||
}
|
||||
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),
|
||||
Bottom(RTErrorObj),
|
||||
/// Indicates that this [ClauseInst] has the same value as the other
|
||||
/// [ClauseInst]. This has two benefits;
|
||||
///
|
||||
@@ -358,21 +256,18 @@ pub enum Clause {
|
||||
}
|
||||
impl Clause {
|
||||
/// Wrap a clause in a refcounted lock
|
||||
pub fn to_inst(self) -> ClauseInst { ClauseInst::new(self) }
|
||||
pub fn into_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)
|
||||
}
|
||||
pub fn into_expr(self, location: CodeLocation) -> Expr { self.into_inst().into_expr(location) }
|
||||
}
|
||||
|
||||
impl Display for Clause {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
impl fmt::Display for Clause {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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::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}]"),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Helper for generating the interpreter's internal representation
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::mem;
|
||||
|
||||
@@ -34,8 +36,7 @@ impl ArgCollector {
|
||||
/// 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>;
|
||||
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].
|
||||
@@ -83,7 +84,7 @@ impl<'a, T: ?Sized, U: ?Sized> NortBuilder<'a, T, U> {
|
||||
IntGenData::Apply(_) => panic!("This is removed after handling"),
|
||||
IntGenData::Lambda(n, rc) => match lambda_chk(n) {
|
||||
false => Ok(path),
|
||||
true => Err((path, *rc))
|
||||
true => Err((path, *rc)),
|
||||
},
|
||||
IntGenData::AppArg(n) => Ok(pushed(path, Some(*n))),
|
||||
IntGenData::AppF => Ok(pushed(path, None)),
|
||||
@@ -100,11 +101,7 @@ impl<'a, T: ?Sized, U: ?Sized> NortBuilder<'a, T, U> {
|
||||
/// 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 {
|
||||
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)));
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::utils::join::join_maps;
|
||||
/// 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() }
|
||||
if let Some(n) = step { format!("{n}") } else { "f".to_string() }
|
||||
}
|
||||
|
||||
/// A branching path selecting some placeholders (but at least one) in a Lambda
|
||||
@@ -59,10 +59,7 @@ impl PathSet {
|
||||
};
|
||||
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();
|
||||
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
|
||||
@@ -71,10 +68,8 @@ impl PathSet {
|
||||
(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)),
|
||||
),
|
||||
(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
|
||||
@@ -102,10 +97,7 @@ impl PathSet {
|
||||
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),
|
||||
])
|
||||
Self::branch(short.steps, [(short_last, new_short), (long.steps[match_len], new_long)])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,22 +111,25 @@ impl PathSet {
|
||||
|
||||
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("");
|
||||
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})")
|
||||
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}x"),
|
||||
None => write!(f, "{step_s}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for PathSet {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "PathSet({self})")
|
||||
}
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "PathSet({self})") }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -146,7 +141,7 @@ mod tests {
|
||||
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)");
|
||||
assert_eq!(format!("{sum}"), "(2>f|3>1)");
|
||||
}
|
||||
|
||||
fn extend_scaffold() -> PathSet {
|
||||
|
||||
@@ -1,129 +1,249 @@
|
||||
use std::mem;
|
||||
//! Executes Orchid code
|
||||
|
||||
use super::context::{Halt, RunContext};
|
||||
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::ExternError;
|
||||
use crate::foreign::error::{RTError, RTErrorObj};
|
||||
use crate::interpreter::apply::{apply_as_atom, substitute};
|
||||
use crate::interpreter::error::{strace, MissingSymbol, StackOverflow};
|
||||
use crate::utils::pure_seq::pushed;
|
||||
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
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Interrupted {
|
||||
/// Cached soft stack to save the interpreter having to rebuild it from the
|
||||
/// bottom.
|
||||
pub stack: Vec<Expr>,
|
||||
}
|
||||
impl Interrupted {
|
||||
/// Continue processing where it was interrupted
|
||||
pub fn resume(self, ctx: RunContext) -> Result<Halt, RunError> { run_stack(self.stack, ctx) }
|
||||
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 }
|
||||
}
|
||||
|
||||
/// Normalize an expression using beta reduction with memoization
|
||||
pub fn run(expr: Expr, ctx: RunContext) -> Result<Halt, RunError> {
|
||||
let mut v = Vec::with_capacity(1000);
|
||||
v.push(expr);
|
||||
run_stack(v, ctx)
|
||||
}
|
||||
/// 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(())
|
||||
}
|
||||
|
||||
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) }));
|
||||
}
|
||||
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)));
|
||||
}
|
||||
/// 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)),
|
||||
}
|
||||
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::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())
|
||||
})?;
|
||||
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((Clause::Apply { f: f.clone(), x }, Res::Cont));
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
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())?
|
||||
},
|
||||
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;
|
||||
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;
|
||||
},
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user