From 5bb8a12fc205ed0a700485b87d4ca76d72bcb836 Mon Sep 17 00:00:00 2001 From: Lawrence Bethlenfalvy Date: Fri, 2 Jun 2023 18:21:10 +0100 Subject: [PATCH] Added define_fn - updated write_fn_step to support attributes, bind names and explicit argument types - added define_fn to generate write_fn_step sequences - updated concatenate to define_fn as an example --- Cargo.lock | 7 ++ Cargo.toml | 1 + src/foreign_macros/define_fn.rs | 128 ++++++++++++++++++++++++++++ src/foreign_macros/mod.rs | 1 + src/foreign_macros/write_fn_step.rs | 55 +++++++----- src/stl/num/operators/add.rs | 5 -- src/stl/str/char_at.rs | 6 +- src/stl/str/concatenate.rs | 43 +++------- src/stl/str/mod.rs | 2 +- 9 files changed, 184 insertions(+), 64 deletions(-) create mode 100644 src/foreign_macros/define_fn.rs diff --git a/Cargo.lock b/Cargo.lock index 5e33481..38c8d17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -298,6 +298,7 @@ dependencies = [ "hashbrown 0.13.2", "itertools", "ordered-float", + "paste", "thiserror", "trait-set", ] @@ -311,6 +312,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "paste" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" + [[package]] name = "proc-macro2" version = "1.0.56" diff --git a/Cargo.toml b/Cargo.toml index 9556126..9c377d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,3 +30,4 @@ itertools = "0.10" dyn-clone = "1.0.11" clap = { version = "4.2.4", features = ["derive"] } trait-set = "0.3.0" +paste = "1.0.12" diff --git a/src/foreign_macros/define_fn.rs b/src/foreign_macros/define_fn.rs new file mode 100644 index 0000000..f8bbef0 --- /dev/null +++ b/src/foreign_macros/define_fn.rs @@ -0,0 +1,128 @@ +#[allow(unused)] // for doc +use crate::foreign::ExternFn; +#[allow(unused)] // for doc +use crate::interpreted::ExprInst; +#[allow(unused)] // for doc +use crate::write_fn_step; + +/// Define a simple n-ary nonvariadic Orchid function with static argument +/// types. +/// +/// This macro relies on [write_fn_step] to define a struct for each step. +/// Because of how Orchid handles state, the arguments must implement [Clone] +/// and [Debug]. All expressions and arguments are accessible as references. +/// +/// First, the alias for the newly introduced [ExprInst] is specified. This step +/// is necessary and a default cannot be provided because any name defined in +/// the macro is invisible to the calling code. In the example, the name `x` is +/// selected. +/// +/// Then a name and optional visibility is specified for the entry point. This +/// will be a zero-size marker struct implementing [ExternFn]. It can also have +/// documentation and attributes. +/// +/// This is followed by the table of arguments. Each defines a name, value type, +/// and a conversion expression which references the [ExprInst] by the name +/// defined in the first step and returns a [Result] of the success type or +/// `Rc`. +/// +/// Finally, the body of the function is provided as an expression which can +/// reference all of the arguments by their names, each bound to a ref of the +/// specified type. +/// +/// ``` +/// use orchidlang::interpreted::Clause; +/// use orchidlang::stl::litconv::with_str; +/// use orchidlang::{define_fn, Literal, Primitive}; +/// +/// define_fn! {expr=x in +/// /// Append a string to another +/// pub Concatenate { +/// a: String as with_str(x, |s| Ok(s.clone())), +/// b: String as with_str(x, |s| Ok(s.clone())) +/// } => { +/// Ok(Clause::P(Primitive::Literal(Literal::Str(a.to_owned() + &b)))) +/// } +/// } +/// ``` +#[macro_export] +macro_rules! define_fn { + (expr=$xname:ident in + $( #[ $attr:meta ] )* + $qual:vis $name:ident { + $arg0:ident: $typ0:ty as $parse0:expr + $(, $arg:ident: $typ:ty as $parse:expr )* + } => $body:expr + ) => {paste::paste!{ + // Generate initial state + $crate::write_fn_step!( + $( #[ $attr ] )* $qual $name + > + [< Internal $name >] + ); + // Enter loop to generate intermediate states + $crate::define_fn!(@MIDDLE $xname [< Internal $name >] ($body) + () + ( + ($arg0: $typ0 as $parse0) + $( ($arg: $typ as $parse) )* + ) + ); + }}; + // Recursive case + (@MIDDLE $xname:ident $name:ident ($body:expr) + // fields that should be included in this struct + ( + $( + ( $arg_prev:ident: $typ_prev:ty ) + )* + ) + // later fields + ( + // field that should be processed by this step + ( $arg0:ident: $typ0:ty as $parse0:expr ) + // ensure that we have a next stage + $( + ( $arg:ident: $typ:ty as $parse:expr ) + )+ + ) + ) => {paste::paste!{ + $crate::write_fn_step!( + $name + { + $( $arg_prev:ident : $typ_prev:ty ),* + } + [< $name $arg0:upper >] + where $arg0:$typ0 = $xname => $parse0; + ); + $crate::define_fn!(@MIDDLE $xname [< $name $arg0:upper >] ($body) + ( + $( ($arg_prev: $typ_prev) )* + ($arg0: $typ0) + ) + ( + $( ($arg: $typ as $parse) )+ + ) + ); + }}; + // recursive base case + (@MIDDLE $xname:ident $name:ident ($body:expr) + // all but one field is included in this struct + ( + $( ($arg_prev:ident: $typ_prev:ty) )* + ) + // the last one is initialized before the body runs + ( + ($arg0:ident: $typ0:ty as $parse0:expr) + ) + ) => { + $crate::write_fn_step!( + $name + { + $( $arg_prev: $typ_prev ),* + } + $arg0:$typ0 = $xname => $parse0; + $body + ); + }; +} diff --git a/src/foreign_macros/mod.rs b/src/foreign_macros/mod.rs index 8355c6e..2e75ed0 100644 --- a/src/foreign_macros/mod.rs +++ b/src/foreign_macros/mod.rs @@ -2,5 +2,6 @@ mod atomic_defaults; mod atomic_impl; mod atomic_inert; mod atomic_redirect; +mod define_fn; 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 index 1f6e1e7..bddf6b3 100644 --- a/src/foreign_macros/write_fn_step.rs +++ b/src/foreign_macros/write_fn_step.rs @@ -1,14 +1,18 @@ #[allow(unused)] // for doc +use crate::define_fn; +#[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. +/// Orchid function. There are no known use cases for it that aren't expressed +/// better with [define_fn] which generates calls to this macro. /// /// 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. +/// discussed below. The newly bound names (here `s` and `i` before `=`) can +/// also receive type annotations. /// /// ``` /// use orchidlang::{write_fn_step, Literal, Primitive}; @@ -21,13 +25,13 @@ use crate::interpreted::ExprInst; /// // Middle state /// write_fn_step!( /// CharAt1 {} -/// CharAt0 where s = |x| with_str(x, |s| Ok(s.clone())) +/// 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) -/// => { +/// 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 { @@ -47,12 +51,12 @@ use crate::interpreted::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. +/// state is also provided, alongside the name and conversion of the next +/// parameter from a [&ExprInst] under the provided alias to a +/// `Result<_, Rc>`. 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 @@ -65,7 +69,9 @@ use crate::interpreted::ExprInst; /// before the body is evaluated. #[macro_export] macro_rules! write_fn_step { - ($quant:vis $name:ident > $next:ident) => { + // write entry stage + ( $( #[ $attr:meta ] )* $quant:vis $name:ident > $next:ident) => { + $( #[ $attr ] )* #[derive(Clone)] $quant struct $name; $crate::externfn_impl!{ @@ -75,12 +81,15 @@ macro_rules! write_fn_step { } } }; + // write middle stage ( - $quant:vis $name:ident { + $( #[ $attr:meta ] )* $quant:vis $name:ident { $( $arg:ident : $typ:ty ),* } - $next:ident where $added:ident = $extract:expr + $next:ident where + $added:ident $( : $added_typ:ty )? = $xname:ident => $extract:expr ; ) => { + $( #[ $attr ] )* #[derive(std::fmt::Debug, Clone)] $quant struct $name { $( $arg: $typ, )* @@ -91,22 +100,24 @@ macro_rules! write_fn_step { $crate::externfn_impl!( $name, |this: &Self, expr_inst: $crate::interpreted::ExprInst| { - let lambda = $extract; + let $xname = &this.expr_inst; + let $added $( :$added_typ )? = $extract?; Ok($next{ $( $arg: this.$arg.clone(), )* - $added: lambda(&this.expr_inst)?, - expr_inst + $added, expr_inst }) } ); }; + // write final stage ( - $quant:vis $name:ident { + $( #[ $attr:meta ] )* $quant:vis $name:ident { $( $arg:ident: $typ:ty ),* } - $added:ident = $extract:expr - => $process:expr + $added:ident $(: $added_typ:ty )? = $xname:ident => $extract:expr ; + $process:expr ) => { + $( #[ $attr ] )* #[derive(std::fmt::Debug, Clone)] $quant struct $name { $( $arg: $typ, )+ @@ -116,8 +127,8 @@ macro_rules! write_fn_step { $crate::atomic_impl!( $name, |Self{ $($arg, )* expr_inst }: &Self, _| { - let lambda = $extract; - let $added = lambda(expr_inst)?; + let $xname = expr_inst; + let $added $(: $added_typ )? = $extract?; $process } ); diff --git a/src/stl/num/operators/add.rs b/src/stl/num/operators/add.rs index b893bc3..4a37b09 100644 --- a/src/stl/num/operators/add.rs +++ b/src/stl/num/operators/add.rs @@ -4,14 +4,10 @@ use super::super::Numeric; use crate::representations::interpreted::ExprInst; use crate::{atomic_impl, atomic_redirect, externfn_impl}; -/// Adds two numbers -/// -/// Next state: [Add1] #[derive(Clone)] pub struct Add2; externfn_impl!(Add2, |_: &Self, x: ExprInst| Ok(Add1 { x })); -/// Prev state: [Add2]; Next state: [Add0] #[derive(Debug, Clone)] pub struct Add1 { x: ExprInst, @@ -23,7 +19,6 @@ externfn_impl!(Add1, |this: &Self, x: ExprInst| { Ok(Add0 { a, x }) }); -/// Prev state: [Add1] #[derive(Debug, Clone)] pub struct Add0 { a: Numeric, diff --git a/src/stl/str/char_at.rs b/src/stl/str/char_at.rs index 47d9de0..d4780b6 100644 --- a/src/stl/str/char_at.rs +++ b/src/stl/str/char_at.rs @@ -6,12 +6,12 @@ use crate::{write_fn_step, Literal, Primitive}; write_fn_step!(pub CharAt2 > CharAt1); write_fn_step!( CharAt1 {} - CharAt0 where s = |x| with_str(x, |s| Ok(s.clone())) + CharAt0 where s = x => with_str(x, |s| Ok(s.clone())) ; ); write_fn_step!( CharAt0 { s: String } - i = |x| with_uint(x, Ok) - => { + 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 { diff --git a/src/stl/str/concatenate.rs b/src/stl/str/concatenate.rs index 221d71d..c9cdfc0 100644 --- a/src/stl/str/concatenate.rs +++ b/src/stl/str/concatenate.rs @@ -1,37 +1,14 @@ -use std::fmt::Debug; - use super::super::litconv::with_str; -use crate::representations::interpreted::{Clause, ExprInst}; +use crate::define_fn; +use crate::representations::interpreted::Clause; use crate::representations::{Literal, Primitive}; -use crate::{atomic_impl, atomic_redirect, externfn_impl}; -/// Concatenates two strings -/// -/// Next state: [Concatenate1] -#[derive(Clone)] -pub struct Concatenate2; -externfn_impl!(Concatenate2, |_: &Self, c: ExprInst| Ok(Concatenate1 { c })); - -/// Prev state: [Concatenate2]; Next state: [Concatenate0] -#[derive(Debug, Clone)] -pub struct Concatenate1 { - c: ExprInst, +define_fn! {expr=x in + /// Append a string to another + pub Concatenate { + a: String as with_str(x, |s| Ok(s.clone())), + b: String as with_str(x, |s| Ok(s.clone())) + } => { + Ok(Clause::P(Primitive::Literal(Literal::Str(a.to_owned() + &b)))) + } } -atomic_redirect!(Concatenate1, c); -atomic_impl!(Concatenate1); -externfn_impl!(Concatenate1, |this: &Self, c: ExprInst| { - with_str(&this.c, |a| Ok(Concatenate0 { a: a.clone(), c })) -}); - -/// Prev state: [Concatenate1] -#[derive(Debug, Clone)] -pub struct Concatenate0 { - a: String, - c: ExprInst, -} -atomic_redirect!(Concatenate0, c); -atomic_impl!(Concatenate0, |Self { a, c }: &Self, _| { - with_str(c, |b| { - Ok(Clause::P(Primitive::Literal(Literal::Str(a.to_owned() + b)))) - }) -}); diff --git a/src/stl/str/mod.rs b/src/stl/str/mod.rs index 1909d90..90ff695 100644 --- a/src/stl/str/mod.rs +++ b/src/stl/str/mod.rs @@ -7,6 +7,6 @@ use crate::pipeline::ConstTree; pub fn str(i: &Interner) -> ConstTree { ConstTree::tree([( i.i("concatenate"), - ConstTree::xfn(concatenate::Concatenate2), + ConstTree::xfn(concatenate::Concatenate), )]) }