fasteval2/evalns.rs
1//! Evaluation Namespaces used for Variable-lookups and custom Functions.
2//!
3//! Several Evaluation Namespace types are defined, each with their own advantages:
4//! * [`EmptyNamespace`](#emptynamespace) -- Useful when you know that your
5//!   expressions don't need to look up any variables.
6//! * BTreeMap -- A simple way to define variables and functions with a map.
7//!   Type aliases: [StringToF64Namespace](#stringtof64namespace),
8//!   [StrToF64Namespace](#strtof64namespace),
9//!   [StringToCallbackNamespace](#stringtocallbacknamespace),
10//!   [StrToCallbackNamespace](#strtocallbacknamespace)
11//! * [`FnMut(&str,Vec<f64>) -> Option<f64>`](#callback-fnmutstrvec---option) --
12//!   Define variables and custom functions using a callback function.
13//! * [`CachedCallbackNamespace`](#cachedcallbacknamespace) -- Like the above
14//!   callback-based Namespace, but results are cached so the callback is not
15//!   queried more than once for a given variable.
16//! * Vec<BTreeMap<String,f64>> -- Define variables with layered maps.
17//!   Each layer is a separate 'scope'.  Higher layers take precedence
18//!   over lower layers.  Very useful for creating scoped higher-level-languages.
19//!   Type alias: [LayeredStringToF64Namespace](#layeredstringtof64namespace)
20//!
21//! # Examples
22//!
23//! ## EmptyNamespace
24//! ```
25//! fn main() -> Result<(), fasteval2::Error> {
26//!     let mut ns = fasteval2::EmptyNamespace;
27//!
28//!     let val = fasteval2::ez_eval("sin(pi()/2)", &mut ns)?;
29//!     assert_eq!(val, 1.0);
30//!
31//!     Ok(())
32//! }
33//! ```
34//!
35//! ## StringToF64Namespace
36//! ```
37//! fn main() -> Result<(), fasteval2::Error> {
38//!     let mut ns = fasteval2::StringToF64Namespace::new();
39//!     ns.insert("x".to_string(), 2.0);
40//!
41//!     let val = fasteval2::ez_eval("x * (x + 1)", &mut ns)?;
42//!     assert_eq!(val, 6.0);
43//!
44//!     Ok(())
45//! }
46//! ```
47//!
48//! ## StrToF64Namespace
49//! ```
50//! fn main() -> Result<(), fasteval2::Error> {
51//!     let mut ns = fasteval2::StrToF64Namespace::new();
52//!     ns.insert("x", 2.0);
53//!
54//!     let val = fasteval2::ez_eval("x * (x + 1)", &mut ns)?;
55//!     assert_eq!(val, 6.0);
56//!
57//!     Ok(())
58//! }
59//! ```
60//!
61//! ## Callback: FnMut(&str,Vec<f64>) -> Option<f64>
62//! ```
63//! fn main() -> Result<(), fasteval2::Error> {
64//!     let mut num_lookups = 0;
65//!     let mut cb = |name:&str, args:Vec<f64>| -> Option<f64> {
66//!         num_lookups += 1;
67//!         match name {
68//!             "x" => Some(2.0),
69//!             _ => None,
70//!         }
71//!     };
72//!
73//!     let val = fasteval2::ez_eval("x * (x + 1)", &mut cb)?;
74//!     assert_eq!(val, 6.0);
75//!     assert_eq!(num_lookups, 2);  // Notice that 'x' was looked-up twice.
76//!
77//!     Ok(())
78//! }
79//! ```
80//!
81//! ## StringToCallbackNamespace
82//! ```
83//! fn main() -> Result<(), fasteval2::Error> {
84//!     let mut ns = fasteval2::StringToCallbackNamespace::new();
85//!     ns.insert("x".to_string(), Box::new(|_args| 2.0));
86//!     ns.insert("double".to_string(), Box::new(|args| {
87//!         args.get(0).map(|arg0| arg0*2.0).unwrap_or(std::f64::NAN)
88//!     }));
89//!
90//!     let val = fasteval2::ez_eval("double(x + 1) + 1", &mut ns)?;
91//!     assert_eq!(val, 7.0);
92//!
93//!     Ok(())
94//! }
95//! ```
96//!
97//! ## StrToCallbackNamespace
98//! ```
99//! fn main() -> Result<(), fasteval2::Error> {
100//!     let mut ns = fasteval2::StrToCallbackNamespace::new();
101//!     ns.insert("x", Box::new(|_args| 2.0));
102//!     ns.insert("double", Box::new(|args| {
103//!         args.get(0).map(|arg0| arg0*2.0).unwrap_or(std::f64::NAN)
104//!     }));
105//!
106//!     let val = fasteval2::ez_eval("double(x + 1) + 1", &mut ns)?;
107//!     assert_eq!(val, 7.0);
108//!
109//!     Ok(())
110//! }
111//! ```
112//!
113//! ## CachedCallbackNamespace
114//! ```
115//! fn main() -> Result<(), fasteval2::Error> {
116//!     let mut num_lookups = 0;
117//!     let val = {
118//!         let cb = |name:&str, args:Vec<f64>| -> Option<f64> {
119//!             num_lookups += 1;
120//!             match name {
121//!                 "x" => {
122//!                     // Pretend that it is very expensive to calculate this,
123//!                     // and that's why we want to use the CachedCallbackNamespace cache.
124//!                     for i in 0..1000000 { /* do work */ }  // Fake Work for this example.
125//!                     Some(2.0)
126//!                 }
127//!                 _ => None,
128//!             }
129//!         };
130//!         let mut ns = fasteval2::CachedCallbackNamespace::new(cb);
131//!
132//!         fasteval2::ez_eval("x * (x + 1)", &mut ns)?
133//!     };
134//!     assert_eq!(val, 6.0);
135//!     assert_eq!(num_lookups, 1);  // Notice that only 1 lookup occurred.
136//!                                  // The second 'x' value was cached.
137//!
138//!     Ok(())
139//! }
140//! ```
141//!
142//! ## LayeredStringToF64Namespace
143//! ```
144//! fn main() -> Result<(), fasteval2::Error> {
145//!     let mut layer1 = fasteval2::StringToF64Namespace::new();
146//!     layer1.insert("x".to_string(), 2.0);
147//!     layer1.insert("y".to_string(), 3.0);
148//!
149//!     let mut layers : fasteval2::LayeredStringToF64Namespace = vec![layer1];
150//!
151//!     let val = fasteval2::ez_eval("x * y", &mut layers)?;
152//!     assert_eq!(val, 6.0);
153//!
154//!     // Let's add another layer which shadows the previous one:
155//!     let mut layer2 = fasteval2::StringToF64Namespace::new();
156//!     layer2.insert("x".to_string(), 3.0);
157//!     layers.push(layer2);
158//!
159//!     let val = fasteval2::ez_eval("x * y", &mut layers)?;
160//!     assert_eq!(val, 9.0);
161//!
162//!     // Remove the top layer and we'll be back to what we had before:
163//!     layers.pop();
164//!
165//!     let val = fasteval2::ez_eval("x * y", &mut layers)?;
166//!     assert_eq!(val, 6.0);
167//!
168//!     Ok(())
169//! }
170//! ```
171//!
172//! ## Custom Namespace Types
173//!
174//! If the pre-defined Namespace types aren't perfect for your application, you
175//! can create your own namespace type -- just implemenet the `EvalNamespace`
176//! trait (and maybe the `Cached` and `Layered` traits too).  Also, as
177//! `fasteval` becomes more mature and is used for more real-life things, I
178//! will continue to add more useful Namespace types.
179//!
180//! Here are a few ideas of possibly-useful custom Namespace types:
181//!
182//! * Vec<Fn(&str,Vec<f64>)->Option<f64>>  --  This would be a `Layered`
183//!   namespace, with each layer having its own callback.  Really powerful!
184//!
185//! * CachedCallbacksNamespace  --  Same as above, but with a cache for each
186//!   layer.  Good for expensive look-ups.
187
188use crate::error::Error;
189
190use std::collections::BTreeMap;
191
192//---- Types:
193
194/// All `fasteval` Namespaces must implement the `EvalNamespace` trait.
195pub trait EvalNamespace {
196    /// Perform a variable/function lookup.
197    ///
198    /// May return cached values.
199    fn lookup(&mut self, name: &str, args: Vec<f64>, keybuf: &mut String) -> Option<f64>;
200}
201
202/// Cache operations for `EvalNamespace`s.
203///
204/// Implement this trait if your Namespace type uses a cache.
205pub trait Cached {
206    /// Creates a new cached entry.  If an entry with the same name already
207    /// exists, an [`AlreadyExists` Error](../error/enum.Error.html#variant.AlreadyExists) is returned.
208    fn cache_create(&mut self, name: String, val: f64) -> Result<(), Error>;
209
210    /// Sets a cached entry.  It doesn't matter whether or not a previous value
211    /// existed with this name.
212    fn cache_set(&mut self, name: String, val: f64);
213
214    /// Clear all cached entries.  Values will be recalculated and cached
215    /// again the next time they are looked up.
216    fn cache_clear(&mut self);
217}
218
219//// I don't want to put this into the public API until it is needed.
220// pub trait Layered {
221//     fn push(&mut self);
222//     fn pop(&mut self);
223// }
224
225/// Use `EmptyNamespace` when you know that you won't be looking up any variables.
226///
227/// It is a zero-sized type, which means it gets optimized-away at compile time.
228///
229/// [See module-level documentation for example.](index.html#emptynamespace)
230///
231pub struct EmptyNamespace;
232
233/// `CachedCallbackNamespace` is useful when your variable/function lookups are expensive.
234///
235/// Each variable+args combo will only be looked up once, and then it will be
236/// cached and re-used for subsequent lookups.
237///
238/// [See module-level documentation for example.](index.html#cachedcallbacknamespace)
239///
240pub struct CachedCallbackNamespace<'a> {
241    cache: BTreeMap<String, f64>,
242    cb: Box<dyn FnMut(&str, Vec<f64>) -> Option<f64> + 'a>, // I think a reference would be more efficient than a Box, but then I would need to use a funky 'let cb=|n|{}; Namespace::new(&cb)' syntax.  The Box results in a super convenient pass-the-cb-by-value API interface.
243}
244
245//// I am commenting these out until I need them in real-life.
246//// (I don't want to add things to the public API until necessary.)
247// pub struct CachedLayeredNamespace<'a> {
248//     caches:Vec<BTreeMap<String,f64>>,
249//     cb    :Box<dyn FnMut(&str, Vec<f64>)->Option<f64> + 'a>,
250// }
251// pub struct Bubble<'a,NS> where NS:EvalNamespace+Layered+'a {
252//     ns   :&'a mut NS,
253//     count:usize,
254// }
255
256//---- Impls:
257
258#[inline(always)]
259fn key_from_nameargs<'a, 'b: 'a>(keybuf: &'a mut String, name: &'b str, args: &[f64]) -> &'a str {
260    if args.is_empty() {
261        name
262    } else {
263        keybuf.clear();
264        keybuf.reserve(name.len() + args.len() * 20);
265        keybuf.push_str(name);
266        for f in args {
267            keybuf.push_str(" , ");
268            keybuf.push_str(&f.to_string());
269        }
270        keybuf.as_str()
271    }
272}
273
274/// Type alias for `BTreeMap<String,f64>`
275pub type StringToF64Namespace = BTreeMap<String, f64>;
276impl EvalNamespace for StringToF64Namespace {
277    #[inline]
278    fn lookup(&mut self, name: &str, args: Vec<f64>, keybuf: &mut String) -> Option<f64> {
279        let key = key_from_nameargs(keybuf, name, &args);
280        self.get(key).copied()
281    }
282}
283
284/// Type alias for `BTreeMap<&'static str,f64>`
285pub type StrToF64Namespace = BTreeMap<&'static str, f64>;
286impl EvalNamespace for StrToF64Namespace {
287    #[inline]
288    fn lookup(&mut self, name: &str, args: Vec<f64>, keybuf: &mut String) -> Option<f64> {
289        let key = key_from_nameargs(keybuf, name, &args);
290        self.get(key).copied()
291    }
292}
293
294/// Type alias for `BTreeMap<String, Box<dyn FnMut(Vec<f64>)->f64>>`
295///
296/// This namespace type provides a very convenient way to register variables
297/// and custom functions.  It is a bit slower than a pure callback, but it has
298/// isolation and composition advantages.
299pub type StringToCallbackNamespace<'a> = BTreeMap<String, Box<dyn FnMut(Vec<f64>) -> f64 + 'a>>;
300impl EvalNamespace for StringToCallbackNamespace<'_> {
301    #[inline]
302    fn lookup(&mut self, name: &str, args: Vec<f64>, _keybuf: &mut String) -> Option<f64> {
303        if let Some(f) = self.get_mut(name) {
304            Some(f(args))
305        } else {
306            None
307        }
308    }
309}
310
311/// Type alias for `BTreeMap<&'static str, Box<dyn FnMut(Vec<f64>)->f64>>`
312///
313/// This namespace type provides a very convenient way to register variables
314/// and custom functions.  It is a bit slower than a pure callback, but it has
315/// isolation and composition advantages.
316pub type StrToCallbackNamespace<'a> = BTreeMap<&'static str, Box<dyn FnMut(Vec<f64>) -> f64 + 'a>>;
317impl EvalNamespace for StrToCallbackNamespace<'_> {
318    #[inline]
319    fn lookup(&mut self, name: &str, args: Vec<f64>, _keybuf: &mut String) -> Option<f64> {
320        if let Some(f) = self.get_mut(name) {
321            Some(f(args))
322        } else {
323            None
324        }
325    }
326}
327
328/// Type alias for `Vec<BTreeMap<String,f64>>`
329pub type LayeredStringToF64Namespace = Vec<BTreeMap<String, f64>>;
330impl EvalNamespace for LayeredStringToF64Namespace {
331    #[inline]
332    fn lookup(&mut self, name: &str, args: Vec<f64>, keybuf: &mut String) -> Option<f64> {
333        let key = key_from_nameargs(keybuf, name, &args);
334
335        for map in self.iter().rev() {
336            if let Some(&val) = map.get(key) {
337                return Some(val);
338            }
339        }
340        None
341    }
342}
343
344// I'm not making a type alias for this because of the un-name-ability of closures:
345impl<F> EvalNamespace for F
346where
347    F: FnMut(&str, Vec<f64>) -> Option<f64>,
348{
349    #[inline]
350    fn lookup(&mut self, name: &str, args: Vec<f64>, _keybuf: &mut String) -> Option<f64> {
351        self(name, args)
352    }
353}
354
355impl EvalNamespace for EmptyNamespace {
356    /// Always returns `None`, indicating that the variable is undefined.
357    #[inline]
358    fn lookup(&mut self, _name: &str, _args: Vec<f64>, _keybuf: &mut String) -> Option<f64> {
359        None
360    }
361}
362
363impl EvalNamespace for CachedCallbackNamespace<'_> {
364    /// Returns a cached value if possible, otherwise delegates to the callback function.
365    fn lookup(&mut self, name: &str, args: Vec<f64>, keybuf: &mut String) -> Option<f64> {
366        let key = key_from_nameargs(keybuf, name, &args);
367
368        if let Some(&val) = self.cache.get(key) {
369            return Some(val);
370        }
371
372        match (self.cb)(name, args) {
373            Some(val) => {
374                self.cache.insert(key.to_string(), val);
375                Some(val)
376            }
377            None => None,
378        }
379    }
380}
381impl Cached for CachedCallbackNamespace<'_> {
382    fn cache_create(&mut self, name: String, val: f64) -> Result<(), Error> {
383        if self.cache.contains_key(&name) {
384            return Err(Error::AlreadyExists);
385        }
386        self.cache.insert(name, val);
387        Ok(())
388    }
389    fn cache_set(&mut self, name: String, val: f64) {
390        self.cache.insert(name, val);
391    }
392    fn cache_clear(&mut self) {
393        self.cache = BTreeMap::new();
394    }
395}
396impl<'a> CachedCallbackNamespace<'a> {
397    #[inline]
398    pub fn new<F>(cb: F) -> Self
399    where
400        F: FnMut(&str, Vec<f64>) -> Option<f64> + 'a,
401    {
402        CachedCallbackNamespace {
403            cache: BTreeMap::new(),
404            cb: Box::new(cb),
405        }
406    }
407}
408
409//// I am not ready to make this part of the public API yet.
410// impl EvalNamespace for CachedLayeredNamespace<'_> {
411//     fn lookup(&mut self, name:&str, args:Vec<f64>, keybuf:&mut String) -> Option<f64> {
412//         let key = key_from_nameargs(keybuf, name, &args);
413//
414//         for map in self.caches.iter().rev() {
415//             if let Some(&val) = map.get(key) { return Some(val); }
416//         }
417//
418//         match (self.cb)(name,args) {
419//             Some(val) => {
420//                 // I'm using this panic-free 'match' structure for performance:
421//                 match self.caches.last_mut() {
422//                     Some(m_ref) => { m_ref.insert(key.to_string(),val); }
423//                     None => (),  // unreachable
424//                 }
425//                 Some(val)
426//             }
427//             None => None,
428//         }
429//     }
430//     fn cache_create(&mut self, name:String, val:f64) -> Result<(),Error> {
431//         match self.caches.last_mut() {
432//             Some(cur_layer) => {
433//                 if cur_layer.contains_key(&name) { return Err(Error::AlreadyExists); }
434//                 cur_layer.insert(name, val);
435//             }
436//             None => return Err(Error::Unreachable),
437//         };
438//         Ok(())
439//     }
440//     fn cache_set(&mut self, name:String, val:f64) {
441//         match self.caches.last_mut() {
442//             Some(m_ref) => { m_ref.insert(name, val); }
443//             None => (),  // unreachable
444//         }
445//     }
446//     fn cache_clear(&mut self) {
447//         self.caches = Vec::with_capacity(self.caches.len());  // Assume the future usage will be similar to historical usage.
448//         self.push();
449//     }
450// }
451// impl Layered for CachedLayeredNamespace<'_> {
452//     #[inline]
453//     fn push(&mut self) {
454//         self.caches.push(BTreeMap::new());
455//     }
456//     #[inline]
457//     fn pop(&mut self) {
458//         self.caches.pop();
459//     }
460// }
461// impl<'a> CachedLayeredNamespace<'a> {
462//     #[inline]
463//     pub fn new<F>(cb:F) -> Self where F:FnMut(&str,Vec<f64>)->Option<f64> + 'a {
464//         let mut ns = CachedLayeredNamespace{
465//             caches:Vec::with_capacity(2),
466//             cb    :Box::new(cb),
467//         };
468//         ns.push();
469//         ns
470//     }
471// }
472
473//// I am not ready to make this part of the public API yet.
474// impl<NS> Bubble<'_,NS> where NS:EvalNamespace+Layered {
475//     pub fn new<'a>(ns:&'a mut NS) -> Bubble<'a,NS> {
476//         Bubble{
477//             ns,
478//             count:0,
479//         }
480//     }
481// }
482// impl<NS> Drop for Bubble<'_,NS> where NS:EvalNamespace+Layered {
483//     fn drop(&mut self) {
484//         while self.count>0 {
485//             self.pop();
486//         }
487//     }
488// }
489// impl<NS> EvalNamespace for Bubble<'_,NS> where NS:EvalNamespace+Layered {
490//     #[inline]
491//     fn lookup(&mut self, name:&str, args:Vec<f64>, keybuf:&mut String) -> Option<f64> {
492//         self.ns.lookup(name,args,keybuf)
493//     }
494//     #[inline]
495//     fn cache_create(&mut self, name:String, val:f64) -> Result<(),Error> {
496//         self.ns.cache_create(name,val)
497//     }
498//     #[inline]
499//     fn cache_set(&mut self, name:String, val:f64) {
500//         self.ns.cache_set(name,val)
501//     }
502//     #[inline]
503//     fn cache_clear(&mut self) {
504//         self.ns.cache_clear()
505//     }
506// }
507// impl<NS> Layered for Bubble<'_,NS> where NS:EvalNamespace+Layered {
508//     #[inline]
509//     fn push(&mut self) {
510//         self.ns.push();
511//         self.count = self.count+1;
512//     }
513//     #[inline]
514//     fn pop(&mut self) {
515//         if self.count>0 {
516//             self.count = self.count+1;
517//             self.ns.pop();
518//         }
519//     }
520// }
521
522//// Commented out until we start using a layered namespace again.
523// #[cfg(test)]
524// mod internal_tests {
525//     use super::*;
526//
527//     #[test]
528//     fn bubble() {
529//         let mut ns = CachedLayeredNamespace::new(|_,_| None);
530//         assert_eq!(ns.caches.len(), 1);
531//         {
532//             let mut bub = Bubble::new(&mut ns);  bub.push();
533//             assert_eq!(bub.ns.caches.len(), 2);
534//             bub.push();
535//             assert_eq!(bub.ns.caches.len(), 3);
536//             bub.push();
537//             assert_eq!(bub.ns.caches.len(), 4);
538//             bub.pop();
539//             assert_eq!(bub.ns.caches.len(), 3);
540//         }
541//         assert_eq!(ns.caches.len(), 1);
542//     }
543// }