1use fusevm::{ChunkBuilder, Op, Value};
14use std::collections::HashMap;
15
16pub struct ArithCompiler<'a> {
30 pub input: &'a str,
32 pub pos: usize,
34 pub builder: ChunkBuilder,
36 pub slots: HashMap<String, u16>,
38 pub next_slot: u16,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq)]
46enum Tok {
47 Num(i64),
48 Float(f64),
49 Ident,
50 Plus,
51 Minus,
52 Mul,
53 Div,
54 Mod,
55 Pow,
56 BitAnd,
57 BitOr,
58 BitXor,
59 BitNot,
60 Shl,
61 Shr,
62 LogAnd,
63 LogOr,
64 LogNot,
65 Eq,
66 Neq,
67 Lt,
68 Gt,
69 Leq,
70 Geq,
71 Assign,
72 PlusAssign,
73 MinusAssign,
74 MulAssign,
75 DivAssign,
76 ModAssign,
77 PreInc,
78 PreDec,
79 PostInc,
80 PostDec,
81 LParen,
82 RParen,
83 Comma,
84 Quest,
85 Colon,
86 Eoi,
87}
88
89impl<'a> ArithCompiler<'a> {
90 pub fn new(input: &'a str) -> Self {
92 Self {
93 input,
94 pos: 0,
95 builder: ChunkBuilder::new(),
96 slots: HashMap::new(),
97 next_slot: 0,
98 }
99 }
100
101 pub fn compile(mut self) -> fusevm::Chunk {
104 self.builder.set_source("$((...))");
105 self.builder.emit(Op::PushFrame, 0);
106 self.expr();
107 self.builder.emit(Op::ReturnValue, 0);
108 self.builder.build()
109 }
110
111 pub fn slot_for(&mut self, name: &str) -> u16 {
113 if let Some(&slot) = self.slots.get(name) {
114 return slot;
115 }
116 let slot = self.next_slot;
117 self.next_slot += 1;
118 self.slots.insert(name.to_string(), slot);
119 slot
120 }
121
122 pub fn collect_identifiers(&self, expr: &str) -> Vec<String> {
127 let bytes = expr.as_bytes();
128 let mut names: Vec<String> = Vec::new();
129 let mut i = 0;
130 while i < bytes.len() {
131 let b = bytes[i];
132 let with_dollar = b == b'$';
136 if with_dollar {
137 if i + 1 >= bytes.len() {
138 i += 1;
139 continue;
140 }
141 if bytes[i + 1] == b'{' {
142 i += 2;
143 let start = i;
144 while i < bytes.len() && bytes[i] != b'}' {
145 i += 1;
146 }
147 let name = expr[start..i].to_string();
148 if !name.is_empty() && !names.contains(&name) {
149 names.push(name);
150 }
151 if i < bytes.len() {
152 i += 1; }
154 continue;
155 }
156 i += 1;
157 let start = i;
158 if bytes
159 .get(i)
160 .copied()
161 .map(|c| c.is_ascii_digit())
162 .unwrap_or(false)
163 {
164 while i < bytes.len() && bytes[i].is_ascii_digit() {
165 i += 1;
166 }
167 let name = expr[start..i].to_string();
168 if !names.contains(&name) {
169 names.push(name);
170 }
171 continue;
172 }
173 }
174 if b.is_ascii_alphabetic() || b == b'_' || (with_dollar && i < bytes.len()) {
175 let start = i;
176 while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') {
177 i += 1;
178 }
179 let name = expr[start..i].to_string();
180 if !name.is_empty() && !names.contains(&name) {
181 names.push(name);
182 }
183 } else {
184 i += 1;
185 }
186 }
187 names
188 }
189
190 fn skip_whitespace(&mut self) {
193 while self.pos < self.input.len() {
194 let b = self.input.as_bytes()[self.pos];
195 if b == b' ' || b == b'\t' || b == b'\n' || b == b'\r' {
196 self.pos += 1;
197 } else {
198 break;
199 }
200 }
201 }
202
203 fn peek_char(&self) -> Option<u8> {
204 self.input.as_bytes().get(self.pos).copied()
205 }
206
207 fn next_char(&mut self) -> Option<u8> {
208 let c = self.input.as_bytes().get(self.pos).copied();
209 if c.is_some() {
210 self.pos += 1;
211 }
212 c
213 }
214
215 fn read_ident(&mut self) -> String {
216 let start = self.pos;
217 while self.pos < self.input.len() {
218 let b = self.input.as_bytes()[self.pos];
219 if b.is_ascii_alphanumeric() || b == b'_' {
220 self.pos += 1;
221 } else {
222 break;
223 }
224 }
225 self.input[start..self.pos].to_string()
226 }
227
228 fn read_number(&mut self) -> Tok {
229 let start = self.pos;
230
231 if self.pos + 1 < self.input.len()
234 && self.input.as_bytes()[self.pos] == b'0'
235 && (self.input.as_bytes()[self.pos + 1] == b'x'
236 || self.input.as_bytes()[self.pos + 1] == b'X')
237 {
238 self.pos += 2;
239 while self.pos < self.input.len() && self.input.as_bytes()[self.pos].is_ascii_hexdigit()
240 {
241 self.pos += 1;
242 }
243 let val = i64::from_str_radix(&self.input[start + 2..self.pos], 16).unwrap_or(0);
244 crate::ported::math::set_lastbase(16);
249 return Tok::Num(val);
250 }
251
252 while self.pos < self.input.len() && self.input.as_bytes()[self.pos].is_ascii_digit() {
255 self.pos += 1;
256 }
257
258 if self.pos < self.input.len() && self.input.as_bytes()[self.pos] == b'#' {
263 let base_str = &self.input[start..self.pos];
264 if let Ok(base) = base_str.parse::<u32>() {
265 if (2..=36).contains(&base) {
266 self.pos += 1; let digit_start = self.pos;
268 while self.pos < self.input.len() {
269 let b = self.input.as_bytes()[self.pos];
270 let in_base = if base <= 10 {
271 b.is_ascii_digit() && (b - b'0') < base as u8
272 } else {
273 b.is_ascii_digit()
274 || (b.is_ascii_alphabetic() && {
275 let v = if b.is_ascii_lowercase() {
276 b - b'a' + 10
277 } else {
278 b - b'A' + 10
279 };
280 (v as u32) < base
281 })
282 };
283 if in_base {
284 self.pos += 1;
285 } else {
286 break;
287 }
288 }
289 let digits = &self.input[digit_start..self.pos];
290 let val = i64::from_str_radix(digits, base).unwrap_or(0);
291 crate::ported::math::set_lastbase(base as i32);
296 return Tok::Num(val);
297 }
298 }
299 }
300
301 let lex_octal = self.pos > start + 1
305 && self.input.as_bytes()[start] == b'0'
306 && self.input.as_bytes()[start + 1].is_ascii_digit()
307 && crate::ported::zsh_h::isset(crate::ported::zsh_h::OCTALZEROES);
308 if lex_octal {
309 let val = i64::from_str_radix(&self.input[start + 1..self.pos], 8).unwrap_or(0);
310 return Tok::Num(val);
311 }
312
313 if self.pos < self.input.len() && self.input.as_bytes()[self.pos] == b'.' {
315 self.pos += 1;
316 while self.pos < self.input.len() && self.input.as_bytes()[self.pos].is_ascii_digit() {
317 self.pos += 1;
318 }
319 let val: f64 = self.input[start..self.pos].parse().unwrap_or(0.0);
320 return Tok::Float(val);
321 }
322
323 let val: i64 = self.input[start..self.pos].parse().unwrap_or(0);
324 Tok::Num(val)
325 }
326
327 fn next_tok(&mut self) -> (Tok, String) {
328 self.skip_whitespace();
329
330 let Some(c) = self.peek_char() else {
331 return (Tok::Eoi, String::new());
332 };
333
334 match c {
335 b'0'..=b'9' => {
336 let tok = self.read_number();
337 (tok, String::new())
338 }
339 b'a'..=b'z' | b'A'..=b'Z' | b'_' => {
340 let name = self.read_ident();
341 (Tok::Ident, name)
342 }
343 b'$' => {
344 self.pos += 1;
350 if self.peek_char() == Some(b'{') {
351 self.pos += 1;
352 let mut name = String::new();
353 while let Some(b) = self.peek_char() {
354 if b == b'}' {
355 self.pos += 1;
356 break;
357 }
358 name.push(b as char);
359 self.pos += 1;
360 }
361 (Tok::Ident, name)
362 } else if let Some(b) = self.peek_char() {
363 if b.is_ascii_digit() {
364 let mut name = String::new();
366 while let Some(d) = self.peek_char() {
367 if !d.is_ascii_digit() {
368 break;
369 }
370 name.push(d as char);
371 self.pos += 1;
372 }
373 (Tok::Ident, name)
374 } else if b.is_ascii_alphabetic() || b == b'_' {
375 let name = self.read_ident();
376 (Tok::Ident, name)
377 } else {
378 (Tok::Num(0), String::new())
381 }
382 } else {
383 (Tok::Num(0), String::new())
384 }
385 }
386 b'+' => {
387 self.pos += 1;
388 match self.peek_char() {
389 Some(b'+') => {
390 self.pos += 1;
391 (Tok::PreInc, String::new())
392 }
393 Some(b'=') => {
394 self.pos += 1;
395 (Tok::PlusAssign, String::new())
396 }
397 _ => (Tok::Plus, String::new()),
398 }
399 }
400 b'-' => {
401 self.pos += 1;
402 match self.peek_char() {
403 Some(b'-') => {
404 self.pos += 1;
405 (Tok::PreDec, String::new())
406 }
407 Some(b'=') => {
408 self.pos += 1;
409 (Tok::MinusAssign, String::new())
410 }
411 _ => (Tok::Minus, String::new()),
412 }
413 }
414 b'*' => {
415 self.pos += 1;
416 match self.peek_char() {
417 Some(b'*') => {
418 self.pos += 1;
419 if self.peek_char() == Some(b'=') {
420 self.pos += 1;
421 (Tok::MulAssign, String::new()) } else {
423 (Tok::Pow, String::new())
424 }
425 }
426 Some(b'=') => {
427 self.pos += 1;
428 (Tok::MulAssign, String::new())
429 }
430 _ => (Tok::Mul, String::new()),
431 }
432 }
433 b'/' => {
434 self.pos += 1;
435 if self.peek_char() == Some(b'=') {
436 self.pos += 1;
437 (Tok::DivAssign, String::new())
438 } else {
439 (Tok::Div, String::new())
440 }
441 }
442 b'%' => {
443 self.pos += 1;
444 if self.peek_char() == Some(b'=') {
445 self.pos += 1;
446 (Tok::ModAssign, String::new())
447 } else {
448 (Tok::Mod, String::new())
449 }
450 }
451 b'&' => {
452 self.pos += 1;
453 if self.peek_char() == Some(b'&') {
454 self.pos += 1;
455 (Tok::LogAnd, String::new())
456 } else {
457 (Tok::BitAnd, String::new())
458 }
459 }
460 b'|' => {
461 self.pos += 1;
462 if self.peek_char() == Some(b'|') {
463 self.pos += 1;
464 (Tok::LogOr, String::new())
465 } else {
466 (Tok::BitOr, String::new())
467 }
468 }
469 b'^' => {
470 self.pos += 1;
471 (Tok::BitXor, String::new())
472 }
473 b'~' => {
474 self.pos += 1;
475 (Tok::BitNot, String::new())
476 }
477 b'!' => {
478 self.pos += 1;
479 if self.peek_char() == Some(b'=') {
480 self.pos += 1;
481 (Tok::Neq, String::new())
482 } else {
483 (Tok::LogNot, String::new())
484 }
485 }
486 b'<' => {
487 self.pos += 1;
488 match self.peek_char() {
489 Some(b'<') => {
490 self.pos += 1;
491 (Tok::Shl, String::new())
492 }
493 Some(b'=') => {
494 self.pos += 1;
495 (Tok::Leq, String::new())
496 }
497 _ => (Tok::Lt, String::new()),
498 }
499 }
500 b'>' => {
501 self.pos += 1;
502 match self.peek_char() {
503 Some(b'>') => {
504 self.pos += 1;
505 (Tok::Shr, String::new())
506 }
507 Some(b'=') => {
508 self.pos += 1;
509 (Tok::Geq, String::new())
510 }
511 _ => (Tok::Gt, String::new()),
512 }
513 }
514 b'=' => {
515 self.pos += 1;
516 if self.peek_char() == Some(b'=') {
517 self.pos += 1;
518 (Tok::Eq, String::new())
519 } else {
520 (Tok::Assign, String::new())
521 }
522 }
523 b'(' => {
524 self.pos += 1;
525 (Tok::LParen, String::new())
526 }
527 b')' => {
528 self.pos += 1;
529 (Tok::RParen, String::new())
530 }
531 b',' => {
532 self.pos += 1;
533 (Tok::Comma, String::new())
534 }
535 b'?' => {
536 self.pos += 1;
537 (Tok::Quest, String::new())
538 }
539 b':' => {
540 self.pos += 1;
541 (Tok::Colon, String::new())
542 }
543 _ => {
544 self.pos += 1;
545 (Tok::Eoi, String::new())
546 }
547 }
548 }
549
550 pub fn expr(&mut self) {
555 self.assign_expr();
556 }
557
558 fn assign_expr(&mut self) {
559 let save_pos = self.pos;
560
561 self.skip_whitespace();
563 if let Some(c) = self.peek_char() {
564 if c.is_ascii_alphabetic() || c == b'_' {
565 let name = self.read_ident();
566 self.skip_whitespace();
567 let (tok, _) = self.peek_tok();
568 match tok {
569 Tok::Assign => {
570 let _ = self.next_tok(); let slot = self.slot_for(&name);
572 self.assign_expr();
573 self.builder.emit(Op::Dup, 0);
574 self.builder.emit(Op::SetSlot(slot), 0);
575 return;
576 }
577 Tok::PlusAssign
578 | Tok::MinusAssign
579 | Tok::MulAssign
580 | Tok::DivAssign
581 | Tok::ModAssign => {
582 let _ = self.next_tok(); let slot = self.slot_for(&name);
584 self.builder.emit(Op::GetSlot(slot), 0);
585 self.assign_expr();
586 match tok {
587 Tok::PlusAssign => self.builder.emit(Op::Add, 0),
588 Tok::MinusAssign => self.builder.emit(Op::Sub, 0),
589 Tok::MulAssign => self.builder.emit(Op::Mul, 0),
590 Tok::DivAssign => self.builder.emit(Op::Div, 0),
591 Tok::ModAssign => self.builder.emit(Op::Mod, 0),
592 _ => unreachable!(),
593 };
594 self.builder.emit(Op::Dup, 0);
595 self.builder.emit(Op::SetSlot(slot), 0);
596 return;
597 }
598 _ => {}
599 }
600 self.pos = save_pos;
602 }
603 }
604
605 self.ternary_expr();
606 }
607
608 fn peek_tok(&mut self) -> (Tok, String) {
609 let save = self.pos;
610 let tok = self.next_tok();
611 self.pos = save;
612 tok
613 }
614
615 fn ternary_expr(&mut self) {
616 self.logor_expr();
617 let (tok, _) = self.peek_tok();
618 if tok == Tok::Quest {
619 let _ = self.next_tok(); let else_jump = self.builder.emit(Op::JumpIfFalse(0), 0);
621 self.expr(); let (colon, _) = self.peek_tok();
623 let end_jump = self.builder.emit(Op::Jump(0), 0);
624 let else_target = self.builder.current_pos();
625 self.builder.patch_jump(else_jump, else_target);
626 if colon == Tok::Colon {
627 let _ = self.next_tok(); }
629 self.expr(); let end_target = self.builder.current_pos();
631 self.builder.patch_jump(end_jump, end_target);
632 }
633 }
634
635 fn logor_expr(&mut self) {
636 self.logand_expr();
637 loop {
638 let (tok, _) = self.peek_tok();
639 if tok == Tok::LogOr {
640 let _ = self.next_tok();
641 let skip = self.builder.emit(Op::JumpIfTrueKeep(0), 0);
642 self.builder.emit(Op::Pop, 0);
643 self.logand_expr();
644 self.builder.patch_jump(skip, self.builder.current_pos());
645 } else {
646 break;
647 }
648 }
649 }
650
651 fn logand_expr(&mut self) {
652 self.bitor_expr();
653 loop {
654 let (tok, _) = self.peek_tok();
655 if tok == Tok::LogAnd {
656 let _ = self.next_tok();
657 let skip = self.builder.emit(Op::JumpIfFalseKeep(0), 0);
658 self.builder.emit(Op::Pop, 0);
659 self.bitor_expr();
660 self.builder.patch_jump(skip, self.builder.current_pos());
661 } else {
662 break;
663 }
664 }
665 }
666
667 fn bitor_expr(&mut self) {
668 self.bitxor_expr();
669 loop {
670 let (tok, _) = self.peek_tok();
671 if tok == Tok::BitOr {
672 let _ = self.next_tok();
673 self.bitxor_expr();
674 self.builder.emit(Op::BitOr, 0);
675 } else {
676 break;
677 }
678 }
679 }
680
681 fn bitxor_expr(&mut self) {
682 self.bitand_expr();
683 loop {
684 let (tok, _) = self.peek_tok();
685 if tok == Tok::BitXor {
686 let _ = self.next_tok();
687 self.bitand_expr();
688 self.builder.emit(Op::BitXor, 0);
689 } else {
690 break;
691 }
692 }
693 }
694
695 fn bitand_expr(&mut self) {
696 self.equality_expr();
697 loop {
698 let (tok, _) = self.peek_tok();
699 if tok == Tok::BitAnd {
700 let _ = self.next_tok();
701 self.equality_expr();
702 self.builder.emit(Op::BitAnd, 0);
703 } else {
704 break;
705 }
706 }
707 }
708
709 fn equality_expr(&mut self) {
710 self.comparison_expr();
711 loop {
712 let (tok, _) = self.peek_tok();
713 match tok {
714 Tok::Eq => {
715 let _ = self.next_tok();
716 self.comparison_expr();
717 self.builder.emit(Op::NumEq, 0);
718 }
719 Tok::Neq => {
720 let _ = self.next_tok();
721 self.comparison_expr();
722 self.builder.emit(Op::NumNe, 0);
723 }
724 _ => break,
725 }
726 }
727 }
728
729 fn comparison_expr(&mut self) {
730 self.shift_expr();
731 loop {
732 let (tok, _) = self.peek_tok();
733 match tok {
734 Tok::Lt => {
735 let _ = self.next_tok();
736 self.shift_expr();
737 self.builder.emit(Op::NumLt, 0);
738 }
739 Tok::Gt => {
740 let _ = self.next_tok();
741 self.shift_expr();
742 self.builder.emit(Op::NumGt, 0);
743 }
744 Tok::Leq => {
745 let _ = self.next_tok();
746 self.shift_expr();
747 self.builder.emit(Op::NumLe, 0);
748 }
749 Tok::Geq => {
750 let _ = self.next_tok();
751 self.shift_expr();
752 self.builder.emit(Op::NumGe, 0);
753 }
754 _ => break,
755 }
756 }
757 }
758
759 fn shift_expr(&mut self) {
760 self.add_expr();
761 loop {
762 let (tok, _) = self.peek_tok();
763 match tok {
764 Tok::Shl => {
765 let _ = self.next_tok();
766 self.add_expr();
767 self.builder.emit(Op::Shl, 0);
768 }
769 Tok::Shr => {
770 let _ = self.next_tok();
771 self.add_expr();
772 self.builder.emit(Op::Shr, 0);
773 }
774 _ => break,
775 }
776 }
777 }
778
779 fn add_expr(&mut self) {
780 self.mul_expr();
781 loop {
782 let (tok, _) = self.peek_tok();
783 match tok {
784 Tok::Plus => {
785 let _ = self.next_tok();
786 self.mul_expr();
787 self.builder.emit(Op::Add, 0);
788 }
789 Tok::Minus => {
790 let _ = self.next_tok();
791 self.mul_expr();
792 self.builder.emit(Op::Sub, 0);
793 }
794 _ => break,
795 }
796 }
797 }
798
799 fn mul_expr(&mut self) {
800 self.pow_expr();
801 loop {
802 let (tok, _) = self.peek_tok();
803 match tok {
804 Tok::Mul => {
805 let _ = self.next_tok();
806 self.pow_expr();
807 self.builder.emit(Op::Mul, 0);
808 }
809 Tok::Div => {
810 let _ = self.next_tok();
811 self.pow_expr();
812 self.builder.emit(Op::Div, 0);
813 }
814 Tok::Mod => {
815 let _ = self.next_tok();
816 self.pow_expr();
817 self.builder.emit(Op::Mod, 0);
818 }
819 _ => break,
820 }
821 }
822 }
823
824 fn pow_expr(&mut self) {
825 self.unary_expr();
826 let (tok, _) = self.peek_tok();
827 if tok == Tok::Pow {
828 let _ = self.next_tok();
829 self.pow_expr(); self.builder.emit(Op::Pow, 0);
831 }
832 }
833
834 fn unary_expr(&mut self) {
835 let (tok, name) = self.peek_tok();
836 match tok {
837 Tok::Minus => {
838 let _ = self.next_tok();
839 self.unary_expr();
840 self.builder.emit(Op::Negate, 0);
841 }
842 Tok::Plus => {
843 let _ = self.next_tok();
844 self.unary_expr();
845 }
847 Tok::LogNot => {
848 let _ = self.next_tok();
849 self.unary_expr();
850 self.builder.emit(Op::LogNot, 0);
851 }
852 Tok::BitNot => {
853 let _ = self.next_tok();
854 self.unary_expr();
855 self.builder.emit(Op::BitNot, 0);
856 }
857 Tok::PreInc => {
858 let _ = self.next_tok();
859 let (_, var_name) = self.next_tok();
861 let slot = self.slot_for(&var_name);
862 self.builder.emit(Op::PreIncSlot(slot), 0);
863 }
864 Tok::PreDec => {
865 let _ = self.next_tok();
866 let (_, var_name) = self.next_tok();
867 let slot = self.slot_for(&var_name);
868 self.builder.emit(Op::GetSlot(slot), 0);
869 self.builder.emit(Op::Dec, 0);
870 self.builder.emit(Op::Dup, 0);
871 self.builder.emit(Op::SetSlot(slot), 0);
872 }
873 _ => self.primary_expr(),
874 }
875 }
876
877 fn primary_expr(&mut self) {
878 let (tok, name) = self.next_tok();
879 match tok {
880 Tok::Num(n) => {
881 self.builder.emit(Op::LoadInt(n), 0);
882 }
883 Tok::Float(f) => {
884 self.builder.emit(Op::LoadFloat(f), 0);
885 }
886 Tok::Ident => {
887 let slot = self.slot_for(&name);
888 self.builder.emit(Op::GetSlot(slot), 0);
889
890 let (post_tok, _) = self.peek_tok();
892 match post_tok {
893 Tok::PreInc => {
894 let _ = self.next_tok();
896 self.builder.emit(Op::Dup, 0); self.builder.emit(Op::Inc, 0);
898 self.builder.emit(Op::SetSlot(slot), 0);
899 }
901 Tok::PreDec => {
902 let _ = self.next_tok();
903 self.builder.emit(Op::Dup, 0);
904 self.builder.emit(Op::Dec, 0);
905 self.builder.emit(Op::SetSlot(slot), 0);
906 }
907 _ => {}
908 }
909 }
910 Tok::LParen => {
911 self.expr();
912 let _ = self.next_tok(); }
914 _ => {
915 self.builder.emit(Op::LoadInt(0), 0);
917 }
918 }
919 }
920}
921
922#[cfg(test)]
932mod tests {
933 use super::*;
934 use fusevm::{VMResult, Value};
935
936 fn eval(expr: &str) -> Value {
939 let chunk = ArithCompiler::new(expr).compile();
940 let mut vm = fusevm::VM::new(chunk);
941 match vm.run() {
942 VMResult::Ok(v) => v,
943 VMResult::Halted => Value::Undef,
944 VMResult::Error(e) => panic!("VM error evaluating {expr:?}: {e}"),
945 }
946 }
947
948 fn eval_int(expr: &str) -> i64 {
949 eval(expr).to_int()
950 }
951
952 fn eval_float(expr: &str) -> f64 {
953 eval(expr).to_float()
954 }
955
956 #[test]
958 fn literal_zero() {
959 assert_eq!(eval_int("0"), 0);
960 }
961
962 #[test]
963 fn literal_small_positive() {
964 assert_eq!(eval_int("42"), 42);
965 }
966
967 #[test]
968 fn literal_large() {
969 assert_eq!(eval_int("1000000000"), 1_000_000_000);
970 }
971
972 #[test]
973 fn literal_hex_lowercase() {
974 assert_eq!(eval_int("0xff"), 255);
975 }
976
977 #[test]
978 fn literal_hex_uppercase() {
979 assert_eq!(eval_int("0XDEAD"), 0xDEAD);
980 }
981
982 #[test]
983 fn literal_hex_mixed() {
984 assert_eq!(eval_int("0xCaFe"), 0xCAFE);
985 }
986
987 #[test]
988 fn literal_octal() {
989 assert_eq!(eval_int("017"), 17);
994 }
995
996 #[test]
997 fn literal_octal_zero_prefix_only() {
998 assert_eq!(eval_int("0"), 0);
1000 }
1001
1002 #[test]
1004 fn literal_float_simple() {
1005 assert!((eval_float("3.14") - 3.14).abs() < 1e-9);
1006 }
1007
1008 #[test]
1009 fn literal_float_no_fractional_digits() {
1010 assert!((eval_float("5.") - 5.0).abs() < 1e-9);
1012 }
1013
1014 #[test]
1016 fn add_two_ints() {
1017 assert_eq!(eval_int("40 + 2"), 42);
1018 }
1019
1020 #[test]
1021 fn sub_two_ints() {
1022 assert_eq!(eval_int("50 - 8"), 42);
1023 }
1024
1025 #[test]
1026 fn sub_into_negative() {
1027 assert_eq!(eval_int("5 - 10"), -5);
1028 }
1029
1030 #[test]
1031 fn add_chain_left_associative() {
1032 assert_eq!(eval_int("1 + 2 + 3 + 4"), 10);
1033 }
1034
1035 #[test]
1036 fn sub_chain_left_associative() {
1037 assert_eq!(eval_int("100 - 1 - 2 - 3"), 94);
1039 }
1040
1041 #[test]
1043 fn mul_two_ints() {
1044 assert_eq!(eval_int("6 * 7"), 42);
1045 }
1046
1047 #[test]
1048 fn div_two_ints() {
1049 assert_eq!(eval_int("84 / 2"), 42);
1050 }
1051
1052 #[test]
1053 fn mod_two_ints() {
1054 assert_eq!(eval_int("17 % 5"), 2);
1055 }
1056
1057 #[test]
1058 fn mod_evenly_divides() {
1059 assert_eq!(eval_int("10 % 5"), 0);
1060 }
1061
1062 #[test]
1064 fn precedence_mul_over_add() {
1065 assert_eq!(eval_int("2 + 3 * 4"), 14);
1066 }
1067
1068 #[test]
1069 fn precedence_div_over_sub() {
1070 assert_eq!(eval_int("20 - 10 / 2"), 15);
1071 }
1072
1073 #[test]
1074 fn precedence_parens_override() {
1075 assert_eq!(eval_int("(2 + 3) * 4"), 20);
1076 }
1077
1078 #[test]
1079 fn precedence_nested_parens() {
1080 assert_eq!(eval_int("((1 + 2) * (3 + 4))"), 21);
1081 }
1082
1083 #[test]
1084 fn precedence_pow_over_mul() {
1085 assert_eq!(eval_int("2 * 3 ** 2"), 18);
1087 }
1088
1089 #[test]
1090 fn pow_right_associative() {
1091 assert_eq!(eval_int("2 ** 3 ** 2"), 512);
1093 }
1094
1095 #[test]
1097 fn unary_minus_literal() {
1098 assert_eq!(eval_int("-5"), -5);
1099 }
1100
1101 #[test]
1102 fn unary_minus_expr() {
1103 assert_eq!(eval_int("-(3 + 4)"), -7);
1104 }
1105
1106 #[test]
1107 fn unary_plus_is_noop() {
1108 assert_eq!(eval_int("+42"), 42);
1109 }
1110
1111 #[test]
1112 fn double_negation_requires_separator() {
1113 assert_eq!(eval_int("- -5"), 5);
1116 assert_eq!(eval_int("-(-5)"), 5);
1117 }
1118
1119 #[test]
1120 fn unary_minus_binds_tighter_than_pow() {
1121 assert_eq!(eval_int("-2 ** 2"), 4);
1126 }
1127
1128 #[test]
1130 fn bitand_basic() {
1131 assert_eq!(eval_int("0xFF & 0x0F"), 0x0F);
1132 }
1133
1134 #[test]
1135 fn bitor_basic() {
1136 assert_eq!(eval_int("0x10 | 0x01"), 0x11);
1137 }
1138
1139 #[test]
1140 fn bitxor_basic() {
1141 assert_eq!(eval_int("0xFF ^ 0x0F"), 0xF0);
1142 }
1143
1144 #[test]
1145 fn bitnot_zero_is_minus_one() {
1146 assert_eq!(eval_int("~0"), -1);
1147 }
1148
1149 #[test]
1150 fn bitnot_one() {
1151 assert_eq!(eval_int("~1"), -2);
1153 }
1154
1155 #[test]
1156 fn bitwise_precedence_and_over_or() {
1157 assert_eq!(eval_int("1 | 2 & 0"), 1);
1159 }
1160
1161 #[test]
1163 fn shl_basic() {
1164 assert_eq!(eval_int("1 << 4"), 16);
1165 }
1166
1167 #[test]
1168 fn shr_basic() {
1169 assert_eq!(eval_int("16 >> 2"), 4);
1170 }
1171
1172 #[test]
1173 fn shl_chain() {
1174 assert_eq!(eval_int("1 << 1 << 2"), 8);
1175 }
1176
1177 #[test]
1179 fn cmp_eq_true() {
1180 assert_eq!(eval_int("5 == 5"), 1);
1181 }
1182
1183 #[test]
1184 fn cmp_eq_false() {
1185 assert_eq!(eval_int("5 == 6"), 0);
1186 }
1187
1188 #[test]
1189 fn cmp_ne_true() {
1190 assert_eq!(eval_int("5 != 6"), 1);
1191 }
1192
1193 #[test]
1194 fn cmp_lt_true() {
1195 assert_eq!(eval_int("3 < 5"), 1);
1196 }
1197
1198 #[test]
1199 fn cmp_lt_false_on_equal() {
1200 assert_eq!(eval_int("5 < 5"), 0);
1201 }
1202
1203 #[test]
1204 fn cmp_le_true_on_equal() {
1205 assert_eq!(eval_int("5 <= 5"), 1);
1206 }
1207
1208 #[test]
1209 fn cmp_gt_true() {
1210 assert_eq!(eval_int("5 > 3"), 1);
1211 }
1212
1213 #[test]
1214 fn cmp_ge_true_on_equal() {
1215 assert_eq!(eval_int("5 >= 5"), 1);
1216 }
1217
1218 #[test]
1220 fn logand_true_true() {
1221 assert_eq!(eval_int("1 && 1"), 1);
1222 }
1223
1224 #[test]
1225 fn logand_short_circuits_on_false() {
1226 assert_eq!(eval_int("0 && 99"), 0);
1228 }
1229
1230 #[test]
1231 fn logor_true_short_circuits() {
1232 assert_eq!(eval_int("1 || 0"), 1);
1234 }
1235
1236 #[test]
1237 fn logor_both_false() {
1238 assert_eq!(eval_int("0 || 0"), 0);
1239 }
1240
1241 #[test]
1242 fn lognot_true() {
1243 assert_eq!(eval_int("!0"), 1);
1244 }
1245
1246 #[test]
1247 fn lognot_false() {
1248 assert_eq!(eval_int("!1"), 0);
1249 }
1250
1251 #[test]
1252 fn lognot_double() {
1253 assert_eq!(eval_int("!!5"), 1);
1255 }
1256
1257 #[test]
1259 fn ternary_true_branch() {
1260 assert_eq!(eval_int("1 ? 10 : 20"), 10);
1261 }
1262
1263 #[test]
1264 fn ternary_false_branch() {
1265 assert_eq!(eval_int("0 ? 10 : 20"), 20);
1266 }
1267
1268 #[test]
1269 fn ternary_condition_is_expression() {
1270 assert_eq!(eval_int("(3 < 5) ? 100 : 200"), 100);
1271 }
1272
1273 #[test]
1274 fn ternary_nested_in_true_branch() {
1275 assert_eq!(eval_int("1 ? (0 ? 1 : 2) : 3"), 2);
1276 }
1277
1278 #[test]
1280 fn float_add() {
1281 assert!((eval_float("1.5 + 2.5") - 4.0).abs() < 1e-9);
1282 }
1283
1284 #[test]
1285 fn float_div_does_not_truncate() {
1286 assert!((eval_float("1.0 / 4.0") - 0.25).abs() < 1e-9);
1288 }
1289
1290 #[test]
1292 fn collect_identifiers_bare() {
1293 let c = ArithCompiler::new("");
1294 let names = c.collect_identifiers("a + b * c");
1295 assert_eq!(names, vec!["a", "b", "c"]);
1296 }
1297
1298 #[test]
1299 fn collect_identifiers_dollar_prefixed() {
1300 let c = ArithCompiler::new("");
1301 let names = c.collect_identifiers("$x + 1");
1302 assert_eq!(names, vec!["x"]);
1303 }
1304
1305 #[test]
1306 fn collect_identifiers_braced() {
1307 let c = ArithCompiler::new("");
1308 let names = c.collect_identifiers("${count} * 2");
1309 assert_eq!(names, vec!["count"]);
1310 }
1311
1312 #[test]
1313 fn collect_identifiers_positional() {
1314 let c = ArithCompiler::new("");
1315 let names = c.collect_identifiers("$1 + $2");
1316 assert_eq!(names, vec!["1", "2"]);
1317 }
1318
1319 #[test]
1320 fn collect_identifiers_dedups() {
1321 let c = ArithCompiler::new("");
1322 let names = c.collect_identifiers("a + a + b + a");
1323 assert_eq!(names, vec!["a", "b"]);
1324 }
1325
1326 #[test]
1327 fn collect_identifiers_ignores_pure_numeric() {
1328 let c = ArithCompiler::new("");
1329 let names = c.collect_identifiers("42 + 7 * 3");
1330 assert!(names.is_empty(), "got names: {names:?}");
1331 }
1332
1333 #[test]
1335 fn slot_for_first_var_gets_slot_zero() {
1336 let mut c = ArithCompiler::new("");
1337 assert_eq!(c.slot_for("x"), 0);
1338 }
1339
1340 #[test]
1341 fn slot_for_second_var_gets_slot_one() {
1342 let mut c = ArithCompiler::new("");
1343 let _ = c.slot_for("x");
1344 assert_eq!(c.slot_for("y"), 1);
1345 }
1346
1347 #[test]
1348 fn slot_for_repeated_name_returns_same_slot() {
1349 let mut c = ArithCompiler::new("");
1350 let s1 = c.slot_for("a");
1351 let s2 = c.slot_for("a");
1352 assert_eq!(s1, s2);
1353 }
1354
1355 #[test]
1357 fn compile_emits_pushframe_and_returnvalue_brackets() {
1358 let chunk = ArithCompiler::new("1").compile();
1359 assert!(
1360 matches!(chunk.ops.first(), Some(Op::PushFrame)),
1361 "first op should be PushFrame, got {:?}",
1362 chunk.ops.first()
1363 );
1364 assert!(
1365 matches!(chunk.ops.last(), Some(Op::ReturnValue)),
1366 "last op should be ReturnValue, got {:?}",
1367 chunk.ops.last()
1368 );
1369 }
1370
1371 #[test]
1372 fn compile_sets_source_marker() {
1373 let chunk = ArithCompiler::new("1").compile();
1374 assert_eq!(chunk.source, "$((...))");
1375 }
1376}