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(), °); } // `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}