Most files suffered major changes

- Less ambiguous syntax
- Better parser (Chumsky only does tokenization now)
- Tidy(|ier) error handling
- Facade for simplified embedding
- External code grouped in (fairly) self-contained Systems
- Dynamic action dispatch
- Many STL additions
This commit is contained in:
2023-08-17 20:47:08 +01:00
parent 751a02a1ec
commit 3fdabc29da
139 changed files with 4269 additions and 1783 deletions

View File

@@ -15,7 +15,7 @@ fn map_at<E>(
mapper: &mut impl FnMut(&Clause) -> Result<Clause, E>,
) -> Result<ExprInst, E> {
source
.try_update(|value| {
.try_update(|value, _loc| {
// Pass right through lambdas
if let Clause::Lambda { args, body } = value {
return Ok((
@@ -87,7 +87,7 @@ pub fn apply(
x: ExprInst,
ctx: Context,
) -> Result<Return, RuntimeError> {
let (state, (gas, inert)) = f.try_update(|clause| match clause {
let (state, (gas, inert)) = f.try_update(|clause, loc| match clause {
// apply an ExternFn or an internal function
Clause::P(Primitive::ExternFn(f)) => {
let clause =
@@ -104,17 +104,12 @@ pub fn apply(
} else {
(body.expr().clause.clone(), (ctx.gas, false))
}),
Clause::Constant(name) => {
let symval = if let Some(sym) = ctx.symbols.get(name) {
sym.clone()
Clause::Constant(name) =>
if let Some(sym) = ctx.symbols.get(name) {
Ok((Clause::Apply { f: sym.clone(), x }, (ctx.gas, false)))
} else {
panic!(
"missing symbol for function {}",
ctx.interner.extern_vec(*name).join("::")
)
};
Ok((Clause::Apply { f: symval, x }, (ctx.gas, false)))
},
Err(RuntimeError::MissingSymbol(*name, loc.clone()))
},
Clause::P(Primitive::Atom(atom)) => {
// take a step in expanding atom
let AtomicReturn { clause, gas, inert } = atom.run(ctx.clone())?;

View File

@@ -1,8 +1,9 @@
use std::fmt::Display;
use std::rc::Rc;
use crate::foreign::ExternError;
use crate::interner::InternedDisplay;
use crate::representations::interpreted::ExprInst;
use crate::{Location, Sym};
/// Problems in the process of execution
#[derive(Clone, Debug)]
@@ -11,6 +12,8 @@ pub enum RuntimeError {
Extern(Rc<dyn ExternError>),
/// Primitive applied as function
NonFunctionApplication(ExprInst),
/// Symbol not in context
MissingSymbol(Sym, Location),
}
impl From<Rc<dyn ExternError>> for RuntimeError {
@@ -19,12 +22,23 @@ impl From<Rc<dyn ExternError>> for RuntimeError {
}
}
impl Display for RuntimeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl InternedDisplay for RuntimeError {
fn fmt_i(
&self,
f: &mut std::fmt::Formatter<'_>,
i: &crate::Interner,
) -> 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(expr) => {
write!(f, "Primitive applied as function at {}", expr.expr().location)
},
Self::MissingSymbol(sym, loc) => {
write!(
f,
"{}, called at {loc} is not loaded",
i.extern_vec(*sym).join("::")
)
},
}
}

View File

@@ -0,0 +1,84 @@
use std::any::{Any, TypeId};
use std::mem;
use std::rc::Rc;
use hashbrown::HashMap;
use trait_set::trait_set;
use super::{run, Context, Return, RuntimeError};
use crate::foreign::ExternError;
use crate::interpreted::{Clause, ExprInst};
use crate::utils::unwrap_or;
use crate::Primitive;
trait_set! {
trait Handler = for<'b> FnMut(&'b dyn Any) -> HandlerRes;
}
/// A table of command handlers
#[derive(Default)]
pub struct HandlerTable<'a> {
handlers: HashMap<TypeId, Box<dyn Handler + 'a>>,
}
impl<'a> HandlerTable<'a> {
/// Create a new [HandlerTable]
pub fn new() -> Self {
Self { handlers: HashMap::new() }
}
/// Add a handler function to interpret a type of atom and decide what happens
/// next. This function can be impure.
pub fn register<T: 'static>(
&mut self,
mut f: impl for<'b> FnMut(&'b T) -> HandlerRes + 'a,
) {
let cb = move |a: &dyn Any| f(a.downcast_ref().expect("found by TypeId"));
let prev = self.handlers.insert(TypeId::of::<T>(), Box::new(cb));
assert!(prev.is_none(), "A handler for this type is already registered");
}
/// Find and execute the corresponding handler for this type
pub fn dispatch(&mut self, arg: &dyn Any) -> Option<HandlerRes> {
self.handlers.get_mut(&arg.type_id()).map(|f| f(arg))
}
/// Combine two non-overlapping handler sets
pub fn combine(mut self, other: Self) -> Self {
for (key, value) in other.handlers {
let prev = self.handlers.insert(key, value);
assert!(prev.is_none(), "Duplicate handlers")
}
self
}
}
/// Various possible outcomes of a [Handler] execution. Ok returns control to
/// the interpreter. The meaning of Err is decided by the value in it.
pub type HandlerRes = Result<ExprInst, Rc<dyn ExternError>>;
/// [run] orchid code, executing any commands it returns using the specified
/// [Handler]s.
pub fn run_handler(
mut expr: ExprInst,
handlers: &mut HandlerTable,
mut ctx: Context,
) -> Result<Return, RuntimeError> {
loop {
let ret = run(expr.clone(), ctx.clone())?;
if ret.gas == Some(0) {
return Ok(ret);
}
let state_ex = ret.state.expr();
let a = if let Clause::P(Primitive::Atom(a)) = &state_ex.clause {
a
} else {
mem::drop(state_ex);
return Ok(ret);
};
expr = unwrap_or!(handlers.dispatch(a.0.as_any()); {
mem::drop(state_ex);
return Ok(ret)
})?;
ctx.gas = ret.gas;
}
}

View File

@@ -2,8 +2,10 @@
mod apply;
mod context;
mod error;
mod handler;
mod run;
pub use context::{Context, Return};
pub use error::RuntimeError;
pub use run::{run, run_handler, Handler, HandlerErr, HandlerParm, HandlerRes};
pub use handler::{run_handler, HandlerRes, HandlerTable};
pub use run::run;

View File

@@ -1,17 +1,14 @@
use std::mem;
use std::rc::Rc;
use super::apply::apply;
use super::context::{Context, Return};
use super::error::RuntimeError;
use crate::foreign::{Atom, Atomic, AtomicReturn, ExternError};
use crate::foreign::AtomicReturn;
use crate::representations::interpreted::{Clause, ExprInst};
use crate::representations::Primitive;
/// 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> {
expr.try_normalize(|cls, loc| -> Result<(Clause, _), RuntimeError> {
let mut i = cls.clone();
while ctx.gas.map(|g| g > 0).unwrap_or(true) {
match &i {
@@ -33,7 +30,8 @@ pub fn run(expr: ExprInst, mut ctx: Context) -> Result<Return, RuntimeError> {
i = clause.clone();
},
Clause::Constant(c) => {
let symval = ctx.symbols.get(c).expect("missing symbol for value");
let symval = (ctx.symbols.get(c))
.ok_or_else(|| RuntimeError::MissingSymbol(*c, loc.clone()))?;
ctx.gas = ctx.gas.map(|g| g - 1); // cost of lookup
i = symval.expr().clause.clone();
},
@@ -46,111 +44,3 @@ pub fn run(expr: ExprInst, mut ctx: Context) -> Result<Return, RuntimeError> {
})?;
Ok(Return { state, gas, inert })
}
/// Opaque inert data that may encode a command to a [Handler]
pub type HandlerParm = Box<dyn Atomic>;
/// 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)
}
}
/// Various possible outcomes of a [Handler] execution. Ok returns control to
/// the interpreter. The meaning of Err is decided by the value in it.
pub type HandlerRes = Result<ExprInst, 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 downcast_ref of [std::any::Any] 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) -> (Self, T)
where
Self: Sized,
{
(self, t)
}
}
impl<F> Handler for F
where
F: FnMut(HandlerParm) -> HandlerRes,
{
fn resolve(&mut self, data: HandlerParm) -> HandlerRes {
self(data)
}
}
impl<T: Handler, U: Handler> Handler for (T, U) {
fn resolve(&mut self, data: HandlerParm) -> HandlerRes {
match self.0.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,
) -> Result<Return, RuntimeError> {
loop {
let ret = run(expr.clone(), ctx.clone())?;
if ret.gas == Some(0) {
return Ok(ret);
}
let state_ex = ret.state.expr();
let a = if let Clause::P(Primitive::Atom(a)) = &state_ex.clause {
a
} else {
mem::drop(state_ex);
return Ok(ret);
};
let boxed = a.clone().0;
expr = match handler.resolve(boxed) {
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;
}
}