in midst of refactor
This commit is contained in:
97
orchidlang/src/foreign/cps_box.rs
Normal file
97
orchidlang/src/foreign/cps_box.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
//! Automated wrappers to make working with CPS commands easier.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use trait_set::trait_set;
|
||||
|
||||
use super::atom::{Atomic, AtomicResult, AtomicReturn, CallData, NotAFunction, RunData};
|
||||
use super::error::{RTError, RTResult};
|
||||
use crate::interpreter::nort::{Clause, Expr};
|
||||
use crate::location::CodeLocation;
|
||||
use crate::utils::ddispatch::{Request, Responder};
|
||||
use crate::utils::pure_seq::pushed_ref;
|
||||
|
||||
trait_set! {
|
||||
/// A "well behaved" type that can be used as payload in a CPS box
|
||||
pub trait CPSPayload = Clone + fmt::Debug + Send + 'static;
|
||||
/// A function to handle a CPS box with a specific payload
|
||||
pub trait CPSHandler<T: CPSPayload> = FnMut(&T, &Expr) -> RTResult<Expr>;
|
||||
}
|
||||
|
||||
/// 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<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, 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, 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, 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) -> RTResult<()> {
|
||||
match self.argc {
|
||||
0 => Err(NotAFunction(self.clone().atom_expr(err_loc.clone())).pack()),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: CPSPayload> Responder for CPSBox<T> {
|
||||
fn respond(&self, _request: Request) {}
|
||||
}
|
||||
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 type_name(&self) -> &'static str { std::any::type_name::<Self>() }
|
||||
fn parser_eq(&self, _: &dyn Atomic) -> bool { false }
|
||||
fn redirect(&mut self) -> Option<&mut Expr> { None }
|
||||
fn run(self: Box<Self>, _: RunData) -> AtomicResult { AtomicReturn::inert(*self) }
|
||||
fn apply(mut self: Box<Self>, call: CallData) -> RTResult<Clause> {
|
||||
self.assert_applicable(&call.location)?;
|
||||
self.argc -= 1;
|
||||
self.continuations.push(call.arg);
|
||||
Ok(self.atom_cls())
|
||||
}
|
||||
fn apply_mut(&mut self, call: CallData) -> RTResult<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())
|
||||
}
|
||||
}
|
||||
212
orchidlang/src/foreign/fn_bridge.rs
Normal file
212
orchidlang/src/foreign/fn_bridge.rs
Normal file
@@ -0,0 +1,212 @@
|
||||
//! Insert Rust functions in Orchid code almost seamlessly
|
||||
|
||||
use std::any::{Any, TypeId};
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use intern_all::{i, Tok};
|
||||
|
||||
use super::atom::{Atomic, AtomicResult, AtomicReturn, CallData, RunData};
|
||||
use super::error::RTResult;
|
||||
use super::to_clause::ToClause;
|
||||
use super::try_from_expr::TryFromExpr;
|
||||
use crate::interpreter::nort::{Clause, Expr};
|
||||
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 [super::fn_bridge::xfn].
|
||||
///
|
||||
/// Container for a unary [FnOnce] that uniquely states the argument and return
|
||||
/// 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 [Thunk], 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,
|
||||
name: Tok<String>,
|
||||
_t: PhantomData<T>,
|
||||
_u: PhantomData<U>,
|
||||
}
|
||||
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(name: Tok<String>, f: F) -> Self
|
||||
where F: FnOnce(T) -> U {
|
||||
Self { name, data: f, _t: PhantomData, _u: PhantomData }
|
||||
}
|
||||
/// Take out the function
|
||||
pub fn get(self) -> F { self.data }
|
||||
}
|
||||
impl<T, U, F: Clone> Clone for Param<T, U, F> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { name: self.name.clone(), data: self.data.clone(), _t: PhantomData, _u: PhantomData }
|
||||
}
|
||||
}
|
||||
impl<T, U, F> fmt::Display for Param<T, U, F> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.name) }
|
||||
}
|
||||
impl<T, U, F> fmt::Debug for Param<T, U, F> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("Param").field(&*self.name).finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// 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) -> RTResult<Self> { Ok(Thunk(expr)) }
|
||||
}
|
||||
|
||||
struct FnMiddleStage<T, U, F> {
|
||||
arg: Expr,
|
||||
f: Param<T, U, F>,
|
||||
}
|
||||
|
||||
impl<T, U, F: Clone> Clone for FnMiddleStage<T, U, F> {
|
||||
fn clone(&self) -> Self { Self { arg: self.arg.clone(), f: self.f.clone() } }
|
||||
}
|
||||
impl<T, U, F> fmt::Debug for FnMiddleStage<T, U, F> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "FnMiddleStage({} {})", self.f, self.arg)
|
||||
}
|
||||
}
|
||||
impl<T, U, F> Responder for FnMiddleStage<T, U, F> {}
|
||||
impl<
|
||||
T: 'static + TryFromExpr,
|
||||
U: 'static + ToClause,
|
||||
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 type_name(&self) -> &'static str { std::any::type_name::<Self>() }
|
||||
fn redirect(&mut self) -> Option<&mut Expr> {
|
||||
// this should be ctfe'd
|
||||
(TypeId::of::<T>() != TypeId::of::<Thunk>()).then_some(&mut self.arg)
|
||||
}
|
||||
fn run(self: Box<Self>, r: RunData) -> AtomicResult {
|
||||
let Self { arg, f: Param { data: f, .. } } = *self;
|
||||
Ok(AtomicReturn::Change(0, f(arg.downcast()?).to_clause(r.location)))
|
||||
}
|
||||
fn apply_mut(&mut self, _: CallData) -> RTResult<Clause> { panic!("Atom should have decayed") }
|
||||
}
|
||||
|
||||
impl<T, U, F> Responder for Param<T, U, F> {}
|
||||
|
||||
impl<
|
||||
T: 'static + TryFromExpr + Clone,
|
||||
U: 'static + ToClause,
|
||||
F: 'static + Clone + Send + FnOnce(T) -> U,
|
||||
> Atomic for Param<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 type_name(&self) -> &'static str { std::any::type_name::<Self>() }
|
||||
fn redirect(&mut self) -> Option<&mut Expr> { None }
|
||||
fn run(self: Box<Self>, _: RunData) -> AtomicResult { AtomicReturn::inert(*self) }
|
||||
fn apply_mut(&mut self, call: CallData) -> RTResult<Clause> {
|
||||
Ok(FnMiddleStage { arg: call.arg, f: self.clone() }.atom_cls())
|
||||
}
|
||||
fn apply(self: Box<Self>, call: CallData) -> RTResult<Clause> {
|
||||
Ok(FnMiddleStage { arg: call.arg, f: *self }.atom_cls())
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a Rust function to Orchid. If you can, register your Rust functions
|
||||
/// statically with functions in [crate::gen::tree].
|
||||
pub fn xfn<const N: usize, Argv, Ret>(
|
||||
name: &str,
|
||||
x: impl Xfn<N, Argv, Ret>,
|
||||
) -> impl Atomic + Clone {
|
||||
x.to_atomic(i(name))
|
||||
}
|
||||
|
||||
/// Trait for functions that can be directly passed to Orchid. Constraints in a
|
||||
/// nutshell:
|
||||
///
|
||||
/// - the function must live as long as ['static]
|
||||
/// - All arguments must implement [TryFromExpr]
|
||||
/// - all but the last argument must implement [Clone] and [Send]
|
||||
/// - the return type must implement [ToClause]
|
||||
///
|
||||
/// Take [Thunk] to consume the argument as-is, without normalization.
|
||||
pub trait Xfn<const N: usize, Argv, Ret>: Clone + Send + 'static {
|
||||
/// Convert Rust type to Orchid function, given a name for logging
|
||||
fn to_atomic(self, name: Tok<String>) -> impl Atomic + Clone;
|
||||
}
|
||||
|
||||
/// 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 xfn_impls {
|
||||
use intern_all::{i, Tok};
|
||||
|
||||
use super::super::atom::Atomic;
|
||||
use super::super::try_from_expr::TryFromExpr;
|
||||
#[allow(unused)] // for doc
|
||||
use super::Thunk;
|
||||
use super::{Param, ToClause, Xfn};
|
||||
|
||||
macro_rules! xfn_variant {
|
||||
(
|
||||
$number:expr,
|
||||
($($t:ident)*)
|
||||
($($alt:expr)*)
|
||||
) => {
|
||||
paste::paste!{
|
||||
impl<
|
||||
$( $t : TryFromExpr + Clone + Send + 'static, )*
|
||||
TLast: TryFromExpr + Clone + 'static,
|
||||
TReturn: ToClause + Send + 'static,
|
||||
TFunction: FnOnce( $( $t , )* TLast )
|
||||
-> TReturn + Clone + Send + 'static
|
||||
> Xfn<$number, ($($t,)* TLast,), TReturn> for TFunction {
|
||||
fn to_atomic(self, name: Tok<String>) -> impl Atomic + Clone {
|
||||
#[allow(unused_variables)]
|
||||
let argc = 0;
|
||||
let stage_n = name.clone();
|
||||
xfn_variant!(@BODY_LOOP self name stage_n argc
|
||||
( $( ( $t [< $t:lower >] ) )* )
|
||||
( $( [< $t:lower >] )* )
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
(@BODY_LOOP $function:ident $name:ident $stage_n:ident $argc:ident (
|
||||
( $Next:ident $next:ident )
|
||||
$( ( $T:ident $t:ident ) )*
|
||||
) $full:tt) => {{
|
||||
Param::new($stage_n, move |$next : $Next| {
|
||||
let $argc = $argc + 1;
|
||||
let $stage_n = i(&format!("{}/{}", $name, $argc));
|
||||
xfn_variant!(@BODY_LOOP $function $name $stage_n $argc ( $( ( $T $t ) )* ) $full)
|
||||
})
|
||||
}};
|
||||
(@BODY_LOOP $function:ident $name:ident $stage_n:ident $argc:ident (
|
||||
|
||||
) ( $( $t:ident )* )) => {{
|
||||
Param::new($stage_n, |last: TLast| $function ( $( $t , )* last ))
|
||||
}};
|
||||
}
|
||||
|
||||
xfn_variant!(1, () (2 3 4 5 6 7 8 9 10 11 12 13 14 15 16));
|
||||
xfn_variant!(2, (A) (1 3 4 5 6 7 8 9 10 11 12 13 14 15 16));
|
||||
xfn_variant!(3, (A B) (1 2 4 5 6 7 8 9 10 11 12 13 14 15 16));
|
||||
xfn_variant!(4, (A B C) (1 2 3 5 6 7 8 9 10 11 12 13 14 15 16));
|
||||
xfn_variant!(5, (A B C D) (1 2 3 4 6 7 8 9 10 11 12 13 14 15 16));
|
||||
xfn_variant!(6, (A B C D E) (1 2 3 4 5 7 8 9 10 11 12 13 14 15 16));
|
||||
xfn_variant!(7, (A B C D E F) (1 2 3 4 5 6 8 9 10 11 12 13 14 15 16));
|
||||
xfn_variant!(8, (A B C D E F G) (1 2 3 4 5 6 7 9 10 11 12 13 14 15 16));
|
||||
xfn_variant!(9, (A B C D E F G H) (1 2 3 4 5 6 7 8 10 11 12 13 14 15 16));
|
||||
// at higher arities rust-analyzer fails to load the project
|
||||
}
|
||||
129
orchidlang/src/foreign/inert.rs
Normal file
129
orchidlang/src/foreign/inert.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
//! An [Atomic] that wraps inert data.
|
||||
|
||||
use std::any::Any;
|
||||
use std::fmt;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use ordered_float::NotNan;
|
||||
|
||||
use super::atom::{Atom, Atomic, AtomicResult, AtomicReturn, CallData, NotAFunction, RunData};
|
||||
use super::error::{RTError, RTResult};
|
||||
use super::try_from_expr::TryFromExpr;
|
||||
use crate::foreign::error::AssertionError;
|
||||
use crate::interpreter::nort::{Clause, Expr};
|
||||
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 Rust functions it also automatically implements [TryFromExpr]
|
||||
/// so that `Inert<MyType>` arguments can be parsed directly.
|
||||
pub trait InertPayload: fmt::Debug + Clone + Send + 'static {
|
||||
/// Typename to be shown in the error when a conversion from [Expr] fails
|
||||
///
|
||||
/// 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
|
||||
fn respond(&self, mut request: Request) {}
|
||||
/// Equality comparison used by the pattern matcher. Since the pattern matcher
|
||||
/// only works with parsed code, you only need to implement this if your type
|
||||
/// is directly parseable.
|
||||
///
|
||||
/// If your type implements [PartialEq], this can simply be implemented as
|
||||
/// ```ignore
|
||||
/// fn strict_eq(&self, other: &Self) -> bool { self == other }
|
||||
/// ```
|
||||
#[allow(unused_variables)]
|
||||
fn strict_eq(&self, other: &Self) -> bool { false }
|
||||
}
|
||||
|
||||
/// 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: 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.0.clone()) } else { self.0.respond(request) }
|
||||
}
|
||||
}
|
||||
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 type_name(&self) -> &'static str { std::any::type_name::<Self>() }
|
||||
|
||||
fn redirect(&mut self) -> Option<&mut Expr> { None }
|
||||
fn run(self: Box<Self>, _: RunData) -> AtomicResult { AtomicReturn::inert(*self) }
|
||||
fn apply_mut(&mut self, call: CallData) -> RTResult<Clause> {
|
||||
Err(NotAFunction(self.clone().atom_expr(call.location)).pack())
|
||||
}
|
||||
fn parser_eq(&self, other: &dyn Atomic) -> bool {
|
||||
other.as_any_ref().downcast_ref::<Self>().map_or(false, |other| self.0.strict_eq(&other.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: InertPayload> TryFromExpr for Inert<T> {
|
||||
fn from_expr(expr: Expr) -> RTResult<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_mut() {
|
||||
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}")),
|
||||
},
|
||||
Ok(cls) => AssertionError::fail(location, "atom", format!("{cls}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: InertPayload + fmt::Display> fmt::Display for Inert<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) }
|
||||
}
|
||||
|
||||
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_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()))
|
||||
}
|
||||
}
|
||||
12
orchidlang/src/foreign/mod.rs
Normal file
12
orchidlang/src/foreign/mod.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
//! Interaction with foreign code
|
||||
//!
|
||||
//! Structures and traits used in the exposure of external functions and values
|
||||
//! to Orchid code
|
||||
pub mod atom;
|
||||
pub mod cps_box;
|
||||
pub mod error;
|
||||
pub mod fn_bridge;
|
||||
pub mod inert;
|
||||
pub mod process;
|
||||
pub mod to_clause;
|
||||
pub mod try_from_expr;
|
||||
40
orchidlang/src/foreign/process.rs
Normal file
40
orchidlang/src/foreign/process.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
//! An [Atomic] implementor that runs a callback and turns into the return
|
||||
//! value. Useful to generate expressions that depend on values in the
|
||||
//! interpreter's context, and to defer the generation of expensive
|
||||
//! subexpressions
|
||||
use std::fmt;
|
||||
|
||||
use super::atom::{Atomic, AtomicResult, AtomicReturn, CallData, RunData};
|
||||
use super::error::RTResult;
|
||||
use super::to_clause::ToClause;
|
||||
use crate::interpreter::nort::{Clause, Expr};
|
||||
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> fmt::Debug for Unstable<F> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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 type_name(&self) -> &'static str { std::any::type_name::<Self>() }
|
||||
|
||||
fn apply_mut(&mut self, _: CallData) -> RTResult<Clause> { panic!("This atom decays instantly") }
|
||||
fn run(self: Box<Self>, run: RunData) -> AtomicResult {
|
||||
let loc = run.location.clone();
|
||||
let clause = self.0(run).to_clause(loc);
|
||||
Ok(AtomicReturn::Change(0, clause))
|
||||
}
|
||||
fn redirect(&mut self) -> Option<&mut Expr> { None }
|
||||
}
|
||||
202
orchidlang/src/foreign/to_clause.rs
Normal file
202
orchidlang/src/foreign/to_clause.rs
Normal file
@@ -0,0 +1,202 @@
|
||||
//! Conversions from Rust values to Orchid expressions. Many APIs and
|
||||
//! [super::fn_bridge] in particular use this to automatically convert values on
|
||||
//! the boundary. The opposite conversion is [super::try_from_expr::TryFromExpr]
|
||||
|
||||
use super::atom::{Atomic, RunData};
|
||||
use super::process::Unstable;
|
||||
use crate::gen::tpl;
|
||||
use crate::gen::traits::Gen;
|
||||
use crate::interpreter::gen_nort::nort_gen;
|
||||
use crate::interpreter::nort::{Clause, ClauseInst, Expr};
|
||||
use crate::location::CodeLocation;
|
||||
use crate::utils::clonable_iter::Clonable;
|
||||
|
||||
/// A trait for things that are infallibly convertible to [ClauseInst]. These
|
||||
/// types can be returned by callbacks passed to [super::fn_bridge::xfn].
|
||||
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 }
|
||||
}
|
||||
|
||||
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::list::end").template(ctx, []),
|
||||
Some(val) => {
|
||||
let atom = Unstable::new(|run| self.to_clause(run.location));
|
||||
tpl::a2(tpl::C("std::list::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()))))
|
||||
})
|
||||
}
|
||||
|
||||
mod implementations {
|
||||
use std::any::Any;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{list, ToClause};
|
||||
use crate::foreign::atom::{Atom, Atomic, AtomicResult, CallData, RunData};
|
||||
use crate::foreign::error::{AssertionError, RTErrorObj, RTResult};
|
||||
use crate::foreign::inert::Inert;
|
||||
use crate::foreign::try_from_expr::TryFromExpr;
|
||||
use crate::gen::tpl;
|
||||
use crate::gen::traits::Gen;
|
||||
use crate::interpreter::gen_nort::nort_gen;
|
||||
use crate::interpreter::nort::{Clause, Expr};
|
||||
use crate::libs::std::tuple::Tuple;
|
||||
use crate::location::CodeLocation;
|
||||
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(RTErrorObj);
|
||||
impl Responder for PendingError {}
|
||||
impl fmt::Debug for PendingError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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 type_name(&self) -> &'static str { std::any::type_name::<Self>() }
|
||||
|
||||
fn redirect(&mut self) -> Option<&mut Expr> { None }
|
||||
fn run(self: Box<Self>, _: RunData) -> AtomicResult { Err(self.0) }
|
||||
fn apply_mut(&mut self, _: CallData) -> RTResult<Clause> {
|
||||
panic!("This atom decays instantly")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ToClause> ToClause for RTResult<T> {
|
||||
fn to_clause(self, location: CodeLocation) -> Clause {
|
||||
match self {
|
||||
Err(e) => PendingError(e).atom_cls(),
|
||||
Ok(t) => t.to_clause(location),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) }
|
||||
}
|
||||
|
||||
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) -> RTResult<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));
|
||||
}
|
||||
Reference in New Issue
Block a user