diff --git a/src/foreign_macros/atomic_impl.rs b/src/foreign_macros/atomic_impl.rs index bb6a7c7..d77196d 100644 --- a/src/foreign_macros/atomic_impl.rs +++ b/src/foreign_macros/atomic_impl.rs @@ -65,7 +65,7 @@ use crate::Primitive; #[macro_export] macro_rules! atomic_impl { ($typ:ident) => { - atomic_impl! {$typ, |this: &Self, _: $crate::interpreter::Context| { + $crate::atomic_impl! {$typ, |this: &Self, _: $crate::interpreter::Context| { use $crate::foreign::ExternFn; Ok(this.clone().to_xfn_cls()) }} diff --git a/src/foreign_macros/externfn_impl.rs b/src/foreign_macros/externfn_impl.rs index 81c2b9b..49165f6 100644 --- a/src/foreign_macros/externfn_impl.rs +++ b/src/foreign_macros/externfn_impl.rs @@ -19,7 +19,7 @@ use crate::{atomic_impl, atomic_redirect}; /// reference to self and a closure. This can be used in conjunction with /// [atomic_impl] and [atomic_redirect] to normalize the argument automatically /// before using it. -/// +/// /// See [atomic_impl] for an example. #[macro_export] macro_rules! externfn_impl { diff --git a/src/foreign_macros/mod.rs b/src/foreign_macros/mod.rs index fb8c37e..8355c6e 100644 --- a/src/foreign_macros/mod.rs +++ b/src/foreign_macros/mod.rs @@ -3,3 +3,4 @@ mod atomic_impl; mod atomic_inert; mod atomic_redirect; mod externfn_impl; +mod write_fn_step; diff --git a/src/foreign_macros/write_fn_step.rs b/src/foreign_macros/write_fn_step.rs new file mode 100644 index 0000000..1f6e1e7 --- /dev/null +++ b/src/foreign_macros/write_fn_step.rs @@ -0,0 +1,125 @@ +#[allow(unused)] // for doc +use crate::foreign::ExternFn; +#[allow(unused)] // for doc +use crate::interpreted::ExprInst; + +/// Write one step in the state machine representing a simple n-ary non-variadic +/// Orchid function. +/// +/// There are three ways to call this macro for the initial state, internal +/// state, and exit state. All of them are demonstrated in one example and +/// discussed below. +/// +/// ``` +/// use orchidlang::{write_fn_step, Literal, Primitive}; +/// use orchidlang::interpreted::Clause; +/// use orchidlang::stl::litconv::{with_str, with_uint}; +/// use orchidlang::stl::RuntimeError; +/// +/// // Initial state +/// write_fn_step!(pub CharAt2 > CharAt1); +/// // Middle state +/// write_fn_step!( +/// CharAt1 {} +/// CharAt0 where s = |x| with_str(x, |s| Ok(s.clone())) +/// ); +/// // Exit state +/// write_fn_step!( +/// CharAt0 { s: String } +/// i = |x| with_uint(x, Ok) +/// => { +/// if let Some(c) = s.chars().nth(i as usize) { +/// Ok(Clause::P(Primitive::Literal(Literal::Char(c)))) +/// } else { +/// RuntimeError::fail( +/// "Character index out of bounds".to_string(), +/// "indexing string", +/// )? +/// } +/// } +/// ); +/// ``` +/// +/// The initial state simply defines an empty marker struct and implements +/// [ExternFn] on it, transitioning into a new struct which is assumed to have a +/// single field called `expr_inst` of type [ExprInst]. +/// +/// The middle state defines a sequence of arguments with types similarly to a +/// struct definition. A field called `expr_inst` of type [ExprInst] is added +/// implicitly, so the first middle state has an empty field list. The next +/// state is also provided, alongside the name and conversion function of the +/// next parameter which is [FnOnce(&ExprInst) -> Result<_, RuntimeError>]. The +/// success type is inferred from the type of the field at the place of its +/// actual definition. This conversion is done in the implementation of +/// [ExternFn] which also places the new [ExprInst] into `expr_inst` on the next +/// state. +/// +/// The final state defines the sequence of all arguments except for the last +/// one with the same syntax used by the middle state, and the name and +/// conversion lambda of the final argument without specifying the type - it is +/// to be inferred. This state also specifies the operation that gets executed +/// when all the arguments are collected. Uniquely, this "function body" isn't +/// specified as a lambda but rather as an expression invoked with all the +/// argument names bound. The arguments here are all references to their actual +/// types except for the last one which is converted from [ExprInst] immediately +/// before the body is evaluated. +#[macro_export] +macro_rules! write_fn_step { + ($quant:vis $name:ident > $next:ident) => { + #[derive(Clone)] + $quant struct $name; + $crate::externfn_impl!{ + $name, + |_: &Self, expr_inst: $crate::interpreted::ExprInst| { + Ok($next{ expr_inst }) + } + } + }; + ( + $quant:vis $name:ident { + $( $arg:ident : $typ:ty ),* + } + $next:ident where $added:ident = $extract:expr + ) => { + #[derive(std::fmt::Debug, Clone)] + $quant struct $name { + $( $arg: $typ, )* + expr_inst: $crate::interpreted::ExprInst, + } + $crate::atomic_redirect!($name, expr_inst); + $crate::atomic_impl!($name); + $crate::externfn_impl!( + $name, + |this: &Self, expr_inst: $crate::interpreted::ExprInst| { + let lambda = $extract; + Ok($next{ + $( $arg: this.$arg.clone(), )* + $added: lambda(&this.expr_inst)?, + expr_inst + }) + } + ); + }; + ( + $quant:vis $name:ident { + $( $arg:ident: $typ:ty ),* + } + $added:ident = $extract:expr + => $process:expr + ) => { + #[derive(std::fmt::Debug, Clone)] + $quant struct $name { + $( $arg: $typ, )+ + expr_inst: $crate::interpreted::ExprInst, + } + $crate::atomic_redirect!($name, expr_inst); + $crate::atomic_impl!( + $name, + |Self{ $($arg, )* expr_inst }: &Self, _| { + let lambda = $extract; + let $added = lambda(expr_inst)?; + $process + } + ); + }; +} diff --git a/src/stl/assertion_error.rs b/src/stl/assertion_error.rs index efad123..6046a22 100644 --- a/src/stl/assertion_error.rs +++ b/src/stl/assertion_error.rs @@ -8,11 +8,13 @@ use crate::representations::interpreted::ExprInst; /// hold. #[derive(Clone)] pub struct AssertionError { - pub value: ExprInst, - pub assertion: &'static str, + value: ExprInst, + assertion: &'static str, } impl AssertionError { + /// Construct, upcast and wrap in a Result that never succeeds for easy + /// short-circuiting pub fn fail( value: ExprInst, assertion: &'static str, @@ -20,6 +22,7 @@ impl AssertionError { return Err(Self { value, assertion }.into_extern()); } + /// Construct and upcast to [ExternError] pub fn ext(value: ExprInst, assertion: &'static str) -> Rc { return Self { value, assertion }.into_extern(); } diff --git a/src/stl/litconv.rs b/src/stl/litconv.rs index 37ec1e0..6cc185e 100644 --- a/src/stl/litconv.rs +++ b/src/stl/litconv.rs @@ -1,3 +1,5 @@ +//! Utility functions that operate on literals. Because of the parallel locked +//! nature of [ExprInst], returning a reference to [Literal] is not possible. use std::rc::Rc; use super::assertion_error::AssertionError; diff --git a/src/stl/mod.rs b/src/stl/mod.rs index 63e77fa..2233af6 100644 --- a/src/stl/mod.rs +++ b/src/stl/mod.rs @@ -3,14 +3,16 @@ mod assertion_error; mod bool; mod conv; mod cpsio; -mod litconv; +pub mod litconv; mod mk_stl; mod num; mod runtime_error; mod str; +pub use assertion_error::AssertionError; pub use cpsio::{handle as handleIO, IO}; pub use mk_stl::mk_stl; +pub use runtime_error::RuntimeError; pub use self::bool::Boolean; pub use self::num::Numeric; diff --git a/src/stl/runtime_error.rs b/src/stl/runtime_error.rs index ad427a5..98a0a3b 100644 --- a/src/stl/runtime_error.rs +++ b/src/stl/runtime_error.rs @@ -11,6 +11,8 @@ pub struct RuntimeError { } impl RuntimeError { + /// Construct, upcast and wrap in a Result that never succeeds for easy + /// short-circuiting pub fn fail( message: String, operation: &'static str, @@ -18,6 +20,7 @@ impl RuntimeError { return Err(Self { message, operation }.into_extern()); } + /// Construct and upcast to [ExternError] pub fn ext(message: String, operation: &'static str) -> Rc { return Self { message, operation }.into_extern(); } diff --git a/src/stl/str/char_at.rs b/src/stl/str/char_at.rs index cc88d53..47d9de0 100644 --- a/src/stl/str/char_at.rs +++ b/src/stl/str/char_at.rs @@ -1,38 +1,17 @@ -use std::fmt::Debug; - use super::super::litconv::{with_str, with_uint}; use super::super::runtime_error::RuntimeError; -use crate::representations::interpreted::{Clause, ExprInst}; -use crate::representations::{Literal, Primitive}; -use crate::{atomic_impl, atomic_redirect, externfn_impl}; +use crate::interpreted::Clause; +use crate::{write_fn_step, Literal, Primitive}; -/// Takes an uint and a string, finds the char in a string at a 0-based index -/// -/// Next state: [CharAt1] -#[derive(Clone)] -pub struct CharAt2; -externfn_impl!(CharAt2, |_: &Self, x: ExprInst| Ok(CharAt1 { x })); - -/// Prev state: [CharAt2]; Next state: [CharAt0] -#[derive(Debug, Clone)] -pub struct CharAt1 { - x: ExprInst, -} -atomic_redirect!(CharAt1, x); -atomic_impl!(CharAt1); -externfn_impl!(CharAt1, |this: &Self, x: ExprInst| { - with_str(&this.x, |s| Ok(CharAt0 { s: s.clone(), x })) -}); - -/// Prev state: [CharAt1] -#[derive(Debug, Clone)] -pub struct CharAt0 { - s: String, - x: ExprInst, -} -atomic_redirect!(CharAt0, x); -atomic_impl!(CharAt0, |Self { s, x }: &Self, _| { - with_uint(x, |i| { +write_fn_step!(pub CharAt2 > CharAt1); +write_fn_step!( + CharAt1 {} + CharAt0 where s = |x| with_str(x, |s| Ok(s.clone())) +); +write_fn_step!( + CharAt0 { s: String } + i = |x| with_uint(x, Ok) + => { if let Some(c) = s.chars().nth(i as usize) { Ok(Clause::P(Primitive::Literal(Literal::Char(c)))) } else { @@ -41,5 +20,5 @@ atomic_impl!(CharAt0, |Self { s, x }: &Self, _| { "indexing string", )? } - }) -}); + } +);