September-october commit

- manual parser
- stl refinements
- all language constructs are now Send
This commit is contained in:
2023-10-11 18:27:50 +01:00
parent 56679dcc01
commit 86e520e8b8
127 changed files with 1666 additions and 1872 deletions

View File

@@ -1,13 +1,16 @@
use std::any::Any;
use std::fmt::Debug;
use std::rc::Rc;
use dyn_clone::DynClone;
use crate::interpreted::ExprInst;
use super::ExternError;
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::Primitive;
/// Information returned by [Atomic::run]. This mirrors
/// [crate::interpreter::Return] but with a clause instead of an Expr.
@@ -24,8 +27,18 @@ pub struct AtomicReturn {
/// Returned by [Atomic::run]
pub type AtomicResult = Result<AtomicReturn, RuntimeError>;
/// 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;
}
/// Functionality the interpreter needs to handle a value
pub trait Atomic: Any + Debug + DynClone + Responder
pub trait Atomic:
Any + Debug + DynClone + StrictEq + Responder + Send
where
Self: 'static,
{
@@ -54,7 +67,7 @@ where
where
Self: Sized,
{
Clause::P(Primitive::Atom(Atom(Box::new(self))))
Clause::Atom(Atom(Box::new(self)))
}
/// Wrap the atom in a new expression instance to be placed in a tree
@@ -73,7 +86,7 @@ where
/// 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
/// to pass control back to the interpreter.
pub struct Atom(pub Box<dyn Atomic>);
impl Atom {
/// Wrap an [Atomic] in a type-erased box
@@ -84,23 +97,26 @@ impl Atom {
/// Get the contained data
#[must_use]
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) -> Result<T, Self> {
/// Test the type of the contained data without downcasting
#[must_use]
pub fn is<T: Atomic>(&self) -> bool { self.data().as_any_ref().is::<T>() }
/// Downcast contained data, panic if it isn't the specified type
#[must_use]
pub fn downcast<T: Atomic>(self) -> T {
*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) }
/// 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
/// if it is not the specified type
pub fn try_downcast<T: Atomic>(self) -> Result<T, Self> {
match self.0.as_any_ref().is::<T>() {
true => Ok(*self.0.as_any().downcast().expect("checked just above")),
false => Err(self),
}
}
/// Test the type of the contained data without downcasting
#[must_use]
pub fn is<T: 'static>(&self) -> bool { self.data().as_any_ref().is::<T>() }
/// Downcast contained data, panic if it isn't the specified type
#[must_use]
pub fn cast<T: 'static>(self) -> T {
*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) }
}
impl Clone for Atom {
@@ -109,6 +125,16 @@ impl Clone for Atom {
impl Debug for Atom {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "##ATOM[{:?}]##", self.data())
write!(f, "{:?}", self.data())
}
}
impl TryFromExprInst for Atom {
fn from_exi(exi: ExprInst) -> Result<Self, Rc<dyn ExternError>> {
let loc = exi.location();
match exi.expr_val().clause {
Clause::Atom(a) => Ok(a),
_ => AssertionError::fail(loc, "atom"),
}
}
}

View File

@@ -7,12 +7,12 @@ use trait_set::trait_set;
use super::{Atomic, ExternFn, InertAtomic, XfnResult};
use crate::interpreted::{Clause, ExprInst};
use crate::interpreter::{Context, HandlerRes};
use crate::utils::pure_push::pushed_ref;
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 + 'static;
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;
}

View File

@@ -3,13 +3,12 @@ use std::fmt::{Debug, Display};
use std::hash::Hash;
use std::rc::Rc;
use dyn_clone::DynClone;
use dyn_clone::{DynClone, clone_box};
use super::XfnResult;
use crate::interpreted::ExprInst;
use crate::interpreter::Context;
use crate::representations::interpreted::Clause;
use crate::Primitive;
/// Errors produced by external code
pub trait ExternError: Display {
@@ -34,7 +33,7 @@ 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 {
pub trait ExternFn: DynClone + Send {
/// Display name of the function
#[must_use]
fn name(&self) -> &str;
@@ -50,7 +49,7 @@ pub trait ExternFn: DynClone {
where
Self: Sized + 'static,
{
Clause::P(Primitive::ExternFn(Box::new(self)))
Clause::ExternFn(ExFn(Box::new(self)))
}
}
@@ -68,3 +67,21 @@ impl Debug for dyn ExternFn {
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()))
}
}

View File

