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 pub assigned: std::collections::HashSet<String>,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq)]
53enum Tok {
54 Num(i64),
55 Float(f64),
56 Ident,
57 Plus,
58 Minus,
59 Mul,
60 Div,
61 Mod,
62 Pow,
63 BitAnd,
64 BitOr,
65 BitXor,
66 BitNot,
67 Shl,
68 Shr,
69 LogAnd,
70 LogOr,
71 LogNot,
72 Eq,
73 Neq,
74 Lt,
75 Gt,
76 Leq,
77 Geq,
78 Assign,
79 PlusAssign,
80 MinusAssign,
81 MulAssign,
82 DivAssign,
83 ModAssign,
84 AndAssign,
90 XorAssign,
91 OrAssign,
92 ShlAssign,
93 ShrAssign,
94 PowAssign,
95 LogAndAssign,
96 LogOrAssign,
97 LogXorAssign,
98 LogXor,
100 PreInc,
101 PreDec,
102 PostInc,
103 PostDec,
104 LParen,
105 RParen,
106 Comma,
107 Quest,
108 Colon,
109 Eoi,
110}
111
112impl<'a> ArithCompiler<'a> {
113 pub fn new(input: &'a str) -> Self {
115 Self {
116 input,
117 pos: 0,
118 builder: ChunkBuilder::new(),
119 slots: HashMap::new(),
120 next_slot: 0,
121 assigned: std::collections::HashSet::new(),
122 }
123 }
124
125 pub fn compile(mut self) -> fusevm::Chunk {
128 self.builder.set_source("$((...))");
129 self.builder.emit(Op::PushFrame, 0);
130 self.expr();
131 self.builder.emit(Op::ReturnValue, 0);
132 self.builder.build()
133 }
134
135 fn emit_setmathvar(&mut self, name: &str, slot: u16, pval: bool) {
152 self.builder.emit(Op::SetSlot(slot), 0);
153 let name_const = self.builder.add_constant(Value::str(name));
154 self.builder.emit(Op::LoadConst(name_const), 0);
155 self.builder.emit(Op::GetSlot(slot), 0);
156 self.builder.emit(Op::LoadInt(pval as i64), 0);
157 self.builder.emit(
158 Op::CallBuiltin(crate::vm_helper::BUILTIN_SET_MATH_VAR, 3),
159 0,
160 );
161 }
162
163 fn emit_getmathparam(&mut self, name: &str) {
175 let name_const = self.builder.add_constant(Value::str(name));
176 self.builder.emit(Op::LoadConst(name_const), 0);
177 self.builder.emit(
178 Op::CallBuiltin(crate::vm_helper::BUILTIN_GET_MATH_VAR, 1),
179 0,
180 );
181 }
182
183 pub fn slot_for(&mut self, name: &str) -> u16 {
184 if let Some(&slot) = self.slots.get(name) {
185 return slot;
186 }
187 let slot = self.next_slot;
188 self.next_slot += 1;
189 self.slots.insert(name.to_string(), slot);
190 slot
191 }
192
193 pub fn collect_identifiers(&self, expr: &str) -> Vec<String> {
198 let bytes = expr.as_bytes();
199 let mut names: Vec<String> = Vec::new();
200 let mut i = 0;
201 while i < bytes.len() {
202 let b = bytes[i];
203 let with_dollar = b == b'$';
207 if with_dollar {
208 if i + 1 >= bytes.len() {
209 i += 1;
210 continue;
211 }
212 if bytes[i + 1] == b'{' {
213 i += 2;
214 let start = i;
215 while i < bytes.len() && bytes[i] != b'}' {
216 i += 1;
217 }
218 let name = expr[start..i].to_string();
219 if !name.is_empty() && !names.contains(&name) {
220 names.push(name);
221 }
222 if i < bytes.len() {
223 i += 1; }
225 continue;
226 }
227 i += 1;
228 let start = i;
229 if bytes
230 .get(i)
231 .copied()
232 .map(|c| c.is_ascii_digit())
233 .unwrap_or(false)
234 {
235 while i < bytes.len() && bytes[i].is_ascii_digit() {
236 i += 1;
237 }
238 let name = expr[start..i].to_string();
239 if !names.contains(&name) {
240 names.push(name);
241 }
242 continue;
243 }
244 }
245 if b.is_ascii_alphabetic() || b == b'_' || (with_dollar && i < bytes.len()) {
246 let start = i;
247 while i < bytes.len() && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') {
248 i += 1;
249 }
250 let name = expr[start..i].to_string();
251 if !name.is_empty() && !names.contains(&name) {
252 names.push(name);
253 }
254 } else {
255 i += 1;
256 }
257 }
258 names
259 }
260
261 fn skip_whitespace(&mut self) {
264 while self.pos < self.input.len() {
265 let b = self.input.as_bytes()[self.pos];
266 if b == b' ' || b == b'\t' || b == b'\n' || b == b'\r' {
267 self.pos += 1;
268 } else {
269 break;
270 }
271 }
272 }
273
274 fn peek_char(&self) -> Option<u8> {
275 self.input.as_bytes().get(self.pos).copied()
276 }
277
278 fn next_char(&mut self) -> Option<u8> {
279 let c = self.input.as_bytes().get(self.pos).copied();
280 if c.is_some() {
281 self.pos += 1;
282 }
283 c
284 }
285
286 fn read_ident(&mut self) -> String {
287 let start = self.pos;
288 while self.pos < self.input.len() {
289 let b = self.input.as_bytes()[self.pos];
290 if b.is_ascii_alphanumeric() || b == b'_' {
291 self.pos += 1;
292 } else {
293 break;
294 }
295 }
296 self.input[start..self.pos].to_string()
297 }
298
299 fn read_number(&mut self) -> Tok {
300 let start = self.pos;
301
302 if self.pos + 1 < self.input.len()
305 && self.input.as_bytes()[self.pos] == b'0'
306 && (self.input.as_bytes()[self.pos + 1] == b'x'
307 || self.input.as_bytes()[self.pos + 1] == b'X')
308 {
309 self.pos += 2;
310 while self.pos < self.input.len() && self.input.as_bytes()[self.pos].is_ascii_hexdigit()
311 {
312 self.pos += 1;
313 }
314 let val = i64::from_str_radix(&self.input[start + 2..self.pos], 16).unwrap_or(0);
315 crate::ported::math::set_lastbase(16);
320 return Tok::Num(val);
321 }
322
323 while self.pos < self.input.len() && self.input.as_bytes()[self.pos].is_ascii_digit() {
326 self.pos += 1;
327 }
328
329 if self.pos < self.input.len()
342 && self.input.as_bytes()[self.pos] == b'#'
343 && !crate::dash_mode::dash_strict()
344 {
345 let base_str = &self.input[start..self.pos];
346 if let Ok(base) = base_str.parse::<u32>() {
347 if (2..=36).contains(&base) {
348 self.pos += 1; let digit_start = self.pos;
350 while self.pos < self.input.len() {
351 let b = self.input.as_bytes()[self.pos];
352 let in_base = if base <= 10 {
353 b.is_ascii_digit() && (b - b'0') < base as u8
354 } else {
355 b.is_ascii_digit()
356 || (b.is_ascii_alphabetic() && {
357 let v = if b.is_ascii_lowercase() {
358 b - b'a' + 10
359 } else {
360 b - b'A' + 10
361 };
362 (v as u32) < base
363 })
364 };
365 if in_base {
366 self.pos += 1;
367 } else {
368 break;
369 }
370 }
371 let digits = &self.input[digit_start..self.pos];
372 let val = i64::from_str_radix(digits, base).unwrap_or(0);
373 crate::ported::math::set_lastbase(base as i32);
378 return Tok::Num(val);
379 }
380 }
381 }
382
383 let lex_octal = self.pos > start + 1
387 && self.input.as_bytes()[start] == b'0'
388 && self.input.as_bytes()[start + 1].is_ascii_digit()
389 && crate::ported::zsh_h::isset(crate::ported::zsh_h::OCTALZEROES);
390 if lex_octal {
391 let val = i64::from_str_radix(&self.input[start + 1..self.pos], 8).unwrap_or(0);
392 return Tok::Num(val);
393 }
394
395 if self.pos < self.input.len() && self.input.as_bytes()[self.pos] == b'.' {
397 self.pos += 1;
398 while self.pos < self.input.len() && self.input.as_bytes()[self.pos].is_ascii_digit() {
399 self.pos += 1;
400 }
401 let val: f64 = self.input[start..self.pos].parse().unwrap_or(0.0);
402 return Tok::Float(val);
403 }
404
405 let val: i64 = self.input[start..self.pos].parse().unwrap_or(0);
406 Tok::Num(val)
407 }
408
409 fn next_tok(&mut self) -> (Tok, String) {
410 self.skip_whitespace();
411
412 let Some(c) = self.peek_char() else {
413 return (Tok::Eoi, String::new());
414 };
415
416 match c {
417 b'0'..=b'9' => {
418 let tok = self.read_number();
419 (tok, String::new())
420 }
421 b'a'..=b'z' | b'A'..=b'Z' | b'_' => {
422 let name = self.read_ident();
423 (Tok::Ident, name)
424 }
425 b'$' => {
426 self.pos += 1;
432 if self.peek_char() == Some(b'{') {
433 self.pos += 1;
434 let mut name = String::new();
435 while let Some(b) = self.peek_char() {
436 if b == b'}' {
437 self.pos += 1;
438 break;
439 }
440 name.push(b as char);
441 self.pos += 1;
442 }
443 (Tok::Ident, name)
444 } else if let Some(b) = self.peek_char() {
445 if b.is_ascii_digit() {
446 let mut name = String::new();
448 while let Some(d) = self.peek_char() {
449 if !d.is_ascii_digit() {
450 break;
451 }
452 name.push(d as char);
453 self.pos += 1;
454 }
455 (Tok::Ident, name)
456 } else if b.is_ascii_alphabetic() || b == b'_' {
457 let name = self.read_ident();
458 (Tok::Ident, name)
459 } else {
460 (Tok::Num(0), String::new())
463 }
464 } else {
465 (Tok::Num(0), String::new())
466 }
467 }
468 b'+' => {
469 self.pos += 1;
470 match self.peek_char() {
471 Some(b'+') if !crate::dash_mode::dash_strict() => {
477 self.pos += 1;
478 (Tok::PreInc, String::new())
479 }
480 Some(b'=') => {
481 self.pos += 1;
482 (Tok::PlusAssign, String::new())
483 }
484 _ => (Tok::Plus, String::new()),
485 }
486 }
487 b'-' => {
488 self.pos += 1;
489 match self.peek_char() {
490 Some(b'-') if !crate::dash_mode::dash_strict() => {
491 self.pos += 1;
492 (Tok::PreDec, String::new())
493 }
494 Some(b'=') => {
495 self.pos += 1;
496 (Tok::MinusAssign, String::new())
497 }
498 _ => (Tok::Minus, String::new()),
499 }
500 }
501 b'*' => {
502 self.pos += 1;
503 match self.peek_char() {
504 Some(b'*') => {
505 self.pos += 1;
506 if self.peek_char() == Some(b'=') {
507 self.pos += 1;
508 (Tok::PowAssign, String::new())
512 } else {
513 (Tok::Pow, String::new())
514 }
515 }
516 Some(b'=') => {
517 self.pos += 1;
518 (Tok::MulAssign, String::new())
519 }
520 _ => (Tok::Mul, String::new()),
521 }
522 }
523 b'/' => {
524 self.pos += 1;
525 if self.peek_char() == Some(b'=') {
526 self.pos += 1;
527 (Tok::DivAssign, String::new())
528 } else {
529 (Tok::Div, String::new())
530 }
531 }
532 b'%' => {
533 self.pos += 1;
534 if self.peek_char() == Some(b'=') {
535 self.pos += 1;
536 (Tok::ModAssign, String::new())
537 } else {
538 (Tok::Mod, String::new())
539 }
540 }
541 b'&' => {
542 self.pos += 1;
543 match self.peek_char() {
544 Some(b'&') => {
545 self.pos += 1;
546 if self.peek_char() == Some(b'=') {
548 self.pos += 1;
549 (Tok::LogAndAssign, String::new())
550 } else {
551 (Tok::LogAnd, String::new())
552 }
553 }
554 Some(b'=') => {
555 self.pos += 1;
556 (Tok::AndAssign, String::new()) }
558 _ => (Tok::BitAnd, String::new()),
559 }
560 }
561 b'|' => {
562 self.pos += 1;
563 match self.peek_char() {
564 Some(b'|') => {
565 self.pos += 1;
566 if self.peek_char() == Some(b'=') {
568 self.pos += 1;
569 (Tok::LogOrAssign, String::new())
570 } else {
571 (Tok::LogOr, String::new())
572 }
573 }
574 Some(b'=') => {
575 self.pos += 1;
576 (Tok::OrAssign, String::new()) }
578 _ => (Tok::BitOr, String::new()),
579 }
580 }
581 b'^' => {
582 self.pos += 1;
583 match self.peek_char() {
584 Some(b'^') => {
585 self.pos += 1;
586 if self.peek_char() == Some(b'=') {
588 self.pos += 1;
589 (Tok::LogXorAssign, String::new())
590 } else {
591 (Tok::LogXor, String::new()) }
593 }
594 Some(b'=') => {
595 self.pos += 1;
596 (Tok::XorAssign, String::new()) }
598 _ => (Tok::BitXor, String::new()),
599 }
600 }
601 b'~' => {
602 self.pos += 1;
603 (Tok::BitNot, String::new())
604 }
605 b'!' => {
606 self.pos += 1;
607 if self.peek_char() == Some(b'=') {
608 self.pos += 1;
609 (Tok::Neq, String::new())
610 } else {
611 (Tok::LogNot, String::new())
612 }
613 }
614 b'<' => {
615 self.pos += 1;
616 match self.peek_char() {
617 Some(b'<') => {
618 self.pos += 1;
619 if self.peek_char() == Some(b'=') {
621 self.pos += 1;
622 (Tok::ShlAssign, String::new())
623 } else {
624 (Tok::Shl, String::new())
625 }
626 }
627 Some(b'=') => {
628 self.pos += 1;
629 (Tok::Leq, String::new())
630 }
631 _ => (Tok::Lt, String::new()),
632 }
633 }
634 b'>' => {
635 self.pos += 1;
636 match self.peek_char() {
637 Some(b'>') => {
638 self.pos += 1;
639 if self.peek_char() == Some(b'=') {
641 self.pos += 1;
642 (Tok::ShrAssign, String::new())
643 } else {
644 (Tok::Shr, String::new())
645 }
646 }
647 Some(b'=') => {
648 self.pos += 1;
649 (Tok::Geq, String::new())
650 }
651 _ => (Tok::Gt, String::new()),
652 }
653 }
654 b'=' => {
655 self.pos += 1;
656 if self.peek_char() == Some(b'=') {
657 self.pos += 1;
658 (Tok::Eq, String::new())
659 } else {
660 (Tok::Assign, String::new())
661 }
662 }
663 b'(' => {
664 self.pos += 1;
665 (Tok::LParen, String::new())
666 }
667 b')' => {
668 self.pos += 1;
669 (Tok::RParen, String::new())
670 }
671 b',' => {
672 self.pos += 1;
673 (Tok::Comma, String::new())
674 }
675 b'?' => {
676 self.pos += 1;
677 (Tok::Quest, String::new())
678 }
679 b':' => {
680 self.pos += 1;
681 (Tok::Colon, String::new())
682 }
683 _ => {
684 self.pos += 1;
685 (Tok::Eoi, String::new())
686 }
687 }
688 }
689
690 pub fn expr(&mut self) {
720 self.comma_expr();
721 }
722
723 fn comma_expr(&mut self) {
727 self.assign_expr();
728 loop {
729 let (tok, _) = self.peek_tok();
730 if tok == Tok::Comma {
731 let _ = self.next_tok();
732 self.builder.emit(Op::Pop, 0);
733 self.assign_expr();
734 } else {
735 break;
736 }
737 }
738 }
739
740 fn assign_expr(&mut self) {
743 let save_pos = self.pos;
744
745 self.skip_whitespace();
746 if let Some(c) = self.peek_char() {
747 if c.is_ascii_alphabetic() || c == b'_' {
748 let name = self.read_ident();
749 self.skip_whitespace();
750 let (tok, _) = self.peek_tok();
751 if tok == Tok::Assign {
752 let _ = self.next_tok(); let slot = self.slot_for(&name);
754 self.assigned.insert(name.clone());
755 self.assign_expr();
756 self.emit_setmathvar(&name, slot, false);
757 return;
758 }
759 if let Some(binop) = compound_assign_op(tok) {
760 let _ = self.next_tok(); let slot = self.slot_for(&name);
762 self.assigned.insert(name.clone());
763 self.assign_expr();
769 self.builder.emit(Op::SetSlot(slot), 0);
770 self.emit_getmathparam(&name);
771 self.builder.emit(Op::GetSlot(slot), 0);
772 self.emit_binop(binop);
773 self.emit_setmathvar(&name, slot, true);
774 return;
775 }
776 self.pos = save_pos;
778 }
779 }
780
781 self.ternary_expr();
782 }
783
784 fn peek_tok(&mut self) -> (Tok, String) {
785 let save = self.pos;
786 let tok = self.next_tok();
787 self.pos = save;
788 tok
789 }
790
791 fn ternary_expr(&mut self) {
793 self.logor_expr();
794 let (tok, _) = self.peek_tok();
795 if tok == Tok::Quest {
796 let _ = self.next_tok(); let else_jump = self.builder.emit(Op::JumpIfFalse(0), 0);
798 self.assign_expr(); let (colon, _) = self.peek_tok();
800 let end_jump = self.builder.emit(Op::Jump(0), 0);
801 let else_target = self.builder.current_pos();
802 self.builder.patch_jump(else_jump, else_target);
803 if colon == Tok::Colon {
804 let _ = self.next_tok(); }
806 self.assign_expr(); let end_target = self.builder.current_pos();
808 self.builder.patch_jump(end_jump, end_target);
809 }
810 }
811
812 fn logor_expr(&mut self) {
816 self.logand_expr();
817 loop {
818 let (tok, _) = self.peek_tok();
819 match tok {
820 Tok::LogOr => {
821 let _ = self.next_tok();
822 let skip = self.builder.emit(Op::JumpIfTrueKeep(0), 0);
823 self.builder.emit(Op::Pop, 0);
824 self.logand_expr();
825 self.builder.patch_jump(skip, self.builder.current_pos());
826 }
827 Tok::LogXor => {
828 let _ = self.next_tok();
829 self.logand_expr();
830 self.builder.emit(
831 Op::CallBuiltin(crate::vm_helper::BUILTIN_ARITH_LOGXOR, 2),
832 0,
833 );
834 }
835 _ => break,
836 }
837 }
838 }
839
840 fn logand_expr(&mut self) {
842 self.equality_expr();
843 loop {
844 let (tok, _) = self.peek_tok();
845 if tok == Tok::LogAnd {
846 let _ = self.next_tok();
847 let skip = self.builder.emit(Op::JumpIfFalseKeep(0), 0);
848 self.builder.emit(Op::Pop, 0);
849 self.equality_expr();
850 self.builder.patch_jump(skip, self.builder.current_pos());
851 } else {
852 break;
853 }
854 }
855 }
856
857 fn equality_expr(&mut self) {
859 self.comparison_expr();
860 loop {
861 let (tok, _) = self.peek_tok();
862 match tok {
863 Tok::Eq => {
864 let _ = self.next_tok();
865 self.comparison_expr();
866 self.builder.emit(Op::NumEq, 0);
867 }
868 Tok::Neq => {
869 let _ = self.next_tok();
870 self.comparison_expr();
871 self.builder.emit(Op::NumNe, 0);
872 }
873 _ => break,
874 }
875 }
876 }
877
878 fn comparison_expr(&mut self) {
880 self.additive_expr();
881 loop {
882 let (tok, _) = self.peek_tok();
883 let op = match tok {
884 Tok::Lt => Op::NumLt,
885 Tok::Gt => Op::NumGt,
886 Tok::Leq => Op::NumLe,
887 Tok::Geq => Op::NumGe,
888 _ => break,
889 };
890 let _ = self.next_tok();
891 self.additive_expr();
892 self.builder.emit(op, 0);
893 }
894 }
895
896 fn additive_expr(&mut self) {
898 self.multiplicative_expr();
899 loop {
900 let (tok, _) = self.peek_tok();
901 let op = match tok {
902 Tok::Plus => Op::Add,
903 Tok::Minus => Op::Sub,
904 _ => break,
905 };
906 let _ = self.next_tok();
907 self.multiplicative_expr();
908 self.builder.emit(op, 0);
909 }
910 }
911
912 fn multiplicative_expr(&mut self) {
914 self.pow_expr();
915 loop {
916 let (tok, _) = self.peek_tok();
917 match tok {
918 Tok::Mul => {
919 let _ = self.next_tok();
920 self.pow_expr();
921 self.builder.emit(Op::Mul, 0);
922 }
923 Tok::Div => {
924 let _ = self.next_tok();
925 self.pow_expr();
926 self.emit_binop(BinOp::Div);
927 }
928 Tok::Mod => {
929 let _ = self.next_tok();
930 self.pow_expr();
931 self.emit_binop(BinOp::Mod);
932 }
933 _ => break,
934 }
935 }
936 }
937
938 fn pow_expr(&mut self) {
941 self.bitor_expr();
942 let (tok, _) = self.peek_tok();
943 if tok == Tok::Pow {
944 let _ = self.next_tok();
945 self.pow_expr(); self.emit_binop(BinOp::Pow);
947 }
948 }
949
950 fn bitor_expr(&mut self) {
952 self.bitxor_expr();
953 loop {
954 let (tok, _) = self.peek_tok();
955 if tok == Tok::BitOr {
956 let _ = self.next_tok();
957 self.bitxor_expr();
958 self.builder.emit(Op::BitOr, 0);
959 } else {
960 break;
961 }
962 }
963 }
964
965 fn bitxor_expr(&mut self) {
967 self.bitand_expr();
968 loop {
969 let (tok, _) = self.peek_tok();
970 if tok == Tok::BitXor {
971 let _ = self.next_tok();
972 self.bitand_expr();
973 self.builder.emit(Op::BitXor, 0);
974 } else {
975 break;
976 }
977 }
978 }
979
980 fn bitand_expr(&mut self) {
982 self.shift_expr();
983 loop {
984 let (tok, _) = self.peek_tok();
985 if tok == Tok::BitAnd {
986 let _ = self.next_tok();
987 self.shift_expr();
988 self.builder.emit(Op::BitAnd, 0);
989 } else {
990 break;
991 }
992 }
993 }
994
995 fn shift_expr(&mut self) {
998 self.unary_expr();
999 loop {
1000 let (tok, _) = self.peek_tok();
1001 let op = match tok {
1002 Tok::Shl => Op::Shl,
1003 Tok::Shr => Op::Shr,
1004 _ => break,
1005 };
1006 let _ = self.next_tok();
1007 self.unary_expr();
1008 self.builder.emit(op, 0);
1009 }
1010 }
1011
1012 fn unary_expr(&mut self) {
1014 let (tok, _) = self.peek_tok();
1015 match tok {
1016 Tok::Minus => {
1017 let _ = self.next_tok();
1018 self.unary_expr();
1019 self.builder.emit(Op::Negate, 0);
1020 }
1021 Tok::Plus => {
1022 let _ = self.next_tok();
1023 self.unary_expr();
1024 }
1026 Tok::LogNot => {
1027 let _ = self.next_tok();
1028 self.unary_expr();
1029 self.builder.emit(Op::LogNot, 0);
1030 }
1031 Tok::BitNot => {
1032 let _ = self.next_tok();
1033 self.unary_expr();
1034 self.builder.emit(Op::BitNot, 0);
1035 }
1036 Tok::PreInc => {
1037 let _ = self.next_tok();
1038 let (_, var_name) = self.next_tok();
1039 let slot = self.slot_for(&var_name);
1040 self.assigned.insert(var_name.clone());
1041 self.emit_getmathparam(&var_name);
1045 self.builder.emit(Op::LoadInt(1), 0);
1046 self.builder.emit(Op::Add, 0);
1047 self.emit_setmathvar(&var_name, slot, true);
1048 }
1049 Tok::PreDec => {
1050 let _ = self.next_tok();
1051 let (_, var_name) = self.next_tok();
1052 let slot = self.slot_for(&var_name);
1053 self.assigned.insert(var_name.clone());
1054 self.emit_getmathparam(&var_name);
1055 self.builder.emit(Op::LoadInt(1), 0);
1056 self.builder.emit(Op::Sub, 0);
1057 self.emit_setmathvar(&var_name, slot, true);
1058 }
1059 _ => self.primary_expr(),
1060 }
1061 }
1062
1063 fn primary_expr(&mut self) {
1064 let (tok, name) = self.next_tok();
1065 match tok {
1066 Tok::Num(n) => {
1067 self.builder.emit(Op::LoadInt(n), 0);
1068 }
1069 Tok::Float(f) => {
1070 self.builder.emit(Op::LoadFloat(f), 0);
1071 }
1072 Tok::Ident => {
1073 let slot = self.slot_for(&name);
1074 self.emit_getmathparam(&name);
1075
1076 let (post_tok, _) = self.peek_tok();
1079 match post_tok {
1080 Tok::PreInc => {
1081 let _ = self.next_tok();
1082 self.assigned.insert(name.clone());
1083 self.builder.emit(Op::Dup, 0); self.builder.emit(Op::LoadInt(1), 0);
1086 self.builder.emit(Op::Add, 0);
1087 self.emit_setmathvar(&name, slot, true);
1088 self.builder.emit(Op::Pop, 0);
1089 }
1090 Tok::PreDec => {
1091 let _ = self.next_tok();
1092 self.assigned.insert(name.clone());
1093 self.builder.emit(Op::Dup, 0);
1094 self.builder.emit(Op::LoadInt(1), 0);
1095 self.builder.emit(Op::Sub, 0);
1096 self.emit_setmathvar(&name, slot, true);
1097 self.builder.emit(Op::Pop, 0);
1098 }
1099 _ => {}
1100 }
1101 }
1102 Tok::LParen => {
1103 self.expr();
1104 let _ = self.next_tok(); }
1106 _ => {
1107 self.builder.emit(Op::LoadInt(0), 0);
1109 }
1110 }
1111 }
1112
1113 fn emit_binop(&mut self, op: BinOp) {
1129 match op {
1130 BinOp::Add => self.builder.emit(Op::Add, 0),
1131 BinOp::Sub => self.builder.emit(Op::Sub, 0),
1132 BinOp::Mul => self.builder.emit(Op::Mul, 0),
1133 BinOp::BitAnd => self.builder.emit(Op::BitAnd, 0),
1134 BinOp::BitOr => self.builder.emit(Op::BitOr, 0),
1135 BinOp::BitXor => self.builder.emit(Op::BitXor, 0),
1136 BinOp::Shl => self.builder.emit(Op::Shl, 0),
1137 BinOp::Shr => self.builder.emit(Op::Shr, 0),
1138 BinOp::Div => self
1139 .builder
1140 .emit(Op::CallBuiltin(crate::vm_helper::BUILTIN_ARITH_DIV, 2), 0),
1141 BinOp::Mod => self
1142 .builder
1143 .emit(Op::CallBuiltin(crate::vm_helper::BUILTIN_ARITH_MOD, 2), 0),
1144 BinOp::Pow => self
1145 .builder
1146 .emit(Op::CallBuiltin(crate::vm_helper::BUILTIN_ARITH_POW, 2), 0),
1147 BinOp::LogAnd => self
1148 .builder
1149 .emit(Op::CallBuiltin(crate::vm_helper::BUILTIN_ARITH_LOGAND, 2), 0),
1150 BinOp::LogOr => self
1151 .builder
1152 .emit(Op::CallBuiltin(crate::vm_helper::BUILTIN_ARITH_LOGOR, 2), 0),
1153 BinOp::LogXor => self
1154 .builder
1155 .emit(Op::CallBuiltin(crate::vm_helper::BUILTIN_ARITH_LOGXOR, 2), 0),
1156 };
1157 }
1158}
1159
1160const C_PREC: &[(Tok, i32)] = &[
1168 (Tok::LogNot, 2),
1169 (Tok::BitNot, 2),
1170 (Tok::PreInc, 2),
1171 (Tok::PreDec, 2),
1172 (Tok::BitAnd, 9),
1173 (Tok::BitXor, 10),
1174 (Tok::BitOr, 11),
1175 (Tok::Mul, 4),
1176 (Tok::Div, 4),
1177 (Tok::Mod, 4),
1178 (Tok::Plus, 5),
1179 (Tok::Minus, 5),
1180 (Tok::Shl, 6),
1181 (Tok::Shr, 6),
1182 (Tok::Lt, 7),
1183 (Tok::Leq, 7),
1184 (Tok::Gt, 7),
1185 (Tok::Geq, 7),
1186 (Tok::Eq, 8),
1187 (Tok::Neq, 8),
1188 (Tok::LogAnd, 12),
1189 (Tok::LogOr, 14),
1190 (Tok::LogXor, 13),
1191 (Tok::Quest, 15),
1192 (Tok::Colon, 16),
1193 (Tok::Pow, 3),
1194 (Tok::Comma, 18),
1195];
1196
1197const Z_PREC: &[(Tok, i32)] = &[
1200 (Tok::LogNot, 2),
1201 (Tok::BitNot, 2),
1202 (Tok::PreInc, 2),
1203 (Tok::PreDec, 2),
1204 (Tok::Shl, 3),
1205 (Tok::Shr, 3),
1206 (Tok::BitAnd, 4),
1207 (Tok::BitXor, 5),
1208 (Tok::BitOr, 6),
1209 (Tok::Pow, 7),
1210 (Tok::Mul, 8),
1211 (Tok::Div, 8),
1212 (Tok::Mod, 8),
1213 (Tok::Plus, 9),
1214 (Tok::Minus, 9),
1215 (Tok::Lt, 10),
1216 (Tok::Leq, 10),
1217 (Tok::Gt, 10),
1218 (Tok::Geq, 10),
1219 (Tok::Eq, 11),
1220 (Tok::Neq, 11),
1221 (Tok::LogAnd, 12),
1222 (Tok::LogOr, 13),
1223 (Tok::LogXor, 13),
1224 (Tok::Quest, 14),
1225 (Tok::Colon, 15),
1226 (Tok::Comma, 17),
1227];
1228
1229fn prec_of(table: &[(Tok, i32)], tok: Tok) -> Option<i32> {
1230 table.iter().find(|(t, _)| *t == tok).map(|(_, p)| *p)
1231}
1232
1233fn prec_tables_agree(ops: &[Tok]) -> bool {
1242 for (i, x) in ops.iter().enumerate() {
1243 for y in &ops[i + 1..] {
1244 let (Some(zx), Some(zy)) = (prec_of(Z_PREC, *x), prec_of(Z_PREC, *y)) else {
1245 continue;
1246 };
1247 let (Some(cx), Some(cy)) = (prec_of(C_PREC, *x), prec_of(C_PREC, *y)) else {
1248 continue;
1249 };
1250 if (zx - zy).signum() != (cx - cy).signum() {
1251 return false;
1252 }
1253 }
1254 }
1255 true
1256}
1257
1258pub fn arith_uncompilable_reason(expr: &str) -> Option<&'static str> {
1268 let b = expr.as_bytes();
1269 for (i, &c) in b.iter().enumerate() {
1270 match c {
1271 b'[' => return Some("array subscript"),
1274 b'"' | 0x9e => return Some("quoted identifier"),
1276 b'#' => return Some("base literal or char constant"),
1279 b'$' => {
1280 let rest = &b[i + 1..];
1291 let mut name = rest.iter().copied();
1292 let first = match name.next() {
1293 Some(b'{') => {
1294 let body: Vec<u8> =
1295 rest.iter().skip(1).take_while(|&&x| x != b'}').copied().collect();
1296 if body.is_empty()
1297 || !body[1..].iter().all(|&x| x.is_ascii_alphanumeric() || x == b'_')
1298 {
1299 return Some("parameter expansion");
1300 }
1301 body[0]
1302 }
1303 Some(x) => x,
1304 None => return Some("parameter expansion"),
1305 };
1306 if !(first.is_ascii_alphabetic() || first == b'_') {
1307 return Some("parameter expansion");
1308 }
1309 }
1310 _ => {}
1311 }
1312 }
1313 let mut i = 0;
1320 while i < b.len() {
1321 if b[i].is_ascii_alphabetic() || b[i] == b'_' {
1322 let start = i;
1323 while i < b.len() && (b[i].is_ascii_alphanumeric() || b[i] == b'_') {
1324 i += 1;
1325 }
1326 let after_ref = start > 0 && matches!(b[start - 1], b'$' | b'{');
1327 if !after_ref
1328 && (expr[start..i].eq_ignore_ascii_case("inf") || expr[start..i].eq_ignore_ascii_case("nan"))
1329 {
1330 return Some("Inf/NaN constant");
1331 }
1332 } else {
1333 i += 1;
1334 }
1335 }
1336 let lower = expr.to_ascii_lowercase();
1338 if lower.contains("0x") || lower.contains("0b") {
1339 return Some("base-tagged literal");
1340 }
1341 if b.windows(2).enumerate().any(|(i, w)| {
1347 w[0] == b'0'
1348 && w[1].is_ascii_digit()
1349 && (i == 0 || !(b[i - 1].is_ascii_alphanumeric() || b[i - 1] == b'_' || b[i - 1] == b'.'))
1350 }) {
1351 return Some("leading-zero literal");
1352 }
1353 if b.windows(2)
1356 .any(|w| w[0].is_ascii_digit() && (w[1] | 0x20) == b'e')
1357 {
1358 return Some("float exponent");
1359 }
1360 let mut prev_ident = false;
1362 for &c in b {
1363 if c == b'(' && prev_ident {
1364 return Some("math function call");
1365 }
1366 prev_ident = c.is_ascii_alphanumeric() || c == b'_';
1367 }
1368 let mut ac = ArithCompiler::new(expr);
1370 let mut ops: Vec<Tok> = Vec::new();
1371 let mut toks: Vec<(Tok, String)> = Vec::new();
1378 loop {
1379 let (tok, text) = ac.next_tok();
1380 if tok == Tok::Eoi {
1381 break;
1382 }
1383 toks.push((tok, text.clone()));
1384 if matches!(tok, Tok::LogAndAssign | Tok::LogOrAssign | Tok::LogXorAssign) {
1393 return Some("logical compound assignment");
1394 }
1395 if prec_of(Z_PREC, tok).is_some() {
1396 ops.push(tok);
1397 }
1398 }
1399 if !prec_tables_agree(&ops) {
1400 return Some("c_precedences-sensitive operator mix");
1401 }
1402 let modifies = |i: usize| {
1403 let next = toks.get(i + 1).map(|t| t.0);
1404 let prev = i.checked_sub(1).map(|p| toks[p].0);
1405 matches!(next, Some(Tok::Assign | Tok::PreInc | Tok::PreDec))
1406 || next.and_then(compound_assign_op).is_some()
1407 || matches!(prev, Some(Tok::PreInc | Tok::PreDec))
1408 };
1409 for (i, (tok, name)) in toks.iter().enumerate() {
1410 if *tok != Tok::Ident || modifies(i) {
1411 continue;
1412 }
1413 let modified_later = toks[i + 1..]
1414 .iter()
1415 .enumerate()
1416 .any(|(j, (t, n))| *t == Tok::Ident && n == name && modifies(i + 1 + j));
1417 if modified_later {
1418 return Some("operand modified later in the expression");
1419 }
1420 }
1421 None
1422}
1423
1424#[derive(Debug, Clone, Copy, PartialEq)]
1427enum BinOp {
1428 Add,
1429 Sub,
1430 Mul,
1431 Div,
1432 Mod,
1433 Pow,
1434 BitAnd,
1435 BitOr,
1436 BitXor,
1437 Shl,
1438 Shr,
1439 LogAnd,
1440 LogOr,
1441 LogXor,
1442}
1443
1444fn compound_assign_op(tok: Tok) -> Option<BinOp> {
1448 Some(match tok {
1449 Tok::PlusAssign => BinOp::Add,
1450 Tok::MinusAssign => BinOp::Sub,
1451 Tok::MulAssign => BinOp::Mul,
1452 Tok::DivAssign => BinOp::Div,
1453 Tok::ModAssign => BinOp::Mod,
1454 Tok::AndAssign => BinOp::BitAnd,
1455 Tok::OrAssign => BinOp::BitOr,
1456 Tok::XorAssign => BinOp::BitXor,
1457 Tok::ShlAssign => BinOp::Shl,
1458 Tok::ShrAssign => BinOp::Shr,
1459 Tok::PowAssign => BinOp::Pow,
1460 Tok::LogAndAssign => BinOp::LogAnd,
1461 Tok::LogOrAssign => BinOp::LogOr,
1462 Tok::LogXorAssign => BinOp::LogXor,
1463 _ => return None,
1464 })
1465}
1466
1467#[cfg(test)]
1477mod tests {
1478 use super::*;
1479 use fusevm::{VMResult, Value};
1480
1481 fn eval(expr: &str) -> Value {
1484 let chunk = ArithCompiler::new(expr).compile();
1485 let mut vm = fusevm::VM::new(chunk);
1486 vm.register_builtin(crate::vm_helper::BUILTIN_ARITH_DIV, |vm, _argc| {
1491 let b = crate::fusevm_bridge::arith_pop_mnumber(vm);
1492 let a = crate::fusevm_bridge::arith_pop_mnumber(vm);
1493 crate::fusevm_bridge::arith_push(crate::fusevm_bridge::arith_div(a, b))
1494 });
1495 vm.register_builtin(crate::vm_helper::BUILTIN_ARITH_MOD, |vm, _argc| {
1496 let b = crate::fusevm_bridge::arith_pop_mnumber(vm);
1497 let a = crate::fusevm_bridge::arith_pop_mnumber(vm);
1498 crate::fusevm_bridge::arith_push(crate::fusevm_bridge::arith_mod(a, b))
1499 });
1500 vm.register_builtin(crate::vm_helper::BUILTIN_ARITH_POW, |vm, _argc| {
1501 let b = crate::fusevm_bridge::arith_pop_mnumber(vm);
1502 let a = crate::fusevm_bridge::arith_pop_mnumber(vm);
1503 crate::fusevm_bridge::arith_push(crate::fusevm_bridge::arith_pow(a, b))
1504 });
1505 match vm.run() {
1506 VMResult::Ok(v) => v,
1507 VMResult::Halted => Value::Undef,
1508 VMResult::Error(e) => panic!("VM error evaluating {expr:?}: {e}"),
1509 }
1510 }
1511
1512 fn eval_int(expr: &str) -> i64 {
1513 eval(expr).to_int()
1514 }
1515
1516 fn eval_float(expr: &str) -> f64 {
1517 eval(expr).to_float()
1518 }
1519
1520 #[test]
1522 fn literal_zero() {
1523 assert_eq!(eval_int("0"), 0);
1524 }
1525
1526 #[test]
1527 fn literal_small_positive() {
1528 assert_eq!(eval_int("42"), 42);
1529 }
1530
1531 #[test]
1532 fn literal_large() {
1533 assert_eq!(eval_int("1000000000"), 1_000_000_000);
1534 }
1535
1536 #[test]
1537 fn literal_hex_lowercase() {
1538 assert_eq!(eval_int("0xff"), 255);
1539 }
1540
1541 #[test]
1542 fn literal_hex_uppercase() {
1543 assert_eq!(eval_int("0XDEAD"), 0xDEAD);
1544 }
1545
1546 #[test]
1547 fn literal_hex_mixed() {
1548 assert_eq!(eval_int("0xCaFe"), 0xCAFE);
1549 }
1550
1551 #[test]
1552 fn literal_octal() {
1553 assert_eq!(eval_int("017"), 17);
1558 }
1559
1560 #[test]
1561 fn literal_octal_zero_prefix_only() {
1562 assert_eq!(eval_int("0"), 0);
1564 }
1565
1566 #[test]
1568 fn literal_float_simple() {
1569 assert!((eval_float("3.14") - 3.14).abs() < 1e-9);
1570 }
1571
1572 #[test]
1573 fn literal_float_no_fractional_digits() {
1574 assert!((eval_float("5.") - 5.0).abs() < 1e-9);
1576 }
1577
1578 #[test]
1580 fn add_two_ints() {
1581 assert_eq!(eval_int("40 + 2"), 42);
1582 }
1583
1584 #[test]
1585 fn sub_two_ints() {
1586 assert_eq!(eval_int("50 - 8"), 42);
1587 }
1588
1589 #[test]
1590 fn sub_into_negative() {
1591 assert_eq!(eval_int("5 - 10"), -5);
1592 }
1593
1594 #[test]
1595 fn add_chain_left_associative() {
1596 assert_eq!(eval_int("1 + 2 + 3 + 4"), 10);
1597 }
1598
1599 #[test]
1600 fn sub_chain_left_associative() {
1601 assert_eq!(eval_int("100 - 1 - 2 - 3"), 94);
1603 }
1604
1605 #[test]
1607 fn mul_two_ints() {
1608 assert_eq!(eval_int("6 * 7"), 42);
1609 }
1610
1611 #[test]
1612 fn div_two_ints() {
1613 assert_eq!(eval_int("84 / 2"), 42);
1614 }
1615
1616 #[test]
1617 fn mod_two_ints() {
1618 assert_eq!(eval_int("17 % 5"), 2);
1619 }
1620
1621 #[test]
1622 fn mod_evenly_divides() {
1623 assert_eq!(eval_int("10 % 5"), 0);
1624 }
1625
1626 #[test]
1628 fn precedence_mul_over_add() {
1629 assert_eq!(eval_int("2 + 3 * 4"), 14);
1630 }
1631
1632 #[test]
1633 fn precedence_div_over_sub() {
1634 assert_eq!(eval_int("20 - 10 / 2"), 15);
1635 }
1636
1637 #[test]
1638 fn precedence_parens_override() {
1639 assert_eq!(eval_int("(2 + 3) * 4"), 20);
1640 }
1641
1642 #[test]
1643 fn precedence_nested_parens() {
1644 assert_eq!(eval_int("((1 + 2) * (3 + 4))"), 21);
1645 }
1646
1647 #[test]
1648 fn precedence_pow_over_mul() {
1649 assert_eq!(eval_int("2 * 3 ** 2"), 18);
1651 }
1652
1653 #[test]
1654 fn pow_right_associative() {
1655 assert_eq!(eval_int("2 ** 3 ** 2"), 512);
1657 }
1658
1659 #[test]
1661 fn unary_minus_literal() {
1662 assert_eq!(eval_int("-5"), -5);
1663 }
1664
1665 #[test]
1666 fn unary_minus_expr() {
1667 assert_eq!(eval_int("-(3 + 4)"), -7);
1668 }
1669
1670 #[test]
1671 fn unary_plus_is_noop() {
1672 assert_eq!(eval_int("+42"), 42);
1673 }
1674
1675 #[test]
1676 fn double_negation_requires_separator() {
1677 assert_eq!(eval_int("- -5"), 5);
1680 assert_eq!(eval_int("-(-5)"), 5);
1681 }
1682
1683 #[test]
1684 fn unary_minus_binds_tighter_than_pow() {
1685 assert_eq!(eval_int("-2 ** 2"), 4);
1690 }
1691
1692 #[test]
1694 fn bitand_basic() {
1695 assert_eq!(eval_int("0xFF & 0x0F"), 0x0F);
1696 }
1697
1698 #[test]
1699 fn bitor_basic() {
1700 assert_eq!(eval_int("0x10 | 0x01"), 0x11);
1701 }
1702
1703 #[test]
1704 fn bitxor_basic() {
1705 assert_eq!(eval_int("0xFF ^ 0x0F"), 0xF0);
1706 }
1707
1708 #[test]
1709 fn bitnot_zero_is_minus_one() {
1710 assert_eq!(eval_int("~0"), -1);
1711 }
1712
1713 #[test]
1714 fn bitnot_one() {
1715 assert_eq!(eval_int("~1"), -2);
1717 }
1718
1719 #[test]
1720 fn bitwise_precedence_and_over_or() {
1721 assert_eq!(eval_int("1 | 2 & 0"), 1);
1723 }
1724
1725 #[test]
1727 fn shl_basic() {
1728 assert_eq!(eval_int("1 << 4"), 16);
1729 }
1730
1731 #[test]
1732 fn shr_basic() {
1733 assert_eq!(eval_int("16 >> 2"), 4);
1734 }
1735
1736 #[test]
1737 fn shl_chain() {
1738 assert_eq!(eval_int("1 << 1 << 2"), 8);
1739 }
1740
1741 #[test]
1743 fn cmp_eq_true() {
1744 assert_eq!(eval_int("5 == 5"), 1);
1745 }
1746
1747 #[test]
1748 fn cmp_eq_false() {
1749 assert_eq!(eval_int("5 == 6"), 0);
1750 }
1751
1752 #[test]
1753 fn cmp_ne_true() {
1754 assert_eq!(eval_int("5 != 6"), 1);
1755 }
1756
1757 #[test]
1758 fn cmp_lt_true() {
1759 assert_eq!(eval_int("3 < 5"), 1);
1760 }
1761
1762 #[test]
1763 fn cmp_lt_false_on_equal() {
1764 assert_eq!(eval_int("5 < 5"), 0);
1765 }
1766
1767 #[test]
1768 fn cmp_le_true_on_equal() {
1769 assert_eq!(eval_int("5 <= 5"), 1);
1770 }
1771
1772 #[test]
1773 fn cmp_gt_true() {
1774 assert_eq!(eval_int("5 > 3"), 1);
1775 }
1776
1777 #[test]
1778 fn cmp_ge_true_on_equal() {
1779 assert_eq!(eval_int("5 >= 5"), 1);
1780 }
1781
1782 #[test]
1784 fn logand_true_true() {
1785 assert_eq!(eval_int("1 && 1"), 1);
1786 }
1787
1788 #[test]
1789 fn logand_short_circuits_on_false() {
1790 assert_eq!(eval_int("0 && 99"), 0);
1792 }
1793
1794 #[test]
1795 fn logor_true_short_circuits() {
1796 assert_eq!(eval_int("1 || 0"), 1);
1798 }
1799
1800 #[test]
1801 fn logor_both_false() {
1802 assert_eq!(eval_int("0 || 0"), 0);
1803 }
1804
1805 #[test]
1806 fn lognot_true() {
1807 assert_eq!(eval_int("!0"), 1);
1808 }
1809
1810 #[test]
1811 fn lognot_false() {
1812 assert_eq!(eval_int("!1"), 0);
1813 }
1814
1815 #[test]
1816 fn lognot_double() {
1817 assert_eq!(eval_int("!!5"), 1);
1819 }
1820
1821 #[test]
1823 fn ternary_true_branch() {
1824 assert_eq!(eval_int("1 ? 10 : 20"), 10);
1825 }
1826
1827 #[test]
1828 fn ternary_false_branch() {
1829 assert_eq!(eval_int("0 ? 10 : 20"), 20);
1830 }
1831
1832 #[test]
1833 fn ternary_condition_is_expression() {
1834 assert_eq!(eval_int("(3 < 5) ? 100 : 200"), 100);
1835 }
1836
1837 #[test]
1838 fn ternary_nested_in_true_branch() {
1839 assert_eq!(eval_int("1 ? (0 ? 1 : 2) : 3"), 2);
1840 }
1841
1842 #[test]
1844 fn float_add() {
1845 assert!((eval_float("1.5 + 2.5") - 4.0).abs() < 1e-9);
1846 }
1847
1848 #[test]
1849 fn float_div_does_not_truncate() {
1850 assert!((eval_float("1.0 / 4.0") - 0.25).abs() < 1e-9);
1852 }
1853
1854 #[test]
1856 fn collect_identifiers_bare() {
1857 let c = ArithCompiler::new("");
1858 let names = c.collect_identifiers("a + b * c");
1859 assert_eq!(names, vec!["a", "b", "c"]);
1860 }
1861
1862 #[test]
1863 fn collect_identifiers_dollar_prefixed() {
1864 let c = ArithCompiler::new("");
1865 let names = c.collect_identifiers("$x + 1");
1866 assert_eq!(names, vec!["x"]);
1867 }
1868
1869 #[test]
1870 fn collect_identifiers_braced() {
1871 let c = ArithCompiler::new("");
1872 let names = c.collect_identifiers("${count} * 2");
1873 assert_eq!(names, vec!["count"]);
1874 }
1875
1876 #[test]
1877 fn collect_identifiers_positional() {
1878 let c = ArithCompiler::new("");
1879 let names = c.collect_identifiers("$1 + $2");
1880 assert_eq!(names, vec!["1", "2"]);
1881 }
1882
1883 #[test]
1884 fn collect_identifiers_dedups() {
1885 let c = ArithCompiler::new("");
1886 let names = c.collect_identifiers("a + a + b + a");
1887 assert_eq!(names, vec!["a", "b"]);
1888 }
1889
1890 #[test]
1891 fn collect_identifiers_ignores_pure_numeric() {
1892 let c = ArithCompiler::new("");
1893 let names = c.collect_identifiers("42 + 7 * 3");
1894 assert!(names.is_empty(), "got names: {names:?}");
1895 }
1896
1897 #[test]
1899 fn slot_for_first_var_gets_slot_zero() {
1900 let mut c = ArithCompiler::new("");
1901 assert_eq!(c.slot_for("x"), 0);
1902 }
1903
1904 #[test]
1905 fn slot_for_second_var_gets_slot_one() {
1906 let mut c = ArithCompiler::new("");
1907 let _ = c.slot_for("x");
1908 assert_eq!(c.slot_for("y"), 1);
1909 }
1910
1911 #[test]
1912 fn slot_for_repeated_name_returns_same_slot() {
1913 let mut c = ArithCompiler::new("");
1914 let s1 = c.slot_for("a");
1915 let s2 = c.slot_for("a");
1916 assert_eq!(s1, s2);
1917 }
1918
1919 #[test]
1921 fn compile_emits_pushframe_and_returnvalue_brackets() {
1922 let chunk = ArithCompiler::new("1").compile();
1923 assert!(
1924 matches!(chunk.ops.first(), Some(Op::PushFrame)),
1925 "first op should be PushFrame, got {:?}",
1926 chunk.ops.first()
1927 );
1928 assert!(
1929 matches!(chunk.ops.last(), Some(Op::ReturnValue)),
1930 "last op should be ReturnValue, got {:?}",
1931 chunk.ops.last()
1932 );
1933 }
1934
1935 #[test]
1936 fn compile_sets_source_marker() {
1937 let chunk = ArithCompiler::new("1").compile();
1938 assert_eq!(chunk.source, "$((...))");
1939 }
1940}