forked from Orchid/orchid
Backup commit
My backspace key started ghosting. Nothing works atm.
This commit is contained in:
@@ -1,45 +1,79 @@
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use dyn_clone::DynClone;
|
||||
use never::Never;
|
||||
|
||||
use super::XfnResult;
|
||||
use crate::ddispatch::request;
|
||||
use crate::error::AssertionError;
|
||||
use crate::interpreted::{ExprInst, TryFromExprInst};
|
||||
use crate::interpreter::{Context, RuntimeError};
|
||||
use crate::representations::interpreted::Clause;
|
||||
use crate::utils::ddispatch::Responder;
|
||||
use crate::{ast, NameLike};
|
||||
use super::error::{ExternError, ExternResult};
|
||||
use crate::interpreter::apply::CallData;
|
||||
use crate::interpreter::context::RunContext;
|
||||
use crate::interpreter::error::RunError;
|
||||
use crate::interpreter::nort;
|
||||
use crate::interpreter::run::RunData;
|
||||
use crate::location::{CodeLocation, SourceRange};
|
||||
use crate::name::NameLike;
|
||||
use crate::parse::parsed;
|
||||
use crate::utils::ddispatch::{request, Request, Responder};
|
||||
|
||||
/// 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,
|
||||
pub clause: nort::Clause,
|
||||
/// Remaining gas
|
||||
pub gas: Option<usize>,
|
||||
/// Whether further normalization is possible by repeated calls to
|
||||
/// [Atomic::run]
|
||||
pub inert: bool,
|
||||
}
|
||||
impl AtomicReturn {
|
||||
/// Report indicating that the value is inert
|
||||
pub fn inert<T: Atomic, E>(this: T, ctx: RunContext) -> Result<Self, E> {
|
||||
Ok(Self { clause: this.atom_cls(), gas: ctx.gas, inert: true })
|
||||
}
|
||||
/// Report indicating that the value has been processed
|
||||
pub fn run<E>(clause: nort::Clause, run: RunData) -> Result<Self, E> {
|
||||
Ok(Self { clause, gas: run.ctx.gas, inert: false })
|
||||
}
|
||||
}
|
||||
|
||||
/// Returned by [Atomic::run]
|
||||
pub type AtomicResult = Result<AtomicReturn, RuntimeError>;
|
||||
pub type AtomicResult = Result<AtomicReturn, RunError>;
|
||||
|
||||
/// Trait for things that are _definitely_ equal.
|
||||
pub trait StrictEq {
|
||||
/// must return true if the objects were produced via the exact same sequence
|
||||
/// of transformations, including any relevant context data. Must return false
|
||||
/// if the objects are of different type, or if their type is [PartialEq]
|
||||
/// and [PartialEq::eq] returns false.
|
||||
fn strict_eq(&self, other: &dyn Any) -> bool;
|
||||
/// General error produced when a non-function [Atom] is applied to something as
|
||||
/// a function.
|
||||
#[derive(Clone)]
|
||||
pub struct NotAFunction(pub nort::Expr);
|
||||
impl ExternError for NotAFunction {}
|
||||
impl Display for NotAFunction {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?} is not a function", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Functionality the interpreter needs to handle a value
|
||||
pub trait Atomic: Any + Debug + DynClone + StrictEq + Responder + Send
|
||||
where
|
||||
Self: 'static,
|
||||
///
|
||||
/// # Lifecycle methods
|
||||
///
|
||||
/// Atomics expose the methods [Atomic::redirect], [Atomic::run],
|
||||
/// [Atomic::apply] and [Atomic::apply_ref] to interact with the interpreter.
|
||||
/// The interpreter first tries to call `redirect` to find a subexpression to
|
||||
/// normalize. If it returns `None` or the subexpression is inert, `run` is
|
||||
/// called. `run` takes ownership of the value and returns a new one.
|
||||
///
|
||||
/// If `run` indicated in its return value that the result is inert and the atom
|
||||
/// is in the position of a function, `apply` or `apply_ref` is called depending
|
||||
/// upon whether the atom is referenced elsewhere. `apply` falls back to
|
||||
/// `apply_ref` so implementing it is considered an optimization to avoid
|
||||
/// excessive copying.
|
||||
///
|
||||
/// Atoms don't generally have to be copyable because clauses are refcounted in
|
||||
/// the interpreter, but Orchid code is always free to duplicate the references
|
||||
/// and apply them as functions to multiple different arguments so atoms that
|
||||
/// represent functions have to support application by-ref without consuming the
|
||||
/// function itself.
|
||||
pub trait Atomic: Any + Debug + Responder + Send
|
||||
where Self: 'static
|
||||
{
|
||||
/// Casts this value to [Any] so that its original value can be salvaged
|
||||
/// during introspection by other external code.
|
||||
@@ -55,45 +89,90 @@ where
|
||||
#[must_use]
|
||||
fn as_any_ref(&self) -> &dyn Any;
|
||||
|
||||
/// Returns a reference to a possible expression held inside the atom which
|
||||
/// can be reduced. For an overview of the lifecycle see [Atomic]
|
||||
fn redirect(&mut self) -> Option<&mut nort::ClauseInst>;
|
||||
|
||||
/// 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: Box<Self>, ctx: Context) -> AtomicResult;
|
||||
/// the computation and return a structure representing the next.
|
||||
///
|
||||
/// For an overview of the lifecycle see [Atomic]
|
||||
fn run(self: Box<Self>, run: RunData) -> AtomicResult;
|
||||
|
||||
/// Combine the function with an argument to produce a new clause. Falls back
|
||||
/// to [Atomic::apply_ref] by default.
|
||||
///
|
||||
/// For an overview of the lifecycle see [Atomic]
|
||||
fn apply(self: Box<Self>, call: CallData) -> ExternResult<nort::Clause> {
|
||||
self.apply_ref(call)
|
||||
}
|
||||
|
||||
/// Combine the function with an argument to produce a new clause
|
||||
///
|
||||
/// For an overview of the lifecycle see [Atomic]
|
||||
fn apply_ref(&self, call: CallData) -> ExternResult<nort::Clause>;
|
||||
|
||||
/// Must return true for atoms parsed from identical source.
|
||||
/// If the atom cannot be parsed from source, it can safely be ignored
|
||||
#[allow(unused_variables)]
|
||||
fn parser_eq(&self, other: &dyn Any) -> bool { false }
|
||||
|
||||
/// Wrap the atom in a clause to be placed in an [AtomicResult].
|
||||
#[must_use]
|
||||
fn atom_cls(self) -> Clause
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Clause::Atom(Atom(Box::new(self)))
|
||||
fn atom_cls(self) -> nort::Clause
|
||||
where Self: Sized {
|
||||
nort::Clause::Atom(Atom(Box::new(self)))
|
||||
}
|
||||
|
||||
/// Shorthand for `self.atom_cls().to_inst()`
|
||||
fn atom_clsi(self) -> nort::ClauseInst
|
||||
where Self: Sized {
|
||||
self.atom_cls().to_inst()
|
||||
}
|
||||
|
||||
/// Wrap the atom in a new expression instance to be placed in a tree
|
||||
#[must_use]
|
||||
fn atom_exi(self) -> ExprInst
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.atom_cls().wrap()
|
||||
fn atom_expr(self, location: CodeLocation) -> nort::Expr
|
||||
where Self: Sized {
|
||||
self.atom_clsi().to_expr(location)
|
||||
}
|
||||
|
||||
/// Wrap the atom in a clause to be placed in a [sourcefile::FileEntry].
|
||||
#[must_use]
|
||||
fn ast_cls<N: NameLike>(self) -> ast::Clause<N>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
ast::Clause::Atom(Atom::new(self))
|
||||
fn ast_cls(self) -> parsed::Clause
|
||||
where Self: Sized + Clone {
|
||||
parsed::Clause::Atom(AtomGenerator::cloner(self))
|
||||
}
|
||||
|
||||
/// Wrap the atom in an expression to be placed in a [sourcefile::FileEntry].
|
||||
#[must_use]
|
||||
fn ast_exp<N: NameLike>(self) -> ast::Expr<N>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.ast_cls().into_expr()
|
||||
fn ast_exp<N: NameLike>(self, range: SourceRange) -> parsed::Expr
|
||||
where Self: Sized + Clone {
|
||||
self.ast_cls().into_expr(range)
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct for generating any number of [Atom]s. Since atoms aren't Clone,
|
||||
/// this represents the ability to create any number of instances of an atom
|
||||
#[derive(Clone)]
|
||||
pub struct AtomGenerator(Arc<dyn Fn() -> Atom + Send + Sync>);
|
||||
impl AtomGenerator {
|
||||
/// Use a factory function to create any number of atoms
|
||||
pub fn new(f: impl Fn() -> Atom + Send + Sync + 'static) -> Self {
|
||||
Self(Arc::new(f))
|
||||
}
|
||||
/// Clone a representative atom when called
|
||||
pub fn cloner(atom: impl Atomic + Clone) -> Self {
|
||||
let lock = Mutex::new(atom);
|
||||
Self::new(move || Atom::new(lock.lock().unwrap().clone()))
|
||||
}
|
||||
/// Generate an atom
|
||||
pub fn run(&self) -> Atom { self.0() }
|
||||
}
|
||||
impl Debug for AtomGenerator {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("AtomGenerator").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +202,7 @@ impl Atom {
|
||||
*self.0.as_any().downcast().expect("Type mismatch on Atom::cast")
|
||||
}
|
||||
/// Normalize the contained data
|
||||
pub fn run(self, ctx: Context) -> AtomicResult { self.0.run(ctx) }
|
||||
pub fn run(self, run: RunData) -> AtomicResult { self.0.run(run) }
|
||||
/// Request a delegate from the encapsulated data
|
||||
pub fn request<T: 'static>(&self) -> Option<T> { request(self.0.as_ref()) }
|
||||
/// Downcast the atom to a concrete atomic type, or return the original atom
|
||||
@@ -134,10 +213,18 @@ impl Atom {
|
||||
false => Err(self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Atom {
|
||||
fn clone(&self) -> Self { Self(dyn_clone::clone_box(self.data())) }
|
||||
/// Downcast an atom by reference
|
||||
pub fn downcast_ref<T: Atomic>(&self) -> Option<&T> {
|
||||
self.0.as_any_ref().downcast_ref()
|
||||
}
|
||||
/// Combine the function with an argument to produce a new clause
|
||||
pub fn apply(self, call: CallData) -> ExternResult<nort::Clause> {
|
||||
self.0.apply(call)
|
||||
}
|
||||
/// Combine the function with an argument to produce a new clause
|
||||
pub fn apply_ref(&self, call: CallData) -> ExternResult<nort::Clause> {
|
||||
self.0.apply_ref(call)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Atom {
|
||||
@@ -146,12 +233,15 @@ impl Debug for Atom {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromExprInst for Atom {
|
||||
fn from_exi(exi: ExprInst) -> XfnResult<Self> {
|
||||
let loc = exi.location();
|
||||
match exi.expr_val().clause {
|
||||
Clause::Atom(a) => Ok(a),
|
||||
_ => AssertionError::fail(loc, "atom"),
|
||||
}
|
||||
impl Responder for Never {
|
||||
fn respond(&self, _request: Request) { match *self {} }
|
||||
}
|
||||
impl Atomic for Never {
|
||||
fn as_any(self: Box<Self>) -> Box<dyn Any> { match *self {} }
|
||||
fn as_any_ref(&self) -> &dyn Any { match *self {} }
|
||||
fn redirect(&mut self) -> Option<&mut nort::ClauseInst> { match *self {} }
|
||||
fn run(self: Box<Self>, _: RunData) -> AtomicResult { match *self {} }
|
||||
fn apply_ref(&self, _: CallData) -> ExternResult<nort::Clause> {
|
||||
match *self {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,99 +4,97 @@ use std::fmt::Debug;
|
||||
|
||||
use trait_set::trait_set;
|
||||
|
||||
use super::{Atomic, ExternFn, InertAtomic, XfnResult};
|
||||
use crate::interpreted::{Clause, ExprInst};
|
||||
use crate::interpreter::{Context, HandlerRes};
|
||||
use super::atom::{Atomic, AtomicResult, AtomicReturn, NotAFunction};
|
||||
use super::error::{ExternError, ExternResult};
|
||||
use crate::interpreter::apply::CallData;
|
||||
use crate::interpreter::nort::{Clause, ClauseInst, Expr};
|
||||
use crate::interpreter::run::RunData;
|
||||
use crate::location::CodeLocation;
|
||||
use crate::utils::ddispatch::{Request, Responder};
|
||||
use crate::utils::pure_seq::pushed_ref;
|
||||
use crate::ConstTree;
|
||||
|
||||
trait_set! {
|
||||
/// A "well behaved" type that can be used as payload in a CPS box
|
||||
pub trait CPSPayload = Clone + Debug + Send + 'static;
|
||||
/// A function to handle a CPS box with a specific payload
|
||||
pub trait CPSHandler<T: CPSPayload> = FnMut(&T, &ExprInst) -> HandlerRes;
|
||||
pub trait CPSHandler<T: CPSPayload> = FnMut(&T, &Expr) -> ExternResult<Expr>;
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
#[must_use]
|
||||
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: Box<Self>, arg: ExprInst, _ctx: Context) -> XfnResult<Clause> {
|
||||
let payload = self.payload.clone();
|
||||
let continuations = pushed_ref(&self.continuations, arg);
|
||||
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
|
||||
/// An Orchid Atom value encapsulating a payload and continuation points
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CPSBox<T: CPSPayload> {
|
||||
/// Number of arguments not provided yet
|
||||
pub argc: usize,
|
||||
/// Details about the command
|
||||
pub payload: T,
|
||||
/// Possible continuations, in the order they were provided
|
||||
pub continuations: Vec<ExprInst>,
|
||||
pub continuations: Vec<Expr>,
|
||||
}
|
||||
impl<T: CPSPayload> CPSBox<T> {
|
||||
/// Create a new command prepared to receive exacly N continuations
|
||||
#[must_use]
|
||||
pub fn new(argc: usize, payload: T) -> Self {
|
||||
debug_assert!(argc > 0, "Null-ary CPS functions are invalid");
|
||||
Self { argc, continuations: Vec::new(), payload }
|
||||
}
|
||||
/// Unpack the wrapped command and the continuation
|
||||
#[must_use]
|
||||
pub fn unpack1(self) -> (T, ExprInst) {
|
||||
let [cont]: [ExprInst; 1] =
|
||||
self.continuations.try_into().expect("size checked");
|
||||
(self.payload, cont)
|
||||
pub fn unpack1(&self) -> (&T, Expr) {
|
||||
match &self.continuations[..] {
|
||||
[cont] => (&self.payload, cont.clone()),
|
||||
_ => panic!("size mismatch"),
|
||||
}
|
||||
}
|
||||
/// Unpack the wrapped command and 2 continuations (usually an async and a
|
||||
/// sync)
|
||||
#[must_use]
|
||||
pub fn unpack2(self) -> (T, ExprInst, ExprInst) {
|
||||
let [c1, c2]: [ExprInst; 2] =
|
||||
self.continuations.try_into().expect("size checked");
|
||||
(self.payload, c1, c2)
|
||||
pub fn unpack2(&self) -> (&T, Expr, Expr) {
|
||||
match &self.continuations[..] {
|
||||
[c1, c2] => (&self.payload, c1.clone(), c2.clone()),
|
||||
_ => panic!("size mismatch"),
|
||||
}
|
||||
}
|
||||
/// Unpack the wrapped command and 3 continuations (usually an async success,
|
||||
/// an async fail and a sync)
|
||||
#[must_use]
|
||||
pub fn unpack3(self) -> (T, ExprInst, ExprInst, ExprInst) {
|
||||
let [c1, c2, c3]: [ExprInst; 3] =
|
||||
self.continuations.try_into().expect("size checked");
|
||||
(self.payload, c1, c2, c3)
|
||||
pub fn unpack3(&self) -> (&T, Expr, Expr, Expr) {
|
||||
match &self.continuations[..] {
|
||||
[c1, c2, c3] => (&self.payload, c1.clone(), c2.clone(), c3.clone()),
|
||||
_ => panic!("size mismatch"),
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_applicable(&self, err_loc: &CodeLocation) -> ExternResult<()> {
|
||||
match self.argc {
|
||||
0 => Err(NotAFunction(self.clone().atom_expr(err_loc.clone())).rc()),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: CPSPayload> InertAtomic for CPSBox<T> {
|
||||
fn type_str() -> &'static str { "a CPS box" }
|
||||
impl<T: CPSPayload> Responder for CPSBox<T> {
|
||||
fn respond(&self, _request: Request) {}
|
||||
}
|
||||
|
||||
/// Like [init_cps] but wrapped in a [ConstTree] for init-time usage
|
||||
#[must_use]
|
||||
pub fn const_cps<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]
|
||||
#[must_use]
|
||||
pub fn init_cps<T: CPSPayload>(argc: usize, payload: T) -> Clause {
|
||||
CPSFn::new(argc, payload).xfn_cls()
|
||||
impl<T: CPSPayload> Atomic for CPSBox<T> {
|
||||
fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> { self }
|
||||
fn as_any_ref(&self) -> &dyn std::any::Any { self }
|
||||
fn parser_eq(&self, _: &dyn std::any::Any) -> bool { false }
|
||||
fn redirect(&mut self) -> Option<&mut ClauseInst> { None }
|
||||
fn run(self: Box<Self>, run: RunData) -> AtomicResult {
|
||||
AtomicReturn::inert(*self, run.ctx)
|
||||
}
|
||||
fn apply(mut self: Box<Self>, call: CallData) -> ExternResult<Clause> {
|
||||
self.assert_applicable(&call.location)?;
|
||||
self.argc -= 1;
|
||||
self.continuations.push(call.arg);
|
||||
Ok(self.atom_cls())
|
||||
}
|
||||
fn apply_ref(&self, call: CallData) -> ExternResult<Clause> {
|
||||
self.assert_applicable(&call.location)?;
|
||||
let new = Self {
|
||||
argc: self.argc - 1,
|
||||
continuations: pushed_ref(&self.continuations, call.arg),
|
||||
payload: self.payload.clone(),
|
||||
};
|
||||
Ok(new.atom_cls())
|
||||
}
|
||||
}
|
||||
|
||||
68
src/foreign/error.rs
Normal file
68
src/foreign/error.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::sync::Arc;
|
||||
|
||||
use dyn_clone::DynClone;
|
||||
|
||||
use crate::location::CodeLocation;
|
||||
|
||||
/// Errors produced by external code
|
||||
pub trait ExternError: Display + Send + Sync + DynClone {
|
||||
/// Convert into trait object
|
||||
#[must_use]
|
||||
fn rc(self) -> Arc<dyn ExternError>
|
||||
where Self: 'static + Sized {
|
||||
Arc::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for dyn ExternError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "ExternError({self})")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for dyn ExternError {}
|
||||
|
||||
/// An error produced by Rust code called form Orchid. The error is type-erased.
|
||||
pub type ExternResult<T> = Result<T, Arc<dyn ExternError>>;
|
||||
|
||||
/// Some expectation (usually about the argument types of a function) did not
|
||||
/// hold.
|
||||
#[derive(Clone)]
|
||||
pub struct AssertionError {
|
||||
location: CodeLocation,
|
||||
message: &'static str,
|
||||
details: String,
|
||||
}
|
||||
|
||||
impl AssertionError {
|
||||
/// Construct, upcast and wrap in a Result that never succeeds for easy
|
||||
/// short-circuiting
|
||||
pub fn fail<T>(
|
||||
location: CodeLocation,
|
||||
message: &'static str,
|
||||
details: String,
|
||||
) -> ExternResult<T> {
|
||||
Err(Self::ext(location, message, details))
|
||||
}
|
||||
|
||||
/// Construct and upcast to [ExternError]
|
||||
pub fn ext(
|
||||
location: CodeLocation,
|
||||
message: &'static str,
|
||||
details: String,
|
||||
) -> Arc<dyn ExternError> {
|
||||
Self { location, message, details }.rc()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AssertionError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Error: expected {}", self.message)?;
|
||||
write!(f, " at {}", self.location)?;
|
||||
write!(f, " details: {}", self.details)
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternError for AssertionError {}
|
||||
@@ -1,94 +0,0 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::hash::Hash;
|
||||
use std::sync::Arc;
|
||||
|
||||
use dyn_clone::{clone_box, DynClone};
|
||||
|
||||
use super::XfnResult;
|
||||
use crate::interpreted::ExprInst;
|
||||
use crate::interpreter::Context;
|
||||
use crate::representations::interpreted::Clause;
|
||||
use crate::{ast, NameLike};
|
||||
|
||||
/// Errors produced by external code
|
||||
pub trait ExternError: Display + Send + Sync + DynClone {
|
||||
/// Convert into trait object
|
||||
#[must_use]
|
||||
fn into_extern(self) -> Arc<dyn ExternError>
|
||||
where
|
||||
Self: 'static + Sized,
|
||||
{
|
||||
Arc::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 + Send {
|
||||
/// Display name of the function
|
||||
#[must_use]
|
||||
fn name(&self) -> &str;
|
||||
/// Combine the function with an argument to produce a new clause
|
||||
fn apply(self: Box<Self>, arg: ExprInst, ctx: Context) -> XfnResult<Clause>;
|
||||
/// 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].
|
||||
#[must_use]
|
||||
fn xfn_cls(self) -> Clause
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
Clause::ExternFn(ExFn(Box::new(self)))
|
||||
}
|
||||
/// Wrap this function in a clause to be placed in a [FileEntry].
|
||||
#[must_use]
|
||||
fn xfn_ast_cls<N: NameLike>(self) -> ast::Clause<N>
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
ast::Clause::ExternFn(ExFn(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())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a black box function that can be applied to a [Clause] to produce
|
||||
/// a new [Clause], typically an [Atom] representing external work, a new [ExFn]
|
||||
/// to take additional arguments, or an Orchid tree to return control to the
|
||||
/// interpreter
|
||||
#[derive(Debug)]
|
||||
pub struct ExFn(pub Box<dyn ExternFn + 'static>);
|
||||
impl ExFn {
|
||||
/// Combine the function with an argument to produce a new clause
|
||||
pub fn apply(self, arg: ExprInst, ctx: Context) -> XfnResult<Clause> {
|
||||
self.0.apply(arg, ctx)
|
||||
}
|
||||
}
|
||||
impl Clone for ExFn {
|
||||
fn clone(&self) -> Self { Self(clone_box(self.0.as_ref())) }
|
||||
}
|
||||
@@ -1,47 +1,16 @@
|
||||
use std::any::{Any, TypeId};
|
||||
use std::fmt::Debug;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::atom::StrictEq;
|
||||
use super::{
|
||||
Atomic, AtomicResult, AtomicReturn, ExternError, ExternFn, XfnResult,
|
||||
};
|
||||
use crate::ddispatch::Responder;
|
||||
use crate::interpreted::{Clause, ExprInst, TryFromExprInst};
|
||||
use crate::interpreter::{run, Context, Return};
|
||||
use crate::systems::codegen::{opt, res};
|
||||
use crate::OrcString;
|
||||
|
||||
/// A trait for things that are infallibly convertible to [Clause]. These types
|
||||
/// can be returned by callbacks passed to the [super::xfn_1ary] family of
|
||||
/// functions.
|
||||
pub trait ToClause: Clone {
|
||||
/// Convert the type to a [Clause].
|
||||
fn to_clause(self) -> Clause;
|
||||
/// Convert to an expression instance via [ToClause].
|
||||
fn to_exi(self) -> ExprInst { self.to_clause().wrap() }
|
||||
}
|
||||
|
||||
impl<T: Atomic + Clone> ToClause for T {
|
||||
fn to_clause(self) -> Clause { self.atom_cls() }
|
||||
}
|
||||
impl ToClause for Clause {
|
||||
fn to_clause(self) -> Clause { self }
|
||||
}
|
||||
impl ToClause for ExprInst {
|
||||
fn to_clause(self) -> Clause { self.expr_val().clause }
|
||||
}
|
||||
impl ToClause for String {
|
||||
fn to_clause(self) -> Clause { OrcString::from(self).atom_cls() }
|
||||
}
|
||||
impl<T: ToClause> ToClause for Option<T> {
|
||||
fn to_clause(self) -> Clause { opt(self.map(|t| t.to_clause().wrap())) }
|
||||
}
|
||||
impl<T: ToClause, U: ToClause> ToClause for Result<T, U> {
|
||||
fn to_clause(self) -> Clause {
|
||||
res(self.map(|t| t.to_clause().wrap()).map_err(|u| u.to_clause().wrap()))
|
||||
}
|
||||
}
|
||||
use super::atom::{Atomic, AtomicResult, AtomicReturn};
|
||||
use super::error::ExternResult;
|
||||
use super::to_clause::ToClause;
|
||||
use super::try_from_expr::TryFromExpr;
|
||||
use crate::interpreter::apply::CallData;
|
||||
use crate::interpreter::context::Halt;
|
||||
use crate::interpreter::nort::{Clause, ClauseInst, Expr};
|
||||
use crate::interpreter::run::{run, RunData};
|
||||
use crate::utils::ddispatch::Responder;
|
||||
|
||||
/// Return a unary lambda wrapped in this struct to take an additional argument
|
||||
/// in a function passed to Orchid through a member of the [super::xfn_1ary]
|
||||
@@ -51,6 +20,11 @@ impl<T: ToClause, U: ToClause> ToClause for Result<T, U> {
|
||||
/// type. Rust functions are never overloaded, but inexplicably the [Fn] traits
|
||||
/// take the argument tuple as a generic parameter which means that it cannot
|
||||
/// be a unique dispatch target.
|
||||
///
|
||||
/// If the function takes an instance of [Lazy], it will contain the expression
|
||||
/// the function was applied to without any specific normalization. If it takes
|
||||
/// any other type, the argument will be fully normalized and cast using the
|
||||
/// type's [TryFromExpr] impl.
|
||||
pub struct Param<T, U, F> {
|
||||
data: F,
|
||||
_t: PhantomData<T>,
|
||||
@@ -60,9 +34,7 @@ unsafe impl<T, U, F: Send> Send for Param<T, U, F> {}
|
||||
impl<T, U, F> Param<T, U, F> {
|
||||
/// Wrap a new function in a parametric struct
|
||||
pub fn new(f: F) -> Self
|
||||
where
|
||||
F: FnOnce(T) -> Result<U, Arc<dyn ExternError>>,
|
||||
{
|
||||
where F: FnOnce(T) -> U {
|
||||
Self { data: f, _t: PhantomData, _u: PhantomData }
|
||||
}
|
||||
/// Take out the function
|
||||
@@ -74,75 +46,91 @@ impl<T, U, F: Clone> Clone for Param<T, U, F> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
T: 'static + TryFromExprInst,
|
||||
U: 'static + ToClause,
|
||||
F: 'static + Clone + Send + FnOnce(T) -> Result<U, Arc<dyn ExternError>>,
|
||||
> ToClause for Param<T, U, F>
|
||||
{
|
||||
fn to_clause(self) -> Clause { self.xfn_cls() }
|
||||
/// A marker struct that gets assigned an expression without normalizing it.
|
||||
/// This behaviour cannot be replicated in usercode, it's implemented with an
|
||||
/// explicit runtime [TypeId] check invoked by [Param].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Thunk(pub Expr);
|
||||
impl TryFromExpr for Thunk {
|
||||
fn from_expr(expr: Expr) -> ExternResult<Self> { Ok(Thunk(expr)) }
|
||||
}
|
||||
|
||||
struct FnMiddleStage<T, U, F> {
|
||||
argument: ExprInst,
|
||||
arg: Expr,
|
||||
f: Param<T, U, F>,
|
||||
}
|
||||
impl<T, U, F> StrictEq for FnMiddleStage<T, U, F> {
|
||||
fn strict_eq(&self, _other: &dyn std::any::Any) -> bool {
|
||||
unimplemented!("This should never be able to appear in a pattern")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U, F: Clone> Clone for FnMiddleStage<T, U, F> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { argument: self.argument.clone(), f: self.f.clone() }
|
||||
}
|
||||
fn clone(&self) -> Self { Self { arg: self.arg.clone(), f: self.f.clone() } }
|
||||
}
|
||||
impl<T, U, F> Debug for FnMiddleStage<T, U, F> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("FnMiddleStage")
|
||||
.field("argument", &self.argument)
|
||||
.field("argument", &self.arg)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
impl<T, U, F> Responder for FnMiddleStage<T, U, F> {}
|
||||
impl<
|
||||
T: 'static + TryFromExprInst,
|
||||
T: 'static + TryFromExpr,
|
||||
U: 'static + ToClause,
|
||||
F: 'static + Clone + FnOnce(T) -> Result<U, Arc<dyn ExternError>> + Send,
|
||||
F: 'static + Clone + FnOnce(T) -> U + Any + Send,
|
||||
> Atomic for FnMiddleStage<T, U, F>
|
||||
{
|
||||
fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> { self }
|
||||
fn as_any_ref(&self) -> &dyn std::any::Any { self }
|
||||
fn run(self: Box<Self>, ctx: Context) -> AtomicResult {
|
||||
let Return { gas, inert, state } = run(self.argument, ctx)?;
|
||||
let clause = match inert {
|
||||
false => state.expr_val().clause,
|
||||
true => (self.f.data)(state.downcast()?)?.to_clause(),
|
||||
};
|
||||
Ok(AtomicReturn { gas, inert: false, clause })
|
||||
fn redirect(&mut self) -> Option<&mut ClauseInst> {
|
||||
// this should be ctfe'd
|
||||
(TypeId::of::<T>() != TypeId::of::<Thunk>()).then(|| &mut self.arg.clause)
|
||||
}
|
||||
fn run(self: Box<Self>, r: RunData) -> AtomicResult {
|
||||
let Self { arg, f: Param { data: f, .. } } = *self;
|
||||
let clause = f(arg.downcast()?).to_clause(r.location);
|
||||
Ok(AtomicReturn { gas: r.ctx.gas, inert: false, clause })
|
||||
}
|
||||
fn apply_ref(&self, _: CallData) -> ExternResult<Clause> {
|
||||
panic!("Atom should have decayed")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U, F> Responder for Param<T, U, F> {}
|
||||
impl<T, U, F> Debug for Param<T, U, F> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Param")
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
T: 'static + TryFromExprInst,
|
||||
T: 'static + TryFromExpr + Clone,
|
||||
U: 'static + ToClause,
|
||||
F: 'static + Clone + Send + FnOnce(T) -> Result<U, Arc<dyn ExternError>>,
|
||||
> ExternFn for Param<T, U, F>
|
||||
F: 'static + Clone + Send + FnOnce(T) -> U,
|
||||
> Atomic for Param<T, U, F>
|
||||
{
|
||||
fn name(&self) -> &str { "anonymous Rust function" }
|
||||
fn apply(self: Box<Self>, arg: ExprInst, _: Context) -> XfnResult<Clause> {
|
||||
Ok(FnMiddleStage { argument: arg, f: *self }.atom_cls())
|
||||
fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> { self }
|
||||
fn as_any_ref(&self) -> &dyn std::any::Any { self }
|
||||
fn redirect(&mut self) -> Option<&mut ClauseInst> { None }
|
||||
fn run(self: Box<Self>, r: RunData) -> AtomicResult {
|
||||
AtomicReturn::inert(*self, r.ctx)
|
||||
}
|
||||
fn apply_ref(&self, call: CallData) -> ExternResult<Clause> {
|
||||
Ok(FnMiddleStage { arg: call.arg, f: self.clone() }.atom_cls())
|
||||
}
|
||||
fn apply(self: Box<Self>, call: CallData) -> ExternResult<Clause> {
|
||||
Ok(FnMiddleStage { arg: call.arg, f: *self }.atom_cls())
|
||||
}
|
||||
}
|
||||
|
||||
/// Conversion functions from [Fn] traits into [Atomic]. Since Rust's type
|
||||
/// system allows overloaded [Fn] implementations, we must specify the arity and
|
||||
/// argument types for this process. Arities are only defined up to 9, but the
|
||||
/// function can always return another call to `xfn_`N`ary` to consume more
|
||||
/// arguments.
|
||||
pub mod constructors {
|
||||
|
||||
|
||||
use std::sync::Arc;
|
||||
use super::super::atom::Atomic;
|
||||
use super::super::try_from_expr::TryFromExpr;
|
||||
#[allow(unused)] // for doc
|
||||
use super::Thunk;
|
||||
use super::{Param, ToClause};
|
||||
use crate::foreign::{ExternError, ExternFn};
|
||||
use crate::interpreted::TryFromExprInst;
|
||||
|
||||
macro_rules! xfn_variant {
|
||||
(
|
||||
@@ -154,18 +142,21 @@ pub mod constructors {
|
||||
#[doc = "Convert a function of " $number " argument(s) into a curried"
|
||||
" Orchid function. See also Constraints summarized:\n\n"
|
||||
"- the callback must live as long as `'static`\n"
|
||||
"- All arguments must implement [TryFromExprInst]\n"
|
||||
"- All arguments must implement [TryFromExpr]\n"
|
||||
"- all but the last argument must implement [Clone] and [Send]\n"
|
||||
"- the return type must implement [ToClause].\n\n"
|
||||
]
|
||||
#[doc = "Take [Lazy] to take the argument as-is,\n"
|
||||
"without normalization\n\n"
|
||||
]
|
||||
#[doc = "Other arities: " $( "[xfn_" $alt "ary], " )+ ]
|
||||
pub fn [< xfn_ $number ary >] <
|
||||
$( $t : TryFromExprInst + Clone + Send + 'static, )*
|
||||
TLast: TryFromExprInst + 'static,
|
||||
$( $t : TryFromExpr + Clone + Send + 'static, )*
|
||||
TLast: TryFromExpr + Clone + 'static,
|
||||
TReturn: ToClause + Send + 'static,
|
||||
TFunction: FnOnce( $( $t , )* TLast )
|
||||
-> Result<TReturn, Arc<dyn ExternError>> + Clone + Send + 'static
|
||||
>(function: TFunction) -> impl ExternFn {
|
||||
-> TReturn + Clone + Send + 'static
|
||||
>(function: TFunction) -> impl Atomic + Clone {
|
||||
xfn_variant!(@BODY_LOOP function
|
||||
( $( ( $t [< $t:lower >] ) )* )
|
||||
( $( [< $t:lower >] )* )
|
||||
@@ -178,7 +169,7 @@ pub mod constructors {
|
||||
$( ( $T:ident $t:ident ) )*
|
||||
) $full:tt) => {
|
||||
Param::new(|$next : $Next| {
|
||||
Ok(xfn_variant!(@BODY_LOOP $function ( $( ( $T $t ) )* ) $full))
|
||||
xfn_variant!(@BODY_LOOP $function ( $( ( $T $t ) )* ) $full)
|
||||
})
|
||||
};
|
||||
(@BODY_LOOP $function:ident () ( $( $t:ident )* )) => {
|
||||
|
||||
172
src/foreign/implementations.rs
Normal file
172
src/foreign/implementations.rs
Normal file
@@ -0,0 +1,172 @@
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::atom::{Atom, Atomic, AtomicResult};
|
||||
use super::error::{ExternError, ExternResult};
|
||||
use super::process::Unstable;
|
||||
use super::to_clause::ToClause;
|
||||
use crate::gen::tpl;
|
||||
use crate::gen::traits::Gen;
|
||||
use crate::interpreter::apply::CallData;
|
||||
use crate::interpreter::error::RunError;
|
||||
use crate::interpreter::gen_nort::nort_gen;
|
||||
use crate::interpreter::nort::{Clause, ClauseInst};
|
||||
use crate::interpreter::run::RunData;
|
||||
use crate::location::CodeLocation;
|
||||
use crate::utils::clonable_iter::Clonable;
|
||||
use crate::utils::ddispatch::Responder;
|
||||
|
||||
impl<T: ToClause> ToClause for Option<T> {
|
||||
fn to_clause(self, location: CodeLocation) -> Clause {
|
||||
let ctx = nort_gen(location.clone());
|
||||
match self {
|
||||
None => tpl::C("std::option::none").template(ctx, []),
|
||||
Some(t) => tpl::A(tpl::C("std::option::some"), tpl::Slot)
|
||||
.template(ctx, [t.to_clause(location)]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToClause, U: ToClause> ToClause for Result<T, U> {
|
||||
fn to_clause(self, location: CodeLocation) -> Clause {
|
||||
let ctx = nort_gen(location.clone());
|
||||
match self {
|
||||
Ok(t) => tpl::A(tpl::C("std::result::ok"), tpl::Slot)
|
||||
.template(ctx, [t.to_clause(location)]),
|
||||
Err(e) => tpl::A(tpl::C("std::result::err"), tpl::Slot)
|
||||
.template(ctx, [e.to_clause(location)]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PendingError(Arc<dyn ExternError>);
|
||||
impl Responder for PendingError {}
|
||||
impl Debug for PendingError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "PendingError({})", self.0)
|
||||
}
|
||||
}
|
||||
impl Atomic for PendingError {
|
||||
fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
|
||||
fn as_any_ref(&self) -> &dyn Any { self }
|
||||
fn redirect(&mut self) -> Option<&mut ClauseInst> { None }
|
||||
fn run(self: Box<Self>, _: RunData) -> AtomicResult {
|
||||
Err(RunError::Extern(self.0))
|
||||
}
|
||||
fn apply_ref(&self, _: CallData) -> ExternResult<Clause> {
|
||||
panic!("This atom decays instantly")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToClause> ToClause for ExternResult<T> {
|
||||
fn to_clause(self, location: CodeLocation) -> Clause {
|
||||
match self {
|
||||
Err(e) => PendingError(e).atom_cls(),
|
||||
Ok(t) => t.to_clause(location),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ListGen<I>(Clonable<I>)
|
||||
where
|
||||
I: Iterator + Send,
|
||||
I::Item: ToClause + Send;
|
||||
impl<I> Clone for ListGen<I>
|
||||
where
|
||||
I: Iterator + Send,
|
||||
I::Item: ToClause + Send,
|
||||
{
|
||||
fn clone(&self) -> Self { Self(self.0.clone()) }
|
||||
}
|
||||
impl<I> ToClause for ListGen<I>
|
||||
where
|
||||
I: Iterator + Send + 'static,
|
||||
I::Item: ToClause + Clone + Send,
|
||||
{
|
||||
fn to_clause(mut self, location: CodeLocation) -> Clause {
|
||||
let ctx = nort_gen(location.clone());
|
||||
match self.0.next() {
|
||||
None => tpl::C("std::lit::end").template(ctx, []),
|
||||
Some(val) => {
|
||||
let atom = Unstable::new(|run| self.to_clause(run.location));
|
||||
tpl::a2(tpl::C("std::lit::cons"), tpl::Slot, tpl::V(atom))
|
||||
.template(ctx, [val.to_clause(location)])
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert an iterator into a lazy-evaluated Orchid list.
|
||||
pub fn list<I>(items: I) -> impl ToClause
|
||||
where
|
||||
I: IntoIterator + Clone + Send + Sync + 'static,
|
||||
I::IntoIter: Send,
|
||||
I::Item: ToClause + Clone + Send,
|
||||
{
|
||||
Unstable::new(move |RunData { location, .. }| {
|
||||
ListGen(Clonable::new(
|
||||
items.clone().into_iter().map(move |t| t.to_clsi(location.clone())),
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
impl<T: ToClause + Clone + Send + Sync + 'static> ToClause for Vec<T> {
|
||||
fn to_clause(self, location: CodeLocation) -> Clause {
|
||||
list(self).to_clause(location)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToClause for Atom {
|
||||
fn to_clause(self, _: CodeLocation) -> Clause { Clause::Atom(self) }
|
||||
}
|
||||
|
||||
mod tuple_impls {
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::ToClause;
|
||||
use crate::foreign::atom::Atomic;
|
||||
use crate::foreign::error::AssertionError;
|
||||
use crate::foreign::implementations::ExternResult;
|
||||
use crate::foreign::inert::Inert;
|
||||
use crate::foreign::try_from_expr::TryFromExpr;
|
||||
use crate::interpreter::nort::{Clause, Expr};
|
||||
use crate::libs::std::tuple::Tuple;
|
||||
use crate::location::CodeLocation;
|
||||
|
||||
macro_rules! gen_tuple_impl {
|
||||
( ($($T:ident)*) ($($t:ident)*)) => {
|
||||
impl<$($T: ToClause),*> ToClause for ($($T,)*) {
|
||||
fn to_clause(self, location: CodeLocation) -> Clause {
|
||||
let ($($t,)*) = self;
|
||||
Inert(Tuple(Arc::new(vec![
|
||||
$($t.to_expr(location.clone()),)*
|
||||
]))).atom_cls()
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($T: TryFromExpr),*> TryFromExpr for ($($T,)*) {
|
||||
fn from_expr(ex: Expr) -> ExternResult<Self> {
|
||||
let Inert(Tuple(slice)) = ex.clone().downcast()?;
|
||||
match &slice[..] {
|
||||
[$($t),*] => Ok(($($t.clone().downcast()?,)*)),
|
||||
_ => AssertionError::fail(ex.location(), "Tuple length mismatch", format!("{ex}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
gen_tuple_impl!((A)(a));
|
||||
gen_tuple_impl!((A B) (a b));
|
||||
gen_tuple_impl!((A B C) (a b c));
|
||||
gen_tuple_impl!((A B C D) (a b c d));
|
||||
gen_tuple_impl!((A B C D E) (a b c d e));
|
||||
gen_tuple_impl!((A B C D E F) (a b c d e f));
|
||||
gen_tuple_impl!((A B C D E F G) (a b c d e f g));
|
||||
gen_tuple_impl!((A B C D E F G H) (a b c d e f g h));
|
||||
gen_tuple_impl!((A B C D E F G H I) (a b c d e f g h i));
|
||||
gen_tuple_impl!((A B C D E F G H I J) (a b c d e f g h i j));
|
||||
gen_tuple_impl!((A B C D E F G H I J K) (a b c d e f g h i j k));
|
||||
gen_tuple_impl!((A B C D E F G H I J K L) (a b c d e f g h i j k l));
|
||||
}
|
||||
@@ -1,28 +1,30 @@
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use ordered_float::NotNan;
|
||||
|
||||
use super::atom::StrictEq;
|
||||
use super::{AtomicResult, AtomicReturn, XfnResult};
|
||||
use crate::error::AssertionError;
|
||||
#[allow(unused)] // for doc
|
||||
// use crate::define_fn;
|
||||
use crate::foreign::Atomic;
|
||||
use crate::interpreted::{Clause, Expr, ExprInst, TryFromExprInst};
|
||||
use crate::interpreter::Context;
|
||||
use crate::systems::stl::Numeric;
|
||||
use super::atom::{Atom, Atomic, AtomicResult, AtomicReturn, NotAFunction};
|
||||
use super::error::{ExternError, ExternResult};
|
||||
use super::try_from_expr::TryFromExpr;
|
||||
use crate::foreign::error::AssertionError;
|
||||
use crate::interpreter::apply::CallData;
|
||||
use crate::interpreter::nort::{Clause, ClauseInst, Expr};
|
||||
use crate::interpreter::run::RunData;
|
||||
use crate::libs::std::number::Numeric;
|
||||
use crate::libs::std::string::OrcString;
|
||||
use crate::utils::ddispatch::{Request, Responder};
|
||||
|
||||
/// A proxy trait that implements [Atomic] for blobs of data in Rust code that
|
||||
/// cannot be processed and always report inert. Since these are expected to be
|
||||
/// parameters of functions defined with [define_fn] it also automatically
|
||||
/// implements [TryFromExprInst] so that a conversion doesn't have to be
|
||||
/// implements [TryFromExpr] so that a conversion doesn't have to be
|
||||
/// provided in argument lists.
|
||||
pub trait InertAtomic: Debug + Clone + Send + 'static {
|
||||
pub trait InertPayload: Debug + Clone + Send + 'static {
|
||||
/// Typename to be shown in the error when a conversion from [ExprInst] fails
|
||||
#[must_use]
|
||||
fn type_str() -> &'static str;
|
||||
///
|
||||
/// This will default to `type_name::<Self>()` when it becomes stable
|
||||
const TYPE_STR: &'static str;
|
||||
/// Proxies to [Responder] so that you don't have to implmeent it manually if
|
||||
/// you need it, but behaves exactly as the default implementation.
|
||||
#[allow(unused_mut, unused_variables)] // definition should show likely usage
|
||||
@@ -37,59 +39,99 @@ pub trait InertAtomic: Debug + Clone + Send + 'static {
|
||||
/// ```
|
||||
fn strict_eq(&self, _: &Self) -> bool { false }
|
||||
}
|
||||
impl<T: InertAtomic> StrictEq for T {
|
||||
fn strict_eq(&self, other: &dyn Any) -> bool {
|
||||
other.downcast_ref().map_or(false, |other| self.strict_eq(other))
|
||||
}
|
||||
|
||||
/// An atom that stores a value and rejects all interpreter interactions. It is
|
||||
/// used to reference foreign data in Orchid.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Inert<T: InertPayload>(pub T);
|
||||
impl<T: InertPayload> Inert<T> {
|
||||
/// Wrap the argument in a type-erased [Atom] for embedding in Orchid
|
||||
/// structures.
|
||||
pub fn atom(t: T) -> Atom { Atom::new(Inert(t)) }
|
||||
}
|
||||
impl<T: InertAtomic> Responder for T {
|
||||
|
||||
impl<T: InertPayload> Deref for Inert<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl<T: InertPayload> DerefMut for Inert<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||
}
|
||||
|
||||
impl<T: InertPayload> Responder for Inert<T> {
|
||||
fn respond(&self, mut request: Request) {
|
||||
if request.can_serve::<T>() {
|
||||
request.serve(self.clone())
|
||||
request.serve(self.0.clone())
|
||||
} else {
|
||||
self.respond(request)
|
||||
self.0.respond(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: InertAtomic> Atomic for T {
|
||||
impl<T: InertPayload> Atomic for Inert<T> {
|
||||
fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
|
||||
fn as_any_ref(&self) -> &dyn Any { self }
|
||||
|
||||
fn run(self: Box<Self>, ctx: Context) -> AtomicResult {
|
||||
Ok(AtomicReturn { gas: ctx.gas, inert: true, clause: self.atom_cls() })
|
||||
fn redirect(&mut self) -> Option<&mut ClauseInst> { None }
|
||||
fn run(self: Box<Self>, run: RunData) -> AtomicResult {
|
||||
AtomicReturn::inert(*self, run.ctx)
|
||||
}
|
||||
fn apply_ref(&self, call: CallData) -> ExternResult<Clause> {
|
||||
Err(NotAFunction(self.clone().atom_expr(call.location)).rc())
|
||||
}
|
||||
fn parser_eq(&self, other: &dyn Any) -> bool {
|
||||
(other.downcast_ref::<Self>())
|
||||
.map_or(false, |other| self.0.strict_eq(&other.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: InertAtomic> TryFromExprInst for T {
|
||||
fn from_exi(exi: ExprInst) -> XfnResult<Self> {
|
||||
let Expr { clause, location } = exi.expr_val();
|
||||
match clause {
|
||||
Clause::Atom(a) => match a.0.as_any().downcast() {
|
||||
Ok(t) => Ok(*t),
|
||||
Err(_) => AssertionError::fail(location, Self::type_str()),
|
||||
impl<T: InertPayload> TryFromExpr for Inert<T> {
|
||||
fn from_expr(expr: Expr) -> ExternResult<Self> {
|
||||
let Expr { clause, location } = expr;
|
||||
match clause.try_unwrap() {
|
||||
Ok(Clause::Atom(at)) => at.try_downcast::<Self>().map_err(|a| {
|
||||
AssertionError::ext(location, T::TYPE_STR, format!("{a:?}"))
|
||||
}),
|
||||
Err(inst) => match &*inst.cls() {
|
||||
Clause::Atom(at) =>
|
||||
at.downcast_ref::<Self>().cloned().ok_or_else(|| {
|
||||
AssertionError::ext(location, T::TYPE_STR, format!("{inst}"))
|
||||
}),
|
||||
cls => AssertionError::fail(location, "atom", format!("{cls}")),
|
||||
},
|
||||
_ => AssertionError::fail(location, "atom"),
|
||||
Ok(cls) => AssertionError::fail(location, "atom", format!("{cls}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InertAtomic for bool {
|
||||
fn type_str() -> &'static str { "bool" }
|
||||
fn strict_eq(&self, other: &Self) -> bool { self == other }
|
||||
}
|
||||
|
||||
impl InertAtomic for usize {
|
||||
fn type_str() -> &'static str { "usize" }
|
||||
fn strict_eq(&self, other: &Self) -> bool { self == other }
|
||||
fn respond(&self, mut request: Request) {
|
||||
request.serve(Numeric::Uint(*self))
|
||||
impl<T: InertPayload + Display> Display for Inert<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl InertAtomic for NotNan<f64> {
|
||||
fn type_str() -> &'static str { "NotNan<f64>" }
|
||||
impl InertPayload for bool {
|
||||
const TYPE_STR: &'static str = "bool";
|
||||
fn strict_eq(&self, other: &Self) -> bool { self == other }
|
||||
fn respond(&self, mut request: Request) {
|
||||
request.serve(Numeric::Float(*self))
|
||||
request.serve_with(|| OrcString::from(self.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl InertPayload for usize {
|
||||
const TYPE_STR: &'static str = "usize";
|
||||
fn strict_eq(&self, other: &Self) -> bool { self == other }
|
||||
fn respond(&self, mut request: Request) {
|
||||
request.serve(Numeric::Uint(*self));
|
||||
request.serve_with(|| OrcString::from(self.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl InertPayload for NotNan<f64> {
|
||||
const TYPE_STR: &'static str = "NotNan<f64>";
|
||||
fn strict_eq(&self, other: &Self) -> bool { self == other }
|
||||
fn respond(&self, mut request: Request) {
|
||||
request.serve(Numeric::Float(*self));
|
||||
request.serve_with(|| OrcString::from(self.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,24 +2,12 @@
|
||||
//!
|
||||
//! Structures and traits used in the exposure of external functions and values
|
||||
//! to Orchid code
|
||||
mod atom;
|
||||
pub mod atom;
|
||||
pub mod cps_box;
|
||||
mod extern_fn;
|
||||
mod fn_bridge;
|
||||
mod inert;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use atom::{Atom, Atomic, AtomicResult, AtomicReturn, StrictEq};
|
||||
pub use extern_fn::{ExFn, ExternError, ExternFn};
|
||||
pub use fn_bridge::constructors::{
|
||||
xfn_1ary, xfn_2ary, xfn_3ary, xfn_4ary, xfn_5ary, xfn_6ary, xfn_7ary,
|
||||
xfn_8ary, xfn_9ary,
|
||||
};
|
||||
pub use fn_bridge::{Param, ToClause};
|
||||
pub use inert::InertAtomic;
|
||||
|
||||
pub use crate::representations::interpreted::Clause;
|
||||
|
||||
/// Return type of the argument to the [xfn_1ary] family of functions
|
||||
pub type XfnResult<T> = Result<T, Arc<dyn ExternError>>;
|
||||
pub mod error;
|
||||
pub mod fn_bridge;
|
||||
pub mod implementations;
|
||||
pub mod inert;
|
||||
pub mod process;
|
||||
pub mod to_clause;
|
||||
pub mod try_from_expr;
|
||||
|
||||
39
src/foreign/process.rs
Normal file
39
src/foreign/process.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::atom::{Atomic, AtomicReturn};
|
||||
use super::error::ExternResult;
|
||||
use super::to_clause::ToClause;
|
||||
use crate::interpreter::apply::CallData;
|
||||
use crate::interpreter::nort::{Clause, ClauseInst};
|
||||
use crate::interpreter::run::RunData;
|
||||
use crate::utils::ddispatch::Responder;
|
||||
|
||||
/// An atom that immediately decays to the result of the function when
|
||||
/// normalized. Can be used to build infinite recursive datastructures from
|
||||
/// Rust.
|
||||
#[derive(Clone)]
|
||||
pub struct Unstable<F>(F);
|
||||
impl<F: FnOnce(RunData) -> R + Send + 'static, R: ToClause> Unstable<F> {
|
||||
/// Wrap a function in an Unstable
|
||||
pub const fn new(f: F) -> Self { Self(f) }
|
||||
}
|
||||
impl<F> Responder for Unstable<F> {}
|
||||
impl<F> Debug for Unstable<F> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Unstable").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
impl<F: FnOnce(RunData) -> R + Send + 'static, R: ToClause> Atomic
|
||||
for Unstable<F>
|
||||
{
|
||||
fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> { self }
|
||||
fn as_any_ref(&self) -> &dyn std::any::Any { self }
|
||||
fn apply_ref(&self, _: CallData) -> ExternResult<Clause> {
|
||||
panic!("This atom decays instantly")
|
||||
}
|
||||
fn run(self: Box<Self>, run: RunData) -> super::atom::AtomicResult {
|
||||
let clause = self.0(run.clone()).to_clause(run.location.clone());
|
||||
AtomicReturn::run(clause, run)
|
||||
}
|
||||
fn redirect(&mut self) -> Option<&mut ClauseInst> { None }
|
||||
}
|
||||
43
src/foreign/to_clause.rs
Normal file
43
src/foreign/to_clause.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use super::atom::Atomic;
|
||||
use crate::interpreter::nort::{Clause, ClauseInst, Expr};
|
||||
use crate::location::CodeLocation;
|
||||
|
||||
/// A trait for things that are infallibly convertible to [ClauseInst]. These
|
||||
/// types can be returned by callbacks passed to the [super::xfn_1ary] family of
|
||||
/// functions.
|
||||
pub trait ToClause: Sized {
|
||||
/// Convert this value to a [Clause]. If your value can only be directly
|
||||
/// converted to a [ClauseInst], you can call `ClauseInst::to_clause` to
|
||||
/// unwrap it if possible or fall back to [Clause::Identity].
|
||||
fn to_clause(self, location: CodeLocation) -> Clause;
|
||||
|
||||
/// Convert the type to a [Clause].
|
||||
fn to_clsi(self, location: CodeLocation) -> ClauseInst {
|
||||
ClauseInst::new(self.to_clause(location))
|
||||
}
|
||||
|
||||
/// Convert to an expression via [ToClause].
|
||||
fn to_expr(self, location: CodeLocation) -> Expr {
|
||||
Expr { clause: self.to_clsi(location.clone()), location }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Atomic + Clone> ToClause for T {
|
||||
fn to_clause(self, _: CodeLocation) -> Clause { self.atom_cls() }
|
||||
}
|
||||
impl ToClause for Clause {
|
||||
fn to_clause(self, _: CodeLocation) -> Clause { self }
|
||||
}
|
||||
impl ToClause for ClauseInst {
|
||||
fn to_clause(self, _: CodeLocation) -> Clause {
|
||||
self.into_cls()
|
||||
}
|
||||
fn to_clsi(self, _: CodeLocation) -> ClauseInst { self }
|
||||
}
|
||||
impl ToClause for Expr {
|
||||
fn to_clause(self, location: CodeLocation) -> Clause {
|
||||
self.clause.to_clause(location)
|
||||
}
|
||||
fn to_clsi(self, _: CodeLocation) -> ClauseInst { self.clause }
|
||||
fn to_expr(self, _: CodeLocation) -> Expr { self }
|
||||
}
|
||||
28
src/foreign/try_from_expr.rs
Normal file
28
src/foreign/try_from_expr.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use super::error::ExternResult;
|
||||
use crate::interpreter::nort::{ClauseInst, Expr};
|
||||
use crate::location::CodeLocation;
|
||||
|
||||
/// Types automatically convertible from an [Expr]. Most notably, this is how
|
||||
/// foreign functions request automatic argument downcasting.
|
||||
pub trait TryFromExpr: Sized {
|
||||
/// Match and clone the value out of an [Expr]
|
||||
fn from_expr(expr: Expr) -> ExternResult<Self>;
|
||||
}
|
||||
|
||||
impl TryFromExpr for Expr {
|
||||
fn from_expr(expr: Expr) -> ExternResult<Self> { Ok(expr) }
|
||||
}
|
||||
|
||||
impl TryFromExpr for ClauseInst {
|
||||
fn from_expr(expr: Expr) -> ExternResult<Self> { Ok(expr.clause.clone()) }
|
||||
}
|
||||
|
||||
/// Request a value of a particular type and also return its location for
|
||||
/// further error reporting
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WithLoc<T>(pub CodeLocation, pub T);
|
||||
impl<T: TryFromExpr> TryFromExpr for WithLoc<T> {
|
||||
fn from_expr(expr: Expr) -> ExternResult<Self> {
|
||||
Ok(Self(expr.location(), T::from_expr(expr)?))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user