use std::any::Any; use std::fmt::{Display, Debug}; use std::hash::Hash; use std::rc::Rc; use dyn_clone::DynClone; use crate::interpreter::{RuntimeError, Context}; use crate::representations::Primitive; pub use crate::representations::interpreted::Clause; use crate::representations::interpreted::ExprInst; // Aliases for concise macros pub type RcError = Rc; pub type AtomicResult = Result<(Clause, Option), RuntimeError>; pub type XfnResult = Result<(Clause, Option), RcError>; pub type RcExpr = ExprInst; pub trait ExternError: Display { fn into_extern(self) -> Rc where Self: 'static + Sized { Rc::new(self) } } /// 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 { fn name(&self) -> &str; fn apply(&self, arg: ExprInst, ctx: Context) -> XfnResult; fn hash(&self, state: &mut dyn std::hash::Hasher) { state.write_str(self.name()) } fn to_xfn_cls(self) -> Clause where Self: Sized + 'static { Clause::P(Primitive::ExternFn(Box::new(self))) } } impl Eq for dyn ExternFn {} impl PartialEq for dyn ExternFn { fn eq(&self, other: &Self) -> bool { self.name() == other.name() } } impl Hash for dyn ExternFn { fn hash(&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()) } } pub trait Atomic: Any + Debug + DynClone where Self: 'static { fn as_any(&self) -> &dyn Any; fn run(&self, ctx: Context) -> AtomicResult; fn to_atom_cls(self) -> Clause where Self: Sized { Clause::P(Primitive::Atom(Atom(Box::new(self)))) } } /// Represents a black box unit of code with its own normalization steps. /// Typically [ExternFn] will produce an [Atom] when applied to a [Clause], /// this [Atom] will then forward `run_*` calls to the argument until it /// yields [InternalError::NonReducible] 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. pub struct Atom(pub Box); impl Atom { pub fn new(data: T) -> Self { Self(Box::new(data) as Box) } pub fn data(&self) -> &dyn Atomic { self.0.as_ref() as &dyn Atomic } pub fn try_cast(&self) -> Result<&T, ()> { self.data().as_any().downcast_ref().ok_or(()) } pub fn is(&self) -> bool { self.data().as_any().is::() } pub fn cast(&self) -> &T { self.data().as_any().downcast_ref() .expect("Type mismatch on Atom::cast") } } impl Clone for Atom { fn clone(&self) -> Self { Self(dyn_clone::clone_box(self.data())) } } impl Debug for Atom { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "##ATOM[{:?}]##", self.data()) } }