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

109
src/foreign/atom.rs Normal file
View File

@@ -0,0 +1,109 @@
use std::any::Any;
use std::fmt::Debug;
use dyn_clone::DynClone;
use crate::interpreted::ExprInst;
use crate::interpreter::{Context, RuntimeError};
use crate::representations::interpreted::Clause;
use crate::Primitive;
/// Information returned by [Atomic::run]. This mirrors
/// [crate::interpreter::Return] but with a clause instead of an Expr.
pub struct AtomicReturn {
/// The next form of the expression
pub clause: Clause,
/// Remaining gas
pub gas: Option<usize>,
/// Whether further normalization is possible by repeated calls to
/// [Atomic::run]
pub inert: bool,
}
impl AtomicReturn {
/// Wrap an inert atomic for delivery to the supervisor
pub fn from_data<D: Atomic>(d: D, c: Context) -> Self {
AtomicReturn { clause: d.atom_cls(), gas: c.gas, inert: false }
}
}
/// Returned by [Atomic::run]
pub type AtomicResult = Result<AtomicReturn, RuntimeError>;
/// Functionality the interpreter needs to handle a value
pub trait Atomic: Any + Debug + DynClone
where
Self: 'static,
{
/// Casts this value to [Any] so that its original value can be salvaged
/// during introspection by other external code. There is no other way to
/// interact with values of unknown types at the moment.
fn as_any(&self) -> &dyn Any;
/// Attempt to normalize this value. If it wraps a value, this should report
/// inert. If it wraps a computation, it should execute one logical step of
/// the computation and return a structure representing the ntext.
fn run(&self, ctx: Context) -> AtomicResult;
/// Wrap the atom in a clause to be placed in an [AtomicResult].
fn atom_cls(self) -> Clause
where
Self: Sized,
{
Clause::P(Primitive::Atom(Atom(Box::new(self))))
}
/// Wrap the atom in a new expression instance to be placed in a tree
fn atom_exi(self) -> ExprInst
where
Self: Sized,
{
self.atom_cls().wrap()
}
}
/// Represents a black box unit of code with its own normalization steps.
/// Typically [ExternFn] will produce an [Atom] when applied to a [Clause],
/// this [Atom] will then forward `run` calls to the argument until it becomes
/// inert at which point the [Atom] will validate and process the argument,
/// returning a different [Atom] intended for processing by external code, a new
/// [ExternFn] to capture an additional argument, or an Orchid expression
/// to pass control back to the interpreter.btop
pub struct Atom(pub Box<dyn Atomic>);
impl Atom {
/// Wrap an [Atomic] in a type-erased box
pub fn new<T: 'static + Atomic>(data: T) -> Self {
Self(Box::new(data) as Box<dyn Atomic>)
}
/// Get the contained data
pub fn data(&self) -> &dyn Atomic {
self.0.as_ref() as &dyn Atomic
}
/// Attempt to downcast contained data to a specific type
pub fn try_cast<T: Atomic>(&self) -> Option<&T> {
self.data().as_any().downcast_ref()
}
/// Test the type of the contained data without downcasting
pub fn is<T: 'static>(&self) -> bool {
self.data().as_any().is::<T>()
}
/// Downcast contained data, panic if it isn't the specified type
pub fn cast<T: 'static>(&self) -> &T {
self.data().as_any().downcast_ref().expect("Type mismatch on Atom::cast")
}
/// Normalize the contained data
pub fn run(&self, ctx: Context) -> AtomicResult {
self.0.run(ctx)
}
}
impl Clone for Atom {
fn clone(&self) -> Self {
Self(dyn_clone::clone_box(self.data()))
}
}
impl Debug for Atom {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "##ATOM[{:?}]##", self.data())
}
}

122
src/foreign/cps_box.rs Normal file
View File