@@ -2,6 +2,7 @@ use std::fmt::Debug;
use std::marker::PhantomData;
use std::rc::Rc;
use super::atom::StrictEq;
use super::{
Atomic, AtomicResult, AtomicReturn, ExternError, ExternFn, XfnResult,
};
@@ -9,7 +10,7 @@ use crate::ddispatch::Responder;
use crate::interpreted::{Clause, ExprInst, TryFromExprInst};
use crate::interpreter::{run, Context, Return};
use crate::systems::codegen::{opt, res};
use crate::{Literal, OrcString};
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
@@ -17,6 +18,8 @@ use crate::{Literal, OrcString};
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 {
@@ -28,14 +31,8 @@ impl ToClause for Clause {
impl ToClause for ExprInst {
fn to_clause(self) -> Clause { self.expr_val().clause }
}
impl ToClause for Literal {
fn to_clause(self) -> Clause { self.into() }
}
impl ToClause for u64 {
fn to_clause(self) -> Clause { Literal::Uint(self).into() }
}
impl ToClause for String {
fn to_clause(self) -> Clause { OrcString::from(self).cls() }
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())) }
@@ -59,31 +56,28 @@ pub struct Param<T, U, F> {
_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(f: F) -> Self
where
F: FnOnce(T) -> Result<U, Rc<dyn ExternError>>,
{
Self { data: f, _t: PhantomData::default(), _u: PhantomData::default() }
Self { 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 {
data: self.data.clone(),
_t: PhantomData::default(),
_u: PhantomData::default(),
}
Self { data: self.data.clone(), _t: PhantomData, _u: PhantomData }
}
}
impl<
T: 'static + TryFromExprInst,
U: 'static + ToClause,
F: 'static + Clone + FnOnce(T) -> Result<U, Rc<dyn ExternError>>,
F: 'static + Clone + Send + FnOnce(T) -> Result<U, Rc<dyn ExternError>>,
> ToClause for Param<T, U, F>
{
fn to_clause(self) -> Clause { self.xfn_cls() }
@@ -93,6 +87,11 @@ struct FnMiddleStage<T, U, F> {
argument: ExprInst,
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 {
@@ -110,7 +109,7 @@ impl<T, U, F> Responder for FnMiddleStage<T, U, F> {}
impl<
T: 'static + TryFromExprInst,
U: 'static + ToClause,
F: 'static + Clone + FnOnce(T) -> Result<U, Rc<dyn ExternError>>,
F: 'static + Clone + FnOnce(T) -> Result<U, Rc<dyn ExternError>> + Send,
> Atomic for FnMiddleStage<T, U, F>
{
fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> { self }
@@ -128,7 +127,7 @@ impl<
impl<
T: 'static + TryFromExprInst,
U: 'static + ToClause,
F: 'static + Clone + FnOnce(T) -> Result<U, Rc<dyn ExternError>>,
F: 'static + Clone + Send + FnOnce(T) -> Result<U, Rc<dyn ExternError>>,
> ExternFn for Param<T, U, F>
{
fn name(&self) -> &str { "anonymous Rust function" }
@@ -155,16 +154,16 @@ pub mod constructors {
" Orchid function. See also Constraints summarized:\n\n"
"- the callback must live as long as `'static`\n"
"- All arguments must implement [TryFromExprInst]\n"
"- all but the last argument must implement [Clone]\n"
"- all but the last argument must implement [Clone] and [Send]\n"
"- the return type must implement [ToClause].\n\n"
]
#[doc = "Other arities: " $( "[xfn_" $alt "ary], " )+ ]
pub fn [< xfn_ $number ary >] <
$( $t : TryFromExprInst + Clone + 'static, )*
$( $t : TryFromExprInst + Clone + Send + 'static, )*
TLast: TryFromExprInst + 'static,
TReturn: ToClause + 'static,
TReturn: ToClause + Send + 'static,
TFunction: FnOnce( $( $t , )* TLast )
-> Result<TReturn, Rc<dyn ExternError>> + Clone + 'static
-> Result<TReturn, Rc<dyn ExternError>> + Clone + Send + 'static
>(function: TFunction) -> impl ExternFn {
xfn_variant!(@BODY_LOOP function
( $( ( $t [< $t:lower >] ) )* )

View File

@@ -2,22 +2,25 @@ use std::any::Any;
use std::fmt::Debug;
use std::rc::Rc;
use ordered_float::NotNan;
use super::atom::StrictEq;
use super::{AtomicResult, AtomicReturn, ExternError};
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::AssertionError;
use crate::systems::stl::Numeric;
use crate::utils::ddispatch::{Request, Responder};
use crate::Primitive;
/// 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
/// provided in argument lists.
pub trait InertAtomic: Debug + Clone + 'static {
pub trait InertAtomic: Debug + Clone + Send + 'static {
/// Typename to be shown in the error when a conversion from [ExprInst] fails
#[must_use]
fn type_str() -> &'static str;
@@ -25,9 +28,29 @@ pub trait InertAtomic: Debug + Clone + 'static {
/// 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 }
/// ```
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))
}
}
impl<T: InertAtomic> Responder for T {
fn respond(&self, request: Request) { self.respond(request) }
fn respond(&self, mut request: Request) {
if request.can_serve::<T>() {
request.serve(self.clone())
} else {
self.respond(request)
}
}
}
impl<T: InertAtomic> Atomic for T {
fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
@@ -42,7 +65,7 @@ impl<T: InertAtomic> TryFromExprInst for T {
fn from_exi(exi: ExprInst) -> Result<Self, Rc<dyn ExternError>> {
let Expr { clause, location } = exi.expr_val();
match clause {
Clause::P(Primitive::Atom(a)) => match a.0.as_any().downcast() {
Clause::Atom(a) => match a.0.as_any().downcast() {
Ok(t) => Ok(*t),
Err(_) => AssertionError::fail(location, Self::type_str()),
},
@@ -50,3 +73,24 @@ impl<T: InertAtomic> TryFromExprInst for T {
}
}
}
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 InertAtomic for NotNan<f64> {
fn 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))
}
}

View File

@@ -10,8 +10,8 @@ mod inert;
use std::rc::Rc;
pub use atom::{Atom, Atomic, AtomicResult, AtomicReturn};
pub use extern_fn::{ExternError, ExternFn};
pub use atom::{Atom, Atomic, AtomicResult, AtomicReturn, StrictEq};
pub use extern_fn::{ExternError, ExternFn, ExFn};
pub use fn_bridge::constructors::{
xfn_1ary, xfn_2ary, xfn_3ary, xfn_4ary, xfn_5ary, xfn_6ary, xfn_7ary,
xfn_8ary, xfn_9ary,