Preparation for sharing

- rustfmt
- clippy
- comments
- README
This commit is contained in:
2023-05-25 19:14:24 +01:00
parent e99ade92ba
commit bc2714aad8
144 changed files with 3734 additions and 3243 deletions

View File

@@ -1,103 +1,126 @@
use super::context::Context;
use super::error::RuntimeError;
use super::Return;
use crate::foreign::AtomicReturn;
use crate::representations::Primitive;
use crate::representations::PathSet;
use crate::representations::interpreted::{ExprInst, Clause};
use crate::representations::interpreted::{Clause, ExprInst};
use crate::representations::{PathSet, Primitive};
use crate::utils::Side;
use super::Return;
use super::error::RuntimeError;
use super::context::Context;
/// 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.
/// 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>
path: &[Side],
source: ExprInst,
mapper: &mut impl FnMut(&Clause) -> Result<Clause, E>,
) -> Result<ExprInst, E> {
source.try_update(|value| {
// Pass right through lambdas
if let Clause::Lambda { args, body } = value {
return Ok((Clause::Lambda {
args: args.clone(),
body: map_at(path, body.clone(), 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)?, ()))
};
// 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.clone(), mapper)?,
x: x.clone(),
},
Side::Right => Clause::Apply {
f: f.clone(),
x: map_at(tail, x.clone(), mapper)?,
}
}, ()))
}
panic!("Invalid path")
}).map(|p| p.0)
source
.try_update(|value| {
// Pass right through lambdas
if let Clause::Lambda { args, body } = value {
return Ok((
Clause::Lambda {
args: args.clone(),
body: map_at(path, body.clone(), 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)?, ()));
};
// 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.clone(), mapper)?,
x: x.clone(),
},
Side::Right => Clause::Apply {
f: f.clone(),
x: map_at(tail, x.clone(), mapper)?,
},
},
(),
));
}
panic!("Invalid path")
})
.map(|p| p.0)
}
/// 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.
fn substitute(paths: &PathSet, value: Clause, body: ExprInst) -> ExprInst {
let PathSet{ steps, next } = paths;
map_at(&steps, body, &mut |checkpoint| -> Result<Clause, !> {
let PathSet { steps, next } = paths;
map_at(steps, body, &mut |checkpoint| -> Result<Clause, !> {
match (checkpoint, next) {
(Clause::Lambda{..}, _) => unreachable!("Handled by map_at"),
(Clause::Lambda { .. }, _) => unreachable!("Handled by map_at"),
(Clause::Apply { f, x }, Some((left, right))) => Ok(Clause::Apply {
f: substitute(&left, value.clone(), f.clone()),
x: substitute(&right, value.clone(), x.clone()),
f: substitute(left, value.clone(), f.clone()),
x: substitute(right, value.clone(), x.clone()),
}),
(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"),
(_, None) =>
panic!("Substitution path ends in something other than LambdaArg"),
(_, Some(_)) =>
panic!("Substitution path leads into something other than Apply"),
}
}).into_ok()
})
.into_ok()
}
/// Apply a function-like expression to a parameter.
/// If any work is being done, gas will be deducted.
pub fn apply(
f: ExprInst, x: ExprInst, ctx: Context
f: ExprInst,
x: ExprInst,
ctx: Context,
) -> Result<Return, RuntimeError> {
let (state, (gas, inert)) = f.clone().try_update(|clause| match clause {
let (state, (gas, inert)) = f.try_update(|clause| match clause {
// apply an ExternFn or an internal function
Clause::P(Primitive::ExternFn(f)) => {
let clause = f.apply(x, ctx.clone())
.map_err(|e| RuntimeError::Extern(e))?;
let clause =
f.apply(x, ctx.clone()).map_err(|e| RuntimeError::Extern(e))?;
Ok((clause, (ctx.gas.map(|g| g - 1), false)))
}
Clause::Lambda{args, body} => Ok(if let Some(args) = args {
},
Clause::Lambda { args, body } => Ok(if let Some(args) = args {
let x_cls = x.expr().clause.clone();
let new_xpr_inst = substitute(args, x_cls, body.clone());
let new_xpr = new_xpr_inst.expr();
// cost of substitution
// XXX: should this be the number of occurrences instead?
(new_xpr.clause.clone(), (ctx.gas.map(|x| x - 1), false))
} else {(body.expr().clause.clone(), (ctx.gas, false))}),
} else {
(body.expr().clause.clone(), (ctx.gas, false))
}),
Clause::Constant(name) => {
let symval = if let Some(sym) = ctx.symbols.get(name) {sym.clone()}
else { panic!("missing symbol for function {}",
ctx.interner.extern_vec(*name).join("::")
)};
Ok((Clause::Apply { f: symval, x, }, (ctx.gas, false)))
}
Clause::P(Primitive::Atom(atom)) => { // take a step in expanding atom
let symval = if let Some(sym) = ctx.symbols.get(name) {
sym.clone()
} else {
panic!(
"missing symbol for function {}",
ctx.interner.extern_vec(*name).join("::")
)
};
Ok((Clause::Apply { f: symval, x }, (ctx.gas, false)))
},
Clause::P(Primitive::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
Clause::Apply { f: fun, x: arg } => {
// take a step in resolving pre-function
let ret = apply(fun.clone(), arg.clone(), ctx.clone())?;
let Return { state, inert, gas } = ret;
Ok((Clause::Apply{ f: state, x }, (gas, inert)))
Ok((Clause::Apply { f: state, x }, (gas, inert)))
},
_ => Err(RuntimeError::NonFunctionApplication(f.clone()))
_ => Err(RuntimeError::NonFunctionApplication(f.clone())),
})?;
Ok(Return { state, gas, inert })
}
}

View File

@@ -1,29 +1,27 @@
use hashbrown::HashMap;
use crate::interner::{Interner, Sym};
use crate::representations::interpreted::ExprInst;
use crate::interner::{Token, Interner};
/// All the data associated with an interpreter run
#[derive(Clone)]
pub struct Context<'a> {
pub symbols: &'a HashMap<Token<Vec<Token<String>>>, ExprInst>,
/// 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,
/// The number of reduction steps the interpreter can take before returning
pub gas: Option<usize>,
}
impl Context<'_> {
pub fn is_stuck(&self, res: Option<usize>) -> bool {
match (res, self.gas) {
(Some(a), Some(b)) => a == b,
(None, None) => false,
(None, Some(_)) => panic!("gas not tracked despite limit"),
(Some(_), None) => panic!("gas tracked without request"),
}
}
}
/// All the data produced by an interpreter run
#[derive(Clone)]
pub struct Return {
/// The new expression tree
pub state: ExprInst,
/// Leftover [Context::gas] if counted
pub gas: Option<usize>,
/// If true, the next run would not modify the expression
pub inert: bool,
}

