fasteval2/
compiler.rs

1//! This module compiles parsed `Expression`s into an optimized AST node called an `Instruction`.
2//! The compiled form is much faster, especially for constants.
3//!
4//! # Compile-time Optimizations
5//!
6//! ## Constant Folding
7//! Operations with constants can be calculated at compile time so
8//! they don't need to be calculated during `eval()`.
9//!
10//! For example, `1 + x + 1` will be compiled into `x + 2`, saving some time during `eval()`.
11//!
12//! If the entire expression only consists of constants (no variables),
13//! then the expression can be reduced to a final result at compile time,
14//! resulting in near-native speed during `eval()`.
15//!
16//! ## Algebraic Simplification
17//! * Subtraction is converted to Addition.
18//! * Division is converted to Multiplication.
19//! * Built-in functions with constant arguments are evaluated.
20//! * Constant terms are combined.
21//! * Logical operator short-circuits are applied and no-op branches are discarded.
22//!
23//! ## Optimized Memory Layout and Execution
24//! * Variable-length `Expression`/`Value` AST nodes are converted into constant-sized `Instruction` nodes.
25//! * The `IC` enumeration helps to eliminate expensive function calls.
26
27#[cfg(feature = "unsafe-vars")]
28use crate::parser::StdFunc::EUnsafeVar;
29use crate::parser::{
30    BinaryOp::{
31        self, EAdd, EDiv, EExp, EMod, EMul, ESub, EAND, EEQ, EGT, EGTE, ELT, ELTE, ENE, EOR,
32    },
33    ExprPair, Expression, PrintFunc,
34    StdFunc::{
35        self, EFunc, EFuncACos, EFuncACosH, EFuncASin, EFuncASinH, EFuncATan, EFuncATanH, EFuncAbs,
36        EFuncCeil, EFuncCos, EFuncCosH, EFuncE, EFuncFloor, EFuncInt, EFuncLog, EFuncMax, EFuncMin,
37        EFuncPi, EFuncRound, EFuncSign, EFuncSin, EFuncSinH, EFuncTan, EFuncTanH, EVar,
38    },
39    UnaryOp::{self, ENeg, ENot, EParentheses, EPos},
40    Value,
41};
42use crate::slab::{CompileSlab, ParseSlab};
43use crate::Error;
44
45/// `true` --> `1.0`,  `false` --> `0.0`
46#[macro_export]
47macro_rules! bool_to_f64 {
48    ($b:expr) => {
49        if $b {
50            1.0
51        } else {
52            0.0
53        }
54    };
55}
56
57/// An `InstructionI` represents an index into `Slab.cs.instrs`.
58///
59/// It behaves much like a pointer or reference, but it is 'safe' (unlike a raw
60/// pointer) and is not managed by the Rust borrow checker (unlike a reference).
61#[derive(Debug, PartialEq, Copy, Clone)]
62pub struct InstructionI(pub usize);
63
64/// This enumeration boosts performance because it eliminates expensive function calls for constant values.
65#[derive(Debug, PartialEq)]
66pub enum IC {
67    I(InstructionI),
68    C(f64),
69}
70
71macro_rules! instr_to_ic {
72    ($cslab:ident, $instr:ident) => {
73        match $instr {
74            IConst(c) => IC::C(c),
75            _ => IC::I($cslab.push_instr($instr)),
76        }
77    };
78}
79macro_rules! ic_to_instr {
80    ($cslab:expr, $dst:ident, $ic:ident) => {
81        match $ic {
82            IC::C(c) => {
83                $dst = IConst(*c);
84                &$dst
85            }
86            IC::I(i) => get_instr!($cslab, i),
87        }
88    };
89}
90
91/// An `Instruction` is an optimized AST node resulting from compilation.
92#[derive(Debug, PartialEq)]
93pub enum Instruction {
94    //---- Primitive Value Types:
95    IConst(f64),
96
97    //---- Unary Ops:
98    // Parentheses is a noop
99    // Pos is a noop
100    INeg(InstructionI),
101    INot(InstructionI),
102    IInv(InstructionI),
103
104    //---- Binary Math Ops:
105    IAdd(InstructionI, IC),
106    // A Sub(x) is converted to an Add(Neg(x)).
107    IMul(InstructionI, IC),
108    // A Div(n,d) is converted to a Mul(n,Inv(d)).
109    IMod {
110        dividend: IC,
111        divisor: IC,
112    },
113    IExp {
114        base: IC,
115        power: IC,
116    },
117
118    //---- Binary Comparison Ops:
119    ILT(IC, IC),
120    ILTE(IC, IC),
121    IEQ(IC, IC),
122    INE(IC, IC),
123    IGTE(IC, IC),
124    IGT(IC, IC),
125
126    //---- Binary Logic Ops:
127    IOR(InstructionI, IC),
128    IAND(InstructionI, IC),
129
130    //---- Callables:
131    IVar(String),
132    #[cfg(feature = "unsafe-vars")]
133    IUnsafeVar {
134        name: String,
135        ptr: *const f64,
136    },
137    IFunc {
138        name: String,
139        args: Vec<IC>,
140    },
141
142    IFuncInt(InstructionI),
143    IFuncCeil(InstructionI),
144    IFuncFloor(InstructionI),
145    IFuncAbs(InstructionI),
146    IFuncSign(InstructionI),
147    IFuncLog {
148        base: IC,
149        of: IC,
150    },
151    IFuncRound {
152        modulus: IC,
153        of: IC,
154    },
155    IFuncMin(InstructionI, IC),
156    IFuncMax(InstructionI, IC),
157
158    IFuncSin(InstructionI),
159    IFuncCos(InstructionI),
160    IFuncTan(InstructionI),
161    IFuncASin(InstructionI),
162    IFuncACos(InstructionI),
163    IFuncATan(InstructionI),
164    IFuncSinH(InstructionI),
165    IFuncCosH(InstructionI),
166    IFuncTanH(InstructionI),
167    IFuncASinH(InstructionI),
168    IFuncACosH(InstructionI),
169    IFuncATanH(InstructionI),
170
171    IPrintFunc(PrintFunc), // Not optimized (it would be pointless because of i/o bottleneck).
172}
173use crate::{eval_var, EvalNamespace};
174#[cfg(feature = "unsafe-vars")]
175use Instruction::IUnsafeVar;
176use Instruction::{
177    IAdd, IConst, IExp, IFunc, IFuncACos, IFuncACosH, IFuncASin, IFuncASinH, IFuncATan, IFuncATanH,
178    IFuncAbs, IFuncCeil, IFuncCos, IFuncCosH, IFuncFloor, IFuncInt, IFuncLog, IFuncMax, IFuncMin,
179    IFuncRound, IFuncSign, IFuncSin, IFuncSinH, IFuncTan, IFuncTanH, IInv, IMod, IMul, INeg, INot,
180    IPrintFunc, IVar, IAND, IEQ, IGT, IGTE, ILT, ILTE, INE, IOR,
181};
182#[cfg(feature = "unsafe-vars")]
183unsafe impl Send for Instruction {}
184
185impl Default for Instruction {
186    fn default() -> Self {
187        IConst(std::f64::NAN)
188    }
189}
190
191/// You must `use` the `Compiler` trait before you can call `.compile()` on parsed `Expression`s.
192pub trait Compiler {
193    /// Turns a parsed `Expression` into a compiled `Instruction`.
194    ///
195    /// Cannot fail, unless you run out of memory.
196    fn compile(
197        &self,
198        pslab: &ParseSlab,
199        cslab: &mut CompileSlab,
200        ns: &mut impl EvalNamespace,
201    ) -> Instruction;
202}
203
204#[derive(Debug)]
205struct ExprSlice<'s> {
206    first: &'s Value,
207    pairs: Vec<&'s ExprPair>,
208}
209impl<'s> ExprSlice<'s> {
210    fn new(first: &Value) -> ExprSlice<'_> {
211        ExprSlice {
212            first,
213            pairs: Vec::with_capacity(8),
214        }
215    }
216    fn from_expr(expr: &Expression) -> ExprSlice<'_> {
217        let mut sl = ExprSlice::new(&expr.first);
218        for exprpairref in expr.pairs.iter() {
219            sl.pairs.push(exprpairref)
220        }
221        sl
222    }
223    fn split(&self, bop: BinaryOp, dst: &mut Vec<ExprSlice<'s>>) {
224        dst.push(ExprSlice::new(&self.first));
225        for exprpair in self.pairs.iter() {
226            if exprpair.0 == bop {
227                dst.push(ExprSlice::new(&exprpair.1));
228            } else {
229                match dst.last_mut() {
230                    Some(cur) => cur.pairs.push(exprpair),
231                    None => (), // unreachable
232                }
233            }
234        }
235    }
236    fn split_multi(
237        &self,
238        search: &[BinaryOp],
239        xsdst: &mut Vec<ExprSlice<'s>>,
240        opdst: &mut Vec<&'s BinaryOp>,
241    ) {
242        xsdst.push(ExprSlice::new(&self.first));
243        for exprpair in self.pairs.iter() {
244            if search.contains(&exprpair.0) {
245                xsdst.push(ExprSlice::new(&exprpair.1));
246                opdst.push(&exprpair.0);
247            } else {
248                match xsdst.last_mut() {
249                    Some(cur) => cur.pairs.push(exprpair),
250                    None => (), // unreachable
251                }
252            }
253        }
254    }
255}
256
257/// Uses [`EPSILON`](https://doc.rust-lang.org/core/f64/constant.EPSILON.html) to determine equality of two `f64`s.
258#[macro_export]
259macro_rules! f64_eq {
260    ($l:ident, $r:literal) => {
261        ($l - $r).abs() <= 8.0 * std::f64::EPSILON
262    };
263    ($l:ident, $r:ident) => {
264        ($l - $r).abs() <= 8.0 * std::f64::EPSILON
265    };
266    ($l:expr, $r:literal) => {
267        ($l - $r).abs() <= 8.0 * std::f64::EPSILON
268    };
269    ($l:expr, $r:expr) => {
270        (($l) - ($r)).abs() <= 8.0 * std::f64::EPSILON
271    };
272}
273
274/// Uses [`EPSILON`](https://doc.rust-lang.org/core/f64/constant.EPSILON.html) to determine inequality of two `f64`s.
275///
276/// This is exactly the same as saying `!f64_eq(x,y)` but it is slightly more efficient.
277#[macro_export]
278macro_rules! f64_ne {
279    ($l:ident, $r:literal) => {
280        ($l - $r).abs() > 8.0 * std::f64::EPSILON
281    };
282    ($l:ident, $r:ident) => {
283        ($l - $r).abs() > 8.0 * std::f64::EPSILON
284    };
285    ($l:expr, $r:literal) => {
286        ($l - $r).abs() > 8.0 * std::f64::EPSILON
287    };
288    ($l:expr, $r:expr) => {
289        (($l) - ($r)).abs() > 8.0 * std::f64::EPSILON
290    };
291}
292fn neg_wrap(instr: Instruction, cslab: &mut CompileSlab) -> Instruction {
293    if let IConst(c) = instr {
294        IConst(-c)
295    } else if let INeg(i) = instr {
296        cslab.take_instr(i)
297    } else {
298        INeg(cslab.push_instr(instr))
299    }
300}
301fn not_wrap(instr: Instruction, cslab: &mut CompileSlab) -> Instruction {
302    if let IConst(c) = instr {
303        IConst(bool_to_f64!(f64_eq!(c, 0.0)))
304    } else if let INot(i) = instr {
305        cslab.take_instr(i)
306    } else {
307        INot(cslab.push_instr(instr))
308    }
309}
310fn inv_wrap(instr: Instruction, cslab: &mut CompileSlab) -> Instruction {
311    if let IConst(c) = instr {
312        IConst(1.0 / c)
313    } else if let IInv(i) = instr {
314        cslab.take_instr(i)
315    } else {
316        IInv(cslab.push_instr(instr))
317    }
318}
319fn compile_mul(instrs: Vec<Instruction>, cslab: &mut CompileSlab) -> Instruction {
320    let mut out = IConst(1.0);
321    let mut out_set = false;
322    let mut const_prod = 1.0;
323    for instr in instrs {
324        if let IConst(c) = instr {
325            const_prod *= c; // Floats don't overflow.
326        } else {
327            if out_set {
328                out = IMul(cslab.push_instr(out), IC::I(cslab.push_instr(instr)));
329            } else {
330                out = instr;
331                out_set = true;
332            }
333        }
334    }
335    if f64_ne!(const_prod, 1.0) {
336        if out_set {
337            out = IMul(cslab.push_instr(out), IC::C(const_prod));
338        } else {
339            out = IConst(const_prod);
340        }
341    }
342    out
343}
344fn compile_add(instrs: Vec<Instruction>, cslab: &mut CompileSlab) -> Instruction {
345    let mut out = IConst(0.0);
346    let mut out_set = false;
347    let mut const_sum = 0.0;
348    for instr in instrs {
349        if let IConst(c) = instr {
350            const_sum += c; // Floats don't overflow.
351        } else {
352            if out_set {
353                out = IAdd(cslab.push_instr(out), IC::I(cslab.push_instr(instr)));
354            } else {
355                out = instr;
356                out_set = true;
357            }
358        }
359    }
360    if f64_ne!(const_sum, 0.0) {
361        if out_set {
362            out = IAdd(cslab.push_instr(out), IC::C(const_sum));
363        } else {
364            out = IConst(const_sum);
365        }
366    }
367    out
368}
369pub(crate) fn log(base: f64, n: f64) -> f64 {
370    // Can't use floating point in 'match' patterns.  :(
371    if f64_eq!(base, 2.0) {
372        return n.log2();
373    }
374    if f64_eq!(base, 10.0) {
375        return n.log10();
376    }
377    n.log(base)
378}
379
380// Can't inline recursive functions:
381fn push_mul_leaves(
382    instrs: &mut Vec<Instruction>,
383    cslab: &mut CompileSlab,
384    li: InstructionI,
385    ric: IC,
386) {
387    // Take 'r' before 'l' for a chance for more efficient memory usage:
388    match ric {
389        IC::I(ri) => {
390            let instr = cslab.take_instr(ri);
391            if let IMul(rli, rric) = instr {
392                push_mul_leaves(instrs, cslab, rli, rric);
393            } else {
394                instrs.push(instr);
395            }
396        }
397        IC::C(c) => instrs.push(IConst(c)),
398    };
399
400    let instr = cslab.take_instr(li);
401    if let IMul(lli, lric) = instr {
402        push_mul_leaves(instrs, cslab, lli, lric);
403    } else {
404        instrs.push(instr);
405    }
406}
407fn push_add_leaves(
408    instrs: &mut Vec<Instruction>,
409    cslab: &mut CompileSlab,
410    li: InstructionI,
411    ric: IC,
412) {
413    // Take 'r' before 'l' for a chance for more efficient memory usage:
414    match ric {
415        IC::I(ri) => {
416            let instr = cslab.take_instr(ri);
417            if let IAdd(rli, rric) = instr {
418                push_add_leaves(instrs, cslab, rli, rric);
419            } else {
420                instrs.push(instr);
421            }
422        }
423        IC::C(c) => instrs.push(IConst(c)),
424    };
425
426    let instr = cslab.take_instr(li);
427    if let IAdd(lli, lric) = instr {
428        push_add_leaves(instrs, cslab, lli, lric);
429    } else {
430        instrs.push(instr);
431    }
432}
433
434impl Compiler for ExprSlice<'_> {
435    fn compile(
436        &self,
437        pslab: &ParseSlab,
438        cslab: &mut CompileSlab,
439        ns: &mut impl EvalNamespace,
440    ) -> Instruction {
441        // Associative:  (2+3)+4 = 2+(3+4)
442        // Commutative:  1+2 = 2+1
443        //
444        //          Only         Only
445        // Neither  Associative  Commutative  Both
446        // -------  -----------  -----------  ----
447        // GTE      (none)       (none)       OR
448        // LTE                                AND
449        // GT                                 NE
450        // LT                                 EQ
451        // Minus (opt with neg & add)         Plus
452        // Div (opt with inv & mul)           Mul
453        // Mod
454        // Exp
455
456        // Find the lowest-priority BinaryOp:
457        let mut lowest_op = match self.pairs.first() {
458            Some(p0) => p0.0,
459            None => return self.first.compile(pslab, cslab, ns),
460        };
461        for exprpair in self.pairs.iter() {
462            if exprpair.0 < lowest_op {
463                lowest_op = exprpair.0
464            }
465        }
466
467        // All comparisons have equal precedence:
468        if lowest_op == EEQ
469            || lowest_op == ENE
470            || lowest_op == ELT
471            || lowest_op == EGT
472            || lowest_op == ELTE
473            || lowest_op == EGTE
474        {
475            let mut ops = Vec::<&BinaryOp>::with_capacity(4);
476            let mut xss = Vec::<ExprSlice>::with_capacity(ops.len() + 1);
477            self.split_multi(&[EEQ, ENE, ELT, EGT, ELTE, EGTE], &mut xss, &mut ops);
478            let mut out = match xss.first() {
479                Some(xs) => xs.compile(pslab, cslab, ns),
480                None => IConst(std::f64::NAN), // unreachable
481            };
482            for (i, op) in ops.into_iter().enumerate() {
483                let instr = match xss.get(i + 1) {
484                    Some(xs) => xs.compile(pslab, cslab, ns),
485                    None => IConst(std::f64::NAN), // unreachable
486                };
487                if let IConst(l) = out {
488                    if let IConst(r) = instr {
489                        out = match op {
490                            EEQ => IConst(bool_to_f64!(f64_eq!(l, r))),
491                            ENE => IConst(bool_to_f64!(f64_ne!(l, r))),
492                            ELT => IConst(bool_to_f64!(l < r)),
493                            EGT => IConst(bool_to_f64!(l > r)),
494                            ELTE => IConst(bool_to_f64!(l <= r)),
495                            EGTE => IConst(bool_to_f64!(l >= r)),
496                            _ => IConst(std::f64::NAN), // unreachable
497                        };
498                        continue;
499                    }
500                }
501                out = match op {
502                    EEQ => IEQ(instr_to_ic!(cslab, out), instr_to_ic!(cslab, instr)),
503                    ENE => INE(instr_to_ic!(cslab, out), instr_to_ic!(cslab, instr)),
504                    ELT => ILT(instr_to_ic!(cslab, out), instr_to_ic!(cslab, instr)),
505                    EGT => IGT(instr_to_ic!(cslab, out), instr_to_ic!(cslab, instr)),
506                    ELTE => ILTE(instr_to_ic!(cslab, out), instr_to_ic!(cslab, instr)),
507                    EGTE => IGTE(instr_to_ic!(cslab, out), instr_to_ic!(cslab, instr)),
508                    _ => IConst(std::f64::NAN), // unreachable
509                };
510            }
511            return out;
512        }
513
514        match lowest_op {
515            EOR => {
516                let mut xss = Vec::<ExprSlice>::with_capacity(4);
517                self.split(EOR, &mut xss);
518                let mut out = IConst(0.0);
519                let mut out_set = false;
520                for xs in xss.iter() {
521                    let instr = xs.compile(pslab, cslab, ns);
522                    if out_set {
523                        out = IOR(cslab.push_instr(out), instr_to_ic!(cslab, instr));
524                    } else {
525                        if let IConst(c) = instr {
526                            if f64_ne!(c, 0.0) {
527                                return instr;
528                            }
529                            // out = instr;     // Skip this 0 value (mostly so I don't complicate my logic in 'if out_set' since I can assume that any set value is non-const).
530                            // out_set = true;
531                        } else {
532                            out = instr;
533                            out_set = true;
534                        }
535                    }
536                }
537                out
538            }
539            EAND => {
540                let mut xss = Vec::<ExprSlice>::with_capacity(4);
541                self.split(EAND, &mut xss);
542                let mut out = IConst(1.0);
543                let mut out_set = false;
544                for xs in xss.iter() {
545                    let instr = xs.compile(pslab, cslab, ns);
546                    if let IConst(c) = instr {
547                        if f64_eq!(c, 0.0) {
548                            return instr;
549                        }
550                    }
551                    if out_set {
552                        if let IConst(_) = out {
553                            // If we get here, we know that the const is non-zero.
554                            out = instr;
555                        } else {
556                            out = IAND(cslab.push_instr(out), instr_to_ic!(cslab, instr));
557                        }
558                    } else {
559                        out = instr;
560                        out_set = true;
561                    }
562                }
563                out
564            }
565            EAdd => {
566                let mut xss = Vec::<ExprSlice>::with_capacity(4);
567                self.split(EAdd, &mut xss);
568                let mut instrs = Vec::<Instruction>::with_capacity(xss.len());
569                for xs in xss {
570                    let instr = xs.compile(pslab, cslab, ns);
571                    if let IAdd(li, ric) = instr {
572                        push_add_leaves(&mut instrs, cslab, li, ric); // Flatten nested structures like "x - 1 + 2 - 3".
573                    } else {
574                        instrs.push(instr);
575                    }
576                }
577                compile_add(instrs, cslab)
578            }
579            ESub => {
580                // Note: We don't need to push_add_leaves from here because Sub has a higher precedence than Add.
581
582                let mut xss = Vec::<ExprSlice>::with_capacity(4);
583                self.split(ESub, &mut xss);
584                let mut instrs = Vec::<Instruction>::with_capacity(xss.len());
585                for (i, xs) in xss.into_iter().enumerate() {
586                    let instr = xs.compile(pslab, cslab, ns);
587                    if i == 0 {
588                        instrs.push(instr);
589                    } else {
590                        instrs.push(neg_wrap(instr, cslab));
591                    }
592                }
593                compile_add(instrs, cslab)
594            }
595            EMul => {
596                let mut xss = Vec::<ExprSlice>::with_capacity(4);
597                self.split(EMul, &mut xss);
598                let mut instrs = Vec::<Instruction>::with_capacity(xss.len());
599                for xs in xss {
600                    let instr = xs.compile(pslab, cslab, ns);
601                    if let IMul(li, ric) = instr {
602                        push_mul_leaves(&mut instrs, cslab, li, ric); // Flatten nested structures like "deg/360 * 2*pi()".
603                    } else {
604                        instrs.push(instr);
605                    }
606                }
607                compile_mul(instrs, cslab)
608            }
609            EDiv => {
610                // Note: We don't need to push_mul_leaves from here because Div has a higher precedence than Mul.
611
612                let mut xss = Vec::<ExprSlice>::with_capacity(4);
613                self.split(EDiv, &mut xss);
614                let mut instrs = Vec::<Instruction>::with_capacity(xss.len());
615                for (i, xs) in xss.into_iter().enumerate() {
616                    let instr = xs.compile(pslab, cslab, ns);
617                    if i == 0 {
618                        instrs.push(instr);
619                    } else {
620                        instrs.push(inv_wrap(instr, cslab));
621                    }
622                }
623                compile_mul(instrs, cslab)
624            }
625            //          EDiv => {
626            //              let mut xss = Vec::<ExprSlice>::with_capacity(4);
627            //              self.split(EDiv, &mut xss);
628            //              let mut out = IConst(1.0); let mut out_set = false;
629            //              let mut const_prod = 1.0;
630            //              let mut is_first = true;
631            //              for xs in xss.iter() {
632            //                  let instr = xs.compile(pslab,cslab,ns);
633            //                  if let IConst(c) = instr {
634            //                      if is_first {
635            //                          const_prod *= c;  // Floats don't overflow.
636            //                      } else {
637            //                          const_prod /= c;
638            //                      }
639            //                  } else {
640            //                      if is_first {
641            //                          if out_set {
642            //                              out = IMul(cslab.push_instr(out), cslab.push_instr(instr));
643            //                          } else {
644            //                              out = instr;
645            //                              out_set = true;
646            //                          }
647            //                      } else {
648            //                          let instr = inv_wrap(instr,cslab);
649            //                          if out_set {
650            //                              out = IMul(cslab.push_instr(out), cslab.push_instr(instr));
651            //                          } else {
652            //                              out = instr;
653            //                              out_set = true;
654            //                          }
655            //                      }
656            //                  }
657            //                  is_first = false;
658            //              }
659            //              if f64_ne!(const_prod,1.0) {
660            //                  if out_set {
661            //                      out = IMul(cslab.push_instr(out), cslab.push_instr(IConst(const_prod)));
662            //                  } else {
663            //                      out = IConst(const_prod);
664            //                  }
665            //              }
666            //              out
667            //          }
668            EMod => {
669                let mut xss = Vec::<ExprSlice>::with_capacity(2);
670                self.split(EMod, &mut xss);
671                let mut out = IConst(0.0);
672                let mut out_set = false;
673                for xs in xss.iter() {
674                    let instr = xs.compile(pslab, cslab, ns);
675                    if out_set {
676                        if let IConst(dividend) = out {
677                            if let IConst(divisor) = instr {
678                                out = IConst(dividend % divisor);
679                                continue;
680                            }
681                        }
682                        out = IMod {
683                            dividend: instr_to_ic!(cslab, out),
684                            divisor: instr_to_ic!(cslab, instr),
685                        };
686                    } else {
687                        out = instr;
688                        out_set = true;
689                    }
690                }
691                out
692            }
693            EExp => {
694                // Right-to-Left Associativity
695                let mut xss = Vec::<ExprSlice>::with_capacity(2);
696                self.split(EExp, &mut xss);
697                let mut out = IConst(0.0);
698                let mut out_set = false;
699                for xs in xss.into_iter().rev() {
700                    let instr = xs.compile(pslab, cslab, ns);
701                    if out_set {
702                        if let IConst(power) = out {
703                            if let IConst(base) = instr {
704                                out = IConst(base.powf(power));
705                                continue;
706                            }
707                        }
708                        out = IExp {
709                            base: instr_to_ic!(cslab, instr),
710                            power: instr_to_ic!(cslab, out),
711                        };
712                    } else {
713                        out = instr;
714                        out_set = true;
715                    }
716                }
717                out
718            }
719            //          EExp => {  // Left-to-Right Associativity
720            //              let mut xss = Vec::<ExprSlice>::with_capacity(2);
721            //              self.split(EExp, &mut xss);
722            //              let mut pow_instrs = Vec::<Instruction>::with_capacity(xss.len()-1);
723            //              let mut base = IConst(0.0);
724            //              for (i,xs) in xss.into_iter().enumerate() {
725            //                  let instr = xs.compile(pslab,cslab,ns);
726            //                  if i==0 {
727            //                      base = instr;
728            //                  } else {
729            //                      pow_instrs.push(instr);
730            //                  }
731            //              }
732            //              let power = compile_mul(pow_instrs,cslab);
733            //              if let IConst(b) = base {
734            //                  if let IConst(p) = power {
735            //                      return IConst(b.powf(p));
736            //                  }
737            //              }
738            //              IExp{base:cslab.push_instr(base), power:cslab.push_instr(power)}
739            //          }
740            ENE | EEQ | EGTE | ELTE | EGT | ELT => IConst(std::f64::NAN), // unreachable
741        }
742    }
743}
744
745impl Compiler for Expression {
746    fn compile(
747        &self,
748        pslab: &ParseSlab,
749        cslab: &mut CompileSlab,
750        ns: &mut impl EvalNamespace,
751    ) -> Instruction {
752        let top = ExprSlice::from_expr(&self);
753        top.compile(pslab, cslab, ns)
754    }
755}
756
757impl Compiler for Value {
758    fn compile(
759        &self,
760        pslab: &ParseSlab,
761        cslab: &mut CompileSlab,
762        ns: &mut impl EvalNamespace,
763    ) -> Instruction {
764        match self {
765            Value::EConstant(c) => IConst(*c),
766            Value::EUnaryOp(u) => u.compile(pslab, cslab, ns),
767            Value::EStdFunc(f) => f.compile(pslab, cslab, ns),
768            Value::EPrintFunc(pf) => IPrintFunc(pf.clone()),
769        }
770    }
771}
772
773impl Compiler for UnaryOp {
774    fn compile(
775        &self,
776        pslab: &ParseSlab,
777        cslab: &mut CompileSlab,
778        ns: &mut impl EvalNamespace,
779    ) -> Instruction {
780        match self {
781            EPos(i) => get_val!(pslab, i).compile(pslab, cslab, ns),
782            ENeg(i) => {
783                let instr = get_val!(pslab, i).compile(pslab, cslab, ns);
784                if let IConst(c) = instr {
785                    IConst(-c)
786                } else {
787                    neg_wrap(instr, cslab)
788                }
789            }
790            ENot(i) => {
791                let instr = get_val!(pslab, i).compile(pslab, cslab, ns);
792                if let IConst(c) = instr {
793                    IConst(bool_to_f64!(f64_eq!(c, 0.0)))
794                } else {
795                    not_wrap(instr, cslab)
796                }
797            }
798            EParentheses(i) => get_expr!(pslab, i).compile(pslab, cslab, ns),
799        }
800    }
801}
802
803impl Compiler for StdFunc {
804    fn compile(
805        &self,
806        pslab: &ParseSlab,
807        cslab: &mut CompileSlab,
808        ns: &mut impl EvalNamespace,
809    ) -> Instruction {
810        match self {
811            EVar(name) => IVar(name.clone()),
812            #[cfg(feature = "unsafe-vars")]
813            EUnsafeVar { name, ptr } => IUnsafeVar {
814                name: name.clone(),
815                ptr: *ptr,
816            },
817            EFunc { name, args: xis } => {
818                let mut args = Vec::<IC>::with_capacity(xis.len());
819                let mut f64_args = Vec::<f64>::with_capacity(xis.len());
820                let mut is_all_const = true;
821                for xi in xis {
822                    let instr = get_expr!(pslab, xi).compile(pslab, cslab, ns);
823                    if let IConst(c) = instr {
824                        f64_args.push(c)
825                    } else {
826                        is_all_const = false;
827                    }
828                    args.push(instr_to_ic!(cslab, instr));
829                }
830                if is_all_const {
831                    let computed_value = eval_var!(ns, name, f64_args, unsafe {
832                        &mut *(&pslab.char_buf as *const _ as *mut _)
833                    });
834                    if let Ok(value) = computed_value {
835                        IConst(value)
836                    } else {
837                        IFunc {
838                            name: name.clone(),
839                            args,
840                        }
841                    }
842                } else {
843                    IFunc {
844                        name: name.clone(),
845                        args,
846                    }
847                }
848            }
849
850            EFuncInt(i) => {
851                let instr = get_expr!(pslab, i).compile(pslab, cslab, ns);
852                if let IConst(c) = instr {
853                    IConst(c.trunc())
854                } else {
855                    IFuncInt(cslab.push_instr(instr))
856                }
857            }
858            EFuncCeil(i) => {
859                let instr = get_expr!(pslab, i).compile(pslab, cslab, ns);
860                if let IConst(c) = instr {
861                    IConst(c.ceil())
862                } else {
863                    IFuncCeil(cslab.push_instr(instr))
864                }
865            }
866            EFuncFloor(i) => {
867                let instr = get_expr!(pslab, i).compile(pslab, cslab, ns);
868                if let IConst(c) = instr {
869                    IConst(c.floor())
870                } else {
871                    IFuncFloor(cslab.push_instr(instr))
872                }
873            }
874            EFuncAbs(i) => {
875                let instr = get_expr!(pslab, i).compile(pslab, cslab, ns);
876                if let IConst(c) = instr {
877                    IConst(c.abs())
878                } else {
879                    IFuncAbs(cslab.push_instr(instr))
880                }
881            }
882            EFuncSign(i) => {
883                let instr = get_expr!(pslab, i).compile(pslab, cslab, ns);
884                if let IConst(c) = instr {
885                    IConst(c.signum())
886                } else {
887                    IFuncSign(cslab.push_instr(instr))
888                }
889            }
890            EFuncLog {
891                base: baseopt,
892                expr: i,
893            } => {
894                let base = match baseopt {
895                    Some(bi) => get_expr!(pslab, bi).compile(pslab, cslab, ns),
896                    None => IConst(10.0),
897                };
898                let instr = get_expr!(pslab, i).compile(pslab, cslab, ns);
899                if let IConst(b) = base {
900                    if let IConst(n) = instr {
901                        return IConst(log(b, n));
902                    }
903                }
904                IFuncLog {
905                    base: instr_to_ic!(cslab, base),
906                    of: instr_to_ic!(cslab, instr),
907                }
908            }
909            EFuncRound {
910                modulus: modopt,
911                expr: i,
912            } => {
913                let modulus = match modopt {
914                    Some(mi) => get_expr!(pslab, mi).compile(pslab, cslab, ns),
915                    None => IConst(1.0),
916                };
917                let instr = get_expr!(pslab, i).compile(pslab, cslab, ns);
918                if let IConst(m) = modulus {
919                    if let IConst(n) = instr {
920                        return IConst((n / m).round() * m); // Floats don't overflow.
921                    }
922                }
923                IFuncRound {
924                    modulus: instr_to_ic!(cslab, modulus),
925                    of: instr_to_ic!(cslab, instr),
926                }
927            }
928            EFuncMin {
929                first: fi,
930                rest: is,
931            } => {
932                let first = get_expr!(pslab, fi).compile(pslab, cslab, ns);
933                let mut rest = Vec::<Instruction>::with_capacity(is.len());
934                for i in is {
935                    rest.push(get_expr!(pslab, i).compile(pslab, cslab, ns));
936                }
937                let mut out = IConst(0.0);
938                let mut out_set = false;
939                let mut const_min = 0.0;
940                let mut const_min_set = false;
941                if let IConst(f) = first {
942                    const_min = f;
943                    const_min_set = true;
944                } else {
945                    out = first;
946                    out_set = true;
947                }
948                for instr in rest {
949                    if let IConst(f) = instr {
950                        if const_min_set {
951                            if f < const_min {
952                                const_min = f;
953                            }
954                        } else {
955                            const_min = f;
956                            const_min_set = true;
957                        }
958                    } else {
959                        if out_set {
960                            out = IFuncMin(cslab.push_instr(out), IC::I(cslab.push_instr(instr)));
961                        } else {
962                            out = instr;
963                            out_set = true;
964                        }
965                    }
966                }
967                if const_min_set {
968                    if out_set {
969                        out = IFuncMin(cslab.push_instr(out), IC::C(const_min));
970                    } else {
971                        out = IConst(const_min);
972                        // out_set = true;  // Comment out so the compiler doesn't complain about unused assignments.
973                    }
974                }
975                //assert!(out_set);
976                out
977            }
978            EFuncMax {
979                first: fi,
980                rest: is,
981            } => {
982                let first = get_expr!(pslab, fi).compile(pslab, cslab, ns);
983                let mut rest = Vec::<Instruction>::with_capacity(is.len());
984                for i in is {
985                    rest.push(get_expr!(pslab, i).compile(pslab, cslab, ns));
986                }
987                let mut out = IConst(0.0);
988                let mut out_set = false;
989                let mut const_max = 0.0;
990                let mut const_max_set = false;
991                if let IConst(f) = first {
992                    const_max = f;
993                    const_max_set = true;
994                } else {
995                    out = first;
996                    out_set = true;
997                }
998                for instr in rest {
999                    if let IConst(f) = instr {
1000                        if const_max_set {
1001                            if f > const_max {
1002                                const_max = f;
1003                            }
1004                        } else {
1005                            const_max = f;
1006                            const_max_set = true;
1007                        }
1008                    } else {
1009                        if out_set {
1010                            out = IFuncMax(cslab.push_instr(out), IC::I(cslab.push_instr(instr)));
1011                        } else {
1012                            out = instr;
1013                            out_set = true;
1014                        }
1015                    }
1016                }
1017                if const_max_set {
1018                    if out_set {
1019                        out = IFuncMax(cslab.push_instr(out), IC::C(const_max));
1020                    } else {
1021                        out = IConst(const_max);
1022                        // out_set = true;  // Comment out so the compiler doesn't complain about unused assignments.
1023                    }
1024                }
1025                //assert!(out_set);
1026                out
1027            }
1028
1029            EFuncE => IConst(std::f64::consts::E),
1030            EFuncPi => IConst(std::f64::consts::PI),
1031
1032            EFuncSin(i) => {
1033                let instr = get_expr!(pslab, i).compile(pslab, cslab, ns);
1034                if let IConst(c) = instr {
1035                    IConst(c.sin())
1036                } else {
1037                    IFuncSin(cslab.push_instr(instr))
1038                }
1039            }
1040            EFuncCos(i) => {
1041                let instr = get_expr!(pslab, i).compile(pslab, cslab, ns);
1042                if let IConst(c) = instr {
1043                    IConst(c.cos())
1044                } else {
1045                    IFuncCos(cslab.push_instr(instr))
1046                }
1047            }
1048            EFuncTan(i) => {
1049                let instr = get_expr!(pslab, i).compile(pslab, cslab, ns);
1050                if let IConst(c) = instr {
1051                    IConst(c.tan())
1052                } else {
1053                    IFuncTan(cslab.push_instr(instr))
1054                }
1055            }
1056            EFuncASin(i) => {
1057                let instr = get_expr!(pslab, i).compile(pslab, cslab, ns);
1058                if let IConst(c) = instr {
1059                    IConst(c.asin())
1060                } else {
1061                    IFuncASin(cslab.push_instr(instr))
1062                }
1063            }
1064            EFuncACos(i) => {
1065                let instr = get_expr!(pslab, i).compile(pslab, cslab, ns);
1066                if let IConst(c) = instr {
1067                    IConst(c.acos())
1068                } else {
1069                    IFuncACos(cslab.push_instr(instr))
1070                }
1071            }
1072            EFuncATan(i) => {
1073                let instr = get_expr!(pslab, i).compile(pslab, cslab, ns);
1074                if let IConst(c) = instr {
1075                    IConst(c.atan())
1076                } else {
1077                    IFuncATan(cslab.push_instr(instr))
1078                }
1079            }
1080            EFuncSinH(i) => {
1081                let instr = get_expr!(pslab, i).compile(pslab, cslab, ns);
1082                if let IConst(c) = instr {
1083                    IConst(c.sinh())
1084                } else {
1085                    IFuncSinH(cslab.push_instr(instr))
1086                }
1087            }
1088            EFuncCosH(i) => {
1089                let instr = get_expr!(pslab, i).compile(pslab, cslab, ns);
1090                if let IConst(c) = instr {
1091                    IConst(c.cosh())
1092                } else {
1093                    IFuncCosH(cslab.push_instr(instr))
1094                }
1095            }
1096            EFuncTanH(i) => {
1097                let instr = get_expr!(pslab, i).compile(pslab, cslab, ns);
1098                if let IConst(c) = instr {
1099                    IConst(c.tanh())
1100                } else {
1101                    IFuncTanH(cslab.push_instr(instr))
1102                }
1103            }
1104            EFuncASinH(i) => {
1105                let instr = get_expr!(pslab, i).compile(pslab, cslab, ns);
1106                if let IConst(c) = instr {
1107                    IConst(c.asinh())
1108                } else {
1109                    IFuncASinH(cslab.push_instr(instr))
1110                }
1111            }
1112            EFuncACosH(i) => {
1113                let instr = get_expr!(pslab, i).compile(pslab, cslab, ns);
1114                if let IConst(c) = instr {
1115                    IConst(c.acosh())
1116                } else {
1117                    IFuncACosH(cslab.push_instr(instr))
1118                }
1119            }
1120            EFuncATanH(i) => {
1121                let instr = get_expr!(pslab, i).compile(pslab, cslab, ns);
1122                if let IConst(c) = instr {
1123                    IConst(c.atanh())
1124                } else {
1125                    IFuncATanH(cslab.push_instr(instr))
1126                }
1127            }
1128        }
1129    }
1130}