forked from Orchid/orchid
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:
109
src/foreign/atom.rs
Normal file
109
src/foreign/atom.rs
Normal 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
122
src/foreign/cps_box.rs
Normal 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
71
src/foreign/extern_fn.rs
Normal 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
17
src/foreign/mod.rs
Normal 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>;
|
||||
Reference in New Issue
Block a user