View File

@@ -1,8 +1,8 @@
use std::fmt::Display;
use std::rc::Rc;
use crate::representations::interpreted::ExprInst;
use crate::foreign::ExternError;
use crate::representations::interpreted::ExprInst;
/// Problems in the process of execution
#[derive(Clone, Debug)]
@@ -21,7 +21,8 @@ impl Display for RuntimeError {
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(loc) => write!(f, "Primitive applied as function at {loc:?}")
Self::NonFunctionApplication(loc) =>
write!(f, "Primitive applied as function at {loc:?}"),
}
}
}
}

View File

@@ -1,8 +1,8 @@
mod apply;
mod error;
mod context;
mod error;
mod run;
pub use context::{Context, Return};
pub use error::RuntimeError;
pub use run::{run, run_handler, Handler, HandlerParm, HandlerRes};
pub use run::{run, run_handler, Handler, HandlerParm, HandlerRes};

View File

@@ -1,106 +1,155 @@
use std::mem;
use std::rc::Rc;
use crate::foreign::{AtomicReturn, Atomic, ExternError, Atom};
use crate::representations::Primitive;
use crate::representations::interpreted::{Clause, ExprInst};
use super::apply::apply;
use super::error::RuntimeError;
use super::context::{Context, Return};
use super::error::RuntimeError;
use crate::foreign::{Atom, Atomic, AtomicReturn, ExternError};
use crate::representations::interpreted::{Clause, ExprInst};
use crate::representations::Primitive;
pub fn run(
expr: ExprInst,
mut ctx: Context
) -> Result<Return, RuntimeError> {
let (state, (gas, inert)) = expr.try_normalize(|cls| -> Result<(Clause, _), RuntimeError> {
let mut i = cls.clone();
while ctx.gas.map(|g| g > 0).unwrap_or(true) {
match &i {
Clause::Apply { f, x } => {
let res = apply(f.clone(), x.clone(), ctx.clone())?;
if res.inert {return Ok((i, (res.gas, true)))}
ctx.gas = res.gas;
i = res.state.expr().clause.clone();
/// 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(|cls| -> Result<(Clause, _), RuntimeError> {
let mut i = cls.clone();
while ctx.gas.map(|g| g > 0).unwrap_or(true) {
match &i {
Clause::Apply { f, x } => {
let res = apply(f.clone(), x.clone(), ctx.clone())?;
if res.inert {
return Ok((i, (res.gas, true)));
}
ctx.gas = res.gas;
i = res.state.expr().clause.clone();
},
Clause::P(Primitive::Atom(data)) => {
let ret = data.run(ctx.clone())?;
let AtomicReturn { clause, gas, inert } = ret;
if inert {
return Ok((i, (gas, true)));
}
ctx.gas = gas;
i = clause.clone();
},
Clause::Constant(c) => {
let symval = ctx.symbols.get(c).expect("missing symbol for value");
ctx.gas = ctx.gas.map(|g| g - 1); // cost of lookup
i = symval.expr().clause.clone();
},
// non-reducible
_ => return Ok((i, (ctx.gas, true))),
}
Clause::P(Primitive::Atom(data)) => {
let ret = data.run(ctx.clone())?;
let AtomicReturn { clause, gas, inert } = ret;
if inert {return Ok((i, (gas, true)))}
ctx.gas = gas;
i = clause.clone();
}
Clause::Constant(c) => {
let symval = ctx.symbols.get(c).expect("missing symbol for value");
ctx.gas = ctx.gas.map(|g| g - 1); // cost of lookup
i = symval.expr().clause.clone();
}
// non-reducible
_ => return Ok((i, (ctx.gas, true)))
}
}
// out of gas
Ok((i, (ctx.gas, false)))
})?;
// out of gas
Ok((i, (ctx.gas, false)))
})?;
Ok(Return { state, gas, inert })
}
/// Opaque inert data that may encode a command to a [Handler]
pub type HandlerParm = Box<dyn Atomic>;
pub type HandlerRes = Result<
Result<ExprInst, Rc<dyn ExternError>>,
HandlerParm
>;
pub trait Handler {
fn resolve(&mut self, data: HandlerParm) -> HandlerRes;
fn then<T: Handler>(self, t: T) -> impl Handler where Self: Sized {
Pair(self, t)
/// Reasons why a [Handler] could not interpret a command. Convertible from
/// either variant
pub enum HandlerErr {
/// The command was addressed to us but its execution resulted in an error
Extern(Rc<dyn ExternError>),
/// This handler is not applicable, either because the [HandlerParm] is not a
/// command or because it's meant for some other handler
NA(HandlerParm),
}
impl From<Rc<dyn ExternError>> for HandlerErr {
fn from(value: Rc<dyn ExternError>) -> Self {
Self::Extern(value)
}
}
impl<T> From<T> for HandlerErr
where
T: ExternError + 'static,
{
fn from(value: T) -> Self {
Self::Extern(value.into_extern())
}
}
impl From<HandlerParm> for HandlerErr {
fn from(value: HandlerParm) -> Self {
Self::NA(value)
}
}
impl<F> Handler for F where F: FnMut(HandlerParm) -> HandlerRes {
/// Various possible outcomes of a [Handler] execution.
pub type HandlerRes = Result<ExprInst, HandlerErr>;
/// A trait for things that may be able to handle commands returned by Orchid
/// code. This trait is implemented for [FnMut(HandlerParm) -> HandlerRes] and
/// [(Handler, Handler)], users are not supposed to implement it themselves.
///
/// A handler receives an arbitrary inert [Atomic] and uses [Atomic::as_any]
/// then [std::any::Any::downcast_ref] to obtain a known type. If this fails, it
/// returns the box in [HandlerErr::NA] which will be passed to the next
/// handler.
pub trait Handler {
/// Attempt to resolve a command with this handler.
fn resolve(&mut self, data: HandlerParm) -> HandlerRes;
/// If this handler isn't applicable, try the other one.
fn or<T: Handler>(self, t: T) -> impl Handler
where
Self: Sized,
{
(self, t)
}
}
impl<F> Handler for F
where
F: FnMut(HandlerParm) -> HandlerRes,
{
fn resolve(&mut self, data: HandlerParm) -> HandlerRes {
self(data)
}
}
pub struct Pair<T, U>(T, U);
impl<T: Handler, U: Handler> Handler for Pair<T, U> {
impl<T: Handler, U: Handler> Handler for (T, U) {
fn resolve(&mut self, data: HandlerParm) -> HandlerRes {
match self.0.resolve(data) {
Ok(out) => Ok(out),
Err(data) => self.1.resolve(data)
Err(HandlerErr::NA(data)) => self.1.resolve(data),
x => x,
}
}
}
/// [run] orchid code, executing any commands it returns using the specified
/// [Handler]s.
pub fn run_handler(
mut expr: ExprInst,
mut handler: impl Handler,
mut ctx: Context
mut ctx: Context,
) -> Result<Return, RuntimeError> {
loop {
let ret = run(expr.clone(), ctx.clone())?;
if ret.gas == Some(0) {
return Ok(ret)
return Ok(ret);
}
let state_ex = ret.state.expr();
let a = if let Clause::P(Primitive::Atom(a)) = &state_ex.clause {a}
else {
let a = if let Clause::P(Primitive::Atom(a)) = &state_ex.clause {
a
} else {
mem::drop(state_ex);
return Ok(ret)
return Ok(ret);
};
let boxed = a.clone().0;
expr = match handler.resolve(boxed) {
Ok(r) => r.map_err(RuntimeError::Extern)?,
Err(e) => return Ok(Return{
gas: ret.gas,
inert: ret.inert,
state: Clause::P(Primitive::Atom(Atom(e))).wrap()
})
Ok(expr) => expr,
Err(HandlerErr::Extern(ext)) => Err(ext)?,
Err(HandlerErr::NA(atomic)) =>
return Ok(Return {
gas: ret.gas,
inert: ret.inert,
state: Clause::P(Primitive::Atom(Atom(atomic))).wrap(),
}),
};
ctx.gas = ret.gas;
}
}
}