forked from Orchid/orchid
in midst of refactor
This commit is contained in:
109
orchidlang/src/interpreter/apply.rs
Normal file
109
orchidlang/src/interpreter/apply.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
use never::Never;
|
||||
|
||||
use super::context::{RunEnv, RunParams};
|
||||
use super::nort::{Clause, ClauseInst, Expr};
|
||||
use super::path_set::{PathSet, Step};
|
||||
use crate::foreign::atom::CallData;
|
||||
use crate::foreign::error::RTResult;
|
||||
|
||||
/// Process the clause at the end of the provided path. Note that paths always
|
||||
/// point to at least one target. Note also that this is not cached as a
|
||||
/// normalization step in the intermediate expressions.
|
||||
fn map_at<E>(
|
||||
mut path: impl Iterator<Item = Step>,
|
||||
source: &Clause,
|
||||
mapper: &mut impl FnMut(&Clause) -> Result<Clause, E>,
|
||||
) -> Result<Clause, E> {
|
||||
// Pass through some unambiguous wrapper clauses
|
||||
match source {
|
||||
Clause::Identity(alt) => return map_at(path, &alt.cls_mut(), mapper),
|
||||
Clause::Lambda { args, body: Expr { location: b_loc, clause } } =>
|
||||
return Ok(Clause::Lambda {
|
||||
args: args.clone(),
|
||||
body: Expr {
|
||||
clause: map_at(path, &clause.cls_mut(), mapper)?.into_inst(),
|
||||
location: b_loc.clone(),
|
||||
},
|
||||
}),
|
||||
_ => (),
|
||||
}
|
||||
Ok(match (source, path.next()) {
|
||||
(Clause::Lambda { .. } | Clause::Identity(_), _) => unreachable!("Handled above"),
|
||||
// If the path ends and this isn't a lambda, process it
|
||||
(val, None) => mapper(val)?,
|
||||
// If it's an Apply, execute the next step in the path
|
||||
(Clause::Apply { f, x }, Some(head)) => {
|
||||
let proc = |x: &Expr| Ok(map_at(path, &x.cls_mut(), mapper)?.into_expr(x.location()));
|
||||
match head {
|
||||
None => Clause::Apply { f: proc(f)?, x: x.clone() },
|
||||
Some(n) => {
|
||||
let i = x.len() - n - 1;
|
||||
let mut argv = x.clone();
|
||||
argv[i] = proc(&x[i])?;
|
||||
Clause::Apply { f: f.clone(), x: argv }
|
||||
},
|
||||
}
|
||||
},
|
||||
(_, Some(_)) => panic!("Path leads into node that isn't Apply or Lambda"),
|
||||
})
|
||||
}
|
||||
|
||||
/// Replace the [Clause::LambdaArg] placeholders at the ends of the [PathSet]
|
||||
/// with the value in the body. Note that a path may point to multiple
|
||||
/// placeholders.
|
||||
#[must_use]
|
||||
pub fn substitute(
|
||||
paths: &PathSet,
|
||||
value: ClauseInst,
|
||||
body: &Clause,
|
||||
on_sub: &mut impl FnMut(),
|
||||
) -> Clause {
|
||||
let PathSet { steps, next } = paths;
|
||||
map_at(steps.iter().cloned(), body, &mut |chkpt| -> Result<Clause, Never> {
|
||||
match (chkpt, next) {
|
||||
(Clause::Lambda { .. } | Clause::Identity(_), _) => {
|
||||
unreachable!("Handled by map_at")
|
||||
},
|
||||
(Clause::Apply { f, x }, Some(conts)) => {
|
||||
let mut argv = x.clone();
|
||||
let f = match conts.get(&None) {
|
||||
None => f.clone(),
|
||||
Some(sp) => substitute(sp, value.clone(), &f.cls_mut(), on_sub).into_expr(f.location()),
|
||||
};
|
||||
for (i, old) in argv.iter_mut().rev().enumerate() {
|
||||
if let Some(sp) = conts.get(&Some(i)) {
|
||||
let tmp = substitute(sp, value.clone(), &old.cls_mut(), on_sub);
|
||||
*old = tmp.into_expr(old.location());
|
||||
}
|
||||
}
|
||||
Ok(Clause::Apply { f, x: argv })
|
||||
},
|
||||
(Clause::LambdaArg, None) => {
|
||||
on_sub();
|
||||
Ok(Clause::Identity(value.clone()))
|
||||
},
|
||||
(_, None) => panic!("Argument path must point to LambdaArg"),
|
||||
(_, Some(_)) => panic!("Argument path can only fork at Apply"),
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|e| match e {})
|
||||
}
|
||||
|
||||
pub(super) fn apply_as_atom(
|
||||
f: Expr,
|
||||
arg: Expr,
|
||||
env: &RunEnv,
|
||||
params: &mut RunParams,
|
||||
) -> RTResult<Clause> {
|
||||
let call = CallData { location: f.location(), arg, env, params };
|
||||
match f.clause.try_unwrap() {
|
||||
Ok(clause) => match clause {
|
||||
Clause::Atom(atom) => Ok(atom.apply(call)?),
|
||||
_ => panic!("Not an atom"),
|
||||
},
|
||||
Err(clsi) => match &mut *clsi.cls_mut() {
|
||||
Clause::Atom(atom) => Ok(atom.apply_mut(call)?),
|
||||
_ => panic!("Not an atom"),
|
||||
},
|
||||
}
|
||||
}
|
||||
33
orchidlang/src/interpreter/error.rs
Normal file
33
orchidlang/src/interpreter/error.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
//! Error produced by the interpreter.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use super::run::State;
|
||||
use crate::foreign::error::{RTError, RTErrorObj};
|
||||
|
||||
/// Error produced by the interpreter. This could be because the code is faulty,
|
||||
/// but equally because gas was being counted and it ran out.
|
||||
#[derive(Debug)]
|
||||
pub enum RunError<'a> {
|
||||
/// A Rust function encountered an error
|
||||
Extern(RTErrorObj),
|
||||
/// Ran out of gas
|
||||
Interrupted(State<'a>),
|
||||
}
|
||||
|
||||
impl<'a, T: RTError + 'static> From<T> for RunError<'a> {
|
||||
fn from(value: T) -> Self { Self::Extern(value.pack()) }
|
||||
}
|
||||
|
||||
impl<'a> From<RTErrorObj> for RunError<'a> {
|
||||
fn from(value: RTErrorObj) -> Self { Self::Extern(value) }
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for RunError<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Interrupted(i) => write!(f, "Ran out of gas:\n{i}"),
|
||||
Self::Extern(e) => write!(f, "Program fault: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
94
orchidlang/src/interpreter/gen_nort.rs
Normal file
94
orchidlang/src/interpreter/gen_nort.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
//! Implementations of [Generable] for [super::nort]
|
||||
|
||||
use intern_all::i;
|
||||
|
||||
use super::nort_builder::NortBuilder;
|
||||
use crate::foreign::atom::Atom;
|
||||
use crate::foreign::to_clause::ToClause;
|
||||
use crate::gen::traits::Generable;
|
||||
use crate::interpreter::nort::{Clause, ClauseInst, Expr};
|
||||
use crate::location::CodeLocation;
|
||||
use crate::name::Sym;
|
||||
|
||||
/// Context data for instantiating templated expressions as [super::nort].
|
||||
/// Instances of this type are created via [nort_gen]
|
||||
pub type NortGenCtx<'a> = (CodeLocation, NortBuilder<'a, str, str>);
|
||||
|
||||
/// Create [NortGenCtx] instances to generate interpreted expressions
|
||||
pub fn nort_gen<'a>(location: CodeLocation) -> NortGenCtx<'a> {
|
||||
(location, NortBuilder::new(&|l| Box::new(move |r| l == r)))
|
||||
}
|
||||
|
||||
impl Generable for Expr {
|
||||
type Ctx<'a> = NortGenCtx<'a>;
|
||||
fn apply(
|
||||
ctx: Self::Ctx<'_>,
|
||||
f_cb: impl FnOnce(Self::Ctx<'_>) -> Self,
|
||||
x_cb: impl FnOnce(Self::Ctx<'_>) -> Self,
|
||||
) -> Self {
|
||||
(ctx.1.apply_logic(|c| f_cb((ctx.0.clone(), c)), |c| x_cb((ctx.0.clone(), c))))
|
||||
.into_expr(ctx.0.clone())
|
||||
}
|
||||
fn arg(ctx: Self::Ctx<'_>, name: &str) -> Self {
|
||||
Clause::arg(ctx.clone(), name).into_expr(ctx.0.clone())
|
||||
}
|
||||
fn atom(ctx: Self::Ctx<'_>, a: Atom) -> Self {
|
||||
Clause::atom(ctx.clone(), a).into_expr(ctx.0.clone())
|
||||
}
|
||||
fn constant<'a>(ctx: Self::Ctx<'_>, name: impl IntoIterator<Item = &'a str>) -> Self {
|
||||
Clause::constant(ctx.clone(), name).into_expr(ctx.0.clone())
|
||||
}
|
||||
fn lambda(ctx: Self::Ctx<'_>, name: &str, body: impl FnOnce(Self::Ctx<'_>) -> Self) -> Self {
|
||||
(ctx.1.lambda_logic(name, |c| body((ctx.0.clone(), c)))).into_expr(ctx.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Generable for ClauseInst {
|
||||
type Ctx<'a> = NortGenCtx<'a>;
|
||||
fn arg(ctx: Self::Ctx<'_>, name: &str) -> Self { Clause::arg(ctx, name).into_inst() }
|
||||
fn atom(ctx: Self::Ctx<'_>, a: Atom) -> Self { Clause::atom(ctx, a).into_inst() }
|
||||
fn constant<'a>(ctx: Self::Ctx<'_>, name: impl IntoIterator<Item = &'a str>) -> Self {
|
||||
Clause::constant(ctx, name).into_inst()
|
||||
}
|
||||
fn lambda(ctx: Self::Ctx<'_>, name: &str, body: impl FnOnce(Self::Ctx<'_>) -> Self) -> Self {
|
||||
(ctx.1.lambda_logic(name, |c| body((ctx.0.clone(), c)).into_expr(ctx.0.clone())))
|
||||
.to_clsi(ctx.0.clone())
|
||||
}
|
||||
fn apply(
|
||||
ctx: Self::Ctx<'_>,
|
||||
f: impl FnOnce(Self::Ctx<'_>) -> Self,
|
||||
x: impl FnOnce(Self::Ctx<'_>) -> Self,
|
||||
) -> Self {
|
||||
(ctx.1.apply_logic(
|
||||
|c| f((ctx.0.clone(), c)).into_expr(ctx.0.clone()),
|
||||
|c| x((ctx.0.clone(), c)).into_expr(ctx.0.clone()),
|
||||
))
|
||||
.to_clsi(ctx.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Generable for Clause {
|
||||
type Ctx<'a> = NortGenCtx<'a>;
|
||||
fn atom(_: Self::Ctx<'_>, a: Atom) -> Self { Clause::Atom(a) }
|
||||
fn constant<'a>(_: Self::Ctx<'_>, name: impl IntoIterator<Item = &'a str>) -> Self {
|
||||
let sym = Sym::new(name.into_iter().map(i)).expect("Empty constant");
|
||||
Clause::Constant(sym)
|
||||
}
|
||||
fn apply(
|
||||
ctx: Self::Ctx<'_>,
|
||||
f: impl FnOnce(Self::Ctx<'_>) -> Self,
|
||||
x: impl FnOnce(Self::Ctx<'_>) -> Self,
|
||||
) -> Self {
|
||||
ctx.1.apply_logic(
|
||||
|c| f((ctx.0.clone(), c)).into_expr(ctx.0.clone()),
|
||||
|c| x((ctx.0.clone(), c)).into_expr(ctx.0.clone()),
|
||||
)
|
||||
}
|
||||
fn arg(ctx: Self::Ctx<'_>, name: &str) -> Self {
|
||||
ctx.1.arg_logic(name);
|
||||
Clause::LambdaArg
|
||||
}
|
||||
fn lambda(ctx: Self::Ctx<'_>, name: &str, body: impl FnOnce(Self::Ctx<'_>) -> Self) -> Self {
|
||||
ctx.1.lambda_logic(name, |c| body((ctx.0.clone(), c)).into_expr(ctx.0.clone()))
|
||||
}
|
||||
}
|
||||
10
orchidlang/src/interpreter/mod.rs
Normal file
10
orchidlang/src/interpreter/mod.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
//! functions to execute Orchid code
|
||||
mod apply;
|
||||
pub mod context;
|
||||
pub mod error;
|
||||
pub mod gen_nort;
|
||||
pub mod handler;
|
||||
pub mod nort;
|
||||
pub mod nort_builder;
|
||||
pub(crate) mod path_set;
|
||||
pub mod run;
|
||||
148
orchidlang/src/interpreter/nort_builder.rs
Normal file
148
orchidlang/src/interpreter/nort_builder.rs
Normal file
@@ -0,0 +1,148 @@
|
||||
//! Helper for generating the interpreter's internal representation
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::mem;
|
||||
|
||||
use substack::Substack;
|
||||
|
||||
use super::nort::{AsDerefMut, Clause, Expr};
|
||||
use super::path_set::PathSet;
|
||||
use crate::utils::pure_seq::pushed;
|
||||
|
||||
enum IntGenData<'a, T: ?Sized> {
|
||||
Lambda(&'a T, &'a RefCell<Option<PathSet>>),
|
||||
/// Counts left steps within a chain of [Clause::Apply] for collapsing.
|
||||
Apply(&'a RefCell<usize>),
|
||||
/// Replaces [IntGenData::Apply] when stepping left into non-apply to record
|
||||
/// a [None] [super::path_set::Step].
|
||||
AppF,
|
||||
/// Replaces [IntGenData::Apply] when stepping right to freeze the value.
|
||||
AppArg(usize),
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> Copy for IntGenData<'a, T> {}
|
||||
impl<'a, T: ?Sized> Clone for IntGenData<'a, T> {
|
||||
fn clone(&self) -> Self { *self }
|
||||
}
|
||||
|
||||
struct ArgCollector(RefCell<Option<PathSet>>);
|
||||
impl ArgCollector {
|
||||
pub fn new() -> Self { Self(RefCell::new(None)) }
|
||||
pub fn into_path(self) -> Option<PathSet> { self.0.into_inner() }
|
||||
}
|
||||
|
||||
/// Strategy used to find the lambda corresponding to a given argument in the
|
||||
/// stack. The function is called on the data associated with the argument, then
|
||||
/// the callback it returns is called on every lambda ancestor's associated
|
||||
/// data from closest to outermost ancestor. The first lambda where this
|
||||
/// callback returns true is considered to own the argument.
|
||||
pub type LambdaPicker<'a, T, U> = &'a dyn for<'b> Fn(&'b U) -> Box<dyn FnMut(&T) -> bool + 'b>;
|
||||
|
||||
/// Bundle of information passed down through recursive fnuctions to instantiate
|
||||
/// runtime [Expr], [super::nort::ClauseInst] or [Clause].
|
||||
///
|
||||
/// The context used by [crate::gen::traits::Gen] to convert templates is which
|
||||
/// includes this type is constructed with [super::gen_nort::nort_gen].
|
||||
pub struct NortBuilder<'a, T: ?Sized, U: ?Sized> {
|
||||
stack: Substack<'a, IntGenData<'a, T>>,
|
||||
lambda_picker: LambdaPicker<'a, T, U>,
|
||||
}
|
||||
impl<'a, T: ?Sized, U: ?Sized> NortBuilder<'a, T, U> {
|
||||
/// Create a new recursive [super::nort] builder from a location that will be
|
||||
pub fn new(lambda_picker: LambdaPicker<'a, T, U>) -> Self {
|
||||
Self { stack: Substack::Bottom, lambda_picker }
|
||||
}
|
||||
/// [Substack::pop] and clone the location
|
||||
fn pop<'b>(&'b self, count: usize) -> NortBuilder<'b, T, U>
|
||||
where 'a: 'b {
|
||||
let mut new = *self;
|
||||
new.stack = *self.stack.pop(count);
|
||||
new
|
||||
}
|
||||
/// [Substack::push] and clone the location
|
||||
fn push<'b>(&'b self, data: IntGenData<'a, T>) -> NortBuilder<'b, T, U>
|
||||
where 'a: 'b {
|
||||
let mut new = *self;
|
||||
new.stack = self.stack.push(data);
|
||||
new
|
||||
}
|
||||
fn non_app_step<V>(self, f: impl FnOnce(NortBuilder<T, U>) -> V) -> V {
|
||||
if let Some(IntGenData::Apply(_)) = self.stack.value() {
|
||||
f(self.pop(1).push(IntGenData::AppF))
|
||||
} else {
|
||||
f(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Climb back through the stack and find a lambda associated with this
|
||||
/// argument, then record the path taken from the lambda to this argument in
|
||||
/// the lambda's mutable cell.
|
||||
pub fn arg_logic(self, name: &'a U) {
|
||||
let mut lambda_chk = (self.lambda_picker)(name);
|
||||
self.non_app_step(|ctx| {
|
||||
let res = ctx.stack.iter().try_fold(vec![], |path, item| match item {
|
||||
IntGenData::Apply(_) => panic!("This is removed after handling"),
|
||||
IntGenData::Lambda(n, rc) => match lambda_chk(n) {
|
||||
false => Ok(path),
|
||||
true => Err((path, *rc)),
|
||||
},
|
||||
IntGenData::AppArg(n) => Ok(pushed(path, Some(*n))),
|
||||
IntGenData::AppF => Ok(pushed(path, None)),
|
||||
});
|
||||
let (mut path, slot) = res.expect_err("Argument not wrapped in matching lambda");
|
||||
path.reverse();
|
||||
match &mut *slot.borrow_mut() {
|
||||
slot @ None => *slot = Some(PathSet::end(path)),
|
||||
Some(slot) => take_mut::take(slot, |p| p.overlay(PathSet::end(path))),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Push a stackframe corresponding to a lambda expression, build the body,
|
||||
/// then record the path set collected by [NortBuilder::arg_logic] calls
|
||||
/// within the body.
|
||||
pub fn lambda_logic(self, name: &T, body: impl FnOnce(NortBuilder<T, U>) -> Expr) -> Clause {
|
||||
let coll = ArgCollector::new();
|
||||
let frame = IntGenData::Lambda(name, &coll.0);
|
||||
let body = self.non_app_step(|ctx| body(ctx.push(frame)));
|
||||
let args = coll.into_path();
|
||||
Clause::Lambda { args, body }
|
||||
}
|
||||
|
||||
/// Logic for collapsing Apply clauses. Different steps of the logic
|
||||
/// communicate via mutable variables on the stack
|
||||
pub fn apply_logic(
|
||||
self,
|
||||
f: impl FnOnce(NortBuilder<T, U>) -> Expr,
|
||||
x: impl FnOnce(NortBuilder<T, U>) -> Expr,
|
||||
) -> Clause {
|
||||
let mut fun: Expr;
|
||||
let arg: Expr;
|
||||
if let Some(IntGenData::Apply(rc)) = self.stack.value() {
|
||||
// argument side commits backidx
|
||||
arg = x(self.pop(1).push(IntGenData::AppArg(*rc.borrow())));
|
||||
// function side increments backidx
|
||||
*rc.borrow_mut() += 1;
|
||||
fun = f(self);
|
||||
} else {
|
||||
// function side starts from backidx 1
|
||||
fun = f(self.push(IntGenData::Apply(&RefCell::new(1))));
|
||||
// argument side commits 0
|
||||
arg = x(self.push(IntGenData::AppArg(0)));
|
||||
};
|
||||
let mut cls_lk = fun.as_deref_mut();
|
||||
if let Clause::Apply { x, f: _ } = &mut *cls_lk {
|
||||
x.push_back(arg);
|
||||
mem::drop(cls_lk);
|
||||
fun.clause.into_cls()
|
||||
} else {
|
||||
mem::drop(cls_lk);
|
||||
Clause::Apply { f: fun, x: [arg].into() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized, U: ?Sized> Copy for NortBuilder<'a, T, U> {}
|
||||
impl<'a, T: ?Sized, U: ?Sized> Clone for NortBuilder<'a, T, U> {
|
||||
fn clone(&self) -> Self { *self }
|
||||
}
|
||||
160
orchidlang/src/interpreter/path_set.rs
Normal file
160
orchidlang/src/interpreter/path_set.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::utils::join::join_maps;
|
||||
|
||||
/// A step into a [super::nort::Clause::Apply]. If [None], it steps to the
|
||||
/// function. If [Some(n)], it steps to the `n`th _last_ argument.
|
||||
pub type Step = Option<usize>;
|
||||
fn print_step(step: Step) -> String {
|
||||
if let Some(n) = step { format!("{n}") } else { "f".to_string() }
|
||||
}
|
||||
|
||||
/// A branching path selecting some placeholders (but at least one) in a Lambda
|
||||
/// expression
|
||||
#[derive(Clone)]
|
||||
pub struct PathSet {
|
||||
/// The single steps through [super::nort::Clause::Apply]
|
||||
pub steps: VecDeque<Step>,
|
||||
/// if Some, it splits at a [super::nort::Clause::Apply]. If None, it ends in
|
||||
/// a [super::nort::Clause::LambdaArg]
|
||||
pub next: Option<HashMap<Step, PathSet>>,
|
||||
}
|
||||
|
||||
impl PathSet {
|
||||
/// Create a path set for more than one target
|
||||
pub fn branch(
|
||||
steps: impl IntoIterator<Item = Step>,
|
||||
conts: impl IntoIterator<Item = (Step, Self)>,
|
||||
) -> Self {
|
||||
let conts = conts.into_iter().collect::<HashMap<_, _>>();
|
||||
assert!(1 < conts.len(), "Branching pathsets need multiple continuations");
|
||||
Self { steps: steps.into_iter().collect(), next: Some(conts) }
|
||||
}
|
||||
|
||||
/// Create a path set for one target
|
||||
pub fn end(steps: impl IntoIterator<Item = Step>) -> Self {
|
||||
Self { steps: steps.into_iter().collect(), next: None }
|
||||
}
|
||||
|
||||
/// Create a path set that points to a slot that is a direct
|
||||
/// child of the given lambda with no applications. In essence, this means
|
||||
/// that this argument will be picked as the value of the expression after an
|
||||
/// arbitrary amount of subsequent discarded parameters.
|
||||
pub fn pick() -> Self { Self { steps: VecDeque::new(), next: None } }
|
||||
|
||||
/// Merge two paths into one path that points to all targets of both. Only
|
||||
/// works if both paths select leaf nodes of the same partial tree.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// if either path selects a node the other path dissects
|
||||
pub fn overlay(self, other: Self) -> Self {
|
||||
let (mut short, mut long) = match self.steps.len() < other.steps.len() {
|
||||
true => (self, other),
|
||||
false => (other, self),
|
||||
};
|
||||
let short_len = short.steps.len();
|
||||
let long_len = long.steps.len();
|
||||
let match_len = (short.steps.iter()).zip(long.steps.iter()).take_while(|(a, b)| a == b).count();
|
||||
// fact: match_len <= short_len <= long_len
|
||||
if short_len == match_len && match_len == long_len {
|
||||
// implies match_len == short_len == long_len
|
||||
match (short.next, long.next) {
|
||||
(None, None) => Self::end(short.steps.iter().cloned()),
|
||||
(Some(_), None) | (None, Some(_)) => {
|
||||
panic!("One of these paths is faulty")
|
||||
},
|
||||
(Some(s), Some(l)) =>
|
||||
Self::branch(short.steps.iter().cloned(), join_maps(s, l, |_, l, r| l.overlay(r))),
|
||||
}
|
||||
} else if short_len == match_len {
|
||||
// implies match_len == short_len < long_len
|
||||
// long.steps[0..match_len] is in steps
|
||||
// long.steps[match_len] becomes the choice of branch below
|
||||
// long.steps[match_len + 1..] is in tail
|
||||
let mut conts = short.next.expect("One path ends inside the other");
|
||||
let tail_steps = long.steps.split_off(match_len + 1);
|
||||
let tail = match long.next {
|
||||
Some(n) => Self::branch(tail_steps, n),
|
||||
None => Self::end(tail_steps),
|
||||
};
|
||||
let branch = long.steps[match_len];
|
||||
let prev_c = conts.remove(&branch);
|
||||
let new_c = if let Some(x) = prev_c { x.overlay(tail) } else { tail };
|
||||
conts.insert(branch, new_c);
|
||||
Self::branch(short.steps, conts)
|
||||
} else {
|
||||
// implies match_len < short_len <= long_len
|
||||
// steps[0..match_len] is in shared
|
||||
// steps[match_len] become the branches below
|
||||
// steps[match_len + 1..] is in new_long and new_short
|
||||
let new_short_steps = short.steps.split_off(match_len + 1);
|
||||
let short_last = short.steps.pop_back().expect("split at n + 1");
|
||||
let new_short = Self { next: short.next.clone(), steps: new_short_steps };
|
||||
let new_long_steps = long.steps.split_off(match_len + 1);
|
||||
let new_long = Self { next: long.next.clone(), steps: new_long_steps };
|
||||
Self::branch(short.steps, [(short_last, new_short), (long.steps[match_len], new_long)])
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepend a step to a path. If it had previously started at a node that is
|
||||
/// at the specified step within an Apply clause, it now starts at the Apply.
|
||||
///
|
||||
/// This is only valid if the new Apply is **separate** from the previous
|
||||
/// root.
|
||||
pub fn prepend(&mut self, step: Step) { self.steps.push_front(step); }
|
||||
}
|
||||
|
||||
impl fmt::Display for PathSet {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let step_s = self.steps.iter().copied().map(print_step).join(">");
|
||||
match &self.next {
|
||||
Some(conts) => {
|
||||
let opts = (conts.iter())
|
||||
.sorted_unstable_by_key(|(k, _)| k.map_or(0, |n| n + 1))
|
||||
.map(|(h, t)| format!("{}>{t}", print_step(*h)))
|
||||
.join("|");
|
||||
if !step_s.is_empty() {
|
||||
write!(f, "{step_s}>")?;
|
||||
}
|
||||
write!(f, "({opts})")
|
||||
},
|
||||
None => write!(f, "{step_s}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for PathSet {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "PathSet({self})") }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_combine() {
|
||||
let ps1 = PathSet { next: None, steps: VecDeque::from([Some(2), None]) };
|
||||
let ps2 = PathSet { next: None, steps: VecDeque::from([Some(3), Some(1)]) };
|
||||
let sum = ps1.clone().overlay(ps2.clone());
|
||||
assert_eq!(format!("{sum}"), "(2>f|3>1)");
|
||||
}
|
||||
|
||||
fn extend_scaffold() -> PathSet {
|
||||
PathSet::branch([None, Some(1), None], [
|
||||
(None, PathSet::end([None, Some(1)])),
|
||||
(Some(1), PathSet::end([None, Some(2)])),
|
||||
])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extend_noclone() {
|
||||
let mut ps = extend_scaffold();
|
||||
ps.prepend(Some(0));
|
||||
assert_eq!(format!("{ps}"), "0>f>1>f>(f>f>1|1>f>2)");
|
||||
}
|
||||
}
|
||||
249
orchidlang/src/interpreter/run.rs
Normal file
249
orchidlang/src/interpreter/run.rs
Normal file
@@ -0,0 +1,249 @@
|
||||
//! Executes Orchid code
|
||||
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::{MutexGuard, TryLockError};
|
||||
use std::{fmt, mem};
|
||||
|
||||
use bound::Bound;
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::context::{Halt, RunEnv, RunParams};
|
||||
use super::error::RunError;
|
||||
use super::nort::{Clause, Expr};
|
||||
use crate::foreign::atom::{AtomicReturn, RunData};
|
||||
use crate::foreign::error::{RTError, RTErrorObj};
|
||||
use crate::interpreter::apply::{apply_as_atom, substitute};
|
||||
use crate::location::CodeLocation;
|
||||
use crate::utils::take_with_output::take_with_output;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Stackframe {
|
||||
expr: Expr,
|
||||
cls: Bound<MutexGuard<'static, Clause>, Expr>,
|
||||
}
|
||||
impl Stackframe {
|
||||
pub fn new(expr: Expr) -> Option<Self> {
|
||||
match Bound::try_new(expr.clone(), |e| e.clause.0.try_lock()) {
|
||||
Ok(cls) => Some(Stackframe { cls, expr }),
|
||||
Err(bound_e) if matches!(bound_e.wrapped(), TryLockError::WouldBlock) => None,
|
||||
Err(bound_e) => panic!("{:?}", bound_e.wrapped()),
|
||||
}
|
||||
}
|
||||
pub fn wait_new(expr: Expr) -> Self {
|
||||
Self { cls: Bound::new(expr.clone(), |e| e.clause.0.lock().unwrap()), expr }
|
||||
}
|
||||
pub fn record_cycle(&mut self) -> RTErrorObj {
|
||||
let err = CyclicalExpression(self.expr.clone()).pack();
|
||||
*self.cls = Clause::Bottom(err.clone());
|
||||
err
|
||||
}
|
||||
}
|
||||
impl Deref for Stackframe {
|
||||
type Target = Clause;
|
||||
fn deref(&self) -> &Self::Target { &self.cls }
|
||||
}
|
||||
impl DerefMut for Stackframe {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.cls }
|
||||
}
|
||||
impl fmt::Display for Stackframe {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}\n at {}", *self.cls, self.expr.location)
|
||||
}
|
||||
}
|
||||
|
||||
/// Interpreter state when processing was interrupted
|
||||
pub struct State<'a> {
|
||||
stack: Vec<Stackframe>,
|
||||
popped: Option<Expr>,
|
||||
env: &'a RunEnv<'a>,
|
||||
}
|
||||
impl<'a> State<'a> {
|
||||
/// Create a new trivial state with a specified stack size and a single
|
||||
/// element on the stack
|
||||
fn new(base: Expr, env: &'a RunEnv<'a>) -> Self {
|
||||
let stack = vec![Stackframe::new(base).expect("Initial state should not be locked")];
|
||||
State { stack, popped: None, env }
|
||||
}
|
||||
|
||||
/// Try to push an expression on the stack, raise appropriate errors if the
|
||||
/// expression is already on the stack (and thus references itself), or if the
|
||||
/// stack now exceeds the pre-defined height
|
||||
fn push_expr(&'_ mut self, expr: Expr, params: &RunParams) -> Result<(), RunError<'a>> {
|
||||
let sf = match Stackframe::new(expr.clone()) {
|
||||
Some(sf) => sf,
|
||||
None => match self.stack.iter_mut().rev().find(|sf| sf.expr.clause.is_same(&expr.clause)) {
|
||||
None => Stackframe::wait_new(expr),
|
||||
Some(sf) => return Err(RunError::Extern(sf.record_cycle())),
|
||||
},
|
||||
};
|
||||
self.stack.push(sf);
|
||||
if params.stack < self.stack.len() {
|
||||
let so = StackOverflow(self.stack.iter().map(|sf| sf.expr.clone()).collect());
|
||||
return Err(RunError::Extern(so.pack()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Process this state until it either completes, runs out of gas as specified
|
||||
/// in the context, or produces an error.
|
||||
pub fn run(mut self, params: &mut RunParams) -> Result<Halt, RunError<'a>> {
|
||||
loop {
|
||||
if params.no_gas() {
|
||||
return Err(RunError::Interrupted(self));
|
||||
}
|
||||
params.use_gas(1);
|
||||
let top = self.stack.last_mut().expect("Stack never empty");
|
||||
let location = top.expr.location();
|
||||
let op = take_with_output(&mut *top.cls, |c| {
|
||||
match step(c, self.popped, location, self.env, params) {
|
||||
Err(e) => (Clause::Bottom(e.clone()), Err(RunError::Extern(e))),
|
||||
Ok((cls, cmd)) => (cls, Ok(cmd)),
|
||||
}
|
||||
})?;
|
||||
self.popped = None;
|
||||
match op {
|
||||
StackOp::Nop => continue,
|
||||
StackOp::Push(ex) => self.push_expr(ex, params)?,
|
||||
StackOp::Swap(ex) => {
|
||||
self.stack.pop().expect("Stack never empty");
|
||||
self.push_expr(ex, params)?
|
||||
},
|
||||
StackOp::Pop => {
|
||||
let ret = self.stack.pop().expect("last_mut called above");
|
||||
if self.stack.is_empty() {
|
||||
if let Some(alt) = self.env.dispatch(&ret.cls, ret.expr.location()) {
|
||||
self.push_expr(alt, params)?;
|
||||
params.use_gas(1);
|
||||
continue;
|
||||
}
|
||||
return Ok(ret.expr);
|
||||
} else {
|
||||
self.popped = Some(ret.expr);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'a> fmt::Display for State<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.stack.iter().rev().join("\n"))
|
||||
}
|
||||
}
|
||||
impl<'a> fmt::Debug for State<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "State({self})") }
|
||||
}
|
||||
|
||||
/// Process an expression with specific resource limits
|
||||
pub fn run<'a>(
|
||||
base: Expr,
|
||||
env: &'a RunEnv<'a>,
|
||||
params: &mut RunParams,
|
||||
) -> Result<Halt, RunError<'a>> {
|
||||
State::new(base, env).run(params)
|
||||
}
|
||||
|
||||
enum StackOp {
|
||||
Pop,
|
||||
Nop,
|
||||
Swap(Expr),
|
||||
Push(Expr),
|
||||
}
|
||||
|
||||
fn step(
|
||||
top: Clause,
|
||||
popped: Option<Expr>,
|
||||
location: CodeLocation,
|
||||
env: &RunEnv,
|
||||
params: &mut RunParams,
|
||||
) -> Result<(Clause, StackOp), RTErrorObj> {
|
||||
match top {
|
||||
Clause::Bottom(err) => Err(err),
|
||||
Clause::LambdaArg => Ok((Clause::Bottom(UnboundArg(location).pack()), StackOp::Nop)),
|
||||
l @ Clause::Lambda { .. } => Ok((l, StackOp::Pop)),
|
||||
Clause::Identity(other) =>
|
||||
Ok((Clause::Identity(other.clone()), StackOp::Swap(other.into_expr(location)))),
|
||||
Clause::Constant(name) => {
|
||||
let expr = env.load(name, location)?;
|
||||
Ok((Clause::Identity(expr.clsi()), StackOp::Swap(expr.clone())))
|
||||
},
|
||||
Clause::Atom(mut at) => {
|
||||
if let Some(delegate) = at.0.redirect() {
|
||||
match popped {
|
||||
Some(popped) => *delegate = popped,
|
||||
None => {
|
||||
let tmp = delegate.clone();
|
||||
return Ok((Clause::Atom(at), StackOp::Push(tmp)));
|
||||
},
|
||||
}
|
||||
}
|
||||
match at.run(RunData { params, env, location })? {
|
||||
AtomicReturn::Inert(at) => Ok((Clause::Atom(at), StackOp::Pop)),
|
||||
AtomicReturn::Change(gas, c) => {
|
||||
params.use_gas(gas);
|
||||
Ok((c, StackOp::Nop))
|
||||
},
|
||||
}
|
||||
},
|
||||
Clause::Apply { mut f, mut x } => {
|
||||
if x.is_empty() {
|
||||
return Ok((Clause::Identity(f.clsi()), StackOp::Swap(f)));
|
||||
}
|
||||
f = match popped {
|
||||
None => return Ok((Clause::Apply { f: f.clone(), x }, StackOp::Push(f))),
|
||||
Some(ex) => ex,
|
||||
};
|
||||
let val = x.pop_front().expect("Empty args handled above");
|
||||
let f_mut = f.clause.cls_mut();
|
||||
let mut cls = match &*f_mut {
|
||||
Clause::Lambda { args, body } => match args {
|
||||
None => Clause::Identity(body.clsi()),
|
||||
Some(args) => substitute(args, val.clsi(), &body.cls_mut(), &mut || params.use_gas(1)),
|
||||
},
|
||||
Clause::Atom(_) => {
|
||||
mem::drop(f_mut);
|
||||
apply_as_atom(f, val, env, params)?
|
||||
},
|
||||
c => unreachable!("Run should never settle on {c}"),
|
||||
};
|
||||
if !x.is_empty() {
|
||||
cls = Clause::Apply { f: cls.into_expr(location), x };
|
||||
}
|
||||
Ok((cls, StackOp::Nop))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct StackOverflow(Vec<Expr>);
|
||||
impl RTError for StackOverflow {}
|
||||
impl fmt::Display for StackOverflow {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "Stack depth exceeded {}:", self.0.len() - 1)?; // 1 for failed call, 1 for current
|
||||
for item in self.0.iter().rev() {
|
||||
match Stackframe::new(item.clone()) {
|
||||
Some(sf) => writeln!(f, "{sf}")?,
|
||||
None => writeln!(f, "Locked frame at {}", item.location)?,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct UnboundArg(CodeLocation);
|
||||
impl RTError for UnboundArg {}
|
||||
impl fmt::Display for UnboundArg {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Unbound argument found at {}. This is likely a codegen error", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct CyclicalExpression(Expr);
|
||||
impl RTError for CyclicalExpression {}
|
||||
impl fmt::Display for CyclicalExpression {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "The expression {} contains itself", self.0)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user