Skip to main content

zsh/
math.rs

1//! Mathematical expression evaluation for zshrs
2//!
3//! Direct port from zsh/Src/math.c
4//!
5//! Supports:
6//! - Integer and floating point arithmetic
7//! - All C operators (+, -, *, /, %, <<, >>, &, |, ^, etc.)
8//! - Zsh ** power operator
9//! - Comparison operators (<, >, <=, >=, ==, !=)
10//! - Logical operators (&&, ||, !)
11//! - Ternary operator (? :)
12//! - Assignment operators (=, +=, -=, *=, /=, etc.)
13//! - Pre/post increment/decrement (++, --)
14//! - Base conversion (`16#FF`, `2#1010`, `[16]FF`)
15//! - Special values (Inf, NaN)
16//! - Variable references and assignment
17
18use std::collections::HashMap;
19
20/// Math number - can be integer or float
21#[derive(Debug, Clone, Copy)]
22pub enum MathNum {
23    Integer(i64),
24    Float(f64),
25    Unset,
26}
27
28impl Default for MathNum {
29    fn default() -> Self {
30        MathNum::Integer(0)
31    }
32}
33
34impl MathNum {
35    pub fn is_zero(&self) -> bool {
36        match self {
37            MathNum::Integer(n) => *n == 0,
38            MathNum::Float(f) => *f == 0.0,
39            MathNum::Unset => true,
40        }
41    }
42
43    pub fn to_int(&self) -> i64 {
44        match self {
45            MathNum::Integer(n) => *n,
46            MathNum::Float(f) => *f as i64,
47            MathNum::Unset => 0,
48        }
49    }
50
51    pub fn to_float(&self) -> f64 {
52        match self {
53            MathNum::Integer(n) => *n as f64,
54            MathNum::Float(f) => *f,
55            MathNum::Unset => 0.0,
56        }
57    }
58
59    pub fn is_float(&self) -> bool {
60        matches!(self, MathNum::Float(_))
61    }
62
63    pub fn is_integer(&self) -> bool {
64        matches!(self, MathNum::Integer(_))
65    }
66}
67
68/// Math tokens - from math.c
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70#[repr(u8)]
71enum MathTok {
72    InPar = 0,      // (
73    OutPar = 1,     // )
74    Not = 2,        // !
75    Comp = 3,       // ~
76    PostPlus = 4,   // x++
77    PostMinus = 5,  // x--
78    UPlus = 6,      // +x
79    UMinus = 7,     // -x
80    And = 8,        // &
81    Xor = 9,        // ^
82    Or = 10,        // |
83    Mul = 11,       // *
84    Div = 12,       // /
85    Mod = 13,       // %
86    Plus = 14,      // +
87    Minus = 15,     // -
88    ShLeft = 16,    // <<
89    ShRight = 17,   // >>
90    Les = 18,       // <
91    Leq = 19,       // <=
92    Gre = 20,       // >
93    Geq = 21,       // >=
94    Deq = 22,       // ==
95    Neq = 23,       // !=
96    DAnd = 24,      // &&
97    DOr = 25,       // ||
98    DXor = 26,      // ^^
99    Quest = 27,     // ?
100    Colon = 28,     // :
101    Eq = 29,        // =
102    PlusEq = 30,    // +=
103    MinusEq = 31,   // -=
104    MulEq = 32,     // *=
105    DivEq = 33,     // /=
106    ModEq = 34,     // %=
107    AndEq = 35,     // &=
108    XorEq = 36,     // ^=
109    OrEq = 37,      // |=
110    ShLeftEq = 38,  // <<=
111    ShRightEq = 39, // >>=
112    DAndEq = 40,    // &&=
113    DOrEq = 41,     // ||=
114    DXorEq = 42,    // ^^=
115    Comma = 43,     // ,
116    Eoi = 44,       // end of input
117    PrePlus = 45,   // ++x
118    PreMinus = 46,  // --x
119    Num = 47,       // number literal
120    Id = 48,        // identifier
121    Power = 49,     // **
122    CId = 50,       // #identifier (char value)
123    PowerEq = 51,   // **=
124    Func = 52,      // function call
125}
126
127const TOKCOUNT: usize = 53;
128
129/// Operator associativity and type flags
130const LR: u16 = 0x0000; // left-to-right
131const RL: u16 = 0x0001; // right-to-left
132const BOOL: u16 = 0x0002; // short-circuit boolean
133
134const OP_A2: u16 = 0x0004; // 2 arguments
135const OP_A2IR: u16 = 0x0008; // 2 args, return int
136const OP_A2IO: u16 = 0x0010; // 2 args, must be int
137const OP_E2: u16 = 0x0020; // 2 args with assignment
138const OP_E2IO: u16 = 0x0040; // 2 args assign, must be int
139const OP_OP: u16 = 0x0080; // expecting operator position
140const OP_OPF: u16 = 0x0100; // followed by operator (after this, next is operator)
141
142/// Zsh precedence table (default)
143static Z_PREC: [u8; TOKCOUNT] = [
144    1, 137, 2, 2, 2, // InPar OutPar Not Comp PostPlus
145    2, 2, 2, 4, 5, // PostMinus UPlus UMinus And Xor
146    6, 8, 8, 8, 9, // Or Mul Div Mod Plus
147    9, 3, 3, 10, 10, // Minus ShLeft ShRight Les Leq
148    10, 10, 11, 11, 12, // Gre Geq Deq Neq DAnd
149    13, 13, 14, 15, 16, // DOr DXor Quest Colon Eq
150    16, 16, 16, 16, 16, // PlusEq MinusEq MulEq DivEq ModEq
151    16, 16, 16, 16, 16, // AndEq XorEq OrEq ShLeftEq ShRightEq
152    16, 16, 16, 17, 200, // DAndEq DOrEq DXorEq Comma Eoi
153    2, 2, 0, 0, 7, // PrePlus PreMinus Num Id Power
154    0, 16, 0, // CId PowerEq Func
155];
156
157/// C precedence table (used with C_PRECEDENCES option)
158static C_PREC: [u8; TOKCOUNT] = [
159    1, 137, 2, 2, 2, 2, 2, 2, 9, 10, 11, 4, 4, 4, 5, 5, 6, 6, 7, 7, 7, 7, 8, 8, 12, 14, 13, 15, 16,
160    17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 200, 2, 2, 0, 0, 3, 0, 17, 0,
161];
162
163/// Operator type table (matches C math.c type[] array)
164static OP_TYPE: [u16; TOKCOUNT] = [
165    // InPar, OutPar, Not, Comp, PostPlus
166    LR,
167    LR | OP_OP | OP_OPF,
168    RL,
169    RL,
170    RL | OP_OP | OP_OPF,
171    // PostMinus, UPlus, UMinus, And, Xor
172    RL | OP_OP | OP_OPF,
173    RL,
174    RL,
175    LR | OP_A2IO,
176    LR | OP_A2IO,
177    // Or, Mul, Div, Mod, Plus
178    LR | OP_A2IO,
179    LR | OP_A2,
180    LR | OP_A2,
181    LR | OP_A2,
182    LR | OP_A2,
183    // Minus, ShLeft, ShRight, Les, Leq
184    LR | OP_A2,
185    LR | OP_A2IO,
186    LR | OP_A2IO,
187    LR | OP_A2IR,
188    LR | OP_A2IR,
189    // Gre, Geq, Deq, Neq, DAnd
190    LR | OP_A2IR,
191    LR | OP_A2IR,
192    LR | OP_A2IR,
193    LR | OP_A2IR,
194    BOOL | OP_A2IO,
195    // DOr, DXor, Quest, Colon, Eq
196    BOOL | OP_A2IO,
197    LR | OP_A2IO,
198    RL | OP_OP,
199    RL | OP_OP,
200    RL | OP_E2,
201    // PlusEq, MinusEq, MulEq, DivEq, ModEq
202    RL | OP_E2,
203    RL | OP_E2,
204    RL | OP_E2,
205    RL | OP_E2,
206    RL | OP_E2,
207    // AndEq, XorEq, OrEq, ShLeftEq, ShRightEq
208    RL | OP_E2IO,
209    RL | OP_E2IO,
210    RL | OP_E2IO,
211    RL | OP_E2IO,
212    RL | OP_E2IO,
213    // DAndEq, DOrEq, DXorEq, Comma, Eoi
214    BOOL | OP_E2IO,
215    BOOL | OP_E2IO,
216    RL | OP_A2IO,
217    RL | OP_A2,
218    RL | OP_OP,
219    // PrePlus, PreMinus, Num, Id, Power
220    RL,
221    RL,
222    LR | OP_OPF,
223    LR | OP_OPF,
224    RL | OP_A2,
225    // CId, PowerEq, Func
226    LR | OP_OPF,
227    RL | OP_E2,
228    LR | OP_OPF,
229];
230
231/// Stack value for the evaluator
232#[derive(Clone)]
233struct MathValue {
234    val: MathNum,
235    lval: Option<String>,
236}
237
238impl Default for MathValue {
239    fn default() -> Self {
240        MathValue {
241            val: MathNum::Integer(0),
242            lval: None,
243        }
244    }
245}
246
247/// Math evaluator state
248pub struct MathEval<'a> {
249    input: &'a str,
250    pos: usize,
251    yyval: MathNum,
252    yylval: String,
253    stack: Vec<MathValue>,
254    mtok: MathTok,
255    unary: bool,
256    noeval: i32,
257    lastbase: i32,
258    prec: &'static [u8; TOKCOUNT],
259    c_precedences: bool,
260    force_float: bool,
261    octal_zeroes: bool,
262    variables: HashMap<String, MathNum>,
263    lastval: i32,
264    pid: i64,
265    error: Option<String>,
266}
267
268impl<'a> MathEval<'a> {
269    pub fn new(input: &'a str) -> Self {
270        MathEval {
271            input,
272            pos: 0,
273            yyval: MathNum::Integer(0),
274            yylval: String::new(),
275            stack: Vec::with_capacity(100),
276            mtok: MathTok::Eoi,
277            unary: true,
278            noeval: 0,
279            lastbase: -1,
280            prec: &Z_PREC,
281            c_precedences: false,
282            force_float: false,
283            octal_zeroes: false,
284            variables: HashMap::new(),
285            lastval: 0,
286            pid: std::process::id() as i64,
287            error: None,
288        }
289    }
290
291    pub fn with_variables(mut self, vars: HashMap<String, MathNum>) -> Self {
292        self.variables = vars;
293        self
294    }
295
296    /// Inject variables from string->string mapping (for shell integration)
297    pub fn with_string_variables(mut self, vars: &HashMap<String, String>) -> Self {
298        for (k, v) in vars {
299            if let Ok(i) = v.parse::<i64>() {
300                self.variables.insert(k.clone(), MathNum::Integer(i));
301            } else if let Ok(f) = v.parse::<f64>() {
302                self.variables.insert(k.clone(), MathNum::Float(f));
303            }
304        }
305        self
306    }
307
308    /// Extract modified variables as string->string mapping (for shell integration)
309    pub fn extract_string_variables(&self) -> HashMap<String, String> {
310        self.variables
311            .iter()
312            .map(|(k, v)| {
313                let s = match v {
314                    MathNum::Integer(i) => i.to_string(),
315                    MathNum::Float(f) => {
316                        if f.fract() == 0.0 && f.abs() < i64::MAX as f64 {
317                            (*f as i64).to_string()
318                        } else {
319                            f.to_string()
320                        }
321                    }
322                    MathNum::Unset => "0".to_string(),
323                };
324                (k.clone(), s)
325            })
326            .collect()
327    }
328
329    pub fn with_c_precedences(mut self, enable: bool) -> Self {
330        self.c_precedences = enable;
331        self.prec = if enable { &C_PREC } else { &Z_PREC };
332        self
333    }
334
335    pub fn with_force_float(mut self, enable: bool) -> Self {
336        self.force_float = enable;
337        self
338    }
339
340    pub fn with_octal_zeroes(mut self, enable: bool) -> Self {
341        self.octal_zeroes = enable;
342        self
343    }
344
345    pub fn with_lastval(mut self, val: i32) -> Self {
346        self.lastval = val;
347        self
348    }
349
350    fn peek(&self) -> Option<char> {
351        self.input[self.pos..].chars().next()
352    }
353
354    fn advance(&mut self) -> Option<char> {
355        let c = self.peek()?;
356        self.pos += c.len_utf8();
357        Some(c)
358    }
359
360    fn is_digit(c: char) -> bool {
361        c.is_ascii_digit()
362    }
363
364    fn is_ident_start(c: char) -> bool {
365        c.is_ascii_alphabetic() || c == '_'
366    }
367
368    fn is_ident(c: char) -> bool {
369        c.is_ascii_alphanumeric() || c == '_'
370    }
371
372    /// Lex a numeric constant
373    fn lex_constant(&mut self) -> MathTok {
374        let _start = self.pos;
375        let mut is_neg = false;
376
377        // Handle leading minus for unary context
378        if self.peek() == Some('-') {
379            is_neg = true;
380            self.advance();
381        }
382
383        // Check for hex/binary/octal
384        if self.peek() == Some('0') {
385            self.advance();
386            match self.peek().map(|c| c.to_ascii_lowercase()) {
387                Some('x') => {
388                    // Hex: 0xFF
389                    self.advance();
390                    let hex_start = self.pos;
391                    while let Some(c) = self.peek() {
392                        if c.is_ascii_hexdigit() || c == '_' {
393                            self.advance();
394                        } else {
395                            break;
396                        }
397                    }
398                    let hex_str: String = self.input[hex_start..self.pos]
399                        .chars()
400                        .filter(|&c| c != '_')
401                        .collect();
402                    let val = i64::from_str_radix(&hex_str, 16).unwrap_or(0);
403                    self.lastbase = 16;
404                    self.yyval = if self.force_float {
405                        MathNum::Float(if is_neg { -(val as f64) } else { val as f64 })
406                    } else {
407                        MathNum::Integer(if is_neg { -val } else { val })
408                    };
409                    return MathTok::Num;
410                }
411                Some('b') => {
412                    // Binary: 0b1010
413                    self.advance();
414                    let bin_start = self.pos;
415                    while let Some(c) = self.peek() {
416                        if c == '0' || c == '1' || c == '_' {
417                            self.advance();
418                        } else {
419                            break;
420                        }
421                    }
422                    let bin_str: String = self.input[bin_start..self.pos]
423                        .chars()
424                        .filter(|&c| c != '_')
425                        .collect();
426                    let val = i64::from_str_radix(&bin_str, 2).unwrap_or(0);
427                    self.lastbase = 2;
428                    self.yyval = if self.force_float {
429                        MathNum::Float(if is_neg { -(val as f64) } else { val as f64 })
430                    } else {
431                        MathNum::Integer(if is_neg { -val } else { val })
432                    };
433                    return MathTok::Num;
434                }
435                _ => {
436                    // Could be octal or just 0
437                    if self.octal_zeroes {
438                        // Check if this looks like octal
439                        let oct_start = self.pos;
440                        let mut is_octal = true;
441                        while let Some(c) = self.peek() {
442                            if c.is_ascii_digit() || c == '_' {
443                                if c >= '8' && c <= '9' {
444                                    is_octal = false;
445                                }
446                                self.advance();
447                            } else if c == '.' || c == 'e' || c == 'E' || c == '#' {
448                                is_octal = false;
449                                break;
450                            } else {
451                                break;
452                            }
453                        }
454                        if is_octal && self.pos > oct_start {
455                            let oct_str: String = self.input[oct_start..self.pos]
456                                .chars()
457                                .filter(|&c| c != '_')
458                                .collect();
459                            let val = i64::from_str_radix(&oct_str, 8).unwrap_or(0);
460                            self.lastbase = 8;
461                            self.yyval = if self.force_float {
462                                MathNum::Float(if is_neg { -(val as f64) } else { val as f64 })
463                            } else {
464                                MathNum::Integer(if is_neg { -val } else { val })
465                            };
466                            return MathTok::Num;
467                        }
468                        self.pos = oct_start;
469                    }
470                    // Put back the 0
471                    self.pos -= 1;
472                }
473            }
474        }
475
476        // Parse decimal integer or float
477        let num_start = self.pos;
478        while let Some(c) = self.peek() {
479            if Self::is_digit(c) || c == '_' {
480                self.advance();
481            } else {
482                break;
483            }
484        }
485
486        // Check for float
487        if self.peek() == Some('.') || self.peek() == Some('e') || self.peek() == Some('E') {
488            // Float
489            if self.peek() == Some('.') {
490                self.advance();
491                while let Some(c) = self.peek() {
492                    if Self::is_digit(c) || c == '_' {
493                        self.advance();
494                    } else {
495                        break;
496                    }
497                }
498            }
499            if self.peek() == Some('e') || self.peek() == Some('E') {
500                self.advance();
501                if self.peek() == Some('+') || self.peek() == Some('-') {
502                    self.advance();
503                }
504                while let Some(c) = self.peek() {
505                    if Self::is_digit(c) || c == '_' {
506                        self.advance();
507                    } else {
508                        break;
509                    }
510                }
511            }
512            let float_str: String = self.input[num_start..self.pos]
513                .chars()
514                .filter(|&c| c != '_')
515                .collect();
516            let val: f64 = float_str.parse().unwrap_or(0.0);
517            self.yyval = MathNum::Float(if is_neg { -val } else { val });
518            return MathTok::Num;
519        }
520
521        // Check for base#value syntax (e.g., 16#FF)
522        if self.peek() == Some('#') {
523            self.advance();
524            let base_str: String = self.input[num_start..self.pos - 1]
525                .chars()
526                .filter(|&c| c != '_')
527                .collect();
528            let base: u32 = base_str.parse().unwrap_or(10);
529            self.lastbase = base as i32;
530
531            let val_start = self.pos;
532            while let Some(c) = self.peek() {
533                if c.is_ascii_alphanumeric() || c == '_' {
534                    self.advance();
535                } else {
536                    break;
537                }
538            }
539            let val_str: String = self.input[val_start..self.pos]
540                .chars()
541                .filter(|&c| c != '_')
542                .collect();
543            let val = i64::from_str_radix(&val_str, base).unwrap_or(0);
544            self.yyval = if self.force_float {
545                MathNum::Float(if is_neg { -(val as f64) } else { val as f64 })
546            } else {
547                MathNum::Integer(if is_neg { -val } else { val })
548            };
549            return MathTok::Num;
550        }
551
552        // Plain integer
553        let int_str: String = self.input[num_start..self.pos]
554            .chars()
555            .filter(|&c| c != '_')
556            .collect();
557        let val: i64 = int_str.parse().unwrap_or(0);
558        self.yyval = if self.force_float {
559            MathNum::Float(if is_neg { -(val as f64) } else { val as f64 })
560        } else {
561            MathNum::Integer(if is_neg { -val } else { val })
562        };
563        MathTok::Num
564    }
565
566    /// Main lexer
567    fn zzlex(&mut self) -> MathTok {
568        self.yyval = MathNum::Integer(0);
569
570        loop {
571            let c = match self.advance() {
572                Some(c) => c,
573                None => return MathTok::Eoi,
574            };
575
576            match c {
577                ' ' | '\t' | '\n' | '"' => continue,
578
579                '+' => {
580                    if self.peek() == Some('+') {
581                        self.advance();
582                        return if self.unary {
583                            MathTok::PrePlus
584                        } else {
585                            MathTok::PostPlus
586                        };
587                    }
588                    if self.peek() == Some('=') {
589                        self.advance();
590                        return MathTok::PlusEq;
591                    }
592                    return if self.unary {
593                        MathTok::UPlus
594                    } else {
595                        MathTok::Plus
596                    };
597                }
598
599                '-' => {
600                    if self.peek() == Some('-') {
601                        self.advance();
602                        return if self.unary {
603                            MathTok::PreMinus
604                        } else {
605                            MathTok::PostMinus
606                        };
607                    }
608                    if self.peek() == Some('=') {
609                        self.advance();
610                        return MathTok::MinusEq;
611                    }
612                    if self.unary {
613                        // Check if followed by digit for negative number
614                        if let Some(next) = self.peek() {
615                            if Self::is_digit(next) || next == '.' {
616                                self.pos -= 1; // Put back the -
617                                return self.lex_constant();
618                            }
619                        }
620                        return MathTok::UMinus;
621                    }
622                    return MathTok::Minus;
623                }
624
625                '(' => return MathTok::InPar,
626                ')' => return MathTok::OutPar,
627
628                '!' => {
629                    if self.peek() == Some('=') {
630                        self.advance();
631                        return MathTok::Neq;
632                    }
633                    return MathTok::Not;
634                }
635
636                '~' => return MathTok::Comp,
637
638                '&' => {
639                    if self.peek() == Some('&') {
640                        self.advance();
641                        if self.peek() == Some('=') {
642                            self.advance();
643                            return MathTok::DAndEq;
644                        }
645                        return MathTok::DAnd;
646                    }
647                    if self.peek() == Some('=') {
648                        self.advance();
649                        return MathTok::AndEq;
650                    }
651                    return MathTok::And;
652                }
653
654                '|' => {
655                    if self.peek() == Some('|') {
656                        self.advance();
657                        if self.peek() == Some('=') {
658                            self.advance();
659                            return MathTok::DOrEq;
660                        }
661                        return MathTok::DOr;
662                    }
663                    if self.peek() == Some('=') {
664                        self.advance();
665                        return MathTok::OrEq;
666                    }
667                    return MathTok::Or;
668                }
669
670                '^' => {
671                    if self.peek() == Some('^') {
672                        self.advance();
673                        if self.peek() == Some('=') {
674                            self.advance();
675                            return MathTok::DXorEq;
676                        }
677                        return MathTok::DXor;
678                    }
679                    if self.peek() == Some('=') {
680                        self.advance();
681                        return MathTok::XorEq;
682                    }
683                    return MathTok::Xor;
684                }
685
686                '*' => {
687                    if self.peek() == Some('*') {
688                        self.advance();
689                        if self.peek() == Some('=') {
690                            self.advance();
691                            return MathTok::PowerEq;
692                        }
693                        return MathTok::Power;
694                    }
695                    if self.peek() == Some('=') {
696                        self.advance();
697                        return MathTok::MulEq;
698                    }
699                    return MathTok::Mul;
700                }
701
702                '/' => {
703                    if self.peek() == Some('=') {
704                        self.advance();
705                        return MathTok::DivEq;
706                    }
707                    return MathTok::Div;
708                }
709
710                '%' => {
711                    if self.peek() == Some('=') {
712                        self.advance();
713                        return MathTok::ModEq;
714                    }
715                    return MathTok::Mod;
716                }
717
718                '<' => {
719                    if self.peek() == Some('<') {
720                        self.advance();
721                        if self.peek() == Some('=') {
722                            self.advance();
723                            return MathTok::ShLeftEq;
724                        }
725                        return MathTok::ShLeft;
726                    }
727                    if self.peek() == Some('=') {
728                        self.advance();
729                        return MathTok::Leq;
730                    }
731                    return MathTok::Les;
732                }
733
734                '>' => {
735                    if self.peek() == Some('>') {
736                        self.advance();
737                        if self.peek() == Some('=') {
738                            self.advance();
739                            return MathTok::ShRightEq;
740                        }
741                        return MathTok::ShRight;
742                    }
743                    if self.peek() == Some('=') {
744                        self.advance();
745                        return MathTok::Geq;
746                    }
747                    return MathTok::Gre;
748                }
749
750                '=' => {
751                    if self.peek() == Some('=') {
752                        self.advance();
753                        return MathTok::Deq;
754                    }
755                    return MathTok::Eq;
756                }
757
758                '$' => {
759                    // $$ = pid
760                    self.yyval = MathNum::Integer(self.pid);
761                    return MathTok::Num;
762                }
763
764                '?' => {
765                    if self.unary {
766                        // $? = lastval
767                        self.yyval = MathNum::Integer(self.lastval as i64);
768                        return MathTok::Num;
769                    }
770                    return MathTok::Quest;
771                }
772
773                ':' => return MathTok::Colon,
774                ',' => return MathTok::Comma,
775
776                '[' => {
777                    // [base]value or output format [#base]
778                    if Self::is_digit(self.peek().unwrap_or('\0')) {
779                        // [base]value
780                        let base_start = self.pos;
781                        while let Some(c) = self.peek() {
782                            if Self::is_digit(c) {
783                                self.advance();
784                            } else {
785                                break;
786                            }
787                        }
788                        if self.peek() != Some(']') {
789                            self.error = Some("bad base syntax".to_string());
790                            return MathTok::Eoi;
791                        }
792                        let base_str: String = self.input[base_start..self.pos].to_string();
793                        let base: u32 = base_str.parse().unwrap_or(10);
794                        self.advance(); // skip ]
795
796                        if !Self::is_digit(self.peek().unwrap_or('\0'))
797                            && !Self::is_ident_start(self.peek().unwrap_or('\0'))
798                        {
799                            self.error = Some("bad base syntax".to_string());
800                            return MathTok::Eoi;
801                        }
802
803                        let val_start = self.pos;
804                        while let Some(c) = self.peek() {
805                            if c.is_ascii_alphanumeric() {
806                                self.advance();
807                            } else {
808                                break;
809                            }
810                        }
811                        let val_str = &self.input[val_start..self.pos];
812                        let val = i64::from_str_radix(val_str, base).unwrap_or(0);
813                        self.lastbase = base as i32;
814                        self.yyval = MathNum::Integer(val);
815                        return MathTok::Num;
816                    }
817                    // Output format specifier [#base] - skip for now
818                    if self.peek() == Some('#') {
819                        while let Some(c) = self.peek() {
820                            if c == ']' {
821                                self.advance();
822                                break;
823                            }
824                            self.advance();
825                        }
826                        continue;
827                    }
828                    self.error = Some("bad output format specification".to_string());
829                    return MathTok::Eoi;
830                }
831
832                '#' => {
833                    // Character code: #\x or ##string
834                    if self.peek() == Some('\\') || self.peek() == Some('#') {
835                        self.advance();
836                        if let Some(ch) = self.advance() {
837                            self.yyval = MathNum::Integer(ch as i64);
838                            return MathTok::Num;
839                        }
840                    }
841                    // #varname - get first char value
842                    let id_start = self.pos;
843                    while let Some(c) = self.peek() {
844                        if Self::is_ident(c) {
845                            self.advance();
846                        } else {
847                            break;
848                        }
849                    }
850                    if self.pos > id_start {
851                        self.yylval = self.input[id_start..self.pos].to_string();
852                        return MathTok::CId;
853                    }
854                    continue;
855                }
856
857                _ => {
858                    if Self::is_digit(c)
859                        || (c == '.' && Self::is_digit(self.peek().unwrap_or('\0')))
860                    {
861                        self.pos -= c.len_utf8();
862                        return self.lex_constant();
863                    }
864
865                    if Self::is_ident_start(c) {
866                        let id_start = self.pos - c.len_utf8();
867                        while let Some(c) = self.peek() {
868                            if Self::is_ident(c) {
869                                self.advance();
870                            } else {
871                                break;
872                            }
873                        }
874
875                        let id = &self.input[id_start..self.pos];
876
877                        // Check for Inf/NaN
878                        let id_lower = id.to_lowercase();
879                        if id_lower == "nan" {
880                            self.yyval = MathNum::Float(f64::NAN);
881                            return MathTok::Num;
882                        }
883                        if id_lower == "inf" {
884                            self.yyval = MathNum::Float(f64::INFINITY);
885                            return MathTok::Num;
886                        }
887
888                        // Check for function call
889                        if self.peek() == Some('(') {
890                            // Skip to closing paren
891                            let func_start = id_start;
892                            self.advance(); // (
893                            let mut depth = 1;
894                            while let Some(c) = self.peek() {
895                                self.advance();
896                                if c == '(' {
897                                    depth += 1;
898                                } else if c == ')' {
899                                    depth -= 1;
900                                    if depth == 0 {
901                                        break;
902                                    }
903                                }
904                            }
905                            self.yylval = self.input[func_start..self.pos].to_string();
906                            return MathTok::Func;
907                        }
908
909                        // Check for array subscript
910                        if self.peek() == Some('[') {
911                            self.advance(); // [
912                            let mut depth = 1;
913                            while let Some(c) = self.peek() {
914                                self.advance();
915                                if c == '[' {
916                                    depth += 1;
917                                } else if c == ']' {
918                                    depth -= 1;
919                                    if depth == 0 {
920                                        break;
921                                    }
922                                }
923                            }
924                        }
925
926                        self.yylval = self.input[id_start..self.pos].to_string();
927                        return MathTok::Id;
928                    }
929
930                    return MathTok::Eoi;
931                }
932            }
933        }
934    }
935
936    fn push(&mut self, val: MathNum, lval: Option<String>) {
937        self.stack.push(MathValue { val, lval });
938    }
939
940    fn pop(&mut self) -> MathNum {
941        if let Some(mv) = self.stack.pop() {
942            if matches!(mv.val, MathNum::Unset) {
943                if let Some(ref name) = mv.lval {
944                    return self.get_variable(name);
945                }
946            }
947            mv.val
948        } else {
949            self.error = Some("stack underflow".to_string());
950            MathNum::Integer(0)
951        }
952    }
953
954    fn pop_with_lval(&mut self) -> MathValue {
955        self.stack.pop().unwrap_or_default()
956    }
957
958    fn get_value(&self, mv: &MathValue) -> MathNum {
959        if matches!(mv.val, MathNum::Unset) {
960            if let Some(ref name) = mv.lval {
961                return self.get_variable(name);
962            }
963        }
964        mv.val
965    }
966
967    fn get_variable(&self, name: &str) -> MathNum {
968        // Strip array subscript if present
969        let base_name = if let Some(bracket) = name.find('[') {
970            &name[..bracket]
971        } else {
972            name
973        };
974        self.variables
975            .get(base_name)
976            .copied()
977            .unwrap_or(MathNum::Integer(0))
978    }
979
980    fn set_variable(&mut self, name: &str, val: MathNum) -> MathNum {
981        let base_name = if let Some(bracket) = name.find('[') {
982            &name[..bracket]
983        } else {
984            name
985        };
986        self.variables.insert(base_name.to_string(), val);
987        val
988    }
989
990    /// Execute binary/unary operator
991    fn op(&mut self, what: MathTok) {
992        if self.error.is_some() {
993            return;
994        }
995
996        let tp = OP_TYPE[what as usize];
997
998        // Binary operators
999        if (tp & (OP_A2 | OP_A2IR | OP_A2IO | OP_E2 | OP_E2IO)) != 0 {
1000            if self.stack.len() < 2 {
1001                self.error = Some("not enough operands".to_string());
1002                return;
1003            }
1004
1005            let b = self.pop();
1006            let mv_a = self.pop_with_lval();
1007            let a = if matches!(mv_a.val, MathNum::Unset) {
1008                if let Some(ref name) = mv_a.lval {
1009                    self.get_variable(name)
1010                } else {
1011                    MathNum::Integer(0)
1012                }
1013            } else {
1014                mv_a.val
1015            };
1016
1017            // Coerce types
1018            let (a, b) = if (tp & (OP_A2IO | OP_E2IO)) != 0 {
1019                // Must be integers
1020                (MathNum::Integer(a.to_int()), MathNum::Integer(b.to_int()))
1021            } else if a.is_float() != b.is_float() && what != MathTok::Comma {
1022                // Different types, coerce to float
1023                (MathNum::Float(a.to_float()), MathNum::Float(b.to_float()))
1024            } else {
1025                (a, b)
1026            };
1027
1028            let result = if self.noeval > 0 {
1029                MathNum::Integer(0)
1030            } else {
1031                let is_float = a.is_float();
1032                match what {
1033                    MathTok::And | MathTok::AndEq => MathNum::Integer(a.to_int() & b.to_int()),
1034                    MathTok::Xor | MathTok::XorEq => MathNum::Integer(a.to_int() ^ b.to_int()),
1035                    MathTok::Or | MathTok::OrEq => MathNum::Integer(a.to_int() | b.to_int()),
1036
1037                    MathTok::Mul | MathTok::MulEq => {
1038                        if is_float {
1039                            MathNum::Float(a.to_float() * b.to_float())
1040                        } else {
1041                            MathNum::Integer(a.to_int().wrapping_mul(b.to_int()))
1042                        }
1043                    }
1044
1045                    MathTok::Div | MathTok::DivEq => {
1046                        if b.is_zero() {
1047                            self.error = Some("division by zero".to_string());
1048                            return;
1049                        }
1050                        if is_float {
1051                            MathNum::Float(a.to_float() / b.to_float())
1052                        } else {
1053                            let bi = b.to_int();
1054                            if bi == -1 {
1055                                MathNum::Integer(a.to_int().wrapping_neg())
1056                            } else {
1057                                MathNum::Integer(a.to_int() / bi)
1058                            }
1059                        }
1060                    }
1061
1062                    MathTok::Mod | MathTok::ModEq => {
1063                        if b.is_zero() {
1064                            self.error = Some("division by zero".to_string());
1065                            return;
1066                        }
1067                        if is_float {
1068                            MathNum::Float(a.to_float() % b.to_float())
1069                        } else {
1070                            let bi = b.to_int();
1071                            if bi == -1 {
1072                                MathNum::Integer(0)
1073                            } else {
1074                                MathNum::Integer(a.to_int() % bi)
1075                            }
1076                        }
1077                    }
1078
1079                    MathTok::Plus | MathTok::PlusEq => {
1080                        if is_float {
1081                            MathNum::Float(a.to_float() + b.to_float())
1082                        } else {
1083                            MathNum::Integer(a.to_int().wrapping_add(b.to_int()))
1084                        }
1085                    }
1086
1087                    MathTok::Minus | MathTok::MinusEq => {
1088                        if is_float {
1089                            MathNum::Float(a.to_float() - b.to_float())
1090                        } else {
1091                            MathNum::Integer(a.to_int().wrapping_sub(b.to_int()))
1092                        }
1093                    }
1094
1095                    MathTok::ShLeft | MathTok::ShLeftEq => {
1096                        MathNum::Integer(a.to_int() << (b.to_int() as u32 & 63))
1097                    }
1098                    MathTok::ShRight | MathTok::ShRightEq => {
1099                        MathNum::Integer(a.to_int() >> (b.to_int() as u32 & 63))
1100                    }
1101
1102                    MathTok::Les => MathNum::Integer(if is_float {
1103                        (a.to_float() < b.to_float()) as i64
1104                    } else {
1105                        (a.to_int() < b.to_int()) as i64
1106                    }),
1107                    MathTok::Leq => MathNum::Integer(if is_float {
1108                        (a.to_float() <= b.to_float()) as i64
1109                    } else {
1110                        (a.to_int() <= b.to_int()) as i64
1111                    }),
1112                    MathTok::Gre => MathNum::Integer(if is_float {
1113                        (a.to_float() > b.to_float()) as i64
1114                    } else {
1115                        (a.to_int() > b.to_int()) as i64
1116                    }),
1117                    MathTok::Geq => MathNum::Integer(if is_float {
1118                        (a.to_float() >= b.to_float()) as i64
1119                    } else {
1120                        (a.to_int() >= b.to_int()) as i64
1121                    }),
1122                    MathTok::Deq => MathNum::Integer(if is_float {
1123                        (a.to_float() == b.to_float()) as i64
1124                    } else {
1125                        (a.to_int() == b.to_int()) as i64
1126                    }),
1127                    MathTok::Neq => MathNum::Integer(if is_float {
1128                        (a.to_float() != b.to_float()) as i64
1129                    } else {
1130                        (a.to_int() != b.to_int()) as i64
1131                    }),
1132
1133                    MathTok::DAnd | MathTok::DAndEq => {
1134                        MathNum::Integer((a.to_int() != 0 && b.to_int() != 0) as i64)
1135                    }
1136                    MathTok::DOr | MathTok::DOrEq => {
1137                        MathNum::Integer((a.to_int() != 0 || b.to_int() != 0) as i64)
1138                    }
1139                    MathTok::DXor | MathTok::DXorEq => {
1140                        let ai = a.to_int() != 0;
1141                        let bi = b.to_int() != 0;
1142                        MathNum::Integer((ai != bi) as i64)
1143                    }
1144
1145                    MathTok::Power | MathTok::PowerEq => {
1146                        let bi = b.to_int();
1147                        if !is_float && bi >= 0 {
1148                            let mut result = 1i64;
1149                            let base = a.to_int();
1150                            for _ in 0..bi {
1151                                result = result.wrapping_mul(base);
1152                            }
1153                            MathNum::Integer(result)
1154                        } else {
1155                            let af = a.to_float();
1156                            let bf = b.to_float();
1157                            if bf <= 0.0 && af == 0.0 {
1158                                self.error = Some("division by zero".to_string());
1159                                return;
1160                            }
1161                            if af < 0.0 && bf != bf.trunc() {
1162                                self.error = Some("imaginary power".to_string());
1163                                return;
1164                            }
1165                            MathNum::Float(af.powf(bf))
1166                        }
1167                    }
1168
1169                    MathTok::Comma => b,
1170                    MathTok::Eq => b,
1171
1172                    _ => MathNum::Integer(0),
1173                }
1174            };
1175
1176            // Handle assignment
1177            if (tp & (OP_E2 | OP_E2IO)) != 0 {
1178                if let Some(ref name) = mv_a.lval {
1179                    let final_val = self.set_variable(name, result);
1180                    self.push(final_val, Some(name.clone()));
1181                } else {
1182                    self.error = Some("lvalue required".to_string());
1183                    self.push(MathNum::Integer(0), None);
1184                }
1185            } else {
1186                self.push(result, None);
1187            }
1188            return;
1189        }
1190
1191        // Unary operators
1192        if self.stack.is_empty() {
1193            self.error = Some("stack empty".to_string());
1194            return;
1195        }
1196
1197        let mv = self.pop_with_lval();
1198        let val = if matches!(mv.val, MathNum::Unset) {
1199            if let Some(ref name) = mv.lval {
1200                self.get_variable(name)
1201            } else {
1202                MathNum::Integer(0)
1203            }
1204        } else {
1205            mv.val
1206        };
1207
1208        match what {
1209            MathTok::Not => {
1210                let result = MathNum::Integer(if val.is_zero() { 1 } else { 0 });
1211                self.push(result, None);
1212            }
1213            MathTok::Comp => {
1214                let result = MathNum::Integer(!val.to_int());
1215                self.push(result, None);
1216            }
1217            MathTok::UPlus => {
1218                self.push(val, None);
1219            }
1220            MathTok::UMinus => {
1221                let result = if val.is_float() {
1222                    MathNum::Float(-val.to_float())
1223                } else {
1224                    MathNum::Integer(-val.to_int())
1225                };
1226                self.push(result, None);
1227            }
1228            MathTok::PostPlus => {
1229                if let Some(ref name) = mv.lval {
1230                    let new_val = if val.is_float() {
1231                        MathNum::Float(val.to_float() + 1.0)
1232                    } else {
1233                        MathNum::Integer(val.to_int() + 1)
1234                    };
1235                    self.set_variable(name, new_val);
1236                }
1237                self.push(val, None); // Return original value
1238            }
1239            MathTok::PostMinus => {
1240                if let Some(ref name) = mv.lval {
1241                    let new_val = if val.is_float() {
1242                        MathNum::Float(val.to_float() - 1.0)
1243                    } else {
1244                        MathNum::Integer(val.to_int() - 1)
1245                    };
1246                    self.set_variable(name, new_val);
1247                }
1248                self.push(val, None);
1249            }
1250            MathTok::PrePlus => {
1251                let new_val = if val.is_float() {
1252                    MathNum::Float(val.to_float() + 1.0)
1253                } else {
1254                    MathNum::Integer(val.to_int() + 1)
1255                };
1256                if let Some(ref name) = mv.lval {
1257                    self.set_variable(name, new_val);
1258                }
1259                self.push(new_val, mv.lval);
1260            }
1261            MathTok::PreMinus => {
1262                let new_val = if val.is_float() {
1263                    MathNum::Float(val.to_float() - 1.0)
1264                } else {
1265                    MathNum::Integer(val.to_int() - 1)
1266                };
1267                if let Some(ref name) = mv.lval {
1268                    self.set_variable(name, new_val);
1269                }
1270                self.push(new_val, mv.lval);
1271            }
1272            MathTok::Quest => {
1273                // Ternary: stack has [cond, true_val, false_val]
1274                // val already popped = false_val
1275                // Need to pop true_val and cond
1276                if self.stack.len() < 2 {
1277                    self.error = Some("?: needs 3 operands".to_string());
1278                    return;
1279                }
1280                let false_val = val;
1281                let true_val = self.pop();
1282                let cond = self.pop();
1283                let result = if !cond.is_zero() { true_val } else { false_val };
1284                self.push(result, None);
1285            }
1286            MathTok::Colon => {
1287                self.error = Some("':' without '?'".to_string());
1288            }
1289            _ => {
1290                self.error = Some("unknown operator".to_string());
1291            }
1292        }
1293    }
1294
1295    /// Short-circuit boolean handling
1296    fn bop(&mut self, tk: MathTok) {
1297        if self.stack.is_empty() {
1298            return;
1299        }
1300        let mv = &self.stack[self.stack.len() - 1];
1301        let val = if matches!(mv.val, MathNum::Unset) {
1302            if let Some(ref name) = mv.lval {
1303                self.get_variable(name)
1304            } else {
1305                MathNum::Integer(0)
1306            }
1307        } else {
1308            mv.val
1309        };
1310
1311        let tst = !val.is_zero();
1312        match tk {
1313            MathTok::DAnd | MathTok::DAndEq => {
1314                if !tst {
1315                    self.noeval += 1;
1316                }
1317            }
1318            MathTok::DOr | MathTok::DOrEq => {
1319                if tst {
1320                    self.noeval += 1;
1321                }
1322            }
1323            _ => {}
1324        }
1325    }
1326
1327    fn top_prec(&self) -> u8 {
1328        self.prec[MathTok::Comma as usize] + 1
1329    }
1330
1331    fn check_unary(&mut self) {
1332        let tp = OP_TYPE[self.mtok as usize];
1333        // After this token, do we expect an operand (unary=true) or operator (unary=false)?
1334        // OP_OPF means "followed by operator" - after this, next should be operator
1335        self.unary = (tp & OP_OPF) == 0;
1336    }
1337
1338    /// Operator-precedence parser - closely follows zsh math.c mathparse()
1339    fn mathparse(&mut self, pc: u8) {
1340        if self.error.is_some() {
1341            return;
1342        }
1343
1344        self.mtok = self.zzlex();
1345
1346        // Handle empty input
1347        if pc == self.top_prec() && self.mtok == MathTok::Eoi {
1348            return;
1349        }
1350
1351        self.check_unary();
1352
1353        while self.prec[self.mtok as usize] <= pc {
1354            if self.error.is_some() {
1355                return;
1356            }
1357
1358            match self.mtok {
1359                MathTok::Num => {
1360                    self.push(self.yyval, None);
1361                }
1362                MathTok::Id => {
1363                    let lval = self.yylval.clone();
1364                    if self.noeval > 0 {
1365                        self.push(MathNum::Integer(0), Some(lval));
1366                    } else {
1367                        self.push(MathNum::Unset, Some(lval));
1368                    }
1369                }
1370                MathTok::CId => {
1371                    let lval = self.yylval.clone();
1372                    let val = if self.noeval > 0 {
1373                        MathNum::Integer(0)
1374                    } else {
1375                        self.get_variable(&lval)
1376                    };
1377                    self.push(val, Some(lval));
1378                }
1379                MathTok::Func => {
1380                    let func_call = self.yylval.clone();
1381                    let val = if self.noeval > 0 {
1382                        MathNum::Integer(0)
1383                    } else {
1384                        self.call_math_func(&func_call)
1385                    };
1386                    self.push(val, None);
1387                }
1388                MathTok::InPar => {
1389                    self.mathparse(self.top_prec());
1390                    if self.mtok != MathTok::OutPar {
1391                        if self.error.is_none() {
1392                            self.error = Some("')' expected".to_string());
1393                        }
1394                        return;
1395                    }
1396                }
1397                MathTok::Quest => {
1398                    // Ternary operator
1399                    if self.stack.is_empty() {
1400                        self.error = Some("bad math expression".to_string());
1401                        return;
1402                    }
1403                    let mv = &self.stack[self.stack.len() - 1];
1404                    let cond = self.get_value(mv);
1405
1406                    let q = !cond.is_zero();
1407                    if !q {
1408                        self.noeval += 1;
1409                    }
1410                    let colon_prec = self.prec[MathTok::Colon as usize];
1411                    self.mathparse(colon_prec - 1);
1412                    if !q {
1413                        self.noeval -= 1;
1414                    }
1415
1416                    if self.mtok != MathTok::Colon {
1417                        if self.error.is_none() {
1418                            self.error = Some("':' expected".to_string());
1419                        }
1420                        return;
1421                    }
1422
1423                    if q {
1424                        self.noeval += 1;
1425                    }
1426                    let quest_prec = self.prec[MathTok::Quest as usize];
1427                    self.mathparse(quest_prec);
1428                    if q {
1429                        self.noeval -= 1;
1430                    }
1431
1432                    self.op(MathTok::Quest);
1433                    continue;
1434                }
1435                _ => {
1436                    // Binary/unary operator
1437                    let otok = self.mtok;
1438                    let onoeval = self.noeval;
1439                    let tp = OP_TYPE[otok as usize];
1440                    if (tp & 0x03) == BOOL {
1441                        self.bop(otok);
1442                    }
1443                    let otok_prec = self.prec[otok as usize];
1444                    // Right-to-left gets same prec, left-to-right gets prec-1
1445                    let adjust = if (tp & 0x01) != RL { 1 } else { 0 };
1446                    self.mathparse(otok_prec - adjust);
1447                    self.noeval = onoeval;
1448                    self.op(otok);
1449                    continue;
1450                }
1451            }
1452
1453            // After operand (Num, Id, Func, InPar), get next token
1454            self.mtok = self.zzlex();
1455            self.check_unary();
1456        }
1457    }
1458
1459    /// Call a math function
1460    fn call_math_func(&mut self, call: &str) -> MathNum {
1461        // Parse function name and args
1462        let paren = call.find('(').unwrap_or(call.len());
1463        let name = &call[..paren];
1464        let args_str = if paren < call.len() {
1465            &call[paren + 1..call.len() - 1]
1466        } else {
1467            ""
1468        };
1469
1470        // Parse arguments
1471        let args: Vec<f64> = if args_str.is_empty() {
1472            vec![]
1473        } else {
1474            args_str
1475                .split(',')
1476                .filter_map(|s| {
1477                    let mut eval = MathEval::new(s.trim());
1478                    eval.variables = self.variables.clone();
1479                    match eval.evaluate() {
1480                        Ok(n) => Some(n.to_float()),
1481                        Err(_) => None,
1482                    }
1483                })
1484                .collect()
1485        };
1486
1487        // Built-in math functions
1488        let result = match name {
1489            "abs" => args.get(0).map(|x| x.abs()).unwrap_or(0.0),
1490            "acos" => args.get(0).map(|x| x.acos()).unwrap_or(0.0),
1491            "asin" => args.get(0).map(|x| x.asin()).unwrap_or(0.0),
1492            "atan" => args.get(0).map(|x| x.atan()).unwrap_or(0.0),
1493            "atan2" => {
1494                let y = args.get(0).copied().unwrap_or(0.0);
1495                let x = args.get(1).copied().unwrap_or(1.0);
1496                y.atan2(x)
1497            }
1498            "ceil" => args.get(0).map(|x| x.ceil()).unwrap_or(0.0),
1499            "cos" => args.get(0).map(|x| x.cos()).unwrap_or(1.0),
1500            "cosh" => args.get(0).map(|x| x.cosh()).unwrap_or(1.0),
1501            "exp" => args.get(0).map(|x| x.exp()).unwrap_or(1.0),
1502            "floor" => args.get(0).map(|x| x.floor()).unwrap_or(0.0),
1503            "hypot" => {
1504                let x = args.get(0).copied().unwrap_or(0.0);
1505                let y = args.get(1).copied().unwrap_or(0.0);
1506                x.hypot(y)
1507            }
1508            "int" => args.get(0).map(|x| x.trunc()).unwrap_or(0.0),
1509            "log" => args.get(0).map(|x| x.ln()).unwrap_or(0.0),
1510            "log10" => args.get(0).map(|x| x.log10()).unwrap_or(0.0),
1511            "log2" => args.get(0).map(|x| x.log2()).unwrap_or(0.0),
1512            "max" => args.iter().copied().fold(f64::NEG_INFINITY, f64::max),
1513            "min" => args.iter().copied().fold(f64::INFINITY, f64::min),
1514            "pow" => {
1515                let base = args.get(0).copied().unwrap_or(0.0);
1516                let exp = args.get(1).copied().unwrap_or(1.0);
1517                base.powf(exp)
1518            }
1519            "rand" => rand::random::<f64>(),
1520            "round" => args.get(0).map(|x| x.round()).unwrap_or(0.0),
1521            "sin" => args.get(0).map(|x| x.sin()).unwrap_or(0.0),
1522            "sinh" => args.get(0).map(|x| x.sinh()).unwrap_or(0.0),
1523            "sqrt" => args.get(0).map(|x| x.sqrt()).unwrap_or(0.0),
1524            "tan" => args.get(0).map(|x| x.tan()).unwrap_or(0.0),
1525            "tanh" => args.get(0).map(|x| x.tanh()).unwrap_or(0.0),
1526            "trunc" => args.get(0).map(|x| x.trunc()).unwrap_or(0.0),
1527            _ => {
1528                self.error = Some(format!("unknown function: {}", name));
1529                0.0
1530            }
1531        };
1532
1533        MathNum::Float(result)
1534    }
1535
1536    /// Evaluate the expression
1537    pub fn evaluate(&mut self) -> Result<MathNum, String> {
1538        self.prec = if self.c_precedences { &C_PREC } else { &Z_PREC };
1539
1540        // Skip leading whitespace and Nularg
1541        while let Some(c) = self.peek() {
1542            if c.is_whitespace() || c == '\u{a1}' {
1543                self.advance();
1544            } else {
1545                break;
1546            }
1547        }
1548
1549        if self.pos >= self.input.len() {
1550            return Ok(MathNum::Integer(0));
1551        }
1552
1553        self.mathparse(self.top_prec());
1554
1555        if let Some(ref err) = self.error {
1556            return Err(err.clone());
1557        }
1558
1559        // Check for trailing characters
1560        while let Some(c) = self.peek() {
1561            if c.is_whitespace() {
1562                self.advance();
1563            } else {
1564                return Err(format!("illegal character: {}", c));
1565            }
1566        }
1567
1568        if self.stack.is_empty() {
1569            return Ok(MathNum::Integer(0));
1570        }
1571
1572        let mv = self.stack.pop().unwrap();
1573        let result = if matches!(mv.val, MathNum::Unset) {
1574            if let Some(ref name) = mv.lval {
1575                self.get_variable(name)
1576            } else {
1577                MathNum::Integer(0)
1578            }
1579        } else {
1580            mv.val
1581        };
1582
1583        Ok(result)
1584    }
1585
1586    /// Get updated variables after evaluation
1587    pub fn get_variables(&self) -> &HashMap<String, MathNum> {
1588        &self.variables
1589    }
1590}
1591
1592/// Convenience function to evaluate a math expression
1593pub fn matheval(expr: &str) -> Result<MathNum, String> {
1594    let mut eval = MathEval::new(expr);
1595    eval.evaluate()
1596}
1597
1598/// Evaluate and return integer
1599pub fn mathevali(expr: &str) -> Result<i64, String> {
1600    matheval(expr).map(|n| n.to_int())
1601}
1602
1603/// Evaluate and return float
1604pub fn mathevalf(expr: &str) -> Result<f64, String> {
1605    matheval(expr).map(|n| n.to_float())
1606}
1607
1608#[cfg(test)]
1609mod tests {
1610    use super::*;
1611
1612    #[test]
1613    fn test_basic_arithmetic() {
1614        assert_eq!(mathevali("1 + 2").unwrap(), 3);
1615        assert_eq!(mathevali("10 - 3").unwrap(), 7);
1616        assert_eq!(mathevali("4 * 5").unwrap(), 20);
1617        assert_eq!(mathevali("20 / 4").unwrap(), 5);
1618        assert_eq!(mathevali("17 % 5").unwrap(), 2);
1619    }
1620
1621    #[test]
1622    fn test_precedence() {
1623        assert_eq!(mathevali("2 + 3 * 4").unwrap(), 14);
1624        assert_eq!(mathevali("(2 + 3) * 4").unwrap(), 20);
1625        assert_eq!(mathevali("2 ** 3 ** 2").unwrap(), 512); // Right associative
1626    }
1627
1628    #[test]
1629    fn test_comparison() {
1630        assert_eq!(mathevali("5 > 3").unwrap(), 1);
1631        assert_eq!(mathevali("5 < 3").unwrap(), 0);
1632        assert_eq!(mathevali("5 == 5").unwrap(), 1);
1633        assert_eq!(mathevali("5 != 3").unwrap(), 1);
1634        assert_eq!(mathevali("5 >= 5").unwrap(), 1);
1635        assert_eq!(mathevali("5 <= 5").unwrap(), 1);
1636    }
1637
1638    #[test]
1639    fn test_logical() {
1640        assert_eq!(mathevali("1 && 1").unwrap(), 1);
1641        assert_eq!(mathevali("1 && 0").unwrap(), 0);
1642        assert_eq!(mathevali("1 || 0").unwrap(), 1);
1643        assert_eq!(mathevali("0 || 0").unwrap(), 0);
1644        assert_eq!(mathevali("!0").unwrap(), 1);
1645        assert_eq!(mathevali("!1").unwrap(), 0);
1646    }
1647
1648    #[test]
1649    fn test_bitwise() {
1650        assert_eq!(mathevali("5 & 3").unwrap(), 1);
1651        assert_eq!(mathevali("5 | 3").unwrap(), 7);
1652        assert_eq!(mathevali("5 ^ 3").unwrap(), 6);
1653        assert_eq!(mathevali("~0").unwrap(), -1);
1654        assert_eq!(mathevali("1 << 4").unwrap(), 16);
1655        assert_eq!(mathevali("16 >> 2").unwrap(), 4);
1656    }
1657
1658    #[test]
1659    fn test_ternary() {
1660        assert_eq!(mathevali("1 ? 10 : 20").unwrap(), 10);
1661        assert_eq!(mathevali("0 ? 10 : 20").unwrap(), 20);
1662        assert_eq!(mathevali("(5 > 3) ? 100 : 200").unwrap(), 100);
1663    }
1664
1665    #[test]
1666    fn test_power() {
1667        assert_eq!(mathevali("2 ** 10").unwrap(), 1024);
1668        assert_eq!(mathevali("3 ** 3").unwrap(), 27);
1669        assert!((mathevalf("2.0 ** 0.5").unwrap() - std::f64::consts::SQRT_2).abs() < 0.0001);
1670    }
1671
1672    #[test]
1673    fn test_float() {
1674        assert!((mathevalf("3.14 + 0.01").unwrap() - 3.15).abs() < 0.0001);
1675        assert!((mathevalf("1.5 * 2.0").unwrap() - 3.0).abs() < 0.0001);
1676    }
1677
1678    #[test]
1679    fn test_unary() {
1680        assert_eq!(mathevali("-5").unwrap(), -5);
1681        assert_eq!(mathevali("- -5").unwrap(), 5); // space needed to avoid --
1682        assert_eq!(mathevali("+5").unwrap(), 5);
1683        assert_eq!(mathevali("-(-5)").unwrap(), 5);
1684    }
1685
1686    #[test]
1687    fn test_base() {
1688        assert_eq!(mathevali("0xFF").unwrap(), 255);
1689        assert_eq!(mathevali("0b1010").unwrap(), 10);
1690        assert_eq!(mathevali("16#FF").unwrap(), 255);
1691        assert_eq!(mathevali("2#1010").unwrap(), 10);
1692        assert_eq!(mathevali("[16]FF").unwrap(), 255);
1693    }
1694
1695    #[test]
1696    fn test_variables() {
1697        let mut vars = HashMap::new();
1698        vars.insert("x".to_string(), MathNum::Integer(10));
1699        vars.insert("y".to_string(), MathNum::Integer(20));
1700
1701        let mut eval = MathEval::new("x + y").with_variables(vars);
1702        assert_eq!(eval.evaluate().unwrap().to_int(), 30);
1703    }
1704
1705    #[test]
1706    fn test_assignment() {
1707        let mut eval = MathEval::new("x = 5");
1708        eval.evaluate().unwrap();
1709        assert_eq!(eval.variables.get("x").unwrap().to_int(), 5);
1710
1711        let mut eval2 = MathEval::new("x = 5, x += 3");
1712        let result = eval2.evaluate().unwrap();
1713        assert_eq!(result.to_int(), 8);
1714    }
1715
1716    #[test]
1717    fn test_increment() {
1718        let mut vars = HashMap::new();
1719        vars.insert("x".to_string(), MathNum::Integer(5));
1720
1721        let mut eval = MathEval::new("++x").with_variables(vars.clone());
1722        assert_eq!(eval.evaluate().unwrap().to_int(), 6);
1723        assert_eq!(eval.variables.get("x").unwrap().to_int(), 6);
1724
1725        let mut eval2 = MathEval::new("x++").with_variables(vars.clone());
1726        assert_eq!(eval2.evaluate().unwrap().to_int(), 5);
1727        assert_eq!(eval2.variables.get("x").unwrap().to_int(), 6);
1728    }
1729
1730    #[test]
1731    fn test_functions() {
1732        assert!((mathevalf("sqrt(4)").unwrap() - 2.0).abs() < 0.0001);
1733        assert!((mathevalf("sin(0)").unwrap()).abs() < 0.0001);
1734        assert!((mathevalf("cos(0)").unwrap() - 1.0).abs() < 0.0001);
1735        assert!((mathevalf("abs(-5)").unwrap() - 5.0).abs() < 0.0001);
1736        assert!((mathevalf("floor(3.7)").unwrap() - 3.0).abs() < 0.0001);
1737        assert!((mathevalf("ceil(3.2)").unwrap() - 4.0).abs() < 0.0001);
1738    }
1739
1740    #[test]
1741    fn test_special_values() {
1742        assert!(mathevalf("Inf").unwrap().is_infinite());
1743        assert!(mathevalf("NaN").unwrap().is_nan());
1744    }
1745
1746    #[test]
1747    fn test_errors() {
1748        assert!(matheval("1 / 0").is_err());
1749        assert!(matheval("1 +").is_err());
1750        assert!(matheval("()").is_ok()); // Empty parens are valid
1751    }
1752
1753    #[test]
1754    fn test_underscore_in_numbers() {
1755        assert_eq!(mathevali("1_000_000").unwrap(), 1000000);
1756        assert_eq!(mathevali("0xFF_FF").unwrap(), 65535);
1757    }
1758
1759    #[test]
1760    fn test_comma_operator() {
1761        assert_eq!(mathevali("1, 2, 3").unwrap(), 3);
1762        assert_eq!(mathevali("(x = 1, y = 2, x + y)").unwrap(), 3);
1763    }
1764}