fasteval3/
slab.rs

1//! A `Slab` is a pre-allocated block of memory, used during the
2//! parse/compile/eval phases to reduce memory allocation/deallocation.
3//!
4//! A `Slab` enables you to perform one single, large allocation at the
5//! beginning of the parse-compile-eval process, rather than many small
6//! allocations.  You can also re-use a `Slab` for multiple expression
7//! parse-compile-eval cycles, greatly reducing the amount of memory
8//! operations.  The `Slab` is the main key to `fasteval3`'s excellent
9//! performance.
10//!
11//! You use `ExpressionI`, `ValueI`, and `InstructionI` index types to refer to
12//! elements within the `Slab`.  These special index types are necessary to
13//! side-step the Rust borrow checker, which is not able to understand
14//! borrow-splitting of contiguous allocations (like arrays).
15//! (In other words, these special index types allows `fasteval3` to mutate a
16//! `Slab` while simultaneously holding references to its contents.)
17//!
18//! You usually won't use any of the `Slab` method directly.  Instead, you'll
19//! just pass a reference to other functions like [`parse()`](../parser/index.html),
20//! [`compile()`](../compiler/trait.Compiler.html) and [`eval()`](../evaler/trait.Evaler.html).
21//! We treat a `Slab` sort of like a Context in other programming systems.
22//!
23//! The `Slab` contains two fields: `ps` ("Parse Slab") and `cs`
24//! ("Compile Slab").  It is structured like this because of Rust's borrowing
25//! rules, so that the two fields can be borrowed and mutated independently.
26//!
27//! If you use the [`ez_eval()`](../ez/fn.ez_eval.html) function, it allocates
28//! a Slab for you.  If you are performing the parse/compile/eval process
29//! yourself, then you'll need to allocate a Slab at the beginning.
30//!
31//! # Examples
32//!
33//! Here is an example of re-using one `Slab` for multiple parse/eval cycles:
34//! ```
35//! use fasteval3::Evaler;  // import this trait so we can call eval().
36//! fn main() -> Result<(), fasteval3::Error> {
37//!     let parser = fasteval3::Parser::new();
38//!     let mut slab = fasteval3::Slab::new();
39//!
40//!     let val = parser.parse("1+2*3-4", &mut slab.ps)?.from(&slab.ps).eval(&slab, &mut fasteval3::EmptyNamespace)?;
41//!     assert_eq!(val, 3.0);
42//!
43//!     // Let's re-use the same slab again to save memory operations.
44//!
45//!     // `parse()` will clear the Slab's data.  It is important that you
46//!     // do not use an old expression after the Slab has been cleared.
47//!     let val = parser.parse("5+6*7-8", &mut slab.ps)?.from(&slab.ps).eval(&slab, &mut fasteval3::EmptyNamespace)?;
48//!     assert_eq!(val, 39.0);
49//!
50//!     Ok(())
51//! }
52//! ```
53
54use crate::compiler::{
55    Instruction::{self, IConst},
56    InstructionI,
57};
58use crate::error::Error;
59use crate::parser::{Expression, ExpressionI, Value, ValueI};
60
61use std::fmt;
62use std::mem;
63
64#[cfg(feature = "unsafe-vars")]
65use std::collections::BTreeMap;
66
67// Eliminate function call overhead:
68macro_rules! get_expr {
69    ($pslab:expr, $i_ref:ident) => {
70        match $pslab.exprs.get($i_ref.0) {
71            Some(expr_ref) => expr_ref,
72            None => &$pslab.def_expr,
73        }
74    };
75}
76macro_rules! get_val {
77    ($pslab:expr, $i_ref:ident) => {
78        match $pslab.vals.get($i_ref.0) {
79            Some(val_ref) => val_ref,
80            None => &$pslab.def_val,
81        }
82    };
83}
84// The CompileSlab::get_instr method is in the hot path of compiled evaluation:
85macro_rules! get_instr {
86    ($cslab:expr, $i_ref:ident) => {
87        match $cslab.instrs.get($i_ref.0) {
88            Some(instr_ref) => instr_ref,
89            None => &$cslab.def_instr,
90        }
91    };
92}
93
94impl ExpressionI {
95    /// Gets an Expression reference from the `ParseSlab`.
96    ///
97    /// This is actually just a convenience function built on top of
98    /// `ParseSlab.get_expr`, but it enables you to perform the entire
99    /// parse/compile/eval process in one line without upsetting the Rust
100    /// borrow checker.  (If you didn't have this function, the borrow checker
101    /// would force you to split the process into at least two lines.)
102    ///
103    #[inline]
104    pub fn from(self, ps: &ParseSlab) -> &Expression {
105        get_expr!(ps, self)
106    }
107}
108impl ValueI {
109    /// Gets a Value reference from the `ParseSlab`.
110    ///
111    /// See the comments on [`ExpressionI::from`](struct.ExpressionI.html#method.from).
112    ///
113    #[inline]
114    pub fn from(self, ps: &ParseSlab) -> &Value {
115        get_val!(ps, self)
116    }
117}
118
119/// [See the `slab module` documentation.](index.html)
120pub struct Slab {
121    pub ps: ParseSlab,
122    pub cs: CompileSlab,
123}
124
125/// `ParseSlab` is where `parse()` results are stored, located at `Slab.ps`.
126///
127/// # Unsafe Variable Registration with `add_unsafe_var()`
128///
129/// (This is documented here because the
130/// [`add_unsafe_var()`](#method.add_unsafe_var) method and its documentation
131/// only appears if `fasteval3` is built with the `unsafe-vars` feature (`cargo
132/// build --features unsafe-vars`).  I want this documentation to appear
133/// regardless of the build mode, so I'm putting it here.)
134///
135/// Here is the function signature of the `add_unsafe_var()` method:
136///
137/// ```text
138/// pub unsafe fn add_unsafe_var(&mut self, name: String, ptr: &f64)
139/// ```
140///
141/// If you are using [Unsafe Variables](../index.html#unsafe-variables), you
142/// need to pre-register the unsafe variable names and pointers *before*
143/// calling `parse()`.  This is because Unsafe Variables are represented
144/// specially in the parse AST; therefore, `parse()` needs to know what
145/// variables are unsafe and which ones are normal so that it can produce the
146/// correct AST.
147///
148/// If you forget to pre-register an unsafe variable before `parse()`, the
149/// variable will be treated like a Normal Variable, and you'll probably get an
150/// [`Undefined`](../error/enum.Error.html#variant.Undefined) error during evaluation.
151///
152/// ## Safety
153///
154/// You must guarantee that Unsafe Variable pointers remain valid for the
155/// lifetime of the resulting expression.  If you continue to use an expression
156/// after the memory of an unsafe variable has been reclaimed, you will have
157/// undefined behavior.
158///
159///
160/// ## Examples
161///
162/// Here is an example of correct and incorrect use of unsafe variable pointers:
163///
164/// ```
165/// use fasteval3::Evaler;    // use this trait so we can call eval().
166/// use fasteval3::Compiler;  // use this trait so we can call compile().
167///
168/// // Here is an example of INCORRECT registration.  DO NOT DO THIS!
169/// #[cfg(feature = "unsafe-vars")]
170/// fn bad_unsafe_var(slab_mut:&mut fasteval3::Slab) {
171///     let bad : f64 = 0.0;
172///
173///     // Saves a pointer to 'bad':
174///     unsafe { slab_mut.ps.add_unsafe_var("bad".to_string(), &bad); }  // `add_unsafe_var()` only exists if the `unsafe-vars` feature is enabled: `cargo test --features unsafe-vars`
175///
176///     // 'bad' goes out-of-scope here, and the pointer we registered is no longer valid!
177///     // This will result in undefined behavior.
178/// }
179/// #[cfg(not(feature = "unsafe-vars"))]
180/// fn main() -> Result<(), fasteval3::Error> {
181///     Ok(())
182/// }
183/// #[cfg(feature = "unsafe-vars")]
184/// fn main() -> Result<(), fasteval3::Error> {
185///     let mut slab = fasteval3::Slab::new();
186///
187///     // The Unsafe Variable will use a pointer to read this memory location:
188///     // You must make sure that this variable stays in-scope as long as the
189///     // expression is in-use.
190///     let mut deg : f64 = 0.0;
191///
192///     // Unsafe Variables must be registered before 'parse()'.
193///     // (Normal Variables only need definitions during the 'eval' phase.)
194///     unsafe { slab.ps.add_unsafe_var("deg".to_string(), &deg); }  // `add_unsafe_var()` only exists if the `unsafe-vars` feature is enabled: `cargo test --features unsafe-vars`
195///
196///     // bad_unsafe_var(&mut slab);  // Don't do it this way.
197///
198///     let expr_str = "sin(deg/360 * 2*pi())";
199///     let expr_ref = fasteval3::Parser::new().parse(expr_str, &mut slab.ps)?.from(&slab.ps);
200///     let mut ns = fasteval3::EmptyNamespace;  // We only define unsafe variables, not normal variables,
201///                                             // so EmptyNamespace is fine.
202///     // The main reason people use Unsafe Variables is to maximize performance.
203///     // Compilation also helps performance, so it is usually used together with Unsafe Variables:
204///     let compiled = expr_ref.compile(&slab.ps, &mut slab.cs, &mut ns);
205///
206///     for d in 0..360 {
207///         deg = d as f64;
208///         let val = fasteval3::eval_compiled!(compiled, &slab, &mut ns);
209///         eprintln!("sin({}°) = {}", deg, val);
210///     }
211///
212///     Ok(())
213/// }
214///
215/// ```
216pub struct ParseSlab {
217    pub(crate) exprs: Vec<Expression>,
218    pub(crate) vals: Vec<Value>,
219    pub(crate) def_expr: Expression,
220    pub(crate) def_val: Value,
221    pub(crate) char_buf: String,
222    #[cfg(feature = "unsafe-vars")]
223    pub(crate) unsafe_vars: BTreeMap<String, *const f64>,
224}
225
226/// `CompileSlab` is where `compile()` results are stored, located at `Slab.cs`.
227pub struct CompileSlab {
228    pub(crate) instrs: Vec<Instruction>,
229    pub(crate) def_instr: Instruction,
230}
231
232impl ParseSlab {
233    /// Returns a reference to the [`Expression`](../parser/struct.Expression.html)
234    /// located at `expr_i` within the `ParseSlab.exprs'.
235    ///
236    /// If `expr_i` is out-of-bounds, a reference to a default `Expression` is returned.
237    ///
238    #[inline]
239    pub fn get_expr(&self, expr_i: ExpressionI) -> &Expression {
240        // I'm using this non-panic match structure to boost performance:
241        self.exprs.get(expr_i.0).map_or(&self.def_expr, |expr_ref| expr_ref)
242    }
243
244    /// Returns a reference to the [`Value`](../parser/enum.Value.html)
245    /// located at `val_i` within the `ParseSlab.vals'.
246    ///
247    /// If `val_i` is out-of-bounds, a reference to a default `Value` is returned.
248    ///
249    #[inline]
250    pub fn get_val(&self, val_i: ValueI) -> &Value {
251        self.vals.get(val_i.0).map_or(&self.def_val, |val_ref| val_ref)
252    }
253
254    /// Appends an `Expression` to `ParseSlab.exprs`.
255    ///
256    /// # Errors
257    ///
258    /// If `ParseSlab.exprs` is already full, a `SlabOverflow` error is returned.
259    ///
260    #[inline]
261    pub(crate) fn push_expr(&mut self, expr: Expression) -> Result<ExpressionI, Error> {
262        let i = self.exprs.len();
263        if i >= self.exprs.capacity() {
264            return Err(Error::SlabOverflow);
265        }
266        self.exprs.push(expr);
267        Ok(ExpressionI(i))
268    }
269
270    /// Appends a `Value` to `ParseSlab.vals`.
271    ///
272    /// # Errors
273    ///
274    /// If `ParseSlab.vals` is already full, a `SlabOverflow` error is returned.
275    ///
276    #[inline]
277    pub(crate) fn push_val(&mut self, val: Value) -> Result<ValueI, Error> {
278        let i = self.vals.len();
279        if i >= self.vals.capacity() {
280            return Err(Error::SlabOverflow);
281        }
282        self.vals.push(val);
283        Ok(ValueI(i))
284    }
285
286    /// Clears all data from `ParseSlab.exprs` and `ParseSlab.vals`.
287    #[inline]
288    pub fn clear(&mut self) {
289        self.exprs.clear();
290        self.vals.clear();
291    }
292
293    /// [See the `add_unsafe_var()` documentation above.](#unsafe-variable-registration-with-add_unsafe_var)
294    #[cfg(feature = "unsafe-vars")]
295    #[allow(clippy::trivially_copy_pass_by_ref)]
296    pub unsafe fn add_unsafe_var(&mut self, name: String, ptr: &f64) {
297        self.unsafe_vars.insert(name, ptr as *const f64);
298    }
299}
300
301impl CompileSlab {
302    /// Returns a reference to the [`Instruction`](../compiler/enum.Instruction.html)
303    /// located at `instr_i` within the `CompileSlab.instrs'.
304    ///
305    /// If `instr_i` is out-of-bounds, a reference to a default `Instruction` is returned.
306    ///
307    #[inline]
308    pub fn get_instr(&self, instr_i: InstructionI) -> &Instruction {
309        self.instrs.get(instr_i.0).map_or(&self.def_instr, |instr_ref| instr_ref)
310    }
311
312    /// Appends an `Instruction` to `CompileSlab.instrs`.
313    pub(crate) fn push_instr(&mut self, instr: Instruction) -> InstructionI {
314        if self.instrs.capacity() == 0 {
315            self.instrs.reserve(32);
316        }
317        let i = self.instrs.len();
318        self.instrs.push(instr);
319        InstructionI(i)
320    }
321
322    /// Removes an `Instruction` from `CompileSlab.instrs` as efficiently as possible.
323    pub(crate) fn take_instr(&mut self, i: InstructionI) -> Instruction {
324        if i.0 == self.instrs.len() - 1 {
325            self.instrs.pop().map_or(IConst(std::f64::NAN), |instr| instr)
326        } else {
327            self.instrs.get_mut(i.0).map_or(IConst(std::f64::NAN), |instr_ref| mem::replace(instr_ref, IConst(std::f64::NAN)))
328        }
329    }
330
331    /// Clears all data from `CompileSlab.instrs`.
332    #[inline]
333    pub fn clear(&mut self) {
334        self.instrs.clear();
335    }
336}
337
338impl Slab {
339    /// Creates a new default-sized `Slab`.
340    #[inline]
341    pub fn new() -> Self {
342        Self::with_capacity(64)
343    }
344
345    /// Creates a new `Slab` with the given capacity.
346    #[inline]
347    pub fn with_capacity(cap: usize) -> Self {
348        Self {
349            ps: ParseSlab {
350                exprs: Vec::with_capacity(cap),
351                vals: Vec::with_capacity(cap),
352                def_expr: Expression::default(),
353                def_val: Value::default(),
354                char_buf: String::with_capacity(64),
355                #[cfg(feature = "unsafe-vars")]
356                unsafe_vars: BTreeMap::new(),
357            },
358            cs: CompileSlab {
359                instrs: Vec::new(), // Don't pre-allocate for compilation.
360                def_instr: Instruction::default(),
361            },
362        }
363    }
364
365    /// Clears all data from [`Slab.ps`](struct.ParseSlab.html) and [`Slab.cs`](struct.CompileSlab.html).
366    #[inline]
367    pub fn clear(&mut self) {
368        self.ps.exprs.clear();
369        self.ps.vals.clear();
370        self.cs.instrs.clear();
371    }
372}
373
374fn write_indexed_list<T>(f: &mut fmt::Formatter, lst: &[T]) -> Result<(), fmt::Error>
375where
376    T: fmt::Debug,
377{
378    write!(f, "{{")?;
379    let mut nonempty = false;
380    for (i, x) in lst.iter().enumerate() {
381        if nonempty {
382            write!(f, ",")?;
383        }
384        nonempty = true;
385        write!(f, " {i}:{x:?}")?;
386    }
387    if nonempty {
388        write!(f, " ")?;
389    }
390    write!(f, "}}")?;
391    Ok(())
392}
393impl fmt::Debug for Slab {
394    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
395        write!(f, "Slab{{ exprs:")?;
396        write_indexed_list(f, &self.ps.exprs)?;
397        write!(f, ", vals:")?;
398        write_indexed_list(f, &self.ps.vals)?;
399        write!(f, ", instrs:")?;
400        write_indexed_list(f, &self.cs.instrs)?;
401        write!(f, " }}")?;
402        Ok(())
403    }
404}
405impl fmt::Debug for ParseSlab {
406    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
407        write!(f, "ParseSlab{{ exprs:")?;
408        write_indexed_list(f, &self.exprs)?;
409        write!(f, ", vals:")?;
410        write_indexed_list(f, &self.vals)?;
411        write!(f, " }}")?;
412        Ok(())
413    }
414}
415impl fmt::Debug for CompileSlab {
416    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
417        write!(f, "CompileSlab{{ instrs:")?;
418        write_indexed_list(f, &self.instrs)?;
419        write!(f, " }}")?;
420        Ok(())
421    }
422}
423
424impl Default for Slab {
425    fn default() -> Self {
426        Self::with_capacity(64)
427    }
428}