//! Adaptor trait to embed Rust values in Orchid expressions use std::any::Any; use std::fmt; use std::sync::{Arc, Mutex}; use never::Never; use super::error::{RTError, RTResult}; use crate::interpreter::context::{RunEnv, RunParams}; use crate::interpreter::nort; use crate::location::{CodeLocation, SourceRange}; use crate::name::NameLike; use crate::parse::lexer::Lexeme; use crate::parse::parsed; use crate::utils::ddispatch::{request, Request, Responder}; /// Information returned by [Atomic::run]. pub enum AtomicReturn { /// No work was done. If the atom takes an argument, it can be provided now Inert(Atom), /// Work was done, returns new clause and consumed gas. 1 gas is already /// consumed by the virtual call, so nonzero values indicate expensive /// operations. Change(usize, nort::Clause), } impl AtomicReturn { /// Report indicating that the value is inert. The result here is always [Ok], /// it's meant to match the return type of [Atomic::run] #[allow(clippy::unnecessary_wraps)] pub fn inert(this: T) -> Result { Ok(Self::Inert(Atom::new(this))) } } /// Returned by [Atomic::run] pub type AtomicResult = RTResult; /// General error produced when a non-function [Atom] is applied to something as /// a function. #[derive(Clone)] pub struct NotAFunction(pub nort::Expr); impl RTError for NotAFunction {} impl fmt::Display for NotAFunction { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?} is not a function", self.0) } } /// Information about a function call presented to an external function pub struct CallData<'a, 'b> { /// Location of the function expression pub location: CodeLocation, /// The argument the function was called on. Functions are curried pub arg: nort::Expr, /// Globally available information such as the list of all constants pub env: &'a RunEnv<'b>, /// Resource limits and other details specific to this interpreter run pub params: &'a mut RunParams, } /// Information about a normalization run presented to an atom pub struct RunData<'a, 'b> { /// Location of the atom pub location: CodeLocation, /// Globally available information such as the list of all constants pub env: &'a RunEnv<'b>, /// Resource limits and other details specific to this interpreter run pub params: &'a mut RunParams, } /// Functionality the interpreter needs to handle a value /// /// # Lifecycle methods /// /// Atomics expose the methods [Atomic::redirect], [Atomic::run], /// [Atomic::apply] and [Atomic::apply_mut] 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_mut` is called depending /// upon whether the atom is referenced elsewhere. `apply` falls back to /// `apply_mut` 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 + fmt::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. /// /// This function should be implemented in exactly one way: /// /// ```ignore /// fn as_any(self: Box) -> Box { self } /// ``` #[must_use] fn as_any(self: Box) -> Box; /// See [Atomic::as_any], exactly the same but for references #[must_use] fn as_any_ref(&self) -> &dyn Any; /// Print the atom's type name. Should only ever be implemented as /// /// ```ignore /// fn type_name(&self) -> &'static str { std::any::type_name::() } /// ``` fn type_name(&self) -> &'static str; /// 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::Expr>; /// 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 next. /// /// For an overview of the lifecycle see [Atomic] fn run(self: Box, run: RunData) -> AtomicResult; /// Combine the function with an argument to produce a new clause. Falls back /// to [Atomic::apply_mut] by default. /// /// For an overview of the lifecycle see [Atomic] fn apply(mut self: Box, call: CallData) -> RTResult { self.apply_mut(call) } /// Combine the function with an argument to produce a new clause /// /// For an overview of the lifecycle see [Atomic] fn apply_mut(&mut self, call: CallData) -> RTResult; /// 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 Atomic) -> bool { false } /// Wrap the atom in a clause to be placed in an [AtomicResult]. #[must_use] 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().into_inst() } /// Wrap the atom in a new expression instance to be placed in a tree #[must_use] fn atom_expr(self, location: CodeLocation) -> nort::Expr where Self: Sized { self.atom_clsi().into_expr(location) } /// Wrap the atom in a clause to be placed in a /// [crate::parse::parsed::SourceLine]. #[must_use] 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 /// [crate::parse::parsed::SourceLine]. #[must_use] fn ast_exp(self, range: SourceRange) -> parsed::Expr where Self: Sized + Clone { self.ast_cls().into_expr(range) } /// Wrap this atomic value in a lexeme. This means that the atom will /// participate in macro reproject, so it must implement [Atomic::parser_eq]. fn lexeme(self) -> Lexeme where Self: Sized + Clone { Lexeme::Atom(AtomGenerator::cloner(self)) } } /// 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 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 fmt::Debug for AtomGenerator { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self.run()) } } impl PartialEq for AtomGenerator { fn eq(&self, other: &Self) -> bool { self.run().0.parser_eq(&*other.run().0) } } /// Represents a black box unit of data with its own normalization steps. /// Typically Rust functions integrated with [super::fn_bridge::xfn] will /// produce and consume [Atom]s to represent both raw data, pending /// computational tasks, and curried partial calls awaiting their next argument. pub struct Atom(pub Box); impl Atom { /// Wrap an [Atomic] in a type-erased box #[must_use] pub fn new(data: T) -> Self { Self(Box::new(data) as Box) } /// Get the contained data #[must_use] pub fn data(&self) -> &dyn Atomic { self.0.as_ref() as &dyn Atomic } /// Test the type of the contained data without downcasting #[must_use] pub fn is(&self) -> bool { self.data().as_any_ref().is::() } /// Downcast contained data, panic if it isn't the specified type #[must_use] pub fn downcast(self) -> T { *self.0.as_any().downcast().expect("Type mismatch on Atom::cast") } /// Normalize the contained data pub fn run(self, run: RunData<'_, '_>) -> AtomicResult { self.0.run(run) } /// Request a delegate from the encapsulated data pub fn request(&self) -> Option { 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(self) -> Result { match self.0.as_any_ref().is::() { true => Ok(*self.0.as_any().downcast().expect("checked just above")), false => Err(self), } } /// Downcast an atom by reference pub fn downcast_ref(&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) -> RTResult { self.0.apply(call) } /// Combine the function with an argument to produce a new clause pub fn apply_mut(&mut self, call: CallData) -> RTResult { self.0.apply_mut(call) } } impl fmt::Debug for Atom { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self.data()) } } impl Responder for Never { fn respond(&self, _request: Request) { match *self {} } } impl Atomic for Never { fn as_any(self: Box) -> Box { match *self {} } fn as_any_ref(&self) -> &dyn Any { match *self {} } fn type_name(&self) -> &'static str { match *self {} } fn redirect(&mut self) -> Option<&mut nort::Expr> { match *self {} } fn run(self: Box, _: RunData) -> AtomicResult { match *self {} } fn apply_mut(&mut self, _: CallData) -> RTResult { match *self {} } }