1use crate::parser::{CaseTerminator, CompoundCommand, CondExpr, ShellCommand, ShellWord};
16use fusevm::{ChunkBuilder, Op, Value};
17use std::collections::HashMap;
18
19pub struct ShellCompiler {
29 builder: ChunkBuilder,
30 slots: HashMap<String, u16>,
32 next_slot: u16,
33 break_patches: Vec<Vec<usize>>,
35 continue_targets: Vec<usize>,
37}
38
39impl ShellCompiler {
40 pub fn new() -> Self {
41 Self {
42 builder: ChunkBuilder::new(),
43 slots: HashMap::new(),
44 next_slot: 0,
45 break_patches: Vec::new(),
46 continue_targets: Vec::new(),
47 }
48 }
49
50 pub fn compile(mut self, commands: &[ShellCommand]) -> fusevm::Chunk {
52 self.builder.emit(Op::PushFrame, 0);
53 for cmd in commands {
54 self.compile_command(cmd);
55 }
56 self.builder.emit(Op::GetStatus, 0);
57 self.builder.emit(Op::ReturnValue, 0);
58 self.builder.build()
59 }
60
61 fn slot_for(&mut self, name: &str) -> u16 {
62 if let Some(&slot) = self.slots.get(name) {
63 return slot;
64 }
65 let slot = self.next_slot;
66 self.next_slot += 1;
67 self.slots.insert(name.to_string(), slot);
68 slot
69 }
70
71 fn compile_command(&mut self, cmd: &ShellCommand) {
72 match cmd {
73 ShellCommand::Simple(simple) => {
74 self.compile_simple(simple);
75 }
76 ShellCommand::Compound(compound) => {
77 self.compile_compound(compound);
78 }
79 ShellCommand::Pipeline(cmds, negated) => {
80 self.compile_pipeline(cmds, *negated);
81 }
82 ShellCommand::List(items) => {
83 self.compile_list(items);
84 }
85 ShellCommand::FunctionDef(name, body) => {
86 let skip_jump = self.builder.emit(Op::Jump(0), 0);
88 let entry_ip = self.builder.current_pos();
89 let name_idx = self.builder.add_name(name);
90 self.builder.add_sub_entry(name_idx, entry_ip);
91 self.builder.emit(Op::PushFrame, 0);
92 self.compile_command(body);
93 self.builder.emit(Op::PopFrame, 0);
94 self.builder.emit(Op::Return, 0);
95 let after = self.builder.current_pos();
96 self.builder.patch_jump(skip_jump, after);
97 }
98 }
99 }
100
101 fn compile_simple(&mut self, simple: &crate::parser::SimpleCommand) {
109 for (var, val, _is_append) in &simple.assignments {
111 self.compile_word(val);
112 let var_idx = self.builder.add_name(var);
113 self.builder.emit(Op::SetVar(var_idx), 0);
114 }
115
116 if simple.words.is_empty() {
117 return; }
119
120 for redir in &simple.redirects {
122 let fd = redir.fd.unwrap_or(match redir.op {
123 crate::parser::RedirectOp::Read
124 | crate::parser::RedirectOp::HereDoc
125 | crate::parser::RedirectOp::HereString
126 | crate::parser::RedirectOp::ReadWrite => 0,
127 _ => 1,
128 }) as u8;
129
130 let op_byte = match redir.op {
131 crate::parser::RedirectOp::Write => fusevm::op::redirect_op::WRITE,
132 crate::parser::RedirectOp::Append => fusevm::op::redirect_op::APPEND,
133 crate::parser::RedirectOp::Read => fusevm::op::redirect_op::READ,
134 crate::parser::RedirectOp::ReadWrite => fusevm::op::redirect_op::READ_WRITE,
135 crate::parser::RedirectOp::Clobber => fusevm::op::redirect_op::CLOBBER,
136 crate::parser::RedirectOp::DupRead => fusevm::op::redirect_op::DUP_READ,
137 crate::parser::RedirectOp::DupWrite => fusevm::op::redirect_op::DUP_WRITE,
138 crate::parser::RedirectOp::WriteBoth => fusevm::op::redirect_op::WRITE_BOTH,
139 crate::parser::RedirectOp::AppendBoth => fusevm::op::redirect_op::APPEND_BOTH,
140 crate::parser::RedirectOp::HereDoc => {
141 if let Some(ref content) = redir.heredoc_content {
143 let idx = self.builder.add_constant(Value::str(content.as_str()));
144 self.builder.emit(Op::HereDoc(idx), 0);
145 }
146 continue;
147 }
148 crate::parser::RedirectOp::HereString => {
149 self.compile_word(&redir.target);
150 self.builder.emit(Op::HereString, 0);
151 continue;
152 }
153 };
154
155 self.compile_word(&redir.target);
156 self.builder.emit(Op::Redirect(fd, op_byte), 0);
157 }
158
159 let argc = simple.words.len() as u8;
161 for word in &simple.words {
162 self.compile_word(word);
163 }
164
165 self.builder.emit(Op::Exec(argc), 0);
167 self.builder.emit(Op::SetStatus, 0);
168 }
169
170 fn compile_pipeline(
181 &mut self,
182 cmds: &[ShellCommand],
183 negated: bool,
184 ) {
185 if cmds.len() == 1 {
186 self.compile_command(&cmds[0]);
188 if negated {
189 self.builder.emit(Op::GetStatus, 0);
190 self.builder.emit(Op::LoadInt(0), 0);
191 self.builder.emit(Op::NumEq, 0);
192 let was_zero = self.builder.emit(Op::JumpIfTrue(0), 0);
194 self.builder.emit(Op::LoadInt(0), 0);
195 self.builder.emit(Op::SetStatus, 0);
196 let end = self.builder.emit(Op::Jump(0), 0);
197 let t = self.builder.current_pos();
198 self.builder.patch_jump(was_zero, t);
199 self.builder.emit(Op::LoadInt(1), 0);
200 self.builder.emit(Op::SetStatus, 0);
201 let e = self.builder.current_pos();
202 self.builder.patch_jump(end, e);
203 }
204 return;
205 }
206
207 let n = cmds.len() as u8;
208 self.builder.emit(Op::PipelineBegin(n), 0);
209
210 for (i, cmd) in cmds.iter().enumerate() {
211 self.compile_command(cmd);
212 if i < cmds.len() - 1 {
213 self.builder.emit(Op::PipelineStage, 0);
214 }
215 }
216
217 self.builder.emit(Op::PipelineEnd, 0);
218 self.builder.emit(Op::SetStatus, 0);
219
220 if negated {
221 self.builder.emit(Op::GetStatus, 0);
222 self.builder.emit(Op::LoadInt(0), 0);
223 self.builder.emit(Op::NumEq, 0);
224 let was_zero = self.builder.emit(Op::JumpIfTrue(0), 0);
225 self.builder.emit(Op::LoadInt(0), 0);
226 self.builder.emit(Op::SetStatus, 0);
227 let end = self.builder.emit(Op::Jump(0), 0);
228 let t = self.builder.current_pos();
229 self.builder.patch_jump(was_zero, t);
230 self.builder.emit(Op::LoadInt(1), 0);
231 self.builder.emit(Op::SetStatus, 0);
232 let e = self.builder.current_pos();
233 self.builder.patch_jump(end, e);
234 }
235 }
236
237 fn compile_list(&mut self, items: &[(ShellCommand, crate::parser::ListOp)]) {
239 for (i, (cmd, op)) in items.iter().enumerate() {
240 match op {
241 crate::parser::ListOp::And => {
242 self.compile_command(cmd);
244 if i + 1 < items.len() {
245 self.builder.emit(Op::GetStatus, 0);
246 let skip = self.builder.emit(Op::JumpIfTrue(0), 0);
247 self.compile_command(&items[i + 1].0);
250 self.builder.patch_jump(skip, self.builder.current_pos());
251 }
252 }
253 crate::parser::ListOp::Or => {
254 self.compile_command(cmd);
256 if i + 1 < items.len() {
257 self.builder.emit(Op::GetStatus, 0);
258 let skip = self.builder.emit(Op::JumpIfFalse(0), 0);
259 self.compile_command(&items[i + 1].0);
261 self.builder.patch_jump(skip, self.builder.current_pos());
262 }
263 }
264 crate::parser::ListOp::Semi => {
265 self.compile_command(cmd);
267 }
268 crate::parser::ListOp::Amp => {
269 self.compile_command(cmd);
270 }
271 crate::parser::ListOp::Newline => {
272 self.compile_command(cmd);
273 }
274 }
275 }
276 }
277
278 fn compile_compound(&mut self, compound: &CompoundCommand) {
279 match compound {
280 CompoundCommand::BraceGroup(cmds) => {
281 for cmd in cmds {
282 self.compile_command(cmd);
283 }
284 }
285
286 CompoundCommand::For { var, words, body } => {
288 let i_slot = self.next_slot;
308 self.next_slot += 1;
309 let len_slot = self.next_slot;
310 self.next_slot += 1;
311 let var_slot = self.slot_for(var);
312
313 let item_count = if let Some(words) = words {
315 words.len()
316 } else {
317 0
318 };
319
320 if let Some(words) = words {
322 for word in words {
323 let s = self.word_to_string(word);
324 let const_idx = self.builder.add_constant(Value::str(s));
325 self.builder.emit(Op::LoadConst(const_idx), 0);
326 }
327 self.builder
328 .emit(Op::MakeArray(item_count as u16), 0);
329 } else {
330 self.builder.emit(Op::MakeArray(0), 0);
333 }
334 let arr_slot = self.next_slot;
335 self.next_slot += 1;
336 self.builder.emit(Op::SetSlot(arr_slot), 0);
337
338 self.builder.emit(Op::LoadInt(0), 0);
340 self.builder.emit(Op::SetSlot(i_slot), 0);
341
342 self.builder.emit(Op::LoadInt(item_count as i64), 0);
344 self.builder.emit(Op::SetSlot(len_slot), 0);
345
346 let loop_top = self.builder.current_pos();
348 self.builder.emit(Op::GetSlot(i_slot), 0);
349 self.builder.emit(Op::GetSlot(len_slot), 0);
350 self.builder.emit(Op::NumLt, 0);
351 let exit_jump = self.builder.emit(Op::JumpIfFalse(0), 0);
352
353 self.builder.emit(Op::GetSlot(i_slot), 0);
356 self.builder.emit(Op::SetSlot(var_slot), 0);
357
358 self.break_patches.push(Vec::new());
360 let continue_pos = self.builder.current_pos(); self.continue_targets.push(0); for cmd in body {
365 self.compile_command(cmd);
366 }
367
368 let continue_target = self.builder.current_pos();
370 if let Some(target) = self.continue_targets.last_mut() {
372 *target = continue_target;
373 }
374
375 self.builder.emit(Op::PreIncSlotVoid(i_slot), 0);
377 self.builder.emit(Op::Jump(loop_top), 0);
378
379 let loop_exit = self.builder.current_pos();
381 self.builder.patch_jump(exit_jump, loop_exit);
382
383 if let Some(breaks) = self.break_patches.pop() {
385 for bp in breaks {
386 self.builder.patch_jump(bp, loop_exit);
387 }
388 }
389 self.continue_targets.pop();
390 }
391
392 CompoundCommand::ForArith {
394 init,
395 cond,
396 step,
397 body,
398 } => {
399 if !init.is_empty() {
401 self.compile_arith_inline(init);
402 self.builder.emit(Op::Pop, 0); }
404
405 let loop_top = self.builder.current_pos();
407 if !cond.is_empty() {
408 self.compile_arith_inline(cond);
409 } else {
411 self.builder.emit(Op::LoadTrue, 0);
412 }
413 let exit_jump = self.builder.emit(Op::JumpIfFalse(0), 0);
414
415 self.break_patches.push(Vec::new());
417 self.continue_targets.push(0);
418
419 for cmd in body {
421 self.compile_command(cmd);
422 }
423
424 let continue_target = self.builder.current_pos();
426 if let Some(target) = self.continue_targets.last_mut() {
427 *target = continue_target;
428 }
429
430 if !step.is_empty() {
432 self.compile_arith_inline(step);
433 self.builder.emit(Op::Pop, 0); }
435
436 self.builder.emit(Op::Jump(loop_top), 0);
438
439 let loop_exit = self.builder.current_pos();
441 self.builder.patch_jump(exit_jump, loop_exit);
442
443 if let Some(breaks) = self.break_patches.pop() {
444 for bp in breaks {
445 self.builder.patch_jump(bp, loop_exit);
446 }
447 }
448 self.continue_targets.pop();
449 }
450
451 CompoundCommand::While { condition, body } => {
453 self.compile_while_loop(condition, body, false);
454 }
455
456 CompoundCommand::Until { condition, body } => {
458 self.compile_while_loop(condition, body, true);
459 }
460
461 CompoundCommand::If {
463 conditions,
464 else_part,
465 } => {
466 let mut end_jumps = Vec::new();
467
468 for (cond_cmds, body_cmds) in conditions {
469 for cmd in cond_cmds {
471 self.compile_command(cmd);
472 }
473 self.builder.emit(Op::GetStatus, 0);
474 let skip_body = self.builder.emit(Op::JumpIfTrue(0), 0);
476
477 for cmd in body_cmds {
479 self.compile_command(cmd);
480 }
481 end_jumps.push(self.builder.emit(Op::Jump(0), 0));
482
483 let after_body = self.builder.current_pos();
485 self.builder.patch_jump(skip_body, after_body);
486 }
487
488 if let Some(else_cmds) = else_part {
490 for cmd in else_cmds {
491 self.compile_command(cmd);
492 }
493 }
494
495 let end = self.builder.current_pos();
497 for ej in end_jumps {
498 self.builder.patch_jump(ej, end);
499 }
500 }
501
502 CompoundCommand::Repeat { count, body } => {
504 let i_slot = self.next_slot;
506 self.next_slot += 1;
507
508 self.compile_arith_inline(count);
509 let count_slot = self.next_slot;
510 self.next_slot += 1;
511 self.builder.emit(Op::SetSlot(count_slot), 0);
512
513 self.builder.emit(Op::LoadInt(0), 0);
515 self.builder.emit(Op::SetSlot(i_slot), 0);
516
517 let loop_top = self.builder.current_pos();
518 self.builder
520 .emit(Op::GetSlot(i_slot), 0);
521 self.builder.emit(Op::GetSlot(count_slot), 0);
522 self.builder.emit(Op::NumLt, 0);
523 let exit_jump = self.builder.emit(Op::JumpIfFalse(0), 0);
524
525 self.break_patches.push(Vec::new());
526 self.continue_targets.push(0);
527
528 for cmd in body {
529 self.compile_command(cmd);
530 }
531
532 let cont = self.builder.current_pos();
533 if let Some(target) = self.continue_targets.last_mut() {
534 *target = cont;
535 }
536
537 self.builder.emit(Op::PreIncSlotVoid(i_slot), 0);
538 self.builder.emit(Op::Jump(loop_top), 0);
539
540 let loop_exit = self.builder.current_pos();
541 self.builder.patch_jump(exit_jump, loop_exit);
542
543 if let Some(breaks) = self.break_patches.pop() {
544 for bp in breaks {
545 self.builder.patch_jump(bp, loop_exit);
546 }
547 }
548 self.continue_targets.pop();
549 }
550
551 CompoundCommand::Try {
553 try_body,
554 always_body,
555 } => {
556 for cmd in try_body {
557 self.compile_command(cmd);
558 }
559 for cmd in always_body {
560 self.compile_command(cmd);
561 }
562 }
563
564 CompoundCommand::Arith(expr) => {
565 self.compile_arith_inline(expr);
566 self.builder.emit(Op::LoadInt(0), 0);
569 self.builder.emit(Op::NumNe, 0);
570 let true_jump = self.builder.emit(Op::JumpIfTrue(0), 0);
572 self.builder.emit(Op::LoadInt(1), 0);
573 self.builder.emit(Op::SetStatus, 0);
574 let end_jump = self.builder.emit(Op::Jump(0), 0);
575 let true_target = self.builder.current_pos();
576 self.builder.patch_jump(true_jump, true_target);
577 self.builder.emit(Op::LoadInt(0), 0);
578 self.builder.emit(Op::SetStatus, 0);
579 let end = self.builder.current_pos();
580 self.builder.patch_jump(end_jump, end);
581 }
582
583 CompoundCommand::Case { word, cases } => {
585 self.compile_word(word);
587 let word_slot = self.next_slot;
588 self.next_slot += 1;
589 self.builder.emit(Op::SetSlot(word_slot), 0);
590
591 let mut end_jumps = Vec::new();
592
593 for (patterns, body, term) in cases {
594 let _next_pattern_jumps: Vec<usize> = Vec::new();
595
596 let body_target_placeholder = self.builder.current_pos();
598 let mut match_jumps = Vec::new();
599
600 for pattern in patterns {
601 self.builder.emit(Op::GetSlot(word_slot), 0);
602 self.compile_word(pattern);
603 self.builder.emit(Op::StrEq, 0);
604 match_jumps.push(self.builder.emit(Op::JumpIfTrue(0), 0));
605 }
606
607 let skip_body = self.builder.emit(Op::Jump(0), 0);
609
610 let body_start = self.builder.current_pos();
612 for mj in match_jumps {
613 self.builder.patch_jump(mj, body_start);
614 }
615
616 for cmd in body {
618 self.compile_command(cmd);
619 }
620
621 match term {
622 CaseTerminator::Break => {
623 end_jumps.push(self.builder.emit(Op::Jump(0), 0));
624 }
625 CaseTerminator::Fallthrough => {
626 }
628 CaseTerminator::Continue => {
629 }
631 }
632
633 let after_body = self.builder.current_pos();
634 self.builder.patch_jump(skip_body, after_body);
635 }
636
637 let end = self.builder.current_pos();
638 for ej in end_jumps {
639 self.builder.patch_jump(ej, end);
640 }
641 }
642
643 CompoundCommand::Cond(expr) => {
645 self.compile_cond(expr);
646 let true_jump = self.builder.emit(Op::JumpIfTrue(0), 0);
648 self.builder.emit(Op::LoadInt(1), 0);
649 self.builder.emit(Op::SetStatus, 0);
650 let end_jump = self.builder.emit(Op::Jump(0), 0);
651 let true_target = self.builder.current_pos();
652 self.builder.patch_jump(true_jump, true_target);
653 self.builder.emit(Op::LoadInt(0), 0);
654 self.builder.emit(Op::SetStatus, 0);
655 let end = self.builder.current_pos();
656 self.builder.patch_jump(end_jump, end);
657 }
658
659 CompoundCommand::Subshell(cmds) => {
661 self.builder.emit(Op::SubshellBegin, 0);
662 for cmd in cmds {
663 self.compile_command(cmd);
664 }
665 self.builder.emit(Op::SubshellEnd, 0);
666 }
667
668 CompoundCommand::Select { var, words, body } => {
670 let var_slot = self.slot_for(var);
673 if let Some(words) = words {
674 for word in words {
675 let s = self.word_to_string(word);
676 let const_idx = self.builder.add_constant(Value::str(s));
677 self.builder.emit(Op::LoadConst(const_idx), 0);
678 self.builder.emit(Op::SetSlot(var_slot), 0);
679 for cmd in body {
680 self.compile_command(cmd);
681 }
682 }
683 }
684 }
685
686 CompoundCommand::Coproc { name: _, body } => {
688 self.compile_command(body);
690 }
691
692 CompoundCommand::WithRedirects(cmd, _redirects) => {
694 self.compile_command(cmd);
696 }
697 }
698 }
699
700 fn compile_cond(&mut self, expr: &CondExpr) {
703 match expr {
704 CondExpr::FileExists(w) => {
706 self.compile_word(w);
707 self.builder.emit(Op::TestFile(fusevm::op::file_test::EXISTS), 0);
708 }
709 CondExpr::FileRegular(w) => {
710 self.compile_word(w);
711 self.builder.emit(Op::TestFile(fusevm::op::file_test::IS_FILE), 0);
712 }
713 CondExpr::FileDirectory(w) => {
714 self.compile_word(w);
715 self.builder.emit(Op::TestFile(fusevm::op::file_test::IS_DIR), 0);
716 }
717 CondExpr::FileSymlink(w) => {
718 self.compile_word(w);
719 self.builder.emit(Op::TestFile(fusevm::op::file_test::IS_SYMLINK), 0);
720 }
721 CondExpr::FileReadable(w) => {
722 self.compile_word(w);
723 self.builder.emit(Op::TestFile(fusevm::op::file_test::IS_READABLE), 0);
724 }
725 CondExpr::FileWritable(w) => {
726 self.compile_word(w);
727 self.builder.emit(Op::TestFile(fusevm::op::file_test::IS_WRITABLE), 0);
728 }
729 CondExpr::FileExecutable(w) => {
730 self.compile_word(w);
731 self.builder.emit(Op::TestFile(fusevm::op::file_test::IS_EXECUTABLE), 0);
732 }
733 CondExpr::FileNonEmpty(w) => {
734 self.compile_word(w);
735 self.builder.emit(Op::TestFile(fusevm::op::file_test::IS_NONEMPTY), 0);
736 }
737
738 CondExpr::StringEmpty(w) => {
740 self.compile_word(w);
741 self.builder.emit(Op::StringLen, 0);
742 self.builder.emit(Op::LoadInt(0), 0);
743 self.builder.emit(Op::NumEq, 0);
744 }
745 CondExpr::StringNonEmpty(w) => {
746 self.compile_word(w);
747 self.builder.emit(Op::StringLen, 0);
748 self.builder.emit(Op::LoadInt(0), 0);
749 self.builder.emit(Op::NumGt, 0);
750 }
751 CondExpr::StringEqual(a, b) => {
752 self.compile_word(a);
753 self.compile_word(b);
754 self.builder.emit(Op::StrEq, 0);
755 }
756 CondExpr::StringNotEqual(a, b) => {
757 self.compile_word(a);
758 self.compile_word(b);
759 self.builder.emit(Op::StrNe, 0);
760 }
761 CondExpr::StringMatch(a, b) => {
762 self.compile_word(a);
764 self.compile_word(b);
765 self.builder.emit(Op::StrEq, 0);
766 }
767 CondExpr::StringLess(a, b) => {
768 self.compile_word(a);
769 self.compile_word(b);
770 self.builder.emit(Op::StrLt, 0);
771 }
772 CondExpr::StringGreater(a, b) => {
773 self.compile_word(a);
774 self.compile_word(b);
775 self.builder.emit(Op::StrGt, 0);
776 }
777
778 CondExpr::NumEqual(a, b) => {
780 self.compile_word(a);
781 self.compile_word(b);
782 self.builder.emit(Op::NumEq, 0);
783 }
784 CondExpr::NumNotEqual(a, b) => {
785 self.compile_word(a);
786 self.compile_word(b);
787 self.builder.emit(Op::NumNe, 0);
788 }
789 CondExpr::NumLess(a, b) => {
790 self.compile_word(a);
791 self.compile_word(b);
792 self.builder.emit(Op::NumLt, 0);
793 }
794 CondExpr::NumLessEqual(a, b) => {
795 self.compile_word(a);
796 self.compile_word(b);
797 self.builder.emit(Op::NumLe, 0);
798 }
799 CondExpr::NumGreater(a, b) => {
800 self.compile_word(a);
801 self.compile_word(b);
802 self.builder.emit(Op::NumGt, 0);
803 }
804 CondExpr::NumGreaterEqual(a, b) => {
805 self.compile_word(a);
806 self.compile_word(b);
807 self.builder.emit(Op::NumGe, 0);
808 }
809
810 CondExpr::Not(inner) => {
812 self.compile_cond(inner);
813 self.builder.emit(Op::LogNot, 0);
814 }
815 CondExpr::And(a, b) => {
816 self.compile_cond(a);
817 let skip = self.builder.emit(Op::JumpIfFalseKeep(0), 0);
818 self.builder.emit(Op::Pop, 0);
819 self.compile_cond(b);
820 self.builder.patch_jump(skip, self.builder.current_pos());
821 }
822 CondExpr::Or(a, b) => {
823 self.compile_cond(a);
824 let skip = self.builder.emit(Op::JumpIfTrueKeep(0), 0);
825 self.builder.emit(Op::Pop, 0);
826 self.compile_cond(b);
827 self.builder.patch_jump(skip, self.builder.current_pos());
828 }
829 }
830 }
831
832 fn compile_word(&mut self, word: &ShellWord) {
834 match word {
835 ShellWord::Literal(s) => {
836 let idx = self.builder.add_constant(Value::str(s.as_str()));
837 self.builder.emit(Op::LoadConst(idx), 0);
838 }
839 ShellWord::SingleQuoted(s) => {
840 let idx = self.builder.add_constant(Value::str(s.as_str()));
841 self.builder.emit(Op::LoadConst(idx), 0);
842 }
843 ShellWord::Variable(name) => {
844 let slot = self.slot_for(name);
845 self.builder.emit(Op::GetSlot(slot), 0);
846 }
847 _ => {
849 let idx = self.builder.add_constant(Value::str(""));
851 self.builder.emit(Op::LoadConst(idx), 0);
852 }
853 }
854 }
855
856 fn compile_while_loop(
858 &mut self,
859 condition: &[ShellCommand],
860 body: &[ShellCommand],
861 is_until: bool,
862 ) {
863 let loop_top = self.builder.current_pos();
864
865 for cmd in condition {
867 self.compile_command(cmd);
868 }
869 self.builder.emit(Op::GetStatus, 0);
870
871 let exit_jump = if is_until {
874 self.builder.emit(Op::JumpIfFalse(0), 0)
875 } else {
876 self.builder.emit(Op::JumpIfTrue(0), 0)
877 };
878
879 self.break_patches.push(Vec::new());
880 self.continue_targets.push(loop_top);
881
882 for cmd in body {
883 self.compile_command(cmd);
884 }
885
886 self.builder.emit(Op::Jump(loop_top), 0);
887
888 let loop_exit = self.builder.current_pos();
889 self.builder.patch_jump(exit_jump, loop_exit);
890
891 if let Some(breaks) = self.break_patches.pop() {
892 for bp in breaks {
893 self.builder.patch_jump(bp, loop_exit);
894 }
895 }
896 self.continue_targets.pop();
897 }
898
899 fn compile_arith_inline(&mut self, expr: &str) {
905 let mut ac = ArithCompiler::new(expr);
906 ac.slots = self.slots.clone();
908 ac.next_slot = self.next_slot;
909 ac.expr();
911 let new_slots = ac.slots.clone();
912 let new_next = ac.next_slot;
913 let chunk = ac.builder.build();
914 self.slots = new_slots;
916 self.next_slot = new_next;
917 for op in &chunk.ops {
919 self.builder.emit(op.clone(), 0);
920 }
921 }
922
923 fn word_to_string(&self, word: &ShellWord) -> String {
924 match word {
925 ShellWord::Literal(s) => s.clone(),
926 ShellWord::SingleQuoted(s) => s.clone(),
927 _ => String::new(), }
929 }
930}
931
932pub struct ArithCompiler<'a> {
944 pub input: &'a str,
945 pub pos: usize,
946 pub builder: ChunkBuilder,
947 pub slots: HashMap<String, u16>,
949 pub next_slot: u16,
950}
951
952#[derive(Debug, Clone, Copy, PartialEq)]
954enum Tok {
955 Num(i64),
956 Float(f64),
957 Ident,
958 Plus,
959 Minus,
960 Mul,
961 Div,
962 Mod,
963 Pow,
964 BitAnd,
965 BitOr,
966 BitXor,
967 BitNot,
968 Shl,
969 Shr,
970 LogAnd,
971 LogOr,
972 LogNot,
973 Eq,
974 Neq,
975 Lt,
976 Gt,
977 Leq,
978 Geq,
979 Assign,
980 PlusAssign,
981 MinusAssign,
982 MulAssign,
983 DivAssign,
984 ModAssign,
985 PreInc,
986 PreDec,
987 PostInc,
988 PostDec,
989 LParen,
990 RParen,
991 Comma,
992 Quest,
993 Colon,
994 Eoi,
995}
996
997impl<'a> ArithCompiler<'a> {
998 pub fn new(input: &'a str) -> Self {
999 Self {
1000 input,
1001 pos: 0,
1002 builder: ChunkBuilder::new(),
1003 slots: HashMap::new(),
1004 next_slot: 0,
1005 }
1006 }
1007
1008
1009 pub fn compile(mut self) -> fusevm::Chunk {
1012 self.builder.set_source("$((...))");
1013 self.builder.emit(Op::PushFrame, 0);
1014 self.expr();
1015 self.builder.emit(Op::ReturnValue, 0);
1016 self.builder.build()
1017 }
1018
1019 fn slot_for(&mut self, name: &str) -> u16 {
1021 if let Some(&slot) = self.slots.get(name) {
1022 return slot;
1023 }
1024 let slot = self.next_slot;
1025 self.next_slot += 1;
1026 self.slots.insert(name.to_string(), slot);
1027 slot
1028 }
1029
1030 fn skip_whitespace(&mut self) {
1033 while self.pos < self.input.len() {
1034 let b = self.input.as_bytes()[self.pos];
1035 if b == b' ' || b == b'\t' || b == b'\n' || b == b'\r' {
1036 self.pos += 1;
1037 } else {
1038 break;
1039 }
1040 }
1041 }
1042
1043 fn peek_char(&self) -> Option<u8> {
1044 self.input.as_bytes().get(self.pos).copied()
1045 }
1046
1047 fn next_char(&mut self) -> Option<u8> {
1048 let c = self.input.as_bytes().get(self.pos).copied();
1049 if c.is_some() {
1050 self.pos += 1;
1051 }
1052 c
1053 }
1054
1055 fn read_ident(&mut self) -> String {
1056 let start = self.pos;
1057 while self.pos < self.input.len() {
1058 let b = self.input.as_bytes()[self.pos];
1059 if b.is_ascii_alphanumeric() || b == b'_' {
1060 self.pos += 1;
1061 } else {
1062 break;
1063 }
1064 }
1065 self.input[start..self.pos].to_string()
1066 }
1067
1068 fn read_number(&mut self) -> Tok {
1069 let start = self.pos;
1070
1071 if self.pos + 1 < self.input.len()
1073 && self.input.as_bytes()[self.pos] == b'0'
1074 && (self.input.as_bytes()[self.pos + 1] == b'x'
1075 || self.input.as_bytes()[self.pos + 1] == b'X')
1076 {
1077 self.pos += 2;
1078 while self.pos < self.input.len()
1079 && self.input.as_bytes()[self.pos].is_ascii_hexdigit()
1080 {
1081 self.pos += 1;
1082 }
1083 let val = i64::from_str_radix(&self.input[start + 2..self.pos], 16).unwrap_or(0);
1084 return Tok::Num(val);
1085 }
1086
1087 if self.pos + 1 < self.input.len()
1089 && self.input.as_bytes()[self.pos] == b'0'
1090 && self.input.as_bytes()[self.pos + 1].is_ascii_digit()
1091 {
1092 while self.pos < self.input.len()
1093 && self.input.as_bytes()[self.pos].is_ascii_digit()
1094 {
1095 self.pos += 1;
1096 }
1097 let val = i64::from_str_radix(&self.input[start + 1..self.pos], 8).unwrap_or(0);
1098 return Tok::Num(val);
1099 }
1100
1101 while self.pos < self.input.len() && self.input.as_bytes()[self.pos].is_ascii_digit() {
1103 self.pos += 1;
1104 }
1105
1106 if self.pos < self.input.len() && self.input.as_bytes()[self.pos] == b'.' {
1108 self.pos += 1;
1109 while self.pos < self.input.len() && self.input.as_bytes()[self.pos].is_ascii_digit() {
1110 self.pos += 1;
1111 }
1112 let val: f64 = self.input[start..self.pos].parse().unwrap_or(0.0);
1113 return Tok::Float(val);
1114 }
1115
1116 let val: i64 = self.input[start..self.pos].parse().unwrap_or(0);
1117 Tok::Num(val)
1118 }
1119
1120 fn next_tok(&mut self) -> (Tok, String) {
1121 self.skip_whitespace();
1122
1123 let Some(c) = self.peek_char() else {
1124 return (Tok::Eoi, String::new());
1125 };
1126
1127 match c {
1128 b'0'..=b'9' => {
1129 let tok = self.read_number();
1130 (tok, String::new())
1131 }
1132 b'a'..=b'z' | b'A'..=b'Z' | b'_' => {
1133 let name = self.read_ident();
1134 (Tok::Ident, name)
1135 }
1136 b'+' => {
1137 self.pos += 1;
1138 match self.peek_char() {
1139 Some(b'+') => { self.pos += 1; (Tok::PreInc, String::new()) }
1140 Some(b'=') => { self.pos += 1; (Tok::PlusAssign, String::new()) }
1141 _ => (Tok::Plus, String::new()),
1142 }
1143 }
1144 b'-' => {
1145 self.pos += 1;
1146 match self.peek_char() {
1147 Some(b'-') => { self.pos += 1; (Tok::PreDec, String::new()) }
1148 Some(b'=') => { self.pos += 1; (Tok::MinusAssign, String::new()) }
1149 _ => (Tok::Minus, String::new()),
1150 }
1151 }
1152 b'*' => {
1153 self.pos += 1;
1154 match self.peek_char() {
1155 Some(b'*') => {
1156 self.pos += 1;
1157 if self.peek_char() == Some(b'=') {
1158 self.pos += 1;
1159 (Tok::MulAssign, String::new()) } else {
1161 (Tok::Pow, String::new())
1162 }
1163 }
1164 Some(b'=') => { self.pos += 1; (Tok::MulAssign, String::new()) }
1165 _ => (Tok::Mul, String::new()),
1166 }
1167 }
1168 b'/' => {
1169 self.pos += 1;
1170 if self.peek_char() == Some(b'=') {
1171 self.pos += 1;
1172 (Tok::DivAssign, String::new())
1173 } else {
1174 (Tok::Div, String::new())
1175 }
1176 }
1177 b'%' => {
1178 self.pos += 1;
1179 if self.peek_char() == Some(b'=') {
1180 self.pos += 1;
1181 (Tok::ModAssign, String::new())
1182 } else {
1183 (Tok::Mod, String::new())
1184 }
1185 }
1186 b'&' => {
1187 self.pos += 1;
1188 if self.peek_char() == Some(b'&') {
1189 self.pos += 1;
1190 (Tok::LogAnd, String::new())
1191 } else {
1192 (Tok::BitAnd, String::new())
1193 }
1194 }
1195 b'|' => {
1196 self.pos += 1;
1197 if self.peek_char() == Some(b'|') {
1198 self.pos += 1;
1199 (Tok::LogOr, String::new())
1200 } else {
1201 (Tok::BitOr, String::new())
1202 }
1203 }
1204 b'^' => { self.pos += 1; (Tok::BitXor, String::new()) }
1205 b'~' => { self.pos += 1; (Tok::BitNot, String::new()) }
1206 b'!' => {
1207 self.pos += 1;
1208 if self.peek_char() == Some(b'=') {
1209 self.pos += 1;
1210 (Tok::Neq, String::new())
1211 } else {
1212 (Tok::LogNot, String::new())
1213 }
1214 }
1215 b'<' => {
1216 self.pos += 1;
1217 match self.peek_char() {
1218 Some(b'<') => { self.pos += 1; (Tok::Shl, String::new()) }
1219 Some(b'=') => { self.pos += 1; (Tok::Leq, String::new()) }
1220 _ => (Tok::Lt, String::new()),
1221 }
1222 }
1223 b'>' => {
1224 self.pos += 1;
1225 match self.peek_char() {
1226 Some(b'>') => { self.pos += 1; (Tok::Shr, String::new()) }
1227 Some(b'=') => { self.pos += 1; (Tok::Geq, String::new()) }
1228 _ => (Tok::Gt, String::new()),
1229 }
1230 }
1231 b'=' => {
1232 self.pos += 1;
1233 if self.peek_char() == Some(b'=') {
1234 self.pos += 1;
1235 (Tok::Eq, String::new())
1236 } else {
1237 (Tok::Assign, String::new())
1238 }
1239 }
1240 b'(' => { self.pos += 1; (Tok::LParen, String::new()) }
1241 b')' => { self.pos += 1; (Tok::RParen, String::new()) }
1242 b',' => { self.pos += 1; (Tok::Comma, String::new()) }
1243 b'?' => { self.pos += 1; (Tok::Quest, String::new()) }
1244 b':' => { self.pos += 1; (Tok::Colon, String::new()) }
1245 _ => {
1246 self.pos += 1;
1247 (Tok::Eoi, String::new())
1248 }
1249 }
1250 }
1251
1252 fn expr(&mut self) {
1257 self.assign_expr();
1258 }
1259
1260 fn assign_expr(&mut self) {
1261 let save_pos = self.pos;
1262
1263 self.skip_whitespace();
1265 if let Some(c) = self.peek_char() {
1266 if c.is_ascii_alphabetic() || c == b'_' {
1267 let name = self.read_ident();
1268 self.skip_whitespace();
1269 let (tok, _) = self.peek_tok();
1270 match tok {
1271 Tok::Assign => {
1272 let _ = self.next_tok(); let slot = self.slot_for(&name);
1274 self.assign_expr();
1275 self.builder.emit(Op::Dup, 0);
1276 self.builder.emit(Op::SetSlot(slot), 0);
1277 return;
1278 }
1279 Tok::PlusAssign | Tok::MinusAssign | Tok::MulAssign
1280 | Tok::DivAssign | Tok::ModAssign => {
1281 let _ = self.next_tok(); let slot = self.slot_for(&name);
1283 self.builder.emit(Op::GetSlot(slot), 0);
1284 self.assign_expr();
1285 match tok {
1286 Tok::PlusAssign => self.builder.emit(Op::Add, 0),
1287 Tok::MinusAssign => self.builder.emit(Op::Sub, 0),
1288 Tok::MulAssign => self.builder.emit(Op::Mul, 0),
1289 Tok::DivAssign => self.builder.emit(Op::Div, 0),
1290 Tok::ModAssign => self.builder.emit(Op::Mod, 0),
1291 _ => unreachable!(),
1292 };
1293 self.builder.emit(Op::Dup, 0);
1294 self.builder.emit(Op::SetSlot(slot), 0);
1295 return;
1296 }
1297 _ => {}
1298 }
1299 self.pos = save_pos;
1301 }
1302 }
1303
1304 self.ternary_expr();
1305 }
1306
1307 fn peek_tok(&mut self) -> (Tok, String) {
1308 let save = self.pos;
1309 let tok = self.next_tok();
1310 self.pos = save;
1311 tok
1312 }
1313
1314 fn ternary_expr(&mut self) {
1315 self.logor_expr();
1316 let (tok, _) = self.peek_tok();
1317 if tok == Tok::Quest {
1318 let _ = self.next_tok(); let else_jump = self.builder.emit(Op::JumpIfFalse(0), 0);
1320 self.expr(); let (colon, _) = self.peek_tok();
1322 let end_jump = self.builder.emit(Op::Jump(0), 0);
1323 let else_target = self.builder.current_pos();
1324 self.builder.patch_jump(else_jump, else_target);
1325 if colon == Tok::Colon {
1326 let _ = self.next_tok(); }
1328 self.expr(); let end_target = self.builder.current_pos();
1330 self.builder.patch_jump(end_jump, end_target);
1331 }
1332 }
1333
1334 fn logor_expr(&mut self) {
1335 self.logand_expr();
1336 loop {
1337 let (tok, _) = self.peek_tok();
1338 if tok == Tok::LogOr {
1339 let _ = self.next_tok();
1340 let skip = self.builder.emit(Op::JumpIfTrueKeep(0), 0);
1341 self.builder.emit(Op::Pop, 0);
1342 self.logand_expr();
1343 self.builder.patch_jump(skip, self.builder.current_pos());
1344 } else {
1345 break;
1346 }
1347 }
1348 }
1349
1350 fn logand_expr(&mut self) {
1351 self.bitor_expr();
1352 loop {
1353 let (tok, _) = self.peek_tok();
1354 if tok == Tok::LogAnd {
1355 let _ = self.next_tok();
1356 let skip = self.builder.emit(Op::JumpIfFalseKeep(0), 0);
1357 self.builder.emit(Op::Pop, 0);
1358 self.bitor_expr();
1359 self.builder.patch_jump(skip, self.builder.current_pos());
1360 } else {
1361 break;
1362 }
1363 }
1364 }
1365
1366 fn bitor_expr(&mut self) {
1367 self.bitxor_expr();
1368 loop {
1369 let (tok, _) = self.peek_tok();
1370 if tok == Tok::BitOr {
1371 let _ = self.next_tok();
1372 self.bitxor_expr();
1373 self.builder.emit(Op::BitOr, 0);
1374 } else {
1375 break;
1376 }
1377 }
1378 }
1379
1380 fn bitxor_expr(&mut self) {
1381 self.bitand_expr();
1382 loop {
1383 let (tok, _) = self.peek_tok();
1384 if tok == Tok::BitXor {
1385 let _ = self.next_tok();
1386 self.bitand_expr();
1387 self.builder.emit(Op::BitXor, 0);
1388 } else {
1389 break;
1390 }
1391 }
1392 }
1393
1394 fn bitand_expr(&mut self) {
1395 self.equality_expr();
1396 loop {
1397 let (tok, _) = self.peek_tok();
1398 if tok == Tok::BitAnd {
1399 let _ = self.next_tok();
1400 self.equality_expr();
1401 self.builder.emit(Op::BitAnd, 0);
1402 } else {
1403 break;
1404 }
1405 }
1406 }
1407
1408 fn equality_expr(&mut self) {
1409 self.comparison_expr();
1410 loop {
1411 let (tok, _) = self.peek_tok();
1412 match tok {
1413 Tok::Eq => {
1414 let _ = self.next_tok();
1415 self.comparison_expr();
1416 self.builder.emit(Op::NumEq, 0);
1417 }
1418 Tok::Neq => {
1419 let _ = self.next_tok();
1420 self.comparison_expr();
1421 self.builder.emit(Op::NumNe, 0);
1422 }
1423 _ => break,
1424 }
1425 }
1426 }
1427
1428 fn comparison_expr(&mut self) {
1429 self.shift_expr();
1430 loop {
1431 let (tok, _) = self.peek_tok();
1432 match tok {
1433 Tok::Lt => {
1434 let _ = self.next_tok();
1435 self.shift_expr();
1436 self.builder.emit(Op::NumLt, 0);
1437 }
1438 Tok::Gt => {
1439 let _ = self.next_tok();
1440 self.shift_expr();
1441 self.builder.emit(Op::NumGt, 0);
1442 }
1443 Tok::Leq => {
1444 let _ = self.next_tok();
1445 self.shift_expr();
1446 self.builder.emit(Op::NumLe, 0);
1447 }
1448 Tok::Geq => {
1449 let _ = self.next_tok();
1450 self.shift_expr();
1451 self.builder.emit(Op::NumGe, 0);
1452 }
1453 _ => break,
1454 }
1455 }
1456 }
1457
1458 fn shift_expr(&mut self) {
1459 self.add_expr();
1460 loop {
1461 let (tok, _) = self.peek_tok();
1462 match tok {
1463 Tok::Shl => {
1464 let _ = self.next_tok();
1465 self.add_expr();
1466 self.builder.emit(Op::Shl, 0);
1467 }
1468 Tok::Shr => {
1469 let _ = self.next_tok();
1470 self.add_expr();
1471 self.builder.emit(Op::Shr, 0);
1472 }
1473 _ => break,
1474 }
1475 }
1476 }
1477
1478 fn add_expr(&mut self) {
1479 self.mul_expr();
1480 loop {
1481 let (tok, _) = self.peek_tok();
1482 match tok {
1483 Tok::Plus => {
1484 let _ = self.next_tok();
1485 self.mul_expr();
1486 self.builder.emit(Op::Add, 0);
1487 }
1488 Tok::Minus => {
1489 let _ = self.next_tok();
1490 self.mul_expr();
1491 self.builder.emit(Op::Sub, 0);
1492 }
1493 _ => break,
1494 }
1495 }
1496 }
1497
1498 fn mul_expr(&mut self) {
1499 self.pow_expr();
1500 loop {
1501 let (tok, _) = self.peek_tok();
1502 match tok {
1503 Tok::Mul => {
1504 let _ = self.next_tok();
1505 self.pow_expr();
1506 self.builder.emit(Op::Mul, 0);
1507 }
1508 Tok::Div => {
1509 let _ = self.next_tok();
1510 self.pow_expr();
1511 self.builder.emit(Op::Div, 0);
1512 }
1513 Tok::Mod => {
1514 let _ = self.next_tok();
1515 self.pow_expr();
1516 self.builder.emit(Op::Mod, 0);
1517 }
1518 _ => break,
1519 }
1520 }
1521 }
1522
1523 fn pow_expr(&mut self) {
1524 self.unary_expr();
1525 let (tok, _) = self.peek_tok();
1526 if tok == Tok::Pow {
1527 let _ = self.next_tok();
1528 self.pow_expr(); self.builder.emit(Op::Pow, 0);
1530 }
1531 }
1532
1533 fn unary_expr(&mut self) {
1534 let (tok, name) = self.peek_tok();
1535 match tok {
1536 Tok::Minus => {
1537 let _ = self.next_tok();
1538 self.unary_expr();
1539 self.builder.emit(Op::Negate, 0);
1540 }
1541 Tok::Plus => {
1542 let _ = self.next_tok();
1543 self.unary_expr();
1544 }
1546 Tok::LogNot => {
1547 let _ = self.next_tok();
1548 self.unary_expr();
1549 self.builder.emit(Op::LogNot, 0);
1550 }
1551 Tok::BitNot => {
1552 let _ = self.next_tok();
1553 self.unary_expr();
1554 self.builder.emit(Op::BitNot, 0);
1555 }
1556 Tok::PreInc => {
1557 let _ = self.next_tok();
1558 let (_, var_name) = self.next_tok();
1560 let slot = self.slot_for(&var_name);
1561 self.builder.emit(Op::PreIncSlot(slot), 0);
1562 }
1563 Tok::PreDec => {
1564 let _ = self.next_tok();
1565 let (_, var_name) = self.next_tok();
1566 let slot = self.slot_for(&var_name);
1567 self.builder.emit(Op::GetSlot(slot), 0);
1568 self.builder.emit(Op::Dec, 0);
1569 self.builder.emit(Op::Dup, 0);
1570 self.builder.emit(Op::SetSlot(slot), 0);
1571 }
1572 _ => self.primary_expr(),
1573 }
1574 }
1575
1576 fn primary_expr(&mut self) {
1577 let (tok, name) = self.next_tok();
1578 match tok {
1579 Tok::Num(n) => {
1580 self.builder.emit(Op::LoadInt(n), 0);
1581 }
1582 Tok::Float(f) => {
1583 self.builder.emit(Op::LoadFloat(f), 0);
1584 }
1585 Tok::Ident => {
1586 let slot = self.slot_for(&name);
1587 self.builder.emit(Op::GetSlot(slot), 0);
1588
1589 let (post_tok, _) = self.peek_tok();
1591 match post_tok {
1592 Tok::PreInc => {
1593 let _ = self.next_tok();
1595 self.builder.emit(Op::Dup, 0); self.builder.emit(Op::Inc, 0);
1597 self.builder.emit(Op::SetSlot(slot), 0);
1598 }
1600 Tok::PreDec => {
1601 let _ = self.next_tok();
1602 self.builder.emit(Op::Dup, 0);
1603 self.builder.emit(Op::Dec, 0);
1604 self.builder.emit(Op::SetSlot(slot), 0);
1605 }
1606 _ => {}
1607 }
1608 }
1609 Tok::LParen => {
1610 self.expr();
1611 let _ = self.next_tok(); }
1613 _ => {
1614 self.builder.emit(Op::LoadInt(0), 0);
1616 }
1617 }
1618 }
1619}
1620
1621#[cfg(test)]
1622mod tests {
1623 use super::*;
1624 use fusevm::{VM, VMResult};
1625
1626 fn eval(expr: &str) -> i64 {
1627 let compiler = ArithCompiler::new(expr);
1628 let chunk = compiler.compile();
1629 let mut vm = VM::new(chunk);
1630 match vm.run() {
1631 VMResult::Ok(Value::Int(n)) => n,
1632 VMResult::Ok(Value::Bool(b)) => b as i64,
1633 VMResult::Ok(Value::Float(f)) => f as i64,
1634 VMResult::Ok(v) => v.to_int(),
1635 other => panic!("expected value, got {:?}", other),
1636 }
1637 }
1638
1639 fn eval_float(expr: &str) -> f64 {
1640 let compiler = ArithCompiler::new(expr);
1641 let chunk = compiler.compile();
1642 let mut vm = VM::new(chunk);
1643 match vm.run() {
1644 VMResult::Ok(v) => v.to_float(),
1645 other => panic!("expected value, got {:?}", other),
1646 }
1647 }
1648
1649 #[test]
1650 fn test_basic_arithmetic() {
1651 assert_eq!(eval("2 + 3"), 5);
1652 assert_eq!(eval("10 - 4"), 6);
1653 assert_eq!(eval("6 * 7"), 42);
1654 assert_eq!(eval("100 / 4"), 25);
1655 assert_eq!(eval("17 % 5"), 2);
1656 }
1657
1658 #[test]
1659 fn test_precedence() {
1660 assert_eq!(eval("2 + 3 * 4"), 14);
1661 assert_eq!(eval("(2 + 3) * 4"), 20);
1662 assert_eq!(eval("2 * 3 + 4 * 5"), 26);
1663 assert_eq!(eval("10 - 2 * 3"), 4);
1664 }
1665
1666 #[test]
1667 fn test_power() {
1668 assert_eq!(eval("2 ** 10"), 1024);
1669 assert_eq!(eval("3 ** 3"), 27);
1670 }
1671
1672 #[test]
1673 fn test_unary() {
1674 assert_eq!(eval("-5"), -5);
1675 assert_eq!(eval("-(-3)"), 3);
1676 assert_eq!(eval("!0"), 1);
1677 assert_eq!(eval("!1"), 0);
1678 assert_eq!(eval("~0"), -1);
1679 }
1680
1681 #[test]
1682 fn test_comparison() {
1683 assert_eq!(eval("3 < 5"), 1);
1684 assert_eq!(eval("5 < 3"), 0);
1685 assert_eq!(eval("3 <= 3"), 1);
1686 assert_eq!(eval("3 == 3"), 1);
1687 assert_eq!(eval("3 != 4"), 1);
1688 assert_eq!(eval("5 > 3"), 1);
1689 assert_eq!(eval("5 >= 5"), 1);
1690 }
1691
1692 #[test]
1693 fn test_bitwise() {
1694 assert_eq!(eval("0xFF & 0x0F"), 0x0F);
1695 assert_eq!(eval("0xF0 | 0x0F"), 0xFF);
1696 assert_eq!(eval("0xFF ^ 0x0F"), 0xF0);
1697 assert_eq!(eval("1 << 10"), 1024);
1698 assert_eq!(eval("1024 >> 5"), 32);
1699 }
1700
1701 #[test]
1702 fn test_logical_short_circuit() {
1703 assert_eq!(eval("1 && 2"), 2); assert_eq!(eval("0 && 2"), 0); assert_eq!(eval("0 || 5"), 5); assert_eq!(eval("1 || 0"), 1); }
1709
1710 #[test]
1711 fn test_ternary() {
1712 assert_eq!(eval("1 ? 42 : 99"), 42);
1713 assert_eq!(eval("0 ? 42 : 99"), 99);
1714 assert_eq!(eval("(3 > 2) ? 10 : 20"), 10);
1715 }
1716
1717 #[test]
1718 fn test_assignment() {
1719 assert_eq!(eval("x = 5"), 5);
1720 assert_eq!(eval("x = 5 + 3"), 8);
1721 }
1722
1723 #[test]
1724 fn test_hex_octal() {
1725 assert_eq!(eval("0xFF"), 255);
1726 assert_eq!(eval("0x10"), 16);
1727 assert_eq!(eval("010"), 8); }
1729
1730 #[test]
1731 fn test_complex_expression() {
1732 assert_eq!(eval("(5 + 3) * 2 - 10 / 5"), 14);
1734 assert_eq!(eval("1 ? (0 ? 1 : 2) : 3"), 2);
1736 }
1737
1738 #[test]
1739 fn test_float() {
1740 assert!((eval_float("3.14 * 2.0") - 6.28).abs() < 0.001);
1741 }
1742
1743 fn run_shell(commands: &[ShellCommand]) -> i64 {
1746 let compiler = ShellCompiler::new();
1747 let chunk = compiler.compile(commands);
1748 let mut vm = VM::new(chunk);
1749 match vm.run() {
1750 VMResult::Ok(v) => v.to_int(),
1751 other => panic!("VM error: {:?}", other),
1752 }
1753 }
1754
1755 #[test]
1756 fn test_for_arith_sum() {
1757 use crate::parser::CompoundCommand;
1758 let cmd = ShellCommand::Compound(CompoundCommand::ForArith {
1760 init: "i = 0".to_string(),
1761 cond: "i < 10".to_string(),
1762 step: "i++".to_string(),
1763 body: vec![
1764 ShellCommand::Compound(CompoundCommand::Arith("sum = sum + i".to_string())),
1765 ],
1766 });
1767 let compiler = ShellCompiler::new();
1768 let chunk = compiler.compile(&[cmd]);
1769
1770 for (i, op) in chunk.ops.iter().enumerate() {
1772 eprintln!("{:3}: {:?}", i, op);
1773 }
1774
1775 let has_set_slot = chunk.ops.iter().any(|op| matches!(op, Op::SetSlot(_)));
1778 let has_jump = chunk.ops.iter().any(|op| matches!(op, Op::Jump(_)));
1779 let has_jump_if_false = chunk.ops.iter().any(|op| matches!(op, Op::JumpIfFalse(_)));
1780 assert!(has_set_slot, "missing SetSlot for loop variable");
1781 assert!(has_jump, "missing Jump for loop backedge");
1782 assert!(has_jump_if_false, "missing JumpIfFalse for loop exit");
1783 }
1784
1785 #[test]
1786 fn test_arith_compound_status() {
1787 use crate::parser::CompoundCommand;
1788 let cmd = ShellCommand::Compound(CompoundCommand::Arith("5 > 3".to_string()));
1790 let compiler = ShellCompiler::new();
1791 let chunk = compiler.compile(&[cmd]);
1792 let mut vm = VM::new(chunk);
1793 let _ = vm.run();
1794 assert_eq!(vm.last_status, 0); let cmd = ShellCommand::Compound(CompoundCommand::Arith("0".to_string()));
1798 let compiler = ShellCompiler::new();
1799 let chunk = compiler.compile(&[cmd]);
1800 let mut vm = VM::new(chunk);
1801 let _ = vm.run();
1802 assert_eq!(vm.last_status, 1); }
1804
1805 #[test]
1806 fn test_if_arith() {
1807 use crate::parser::CompoundCommand;
1808 let cmd = ShellCommand::Compound(CompoundCommand::If {
1810 conditions: vec![(
1811 vec![ShellCommand::Compound(CompoundCommand::Arith("1".to_string()))],
1812 vec![ShellCommand::Compound(CompoundCommand::Arith(
1813 "result = 42".to_string(),
1814 ))],
1815 )],
1816 else_part: None,
1817 });
1818 let compiler = ShellCompiler::new();
1819 let chunk = compiler.compile(&[cmd]);
1820 let mut vm = VM::new(chunk);
1821 let _ = vm.run();
1822 assert_eq!(vm.last_status, 0);
1823 }
1824
1825 #[test]
1826 fn test_repeat_loop() {
1827 use crate::parser::CompoundCommand;
1828 let cmd = ShellCommand::Compound(CompoundCommand::Repeat {
1829 count: "5".to_string(),
1830 body: vec![ShellCommand::Compound(CompoundCommand::Arith(
1831 "count = count + 1".to_string(),
1832 ))],
1833 });
1834 let compiler = ShellCompiler::new();
1835 let chunk = compiler.compile(&[cmd]);
1836 let mut vm = VM::new(chunk);
1837 let _ = vm.run();
1838 assert_eq!(vm.last_status, 0);
1839 }
1840
1841 #[test]
1842 fn test_simple_command_compiles() {
1843 use crate::parser::SimpleCommand;
1844 let cmd = ShellCommand::Simple(SimpleCommand {
1846 assignments: vec![],
1847 words: vec![
1848 ShellWord::Literal("echo".to_string()),
1849 ShellWord::Literal("hello".to_string()),
1850 ShellWord::Literal("world".to_string()),
1851 ],
1852 redirects: vec![],
1853 });
1854 let compiler = ShellCompiler::new();
1855 let chunk = compiler.compile(&[cmd]);
1856 let has_exec = chunk.ops.iter().any(|op| matches!(op, Op::Exec(3)));
1857 assert!(has_exec, "expected Exec(3) for 'echo hello world'");
1858 }
1859
1860 #[test]
1861 fn test_assignment_compiles() {
1862 use crate::parser::SimpleCommand;
1863 let cmd = ShellCommand::Simple(SimpleCommand {
1865 assignments: vec![("X".to_string(), ShellWord::Literal("42".to_string()), false)],
1866 words: vec![],
1867 redirects: vec![],
1868 });
1869 let compiler = ShellCompiler::new();
1870 let chunk = compiler.compile(&[cmd]);
1871 let has_set = chunk.ops.iter().any(|op| matches!(op, Op::SetVar(_)));
1872 assert!(has_set, "expected SetVar for assignment");
1873 }
1874
1875 #[test]
1876 fn test_pipeline_compiles() {
1877 use crate::parser::SimpleCommand;
1878 let cmds = vec![
1880 ShellCommand::Simple(SimpleCommand {
1881 assignments: vec![],
1882 words: vec![ShellWord::Literal("ls".to_string())],
1883 redirects: vec![],
1884 }),
1885 ShellCommand::Simple(SimpleCommand {
1886 assignments: vec![],
1887 words: vec![
1888 ShellWord::Literal("grep".to_string()),
1889 ShellWord::Literal("foo".to_string()),
1890 ],
1891 redirects: vec![],
1892 }),
1893 ];
1894 let cmd = ShellCommand::Pipeline(cmds, false);
1895 let compiler = ShellCompiler::new();
1896 let chunk = compiler.compile(&[cmd]);
1897 let has_begin = chunk.ops.iter().any(|op| matches!(op, Op::PipelineBegin(2)));
1898 let has_end = chunk.ops.iter().any(|op| matches!(op, Op::PipelineEnd));
1899 let has_stage = chunk.ops.iter().any(|op| matches!(op, Op::PipelineStage));
1900 assert!(has_begin, "expected PipelineBegin(2)");
1901 assert!(has_stage, "expected PipelineStage");
1902 assert!(has_end, "expected PipelineEnd");
1903 }
1904
1905 #[test]
1906 fn test_redirect_compiles() {
1907 use crate::parser::{Redirect, RedirectOp, SimpleCommand};
1908 let cmd = ShellCommand::Simple(SimpleCommand {
1910 assignments: vec![],
1911 words: vec![
1912 ShellWord::Literal("echo".to_string()),
1913 ShellWord::Literal("hi".to_string()),
1914 ],
1915 redirects: vec![Redirect {
1916 fd: None,
1917 op: RedirectOp::Write,
1918 target: ShellWord::Literal("/tmp/out".to_string()),
1919 heredoc_content: None,
1920 fd_var: None,
1921 }],
1922 });
1923 let compiler = ShellCompiler::new();
1924 let chunk = compiler.compile(&[cmd]);
1925 let has_redirect = chunk.ops.iter().any(|op| matches!(op, Op::Redirect(1, 0))); assert!(has_redirect, "expected Redirect(1, 0) for > /tmp/out");
1927 }
1928
1929 #[test]
1930 fn test_heredoc_compiles() {
1931 use crate::parser::{Redirect, RedirectOp, SimpleCommand};
1932 let cmd = ShellCommand::Simple(SimpleCommand {
1934 assignments: vec![],
1935 words: vec![ShellWord::Literal("cat".to_string())],
1936 redirects: vec![Redirect {
1937 fd: None,
1938 op: RedirectOp::HereDoc,
1939 target: ShellWord::Literal("EOF".to_string()),
1940 heredoc_content: Some("hello\n".to_string()),
1941 fd_var: None,
1942 }],
1943 });
1944 let compiler = ShellCompiler::new();
1945 let chunk = compiler.compile(&[cmd]);
1946 let has_heredoc = chunk.ops.iter().any(|op| matches!(op, Op::HereDoc(_)));
1947 assert!(has_heredoc, "expected HereDoc op");
1948 }
1949
1950 #[test]
1951 fn test_case_compiles() {
1952 use crate::parser::CompoundCommand;
1953 let cmd = ShellCommand::Compound(CompoundCommand::Case {
1955 word: ShellWord::Literal("hello".to_string()),
1956 cases: vec![
1957 (
1958 vec![ShellWord::Literal("hello".to_string())],
1959 vec![ShellCommand::Compound(CompoundCommand::Arith("result = 1".to_string()))],
1960 CaseTerminator::Break,
1961 ),
1962 (
1963 vec![ShellWord::Literal("world".to_string())],
1964 vec![ShellCommand::Compound(CompoundCommand::Arith("result = 2".to_string()))],
1965 CaseTerminator::Break,
1966 ),
1967 ],
1968 });
1969 let compiler = ShellCompiler::new();
1970 let chunk = compiler.compile(&[cmd]);
1971 let has_streq = chunk.ops.iter().any(|op| matches!(op, Op::StrEq));
1973 assert!(has_streq, "expected StrEq for case pattern");
1974 }
1975
1976 #[test]
1977 fn test_cond_file_test() {
1978 use crate::parser::CompoundCommand;
1979 let cmd = ShellCommand::Compound(CompoundCommand::Cond(CondExpr::FileRegular(
1981 ShellWord::Literal("/etc/passwd".to_string()),
1982 )));
1983 let compiler = ShellCompiler::new();
1984 let chunk = compiler.compile(&[cmd]);
1985 let has_test = chunk.ops.iter().any(|op| matches!(op, Op::TestFile(0))); assert!(has_test, "expected TestFile(IS_FILE)");
1987 }
1988
1989 #[test]
1990 fn test_cond_string_compare() {
1991 use crate::parser::CompoundCommand;
1992 let cmd = ShellCommand::Compound(CompoundCommand::Cond(CondExpr::StringEqual(
1994 ShellWord::Literal("abc".to_string()),
1995 ShellWord::Literal("abc".to_string()),
1996 )));
1997 let compiler = ShellCompiler::new();
1998 let chunk = compiler.compile(&[cmd]);
1999 let has_streq = chunk.ops.iter().any(|op| matches!(op, Op::StrEq));
2000 assert!(has_streq, "expected StrEq for string comparison");
2001 }
2002
2003 #[test]
2004 fn test_cond_logical() {
2005 use crate::parser::CompoundCommand;
2006 let cmd = ShellCommand::Compound(CompoundCommand::Cond(CondExpr::And(
2008 Box::new(CondExpr::FileRegular(ShellWord::Literal("/etc/passwd".to_string()))),
2009 Box::new(CondExpr::FileDirectory(ShellWord::Literal("/tmp".to_string()))),
2010 )));
2011 let compiler = ShellCompiler::new();
2012 let chunk = compiler.compile(&[cmd]);
2013 let has_short_circuit = chunk
2014 .ops
2015 .iter()
2016 .any(|op| matches!(op, Op::JumpIfFalseKeep(_)));
2017 assert!(has_short_circuit, "expected short-circuit && in [[ ]]");
2018 }
2019
2020 #[test]
2021 fn test_list_and_or() {
2022 use crate::parser::{ListOp, SimpleCommand};
2023 let items = vec![
2025 (
2026 ShellCommand::Compound(CompoundCommand::Arith("1".to_string())),
2027 ListOp::And,
2028 ),
2029 (
2030 ShellCommand::Simple(SimpleCommand {
2031 assignments: vec![],
2032 words: vec![
2033 ShellWord::Literal("echo".to_string()),
2034 ShellWord::Literal("yes".to_string()),
2035 ],
2036 redirects: vec![],
2037 }),
2038 ListOp::Semi,
2039 ),
2040 ];
2041 let cmd = ShellCommand::List(items);
2042 let compiler = ShellCompiler::new();
2043 let chunk = compiler.compile(&[cmd]);
2044 let has_get_status = chunk.ops.iter().any(|op| matches!(op, Op::GetStatus));
2045 assert!(has_get_status, "expected GetStatus for && list");
2046 }
2047
2048 #[test]
2049 fn test_function_def_compiles() {
2050 let cmd = ShellCommand::FunctionDef(
2052 "myfunc".to_string(),
2053 Box::new(ShellCommand::Compound(CompoundCommand::Arith(
2054 "x = 42".to_string(),
2055 ))),
2056 );
2057 let compiler = ShellCompiler::new();
2058 let chunk = compiler.compile(&[cmd]);
2059 assert!(!chunk.sub_entries.is_empty(), "expected sub entry for function");
2060 let has_return = chunk.ops.iter().any(|op| matches!(op, Op::Return));
2061 assert!(has_return, "expected Return in function body");
2062 }
2063
2064 fn compile_and_run(commands: &[ShellCommand]) -> VM {
2070 let compiler = ShellCompiler::new();
2071 let chunk = compiler.compile(commands);
2072 let mut vm = VM::new(chunk);
2073 let _ = vm.run();
2074 vm
2075 }
2076
2077 #[test]
2078 fn test_exec_file_test_exists() {
2079 use crate::parser::CompoundCommand;
2080 let cmd = ShellCommand::Compound(CompoundCommand::Cond(
2082 CondExpr::FileExists(ShellWord::Literal("/tmp".to_string())),
2083 ));
2084 let vm = compile_and_run(&[cmd]);
2085 assert_eq!(vm.last_status, 0, "/tmp should exist");
2086 }
2087
2088 #[test]
2089 fn test_exec_file_test_not_exists() {
2090 use crate::parser::CompoundCommand;
2091 let cmd = ShellCommand::Compound(CompoundCommand::Cond(
2093 CondExpr::FileExists(ShellWord::Literal("/nonexistent_path_xyz".to_string())),
2094 ));
2095 let vm = compile_and_run(&[cmd]);
2096 assert_eq!(vm.last_status, 1, "/nonexistent should not exist");
2097 }
2098
2099 #[test]
2100 fn test_exec_file_is_dir() {
2101 use crate::parser::CompoundCommand;
2102 let cmd = ShellCommand::Compound(CompoundCommand::Cond(
2104 CondExpr::FileDirectory(ShellWord::Literal("/tmp".to_string())),
2105 ));
2106 let vm = compile_and_run(&[cmd]);
2107 assert_eq!(vm.last_status, 0, "/tmp should be a directory");
2108 }
2109
2110 #[test]
2111 fn test_exec_file_is_regular() {
2112 use crate::parser::CompoundCommand;
2113 let cmd = ShellCommand::Compound(CompoundCommand::Cond(
2115 CondExpr::FileRegular(ShellWord::Literal("/etc/hosts".to_string())),
2116 ));
2117 let vm = compile_and_run(&[cmd]);
2118 assert_eq!(vm.last_status, 0, "/etc/hosts should be a regular file");
2119 }
2120
2121 #[test]
2122 fn test_exec_string_equal() {
2123 use crate::parser::CompoundCommand;
2124 let cmd = ShellCommand::Compound(CompoundCommand::Cond(
2126 CondExpr::StringEqual(
2127 ShellWord::Literal("abc".to_string()),
2128 ShellWord::Literal("abc".to_string()),
2129 ),
2130 ));
2131 let vm = compile_and_run(&[cmd]);
2132 assert_eq!(vm.last_status, 0);
2133 }
2134
2135 #[test]
2136 fn test_exec_string_not_equal() {
2137 use crate::parser::CompoundCommand;
2138 let cmd = ShellCommand::Compound(CompoundCommand::Cond(
2140 CondExpr::StringEqual(
2141 ShellWord::Literal("abc".to_string()),
2142 ShellWord::Literal("xyz".to_string()),
2143 ),
2144 ));
2145 let vm = compile_and_run(&[cmd]);
2146 assert_eq!(vm.last_status, 1);
2147 }
2148
2149 #[test]
2150 fn test_exec_string_empty() {
2151 use crate::parser::CompoundCommand;
2152 let cmd = ShellCommand::Compound(CompoundCommand::Cond(
2154 CondExpr::StringEmpty(ShellWord::Literal("".to_string())),
2155 ));
2156 let vm = compile_and_run(&[cmd]);
2157 assert_eq!(vm.last_status, 0);
2158
2159 let cmd = ShellCommand::Compound(CompoundCommand::Cond(
2161 CondExpr::StringEmpty(ShellWord::Literal("notempty".to_string())),
2162 ));
2163 let vm = compile_and_run(&[cmd]);
2164 assert_eq!(vm.last_status, 1);
2165 }
2166
2167 #[test]
2168 fn test_exec_cond_and() {
2169 use crate::parser::CompoundCommand;
2170 let cmd = ShellCommand::Compound(CompoundCommand::Cond(
2172 CondExpr::And(
2173 Box::new(CondExpr::FileDirectory(ShellWord::Literal("/tmp".to_string()))),
2174 Box::new(CondExpr::FileExists(ShellWord::Literal("/tmp".to_string()))),
2175 ),
2176 ));
2177 let vm = compile_and_run(&[cmd]);
2178 assert_eq!(vm.last_status, 0);
2179 }
2180
2181 #[test]
2182 fn test_exec_cond_and_short_circuit() {
2183 use crate::parser::CompoundCommand;
2184 let cmd = ShellCommand::Compound(CompoundCommand::Cond(
2186 CondExpr::And(
2187 Box::new(CondExpr::FileRegular(ShellWord::Literal("/nonexistent".to_string()))),
2188 Box::new(CondExpr::FileDirectory(ShellWord::Literal("/tmp".to_string()))),
2189 ),
2190 ));
2191 let vm = compile_and_run(&[cmd]);
2192 assert_eq!(vm.last_status, 1);
2193 }
2194
2195 #[test]
2196 fn test_exec_cond_or() {
2197 use crate::parser::CompoundCommand;
2198 let cmd = ShellCommand::Compound(CompoundCommand::Cond(
2200 CondExpr::Or(
2201 Box::new(CondExpr::FileRegular(ShellWord::Literal("/nonexistent".to_string()))),
2202 Box::new(CondExpr::FileDirectory(ShellWord::Literal("/tmp".to_string()))),
2203 ),
2204 ));
2205 let vm = compile_and_run(&[cmd]);
2206 assert_eq!(vm.last_status, 0);
2207 }
2208
2209 #[test]
2210 fn test_exec_cond_not() {
2211 use crate::parser::CompoundCommand;
2212 let cmd = ShellCommand::Compound(CompoundCommand::Cond(
2214 CondExpr::Not(Box::new(CondExpr::FileRegular(
2215 ShellWord::Literal("/nonexistent".to_string()),
2216 ))),
2217 ));
2218 let vm = compile_and_run(&[cmd]);
2219 assert_eq!(vm.last_status, 0);
2220 }
2221
2222 #[test]
2223 fn test_exec_if_true_branch() {
2224 use crate::parser::CompoundCommand;
2225 let cmd = ShellCommand::Compound(CompoundCommand::If {
2228 conditions: vec![(
2229 vec![ShellCommand::Compound(CompoundCommand::Arith("1".to_string()))],
2230 vec![ShellCommand::Compound(CompoundCommand::Arith("result = 42".to_string()))],
2231 )],
2232 else_part: Some(vec![
2233 ShellCommand::Compound(CompoundCommand::Arith("result = 99".to_string())),
2234 ]),
2235 });
2236 let vm = compile_and_run(&[cmd]);
2237 assert_eq!(vm.last_status, 0); }
2239
2240 #[test]
2241 fn test_exec_if_false_branch() {
2242 use crate::parser::CompoundCommand;
2243 let cmd = ShellCommand::Compound(CompoundCommand::If {
2245 conditions: vec![(
2246 vec![ShellCommand::Compound(CompoundCommand::Arith("0".to_string()))],
2247 vec![ShellCommand::Compound(CompoundCommand::Arith("result = 42".to_string()))],
2248 )],
2249 else_part: Some(vec![
2250 ShellCommand::Compound(CompoundCommand::Arith("result = 99".to_string())),
2251 ]),
2252 });
2253 let vm = compile_and_run(&[cmd]);
2254 assert_eq!(vm.last_status, 0); }
2256
2257 #[test]
2258 fn test_exec_numeric_comparison() {
2259 use crate::parser::CompoundCommand;
2260 let cmd = ShellCommand::Compound(CompoundCommand::Cond(
2262 CondExpr::NumGreater(
2263 ShellWord::Literal("5".to_string()),
2264 ShellWord::Literal("3".to_string()),
2265 ),
2266 ));
2267 let vm = compile_and_run(&[cmd]);
2268 assert_eq!(vm.last_status, 0);
2269
2270 let cmd = ShellCommand::Compound(CompoundCommand::Cond(
2272 CondExpr::NumGreater(
2273 ShellWord::Literal("2".to_string()),
2274 ShellWord::Literal("3".to_string()),
2275 ),
2276 ));
2277 let vm = compile_and_run(&[cmd]);
2278 assert_eq!(vm.last_status, 1);
2279 }
2280
2281 #[test]
2282 fn test_exec_arith_zero_is_false() {
2283 use crate::parser::CompoundCommand;
2284 let cmd = ShellCommand::Compound(CompoundCommand::Arith("0".to_string()));
2286 let vm = compile_and_run(&[cmd]);
2287 assert_eq!(vm.last_status, 1);
2288 }
2289
2290 #[test]
2291 fn test_exec_arith_nonzero_is_true() {
2292 use crate::parser::CompoundCommand;
2293 let cmd = ShellCommand::Compound(CompoundCommand::Arith("42".to_string()));
2295 let vm = compile_and_run(&[cmd]);
2296 assert_eq!(vm.last_status, 0);
2297 }
2298
2299 #[test]
2300 fn test_exec_nested_arith_comparison() {
2301 use crate::parser::CompoundCommand;
2302 let cmd = ShellCommand::Compound(CompoundCommand::Arith("5 > 3 && 2 < 10".to_string()));
2304 let vm = compile_and_run(&[cmd]);
2305 assert_eq!(vm.last_status, 0);
2306
2307 let cmd = ShellCommand::Compound(CompoundCommand::Arith("5 > 3 && 2 > 10".to_string()));
2309 let vm = compile_and_run(&[cmd]);
2310 assert_eq!(vm.last_status, 1);
2311 }
2312}