bug fixes and performance improvements

This commit is contained in:
2023-05-07 22:35:38 +01:00
parent f3ce910f66
commit a604e40bad
167 changed files with 5965 additions and 4229 deletions

104
src/interpreter/apply.rs Normal file
View File

@@ -0,0 +1,104 @@
use crate::foreign::Atom;
use crate::representations::Primitive;
use crate::representations::PathSet;
use crate::representations::interpreted::{ExprInst, Clause};
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.
fn map_at<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")
})
}
fn substitute(paths: &PathSet, value: Clause, body: ExprInst) -> ExprInst {
let PathSet{ steps, next } = paths;
map_at(&steps, body, &mut |checkpoint| -> Result<Clause, !> {
match (checkpoint, next) {
(Clause::Lambda{..}, _) => unreachable!("Handled by map_at"),
(Clause::Apply { f, x }, Some((left, right))) => Ok(Clause::Apply {
f: substitute(&left, value.clone(), f.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"),
}
}).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, mut ctx: Context
) -> Result<Return, RuntimeError> {
let state = f.clone().try_update(|clause| match clause {
// apply an ExternFn or an internal function
Clause::P(Primitive::ExternFn(f)) => {
let (clause, gas) = f.apply(x, ctx.clone())
.map_err(|e| RuntimeError::Extern(e))?;
ctx.gas = gas.map(|g| g - 1); // cost of extern call
Ok(clause)
}
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?
ctx.gas = ctx.gas.map(|x| x - 1);
new_xpr.clause.clone()
} else {body.expr().clause.clone()}),
Clause::Constant(name) => {
let symval = ctx.symbols.get(name).expect("missing symbol for function").clone();
ctx.gas = ctx.gas.map(|x| x - 1); // cost of lookup
Ok(Clause::Apply { f: symval, x, })
}
Clause::P(Primitive::Atom(Atom(atom))) => { // take a step in expanding atom
let (clause, gas) = atom.run(ctx.clone())?;
ctx.gas = gas.map(|x| x - 1); // cost of dispatch
Ok(Clause::Apply { f: clause.wrap(), x })
},
Clause::Apply{ f: fun, x: arg } => { // take a step in resolving pre-function
let res = apply(fun.clone(), arg.clone(), ctx.clone())?;
ctx.gas = res.gas; // if work has been done, it has been paid
Ok(Clause::Apply{ f: res.state, x })
},
_ => Err(RuntimeError::NonFunctionApplication(f.clone()))
})?;
Ok(Return { state, gas: ctx.gas })
}

View File

@@ -0,0 +1,27 @@
use hashbrown::HashMap;
use crate::representations::interpreted::ExprInst;
use crate::interner::Token;
#[derive(Clone)]
pub struct Context<'a> {
pub symbols: &'a HashMap<Token<Vec<Token<String>>>, ExprInst>,
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"),
}
}
}
#[derive(Clone)]
pub struct Return {
pub state: ExprInst,
pub gas: Option<usize>,
}

27
src/interpreter/error.rs Normal file
View File

@@ -0,0 +1,27 @@
use std::fmt::Display;
use std::rc::Rc;
use crate::representations::interpreted::ExprInst;
use crate::foreign::ExternError;
/// Problems in the process of execution
#[derive(Clone)]
pub enum RuntimeError {
Extern(Rc<dyn ExternError>),
NonFunctionApplication(ExprInst),
}
impl From<Rc<dyn ExternError>> for RuntimeError {
fn from(value: Rc<dyn ExternError>) -> Self {
Self::Extern(value)
}
}
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:?}")
}
}
}

8
src/interpreter/mod.rs Normal file
View File

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

39
src/interpreter/run.rs Normal file
View File

@@ -0,0 +1,39 @@
use crate::foreign::Atom;
use crate::representations::Primitive;
use crate::representations::interpreted::{Clause, ExprInst};
use super::apply::apply;
use super::error::RuntimeError;
use super::context::{Context, Return};
pub fn run(expr: ExprInst, mut ctx: Context)
-> Result<Return, RuntimeError>
{
let state = 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 ctx.is_stuck(res.gas) {return Ok(i)}
ctx.gas = res.gas;
i = res.state.expr().clause.clone();
}
Clause::P(Primitive::Atom(Atom(data))) => {
let (clause, gas) = data.run(ctx.clone())?;
if ctx.is_stuck(gas) {return Ok(i)}
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();
}
_ => return Ok(i)
}
}
Ok(i)
})?;
Ok(Return { state, gas: ctx.gas })
}