@@ -0,0 +1,122 @@
//! Automated wrappers to make working with CPS commands easier.
use std::fmt::Debug;
use std::iter;
use trait_set::trait_set;
use super::{Atomic, AtomicResult, AtomicReturn, ExternFn, XfnResult};
use crate::interpreted::{Clause, ExprInst};
use crate::interpreter::{Context, HandlerRes};
use crate::{atomic_defaults, ConstTree};
trait_set! {
/// A "well behaved" type that can be used as payload in a CPS box
pub trait CPSPayload = Clone + Debug + 'static;
/// A function to handle a CPS box with a specific payload
pub trait CPSHandler<T: CPSPayload> = FnMut(&T, &ExprInst) -> HandlerRes;
}
/// The pre-argument version of CPSBox
#[derive(Debug, Clone)]
struct CPSFn<T: CPSPayload> {
pub argc: usize,
pub continuations: Vec<ExprInst>,
pub payload: T,
}
impl<T: CPSPayload> CPSFn<T> {
fn new(argc: usize, payload: T) -> Self {
debug_assert!(
argc > 0,
"Null-ary CPS functions are invalid, use an Atom instead"
);
Self { argc, continuations: Vec::new(), payload }
}
}
impl<T: CPSPayload> ExternFn for CPSFn<T> {
fn name(&self) -> &str {
"CPS function without argument"
}
fn apply(&self, arg: ExprInst, _ctx: Context) -> XfnResult {
let payload = self.payload.clone();
let continuations = (self.continuations.iter())
.cloned()
.chain(iter::once(arg))
.collect::<Vec<_>>();
if self.argc == 1 {
Ok(CPSBox { payload, continuations }.atom_cls())
} else {
Ok(CPSFn { argc: self.argc - 1, payload, continuations }.xfn_cls())
}
}
}
/// An inert Orchid Atom value encapsulating a payload and a continuation point
#[derive(Debug, Clone)]
pub struct CPSBox<T: CPSPayload> {
/// Details about the command
pub payload: T,
/// Possible continuations, in the order they were provided
pub continuations: Vec<ExprInst>,
}
impl<T: CPSPayload> CPSBox<T> {
/// Assert that the command was instantiated with the correct number of
/// possible continuations. This is decided by the native bindings, not user
/// code, therefore this error may be uncovered by usercode but can never be
/// produced at will.
pub fn assert_count(&self, expect: usize) {
let real = self.continuations.len();
debug_assert!(
real == expect,
"Tried to read {expect} argument(s) but {real} were provided for {:?}",
self.payload
)
}
/// Unpack the wrapped command and the continuation
pub fn unpack1(&self) -> (&T, &ExprInst) {
self.assert_count(1);
(&self.payload, &self.continuations[0])
}
/// Unpack the wrapped command and 2 continuations (usually an async and a
/// sync)
pub fn unpack2(&self) -> (&T, &ExprInst, &ExprInst) {
self.assert_count(2);
(&self.payload, &self.continuations[0], &self.continuations[1])
}
/// Unpack the wrapped command and 3 continuations (usually an async success,
/// an async fail and a sync)
pub fn unpack3(&self) -> (&T, &ExprInst, &ExprInst, &ExprInst) {
self.assert_count(3);
(
&self.payload,
&self.continuations[0],
&self.continuations[1],
&self.continuations[2],
)
}
}
impl<T: CPSPayload> Atomic for CPSBox<T> {
atomic_defaults!();
fn run(&self, ctx: Context) -> AtomicResult {
Ok(AtomicReturn {
clause: self.clone().atom_cls(),
gas: ctx.gas,
inert: true,
})
}
}
/// Like [init_cps] but wrapped in a [ConstTree] for init-time usage
pub fn mk_const<T: CPSPayload>(argc: usize, payload: T) -> ConstTree {
ConstTree::xfn(CPSFn::new(argc, payload))
}
/// Construct a CPS function which takes an argument and then acts inert
/// so that command executors can receive it.
///
/// This function is meant to be used in an external function defined with
/// [crate::define_fn]. For usage in a [ConstTree], see [mk_const]
pub fn init_cps<T: CPSPayload>(argc: usize, payload: T) -> Clause {
CPSFn::new(argc, payload).xfn_cls()
}

71
src/foreign/extern_fn.rs Normal file
View File

@@ -0,0 +1,71 @@
use std::error::Error;
use std::fmt::{Debug, Display};
use std::hash::Hash;
use std::rc::Rc;
use dyn_clone::DynClone;
use crate::interpreted::ExprInst;
use crate::interpreter::Context;
use crate::representations::interpreted::Clause;
use crate::Primitive;
/// Returned by [ExternFn::apply]
pub type XfnResult = Result<Clause, Rc<dyn ExternError>>;
/// Errors produced by external code
pub trait ExternError: Display {
/// Convert into trait object
fn into_extern(self) -> Rc<dyn ExternError>
where
Self: 'static + Sized,
{
Rc::new(self)
}
}
impl Debug for dyn ExternError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self}")
}
}
impl Error for dyn ExternError {}
/// Represents an externally defined function from the perspective of
/// the executor. Since Orchid lacks basic numerical operations,
/// these are also external functions.
pub trait ExternFn: DynClone {
/// Display name of the function
fn name(&self) -> &str;
/// Combine the function with an argument to produce a new clause
fn apply(&self, arg: ExprInst, ctx: Context) -> XfnResult;
/// Hash the name to get a somewhat unique hash.
fn hash(&self, mut state: &mut dyn std::hash::Hasher) {
self.name().hash(&mut state)
}
/// Wrap this function in a clause to be placed in an [AtomicResult].
fn xfn_cls(self) -> Clause
where
Self: Sized + 'static,
{
Clause::P(Primitive::ExternFn(Box::new(self)))
}
}
impl Eq for dyn ExternFn {}
impl PartialEq for dyn ExternFn {
fn eq(&self, other: &Self) -> bool {
self.name() == other.name()
}
}
impl Hash for dyn ExternFn {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name().hash(state)
}
}
impl Debug for dyn ExternFn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "##EXTERN[{}]##", self.name())
}
}

17
src/foreign/mod.rs Normal file
View File

@@ -0,0 +1,17 @@
//! Interaction with foreign code
//!
//! Structures and traits used in the exposure of external functions and values
//! to Orchid code
mod atom;
pub mod cps_box;
mod extern_fn;
use std::rc::Rc;
pub use atom::{Atom, Atomic, AtomicResult, AtomicReturn};
pub use extern_fn::{ExternError, ExternFn, XfnResult};
pub use crate::representations::interpreted::Clause;
/// A type-erased error in external code
pub type RcError = Rc<dyn ExternError>;