From 09295c70f640606ec7038735ba7fc940d4dcfd27 Mon Sep 17 00:00:00 2001 From: vic1707 <28602203+vic1707@users.noreply.github.com> Date: Sun, 26 Nov 2023 23:08:29 +0100 Subject: [PATCH 01/12] Support for static and dynamic fn pointers Co-authored-by: Gabin Lefranc --- src/context.rs | 2 +- src/parser.rs | 2 +- src/tests/thread_safety.rs | 3 +- src/token/function.rs | 124 ++++++++++++++++++++++++++++++++++--- src/token/mod.rs | 2 +- 5 files changed, 120 insertions(+), 13 deletions(-) diff --git a/src/context.rs b/src/context.rs index 1caebd6..28aa5c6 100644 --- a/src/context.rs +++ b/src/context.rs @@ -4,7 +4,7 @@ use std::collections::{HashMap, HashSet}; use crate::token::Function; /// Represents a symbol in the context. -#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] +#[derive(Debug, PartialEq, PartialOrd, Clone)] #[non_exhaustive] pub enum Symbol { /// A variable. diff --git a/src/parser.rs b/src/parser.rs index 83481b5..5bbe01b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -230,7 +230,7 @@ impl<'input, 'ctx> ParserImpl<'input, 'ctx> { let ident = self .ctx .get(name) - .copied() + .cloned() .map_or_else(|| Identifier::from_str(name), Into::into); let el = match ident { diff --git a/src/tests/thread_safety.rs b/src/tests/thread_safety.rs index a5d91d2..9027c9f 100644 --- a/src/tests/thread_safety.rs +++ b/src/tests/thread_safety.rs @@ -3,7 +3,7 @@ use crate::{ context::{Context, Symbol}, element::{BinOp, Element, FunctionCall, UnOp}, parser::{ErrorKind, ParseError, Parser}, - token::{Function, Identifier, Operator}, + token::{FnPointer, Function, Identifier, Operator}, xprs::{BindError, EvalError, Xprs}, }; @@ -24,6 +24,7 @@ const fn test_thread_safety() { is_sized_send_sync_unpin::(); is_sized_send_sync_unpin::(); // token module + is_sized_send_sync_unpin::(); is_sized_send_sync_unpin::(); is_sized_send_sync_unpin::(); is_sized_send_sync_unpin::(); diff --git a/src/token/function.rs b/src/token/function.rs index 2a0bafd..fbe8e5d 100644 --- a/src/token/function.rs +++ b/src/token/function.rs @@ -1,35 +1,121 @@ +/* Built-in imports */ +extern crate alloc; +use alloc::sync::Arc; +use core::{cmp::Ordering, fmt, ops::Deref}; + /// Represents a mathematical function core informations. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +#[derive(Debug, PartialEq, PartialOrd, Clone)] #[non_exhaustive] pub struct Function { /// The name of the function. pub name: &'static str, /// The function's implementation. - pub func: fn(&[f64]) -> f64, + pub func: FnPointer, /// The optional number of arguments the function accepts. /// If [`None`], the function is variadic. pub nb_args: Option, } impl Function { - /// Creates a new [`Function`] from the function components. + /// Creates a new [`Function`] from static function components. /// Note that the fn pointer must be a function that takes a slice of f64 as argument and returns a f64. /// So make sure to wrap your function in a closure if it doesn't match the signature. /// For convenience, you can use the [`xprs_fn!`] macro. /// /// [`Function`] needs a fn taking a slice because Rust variadics are not available yet. #[inline] - pub const fn new( + pub const fn new_static( name: &'static str, func: fn(&[f64]) -> f64, nb_args: Option, ) -> Self { Self { name, - func, + func: FnPointer::Static(func), nb_args, } } + + /// Creates a new [`Function`] from dynamic function components. + /// Note that the fn pointer must be a function that takes a slice of f64 as argument and returns a f64. + /// So make sure to wrap your function in a closure if it doesn't match the signature. + /// For convenience, you can use the [`xprs_fn!`] macro. + /// + /// [`Function`] needs a fn taking a slice because Rust variadics are not available yet. + #[inline] + pub fn new_dyn( + name: &'static str, + func: impl Fn(&[f64]) -> f64 + Send + Sync + 'static, + nb_args: Option, + ) -> Self { + Self { + name, + func: FnPointer::Dyn(Arc::new(func)), + nb_args, + } + } +} + +/// A dynamic function reference. +type DynFn = dyn Fn(&[f64]) -> f64 + Send + Sync; + +/// Enum that holds a function reference. +/// Either a static one, or a dynamic one. +#[derive(Clone)] +pub enum FnPointer { + /// A static function reference. + Static(fn(&[f64]) -> f64), + /// A dynamic function reference. + Dyn(Arc), +} + +impl fmt::Debug for FnPointer { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Self::Static(_) => write!(fmt, "Static"), + Self::Dyn(_) => write!(fmt, "Dyn"), + } + } +} + +impl Deref for FnPointer { + type Target = dyn Fn(&[f64]) -> f64; + + fn deref(&self) -> &Self::Target { + #[allow(clippy::pattern_type_mismatch)] // dunno how to fix this + match self { + Self::Static(func) => func, + Self::Dyn(func) => func.as_ref(), + } + } +} + +impl PartialEq for FnPointer { + #[inline] + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (&Self::Static(func1), &Self::Static(func2)) => func1 == func2, + (&Self::Dyn(ref func1), &Self::Dyn(ref func2)) => { + Arc::ptr_eq(func1, func2) + }, + _ => false, + } + } +} + +impl PartialOrd for FnPointer { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (&Self::Static(func1), &Self::Static(func2)) => { + Some(func1.cmp(&func2)) + }, + (&Self::Dyn(ref func1), &Self::Dyn(ref func2)) => { + Some(Arc::as_ptr(func1).cmp(&Arc::as_ptr(func2))) + }, + _ => None, + } + } } /// Macro for defining functions for xprs' context easily, with optional variadic support. @@ -41,21 +127,41 @@ impl Function { macro_rules! xprs_fn { // variadics ($name:expr, $function:expr) => { - $crate::Function::new($name, $function, None) + $crate::Function::new_static($name, $function, None) + }; + ($name:expr, dyn $function:expr) => { + $crate::Function::new_dyn($name, $function, None) }; ($function:expr) => { - $crate::Function::new(stringify!($function), $function, None) + $crate::Function::new_static(stringify!($function), $function, None) + }; + (dyn $function:expr) => { + $crate::Function::new_dyn(stringify!($function), $function, None) }; // fixed args ($name:expr, $function:expr, $nb_args:tt) => { - $crate::Function::new( + $crate::Function::new_static( + $name, + $crate::xprs_fn!(wrap $function, $nb_args), + Some($nb_args), + ) + }; + ($name:expr, dyn $function:expr, $nb_args:tt) => { + $crate::Function::new_dyn( $name, $crate::xprs_fn!(wrap $function, $nb_args), Some($nb_args), ) }; ($function:expr, $nb_args:tt) => { - $crate::Function::new( + $crate::Function::new_static( + stringify!($function), + $crate::xprs_fn!(wrap $function, $nb_args), + Some($nb_args), + ) + }; + (dyn $function:expr, $nb_args:tt) => { + $crate::Function::new_dyn( stringify!($function), $crate::xprs_fn!(wrap $function, $nb_args), Some($nb_args), diff --git a/src/token/mod.rs b/src/token/mod.rs index 4ef9c03..7c72682 100644 --- a/src/token/mod.rs +++ b/src/token/mod.rs @@ -6,6 +6,6 @@ mod identifier; /// The operator module. mod operator; /* Exports */ -pub use function::Function; +pub use function::{FnPointer, Function}; pub use identifier::Identifier; pub use operator::Operator; From 6c2fb658dcc2c66e55345a646ccdfcceea24bd28 Mon Sep 17 00:00:00 2001 From: vic1707 <28602203+vic1707@users.noreply.github.com> Date: Sun, 26 Nov 2023 23:29:18 +0100 Subject: [PATCH 02/12] WIP: HOF tests - one doctest isn't working (assert_panic one) --- src/context.rs | 3 ++- src/tests/xprs/hof.rs | 29 +++++++++++++++++++++++++++++ src/tests/xprs/mod.rs | 1 + src/token/function.rs | 21 +++++++++++---------- 4 files changed, 43 insertions(+), 11 deletions(-) create mode 100644 src/tests/xprs/hof.rs diff --git a/src/context.rs b/src/context.rs index 28aa5c6..898b2f0 100644 --- a/src/context.rs +++ b/src/context.rs @@ -38,7 +38,8 @@ impl From for Symbol { /// let mut context = Context::default() /// .with_expected_vars(["y"].into()) /// .with_var("x", 42.0) -/// .with_fn(sin_xprs_func); +/// // clone because assert_eq! is used later +/// .with_fn(sin_xprs_func.clone()); /// /// let x_var = context.get("x"); /// assert_eq!(x_var, Some(&Symbol::Variable(42.0))); diff --git a/src/tests/xprs/hof.rs b/src/tests/xprs/hof.rs new file mode 100644 index 0000000..0c1ea0f --- /dev/null +++ b/src/tests/xprs/hof.rs @@ -0,0 +1,29 @@ +/* Crate imports */ +use crate::{Parser, Xprs, xprs_fn, Context}; + +macro_rules! assert_f64_eq { + ($left:expr, $right:expr) => { + assert!(($left - $right).abs() < f64::EPSILON); + }; +} + +#[test] +fn test_higher_order_functions() { + let xprs_hof = Xprs::try_from("2x + y").unwrap(); + let fn_hof = xprs_hof.bind2("x", "y").unwrap(); + let hof = xprs_fn!("hof", dyn fn_hof, 2); + let ctx = Context::default().with_fn(hof); + let parser = Parser::new_with_ctx(ctx); + + let bare_use = parser.parse("hof(2, 3)").unwrap(); + assert_f64_eq!(bare_use.eval_unchecked(&[].into()), 7.0_f64); + + let invalid_use = parser.parse("hof(2)"); + assert!(invalid_use.is_err(), "Expected error for invalid use of hof"); + + let complex_use = parser.parse("hof(2, 3) + 3 * hof(4, 5)").unwrap(); + assert_f64_eq!(complex_use.eval_unchecked(&[].into()), 46.0_f64); + + let nested_var_use = parser.parse("hof(x, hof(2, 3))").unwrap(); + assert_f64_eq!(nested_var_use.eval_unchecked(&[("x", 42.0_f64)].into()), 91.0_f64); +} diff --git a/src/tests/xprs/mod.rs b/src/tests/xprs/mod.rs index 5c12c49..1558ee9 100644 --- a/src/tests/xprs/mod.rs +++ b/src/tests/xprs/mod.rs @@ -1,3 +1,4 @@ /* Modules */ mod eval; +mod hof; mod simplify; diff --git a/src/token/function.rs b/src/token/function.rs index fbe8e5d..8286232 100644 --- a/src/token/function.rs +++ b/src/token/function.rs @@ -121,6 +121,7 @@ impl PartialOrd for FnPointer { /// Macro for defining functions for xprs' context easily, with optional variadic support. /// This macro is provided for convenience, since [`Function`] needs a fn taking a slice of [`f64`] as argument. /// The macro will wrap your function in a closure depending on the number of arguments you provide. +/// Be aware that the provided function gets moved into the closure, so if you want to use it again, you'll have to clone it. /// /// Don't provide the number of arguments if your function is variadic (takes any number of arguments). #[macro_export] @@ -170,42 +171,42 @@ macro_rules! xprs_fn { //// closure wraping //// (wrap $function:expr, 0) => { - |_| $function() + move |_| $function() }; (wrap $function:expr, 1) => { #[allow(clippy::indexing_slicing)] - |args| $function(args[0]) + move |args| $function(args[0]) }; (wrap $function:expr, 2) => { #[allow(clippy::indexing_slicing)] - |args| $function(args[0], args[1]) + move |args| $function(args[0], args[1]) }; (wrap $function:expr, 3) => { #[allow(clippy::indexing_slicing)] - |args| $function(args[0], args[1], args[2]) + move |args| $function(args[0], args[1], args[2]) }; (wrap $function:expr, 4) => { #[allow(clippy::indexing_slicing)] - |args| $function(args[0], args[1], args[2], args[3]) + move |args| $function(args[0], args[1], args[2], args[3]) }; (wrap $function:expr, 5) => { #[allow(clippy::indexing_slicing)] - |args| $function(args[0], args[1], args[2], args[3], args[4]) + move |args| $function(args[0], args[1], args[2], args[3], args[4]) }; (wrap $function:expr, 6) => { #[allow(clippy::indexing_slicing)] - |args| $function(args[0], args[1], args[2], args[3], args[4], args[5]) + move |args| $function(args[0], args[1], args[2], args[3], args[4], args[5]) }; (wrap $function:expr, 7) => { #[allow(clippy::indexing_slicing)] - |args| $function(args[0], args[1], args[2], args[3], args[4], args[5], args[6]) + move |args| $function(args[0], args[1], args[2], args[3], args[4], args[5], args[6]) }; (wrap $function:expr, 8) => { #[allow(clippy::indexing_slicing)] - |args| $function(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]) + move |args| $function(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]) }; (wrap $function:expr, 9) => { #[allow(clippy::indexing_slicing)] - |args| $function(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]) + move |args| $function(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]) }; } From a3c718376f09829a29b47e4b503f4385015f882e Mon Sep 17 00:00:00 2001 From: vic1707 <28602203+vic1707@users.noreply.github.com> Date: Sun, 26 Nov 2023 23:32:21 +0100 Subject: [PATCH 03/12] fmt --- src/tests/xprs/hof.rs | 12 +++++++++--- src/token/function.rs | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/tests/xprs/hof.rs b/src/tests/xprs/hof.rs index 0c1ea0f..83add8c 100644 --- a/src/tests/xprs/hof.rs +++ b/src/tests/xprs/hof.rs @@ -1,5 +1,5 @@ /* Crate imports */ -use crate::{Parser, Xprs, xprs_fn, Context}; +use crate::{xprs_fn, Context, Parser, Xprs}; macro_rules! assert_f64_eq { ($left:expr, $right:expr) => { @@ -19,11 +19,17 @@ fn test_higher_order_functions() { assert_f64_eq!(bare_use.eval_unchecked(&[].into()), 7.0_f64); let invalid_use = parser.parse("hof(2)"); - assert!(invalid_use.is_err(), "Expected error for invalid use of hof"); + assert!( + invalid_use.is_err(), + "Expected error for invalid use of hof" + ); let complex_use = parser.parse("hof(2, 3) + 3 * hof(4, 5)").unwrap(); assert_f64_eq!(complex_use.eval_unchecked(&[].into()), 46.0_f64); let nested_var_use = parser.parse("hof(x, hof(2, 3))").unwrap(); - assert_f64_eq!(nested_var_use.eval_unchecked(&[("x", 42.0_f64)].into()), 91.0_f64); + assert_f64_eq!( + nested_var_use.eval_unchecked(&[("x", 42.0_f64)].into()), + 91.0_f64 + ); } diff --git a/src/token/function.rs b/src/token/function.rs index 8286232..61caaa4 100644 --- a/src/token/function.rs +++ b/src/token/function.rs @@ -40,7 +40,7 @@ impl Function { /// Note that the fn pointer must be a function that takes a slice of f64 as argument and returns a f64. /// So make sure to wrap your function in a closure if it doesn't match the signature. /// For convenience, you can use the [`xprs_fn!`] macro. - /// + /// /// [`Function`] needs a fn taking a slice because Rust variadics are not available yet. #[inline] pub fn new_dyn( From bd74728b190c767073f25852d8936447c3a8c7aa Mon Sep 17 00:00:00 2001 From: vic1707 <28602203+vic1707@users.noreply.github.com> Date: Mon, 27 Nov 2023 14:17:12 +0100 Subject: [PATCH 04/12] fix doctest --- src/xprs.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/xprs.rs b/src/xprs.rs index 84da9b8..2ca3906 100644 --- a/src/xprs.rs +++ b/src/xprs.rs @@ -87,11 +87,12 @@ impl Xprs<'_> { /// /// ``` /// use xprs::Xprs; + /// # use core::panic; /// # macro_rules! assert_panic { /// # ($($t:tt)*) => { - /// # std::panic::catch_unwind(|| { + /// # panic::catch_unwind(panic::AssertUnwindSafe(|| { /// # $($t)* - /// # }).is_err() + /// # })).is_err() /// # } /// # } /// use std::collections::HashMap; From 179c67d585c05e8d1031fdf25a996bca0f5e92a0 Mon Sep 17 00:00:00 2001 From: vic1707 <28602203+vic1707@users.noreply.github.com> Date: Mon, 27 Nov 2023 14:34:00 +0100 Subject: [PATCH 05/12] generalize `assert_f64_eq` --- src/tests/issues/issue_15.rs | 8 ++++---- src/tests/macros.rs | 10 ++++++++++ src/tests/mod.rs | 1 + src/tests/xprs/eval.rs | 8 ++++---- src/tests/xprs/hof.rs | 7 +------ 5 files changed, 20 insertions(+), 14 deletions(-) create mode 100644 src/tests/macros.rs diff --git a/src/tests/issues/issue_15.rs b/src/tests/issues/issue_15.rs index 3d4d496..de400be 100644 --- a/src/tests/issues/issue_15.rs +++ b/src/tests/issues/issue_15.rs @@ -2,10 +2,9 @@ #![allow(clippy::min_ident_chars)] use core::f64::consts::E; /* Crate imports */ +use super::super::macros::assert_f64_eq; use crate::Parser; -const ERROR_MARGIN: f64 = f64::EPSILON; - const VALID_TEST_CASES: [(&str, f64); 9] = [ ("1", 1.0), ("1.2", 1.2), @@ -31,8 +30,9 @@ fn parse_number() { let result = parser.parse(input).unwrap().eval(&[].into()); assert!(result.is_ok(), "Should have parsed: '{input}'."); let value = result.unwrap(); - assert!( - (value - expected).abs() < ERROR_MARGIN, + assert_f64_eq!( + value, + expected, "{input}\nExpected: {expected}, got: {value}" ); } diff --git a/src/tests/macros.rs b/src/tests/macros.rs new file mode 100644 index 0000000..9399266 --- /dev/null +++ b/src/tests/macros.rs @@ -0,0 +1,10 @@ +macro_rules! assert_f64_eq { + ($left:expr, $right:expr) => { + assert!(($left - $right).abs() < f64::EPSILON); + }; + ($left:expr, $right:expr, $($arg:tt)+) => { + assert!(($left - $right).abs() < f64::EPSILON, $($arg)+); + }; +} + +pub(crate) use assert_f64_eq; diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 0487e9f..d43b718 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,5 +1,6 @@ /* Modules */ mod issues; +mod macros; mod parser; mod thread_safety; mod xprs; diff --git a/src/tests/xprs/eval.rs b/src/tests/xprs/eval.rs index 86f8813..346e597 100644 --- a/src/tests/xprs/eval.rs +++ b/src/tests/xprs/eval.rs @@ -1,10 +1,9 @@ /* Built-in imports */ use std::collections::HashMap; /* Crate imports */ +use super::super::macros::assert_f64_eq; use crate::Parser; -const ERROR_MARGIN: f64 = f64::EPSILON; - // shitty type because of clippy and default numeric fallback // https://github.com/rust-lang/rust-clippy/issues/11535 type InputVarsResult = (&'static str, &'static [(&'static str, f64)], f64); @@ -46,8 +45,9 @@ fn test_valid_eval() { let var_map: HashMap<&str, f64> = vars.iter().copied().collect(); let xprs = parser.parse(input).unwrap(); let result = xprs.eval(&var_map).unwrap(); - assert!( - (result - expected).abs() < ERROR_MARGIN, + assert_f64_eq!( + result, + expected, "{input}\nExpected: {expected}, got: {result}" ); } diff --git a/src/tests/xprs/hof.rs b/src/tests/xprs/hof.rs index 83add8c..9a3ac25 100644 --- a/src/tests/xprs/hof.rs +++ b/src/tests/xprs/hof.rs @@ -1,12 +1,7 @@ /* Crate imports */ +use super::super::macros::assert_f64_eq; use crate::{xprs_fn, Context, Parser, Xprs}; -macro_rules! assert_f64_eq { - ($left:expr, $right:expr) => { - assert!(($left - $right).abs() < f64::EPSILON); - }; -} - #[test] fn test_higher_order_functions() { let xprs_hof = Xprs::try_from("2x + y").unwrap(); From 312ff4cc2a6e2db14b70edbbb76e0b0ad807b278 Mon Sep 17 00:00:00 2001 From: vic1707 <28602203+vic1707@users.noreply.github.com> Date: Mon, 27 Nov 2023 15:25:59 +0100 Subject: [PATCH 06/12] fix clippy --- src/token/function.rs | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/token/function.rs b/src/token/function.rs index 61caaa4..ab56bac 100644 --- a/src/token/function.rs +++ b/src/token/function.rs @@ -82,10 +82,9 @@ impl Deref for FnPointer { type Target = dyn Fn(&[f64]) -> f64; fn deref(&self) -> &Self::Target { - #[allow(clippy::pattern_type_mismatch)] // dunno how to fix this - match self { - Self::Static(func) => func, - Self::Dyn(func) => func.as_ref(), + match *self { + Self::Static(ref func) => func, + Self::Dyn(ref func) => func.as_ref(), } } } @@ -93,12 +92,15 @@ impl Deref for FnPointer { impl PartialEq for FnPointer { #[inline] fn eq(&self, other: &Self) -> bool { - match (self, other) { - (&Self::Static(func1), &Self::Static(func2)) => func1 == func2, - (&Self::Dyn(ref func1), &Self::Dyn(ref func2)) => { - Arc::ptr_eq(func1, func2) + match *self { + Self::Static(func1) => match *other { + Self::Static(func2) => func1 == func2, + Self::Dyn(_) => false, + }, + Self::Dyn(ref func1) => match *other { + Self::Dyn(ref func2) => Arc::ptr_eq(func1, func2), + Self::Static(_) => false, }, - _ => false, } } } @@ -106,14 +108,17 @@ impl PartialEq for FnPointer { impl PartialOrd for FnPointer { #[inline] fn partial_cmp(&self, other: &Self) -> Option { - match (self, other) { - (&Self::Static(func1), &Self::Static(func2)) => { - Some(func1.cmp(&func2)) + match *self { + Self::Static(func1) => match *other { + Self::Static(func2) => Some(func1.cmp(&func2)), + Self::Dyn(_) => None, }, - (&Self::Dyn(ref func1), &Self::Dyn(ref func2)) => { - Some(Arc::as_ptr(func1).cmp(&Arc::as_ptr(func2))) + Self::Dyn(ref func1) => match *other { + Self::Dyn(ref func2) => { + Some(Arc::as_ptr(func1).cmp(&Arc::as_ptr(func2))) + }, + Self::Static(_) => None, }, - _ => None, } } } From 70190501f1758fcb495f470b91c3dc2e89615835 Mon Sep 17 00:00:00 2001 From: vic1707 <28602203+vic1707@users.noreply.github.com> Date: Tue, 28 Nov 2023 15:11:18 +0100 Subject: [PATCH 07/12] HOF docs --- README.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ad28f99..29fb3f8 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,23 @@ fn main() { ## Higher order functions -TODO: Coming soon +You can define functions in a context based on a previously parsed expression. + +```rust +use xprs::{xprs_fn, Context, Parser, Xprs}; + +fn main() { + let xprs_hof = Xprs::try_from("2x + y").unwrap(); + let fn_hof = xprs_hof.bind2("x", "y").unwrap(); + let hof = xprs_fn!("hof", dyn fn_hof, 2); + let ctx = Context::default().with_fn(hof); + let parser = Parser::new_with_ctx(ctx); + + let xprs = parser.parse("hof(2, 3)").unwrap(); + + println!("hof(2, 3) = {}", xprs.eval_no_vars().unwrap()); +} +``` These examples and others can be found in the [examples](./examples) directory. From f04e44993298ebf09770953442cb9a8e9b1e63f0 Mon Sep 17 00:00:00 2001 From: vic1707 <28602203+vic1707@users.noreply.github.com> Date: Tue, 28 Nov 2023 15:13:23 +0100 Subject: [PATCH 08/12] fix test --- src/xprs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xprs.rs b/src/xprs.rs index 2ca3906..edeecf2 100644 --- a/src/xprs.rs +++ b/src/xprs.rs @@ -87,7 +87,7 @@ impl Xprs<'_> { /// /// ``` /// use xprs::Xprs; - /// # use core::panic; + /// # use std::panic; /// # macro_rules! assert_panic { /// # ($($t:tt)*) => { /// # panic::catch_unwind(panic::AssertUnwindSafe(|| { From 3dadf992d4f61a12789d4e4cea623a844287d939 Mon Sep 17 00:00:00 2001 From: vic1707 <28602203+vic1707@users.noreply.github.com> Date: Wed, 29 Nov 2023 11:59:44 +0100 Subject: [PATCH 09/12] HOF docs in `lib.rs` --- src/lib.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 09a14cb..758309f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -224,7 +224,23 @@ //! //! ## Higher order functions //! -//! TODO: Coming soon +//! You can define functions in a context based on a previously parsed expression. +//! +//! ```rust +//! use xprs::{xprs_fn, Context, Parser, Xprs}; +//! +//! fn main() { +//! let xprs_hof = Xprs::try_from("2x + y").unwrap(); +//! let fn_hof = xprs_hof.bind2("x", "y").unwrap(); +//! let hof = xprs_fn!("hof", dyn fn_hof, 2); +//! let ctx = Context::default().with_fn(hof); +//! let parser = Parser::new_with_ctx(ctx); +//! +//! let xprs = parser.parse("hof(2, 3)").unwrap(); +//! +//! println!("hof(2, 3) = {}", xprs.eval_no_vars().unwrap()); +//! } +//! ``` //! //! These examples and others can be found in the [examples](./examples) directory. //! From e28ba2e878ab6802ed4e108ddf27bcb472fcb190 Mon Sep 17 00:00:00 2001 From: vic1707 <28602203+vic1707@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:15:34 +0100 Subject: [PATCH 10/12] fix test & explain Function `static` vs `dyn` --- README.md | 14 ++++++++++++-- src/lib.rs | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 58c2392..c159b6b 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ fn double(x: f64) -> f64 { x * 2.0 } -const DOUBLE: Function = Function::new("double", move |args| double(args[0]), Some(1)); +const DOUBLE: Function = Function::new_static("double", move |args| double(args[0]), Some(1)); // or with the macro (will do an automatic wrapping) const DOUBLE_MACRO: Function = xprs_fn!("double", double, 1); @@ -142,9 +142,19 @@ fn variadic_sum(args: &[f64]) -> f64 { args.iter().sum() } -const SUM: Function = Function::new("sum", variadic_sum, None); +const SUM: Function = Function::new_static("sum", variadic_sum, None); // or with the macro (no wrapping is done for variadic functions) const SUM_MACRO: Function = xprs_fn!("sum", variadic_sum); + +// if a functions captures a variable (cannot be coerced to a static function) +const X: f64 = 42.0; +fn show_capture() { + let captures = |arg: f64| { X + arg }; + + let CAPTURES: Function = Function::new_dyn("captures", move |args| captures(args[0]), Some(1)); + // or with the macro (will do an automatic wrapping) + let CAPTURES_MACRO: Function = xprs_fn!("captures", dyn captures, 1); +} ``` To use a [`Context`] and a [`Parser`] you can do the following: diff --git a/src/lib.rs b/src/lib.rs index 758309f..1779f1e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -134,7 +134,7 @@ //! x * 2.0 //! } //! -//! const DOUBLE: Function = Function::new("double", move |args| double(args[0]), Some(1)); +//! const DOUBLE: Function = Function::new_static("double", move |args| double(args[0]), Some(1)); //! // or with the macro (will do an automatic wrapping) //! const DOUBLE_MACRO: Function = xprs_fn!("double", double, 1); //! @@ -142,9 +142,19 @@ //! args.iter().sum() //! } //! -//! const SUM: Function = Function::new("sum", variadic_sum, None); +//! const SUM: Function = Function::new_static("sum", variadic_sum, None); //! // or with the macro (no wrapping is done for variadic functions) //! const SUM_MACRO: Function = xprs_fn!("sum", variadic_sum); +//! +//! // if a functions captures a variable (cannot be coerced to a static function) +//! const X: f64 = 42.0; +//! fn show_capture() { +//! let captures = |arg: f64| { X + arg }; +//! +//! let CAPTURES: Function = Function::new_dyn("captures", move |args| captures(args[0]), Some(1)); +//! // or with the macro (will do an automatic wrapping) +//! let CAPTURES_MACRO: Function = xprs_fn!("captures", dyn captures, 1); +//! } //! ``` //! //! To use a [`Context`] and a [`Parser`] you can do the following: From f7f0c91d8f84cdeba32e95d17639fd27be377dff Mon Sep 17 00:00:00 2001 From: vic1707 <28602203+vic1707@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:24:22 +0100 Subject: [PATCH 11/12] fix clippy on nightly --- src/token/function.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/token/function.rs b/src/token/function.rs index c5a9b33..129908d 100644 --- a/src/token/function.rs +++ b/src/token/function.rs @@ -43,11 +43,10 @@ impl Function { /// /// [`Function`] needs a fn taking a slice because Rust variadics are not available yet. #[inline] - pub fn new_dyn( - name: &'static str, - func: impl Fn(&[f64]) -> f64 + Send + Sync + 'static, - nb_args: Option, - ) -> Self { + pub fn new_dyn(name: &'static str, func: T, nb_args: Option) -> Self + where + T: Fn(&[f64]) -> f64 + Send + Sync + 'static, + { Self { name, func: FnPointer::Dyn(Arc::new(func)), From e09b5ed682b419fd911a395088d28f285abf740b Mon Sep 17 00:00:00 2001 From: vic1707 <28602203+vic1707@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:25:40 +0100 Subject: [PATCH 12/12] trigger CI?