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 for cmd in commands {
55 self.compile_command(cmd);
56 }
57 self.builder.emit(Op::GetStatus, 0);
59 self.builder.build()
60 }
61
62 fn slot_for(&mut self, name: &str) -> u16 {
63 if let Some(&slot) = self.slots.get(name) {
64 return slot;
65 }
66 let slot = self.next_slot;
67 self.next_slot += 1;
68 self.slots.insert(name.to_string(), slot);
69 slot
70 }
71
72 fn compile_command(&mut self, cmd: &ShellCommand) {
73 match cmd {
74 ShellCommand::Simple(simple) => {
75 self.compile_simple(simple);
76 }
77 ShellCommand::Compound(compound) => {
78 self.compile_compound(compound);
79 }
80 ShellCommand::Pipeline(cmds, negated) => {
81 self.compile_pipeline(cmds, *negated);
82 }
83 ShellCommand::List(items) => {
84 self.compile_list(items);
85 }
86 ShellCommand::FunctionDef(name, body) => {
87 let skip_jump = self.builder.emit(Op::Jump(0), 0);
89 let entry_ip = self.builder.current_pos();
90 let name_idx = self.builder.add_name(name);
91 self.builder.add_sub_entry(name_idx, entry_ip);
92 self.builder.emit(Op::PushFrame, 0);
93 self.compile_command(body);
94 self.builder.emit(Op::PopFrame, 0);
95 self.builder.emit(Op::Return, 0);
96 let after = self.builder.current_pos();
97 self.builder.patch_jump(skip_jump, after);
98 }
99 }
100 }
101
102 fn compile_simple(&mut self, simple: &crate::parser::SimpleCommand) {
110 for (var, val, _is_append) in &simple.assignments {
112 self.compile_word(val);
113 let var_idx = self.builder.add_name(var);
114 self.builder.emit(Op::SetVar(var_idx), 0);
115 }
116
117 if simple.words.is_empty() {
118 return; }
120
121 for redir in &simple.redirects {
123 let fd = redir.fd.unwrap_or(match redir.op {
124 crate::parser::RedirectOp::Read
125 | crate::parser::RedirectOp::HereDoc
126 | crate::parser::RedirectOp::HereString
127 | crate::parser::RedirectOp::ReadWrite => 0,
128 _ => 1,
129 }) as u8;
130
131 let op_byte = match redir.op {
132 crate::parser::RedirectOp::Write => fusevm::op::redirect_op::WRITE,
133 crate::parser::RedirectOp::Append => fusevm::op::redirect_op::APPEND,
134 crate::parser::RedirectOp::Read => fusevm::op::redirect_op::READ,
135 crate::parser::RedirectOp::ReadWrite => fusevm::op::redirect_op::READ_WRITE,
136 crate::parser::RedirectOp::Clobber => fusevm::op::redirect_op::CLOBBER,
137 crate::parser::RedirectOp::DupRead => fusevm::op::redirect_op::DUP_READ,
138 crate::parser::RedirectOp::DupWrite => fusevm::op::redirect_op::DUP_WRITE,
139 crate::parser::RedirectOp::WriteBoth => fusevm::op::redirect_op::WRITE_BOTH,
140 crate::parser::RedirectOp::AppendBoth => fusevm::op::redirect_op::APPEND_BOTH,
141 crate::parser::RedirectOp::HereDoc => {
142 if let Some(ref content) = redir.heredoc_content {
144 let idx = self.builder.add_constant(Value::str(content.as_str()));
145 self.builder.emit(Op::HereDoc(idx), 0);
146 }
147 continue;
148 }
149 crate::parser::RedirectOp::HereString => {
150 self.compile_word(&redir.target);
151 self.builder.emit(Op::HereString, 0);
152 continue;
153 }
154 };
155
156 self.compile_word(&redir.target);
157 self.builder.emit(Op::Redirect(fd, op_byte), 0);
158 }
159
160 if let ShellWord::Literal(cmd_name) = &simple.words[0] {
162 if let Some(builtin_id) = fusevm::shell_builtins::builtin_id(cmd_name) {
163 let argc = (simple.words.len() - 1) as u8;
165 for word in &simple.words[1..] {
166 self.compile_word(word);
167 }
168 self.builder.emit(Op::CallBuiltin(builtin_id, argc), 0);
170 self.builder.emit(Op::SetStatus, 0);
171 return;
172 }
173 }
174
175 let argc = simple.words.len() as u8;
177 for word in &simple.words {
178 self.compile_word(word);
179 }
180
181 self.builder.emit(Op::Exec(argc), 0);
183 self.builder.emit(Op::SetStatus, 0);
184 }
185
186 fn compile_pipeline(&mut self, cmds: &[ShellCommand], negated: bool) {
197 if cmds.len() == 1 {
198 self.compile_command(&cmds[0]);
200 if negated {
201 self.builder.emit(Op::GetStatus, 0);
202 self.builder.emit(Op::LoadInt(0), 0);
203 self.builder.emit(Op::NumEq, 0);
204 let was_zero = self.builder.emit(Op::JumpIfTrue(0), 0);
206 self.builder.emit(Op::LoadInt(0), 0);
207 self.builder.emit(Op::SetStatus, 0);
208 let end = self.builder.emit(Op::Jump(0), 0);
209 let t = self.builder.current_pos();
210 self.builder.patch_jump(was_zero, t);
211 self.builder.emit(Op::LoadInt(1), 0);
212 self.builder.emit(Op::SetStatus, 0);
213 let e = self.builder.current_pos();
214 self.builder.patch_jump(end, e);
215 }
216 return;
217 }
218
219 let n = cmds.len() as u8;
220 self.builder.emit(Op::PipelineBegin(n), 0);
221
222 for (i, cmd) in cmds.iter().enumerate() {
223 self.compile_command(cmd);
224 if i < cmds.len() - 1 {
225 self.builder.emit(Op::PipelineStage, 0);
226 }
227 }
228
229 self.builder.emit(Op::PipelineEnd, 0);
230 self.builder.emit(Op::SetStatus, 0);
231
232 if negated {
233 self.builder.emit(Op::GetStatus, 0);
234 self.builder.emit(Op::LoadInt(0), 0);
235 self.builder.emit(Op::NumEq, 0);
236 let was_zero = self.builder.emit(Op::JumpIfTrue(0), 0);
237 self.builder.emit(Op::LoadInt(0), 0);
238 self.builder.emit(Op::SetStatus, 0);
239 let end = self.builder.emit(Op::Jump(0), 0);
240 let t = self.builder.current_pos();
241 self.builder.patch_jump(was_zero, t);
242 self.builder.emit(Op::LoadInt(1), 0);
243 self.builder.emit(Op::SetStatus, 0);
244 let e = self.builder.current_pos();
245 self.builder.patch_jump(end, e);
246 }
247 }
248
249 fn compile_list(&mut self, items: &[(ShellCommand, crate::parser::ListOp)]) {
251 for (i, (cmd, op)) in items.iter().enumerate() {
252 match op {
253 crate::parser::ListOp::And => {
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::JumpIfTrue(0), 0);
259 self.compile_command(&items[i + 1].0);
262 self.builder.patch_jump(skip, self.builder.current_pos());
263 }
264 }
265 crate::parser::ListOp::Or => {
266 self.compile_command(cmd);
268 if i + 1 < items.len() {
269 self.builder.emit(Op::GetStatus, 0);
270 let skip = self.builder.emit(Op::JumpIfFalse(0), 0);
271 self.compile_command(&items[i + 1].0);
273 self.builder.patch_jump(skip, self.builder.current_pos());
274 }
275 }
276 crate::parser::ListOp::Semi => {
277 self.compile_command(cmd);
279 }
280 crate::parser::ListOp::Amp => {
281 self.compile_command(cmd);
282 }
283 crate::parser::ListOp::Newline => {
284 self.compile_command(cmd);
285 }
286 }
287 }
288 }
289
290 fn compile_compound(&mut self, compound: &CompoundCommand) {
291 match compound {
292 CompoundCommand::BraceGroup(cmds) => {
293 for cmd in cmds {
294 self.compile_command(cmd);
295 }
296 }
297
298 CompoundCommand::For { var, words, body } => {
300 let i_slot = self.next_slot;
320 self.next_slot += 1;
321 let len_slot = self.next_slot;
322 self.next_slot += 1;
323 let var_idx = self.builder.add_name(var);
324
325 let item_count = if let Some(words) = words {
327 words.len()
328 } else {
329 0
330 };
331
332 if let Some(words) = words {
334 for word in words {
335 let s = self.word_to_string(word);
336 let const_idx = self.builder.add_constant(Value::str(s));
337 self.builder.emit(Op::LoadConst(const_idx), 0);
338 }
339 self.builder.emit(Op::MakeArray(item_count as u16), 0);
340 } else {
341 self.builder.emit(Op::MakeArray(0), 0);
344 }
345 let arr_slot = self.next_slot;
346 self.next_slot += 1;
347 self.builder.emit(Op::SetSlot(arr_slot), 0);
348
349 self.builder.emit(Op::LoadInt(0), 0);
351 self.builder.emit(Op::SetSlot(i_slot), 0);
352
353 self.builder.emit(Op::LoadInt(item_count as i64), 0);
355 self.builder.emit(Op::SetSlot(len_slot), 0);
356
357 let loop_top = self.builder.current_pos();
359 self.builder.emit(Op::GetSlot(i_slot), 0);
360 self.builder.emit(Op::GetSlot(len_slot), 0);
361 self.builder.emit(Op::NumLt, 0);
362 let exit_jump = self.builder.emit(Op::JumpIfFalse(0), 0);
363
364 self.builder.emit(Op::GetSlot(i_slot), 0);
366 self.builder.emit(Op::SlotArrayGet(arr_slot), 0);
367 self.builder.emit(Op::SetVar(var_idx), 0);
368
369 self.break_patches.push(Vec::new());
371 let continue_pos = self.builder.current_pos(); self.continue_targets.push(0); for cmd in body {
376 self.compile_command(cmd);
377 }
378
379 let continue_target = self.builder.current_pos();
381 if let Some(target) = self.continue_targets.last_mut() {
383 *target = continue_target;
384 }
385
386 self.builder.emit(Op::PreIncSlotVoid(i_slot), 0);
388 self.builder.emit(Op::Jump(loop_top), 0);
389
390 let loop_exit = self.builder.current_pos();
392 self.builder.patch_jump(exit_jump, loop_exit);
393
394 if let Some(breaks) = self.break_patches.pop() {
396 for bp in breaks {
397 self.builder.patch_jump(bp, loop_exit);
398 }
399 }
400 self.continue_targets.pop();
401 }
402
403 CompoundCommand::ForArith {
405 init,
406 cond,
407 step,
408 body,
409 } => {
410 if !init.is_empty() {
412 self.compile_arith_inline(init);
413 self.builder.emit(Op::Pop, 0); }
415
416 let loop_top = self.builder.current_pos();
418 if !cond.is_empty() {
419 self.compile_arith_inline(cond);
420 } else {
422 self.builder.emit(Op::LoadTrue, 0);
423 }
424 let exit_jump = self.builder.emit(Op::JumpIfFalse(0), 0);
425
426 self.break_patches.push(Vec::new());
428 self.continue_targets.push(0);
429
430 for cmd in body {
432 self.compile_command(cmd);
433 }
434
435 let continue_target = self.builder.current_pos();
437 if let Some(target) = self.continue_targets.last_mut() {
438 *target = continue_target;
439 }
440
441 if !step.is_empty() {
443 self.compile_arith_inline(step);
444 self.builder.emit(Op::Pop, 0); }
446
447 self.builder.emit(Op::Jump(loop_top), 0);
449
450 let loop_exit = self.builder.current_pos();
452 self.builder.patch_jump(exit_jump, loop_exit);
453
454 if let Some(breaks) = self.break_patches.pop() {
455 for bp in breaks {
456 self.builder.patch_jump(bp, loop_exit);
457 }
458 }
459 self.continue_targets.pop();
460 }
461
462 CompoundCommand::While { condition, body } => {
464 self.compile_while_loop(condition, body, false);
465 }
466
467 CompoundCommand::Until { condition, body } => {
469 self.compile_while_loop(condition, body, true);
470 }
471
472 CompoundCommand::If {
474 conditions,
475 else_part,
476 } => {
477 let mut end_jumps = Vec::new();
478
479 for (cond_cmds, body_cmds) in conditions {
480 for cmd in cond_cmds {
482 self.compile_command(cmd);
483 }
484 self.builder.emit(Op::GetStatus, 0);
485 let skip_body = self.builder.emit(Op::JumpIfTrue(0), 0);
487
488 for cmd in body_cmds {
490 self.compile_command(cmd);
491 }
492 end_jumps.push(self.builder.emit(Op::Jump(0), 0));
493
494 let after_body = self.builder.current_pos();
496 self.builder.patch_jump(skip_body, after_body);
497 }
498
499 if let Some(else_cmds) = else_part {
501 for cmd in else_cmds {
502 self.compile_command(cmd);
503 }
504 }
505
506 let end = self.builder.current_pos();
508 for ej in end_jumps {
509 self.builder.patch_jump(ej, end);
510 }
511 }
512
513 CompoundCommand::Repeat { count, body } => {
515 let i_slot = self.next_slot;
517 self.next_slot += 1;
518
519 self.compile_arith_inline(count);
520 let count_slot = self.next_slot;
521 self.next_slot += 1;
522 self.builder.emit(Op::SetSlot(count_slot), 0);
523
524 self.builder.emit(Op::LoadInt(0), 0);
526 self.builder.emit(Op::SetSlot(i_slot), 0);
527
528 let loop_top = self.builder.current_pos();
529 self.builder.emit(Op::GetSlot(i_slot), 0);
531 self.builder.emit(Op::GetSlot(count_slot), 0);
532 self.builder.emit(Op::NumLt, 0);
533 let exit_jump = self.builder.emit(Op::JumpIfFalse(0), 0);
534
535 self.break_patches.push(Vec::new());
536 self.continue_targets.push(0);
537
538 for cmd in body {
539 self.compile_command(cmd);
540 }
541
542 let cont = self.builder.current_pos();
543 if let Some(target) = self.continue_targets.last_mut() {
544 *target = cont;
545 }
546
547 self.builder.emit(Op::PreIncSlotVoid(i_slot), 0);
548 self.builder.emit(Op::Jump(loop_top), 0);
549
550 let loop_exit = self.builder.current_pos();
551 self.builder.patch_jump(exit_jump, loop_exit);
552
553 if let Some(breaks) = self.break_patches.pop() {
554 for bp in breaks {
555 self.builder.patch_jump(bp, loop_exit);
556 }
557 }
558 self.continue_targets.pop();
559 }
560
561 CompoundCommand::Try {
563 try_body,
564 always_body,
565 } => {
566 for cmd in try_body {
567 self.compile_command(cmd);
568 }
569 for cmd in always_body {
570 self.compile_command(cmd);
571 }
572 }
573
574 CompoundCommand::Arith(expr) => {
575 self.compile_arith_inline(expr);
576 self.builder.emit(Op::LoadInt(0), 0);
579 self.builder.emit(Op::NumNe, 0);
580 let true_jump = self.builder.emit(Op::JumpIfTrue(0), 0);
582 self.builder.emit(Op::LoadInt(1), 0);
583 self.builder.emit(Op::SetStatus, 0);
584 let end_jump = self.builder.emit(Op::Jump(0), 0);
585 let true_target = self.builder.current_pos();
586 self.builder.patch_jump(true_jump, true_target);
587 self.builder.emit(Op::LoadInt(0), 0);
588 self.builder.emit(Op::SetStatus, 0);
589 let end = self.builder.current_pos();
590 self.builder.patch_jump(end_jump, end);
591 }
592
593 CompoundCommand::Case { word, cases } => {
595 self.compile_word(word);
597 let word_slot = self.next_slot;
598 self.next_slot += 1;
599 self.builder.emit(Op::SetSlot(word_slot), 0);
600
601 let mut end_jumps = Vec::new();
602
603 for (patterns, body, term) in cases {
604 let _next_pattern_jumps: Vec<usize> = Vec::new();
605
606 let body_target_placeholder = self.builder.current_pos();
608 let mut match_jumps = Vec::new();
609
610 for pattern in patterns {
611 self.builder.emit(Op::GetSlot(word_slot), 0);
612 self.compile_word(pattern);
613 self.builder.emit(Op::StrEq, 0);
614 match_jumps.push(self.builder.emit(Op::JumpIfTrue(0), 0));
615 }
616
617 let skip_body = self.builder.emit(Op::Jump(0), 0);
619
620 let body_start = self.builder.current_pos();
622 for mj in match_jumps {
623 self.builder.patch_jump(mj, body_start);
624 }
625
626 for cmd in body {
628 self.compile_command(cmd);
629 }
630
631 match term {
632 CaseTerminator::Break => {
633 end_jumps.push(self.builder.emit(Op::Jump(0), 0));
634 }
635 CaseTerminator::Fallthrough => {
636 }
638 CaseTerminator::Continue => {
639 }
641 }
642
643 let after_body = self.builder.current_pos();
644 self.builder.patch_jump(skip_body, after_body);
645 }
646
647 let end = self.builder.current_pos();
648 for ej in end_jumps {
649 self.builder.patch_jump(ej, end);
650 }
651 }
652
653 CompoundCommand::Cond(expr) => {
655 self.compile_cond(expr);
656 let true_jump = self.builder.emit(Op::JumpIfTrue(0), 0);
658 self.builder.emit(Op::LoadInt(1), 0);
659 self.builder.emit(Op::SetStatus, 0);
660 let end_jump = self.builder.emit(Op::Jump(0), 0);
661 let true_target = self.builder.current_pos();
662 self.builder.patch_jump(true_jump, true_target);
663 self.builder.emit(Op::LoadInt(0), 0);
664 self.builder.emit(Op::SetStatus, 0);
665 let end = self.builder.current_pos();
666 self.builder.patch_jump(end_jump, end);
667 }
668
669 CompoundCommand::Subshell(cmds) => {
671 self.builder.emit(Op::SubshellBegin, 0);
672 for cmd in cmds {
673 self.compile_command(cmd);
674 }
675 self.builder.emit(Op::SubshellEnd, 0);
676 }
677
678 CompoundCommand::Select { var, words, body } => {
680 let var_slot = self.slot_for(var);
683 if let Some(words) = words {
684 for word in words {
685 let s = self.word_to_string(word);
686 let const_idx = self.builder.add_constant(Value::str(s));
687 self.builder.emit(Op::LoadConst(const_idx), 0);
688 self.builder.emit(Op::SetSlot(var_slot), 0);
689 for cmd in body {
690 self.compile_command(cmd);
691 }
692 }
693 }
694 }
695
696 CompoundCommand::Coproc { name: _, body } => {
698 self.compile_command(body);
700 }
701
702 CompoundCommand::WithRedirects(cmd, _redirects) => {
704 self.compile_command(cmd);
706 }
707 }
708 }
709
710 fn compile_cond(&mut self, expr: &CondExpr) {
713 match expr {
714 CondExpr::FileExists(w) => {
716 self.compile_word(w);
717 self.builder
718 .emit(Op::TestFile(fusevm::op::file_test::EXISTS), 0);
719 }
720 CondExpr::FileRegular(w) => {
721 self.compile_word(w);
722 self.builder
723 .emit(Op::TestFile(fusevm::op::file_test::IS_FILE), 0);
724 }
725 CondExpr::FileDirectory(w) => {
726 self.compile_word(w);
727 self.builder
728 .emit(Op::TestFile(fusevm::op::file_test::IS_DIR), 0);
729 }
730 CondExpr::FileSymlink(w) => {
731 self.compile_word(w);
732 self.builder
733 .emit(Op::TestFile(fusevm::op::file_test::IS_SYMLINK), 0);
734 }
735 CondExpr::FileReadable(w) => {
736 self.compile_word(w);
737 self.builder
738 .emit(Op::TestFile(fusevm::op::file_test::IS_READABLE), 0);
739 }
740 CondExpr::FileWritable(w) => {
741 self.compile_word(w);
742 self.builder
743 .emit(Op::TestFile(fusevm::op::file_test::IS_WRITABLE), 0);
744 }
745 CondExpr::FileExecutable(w) => {
746 self.compile_word(w);
747 self.builder
748 .emit(Op::TestFile(fusevm::op::file_test::IS_EXECUTABLE), 0);
749 }
750 CondExpr::FileNonEmpty(w) => {
751 self.compile_word(w);
752 self.builder
753 .emit(Op::TestFile(fusevm::op::file_test::IS_NONEMPTY), 0);
754 }
755
756 CondExpr::StringEmpty(w) => {
758 self.compile_word(w);
759 self.builder.emit(Op::StringLen, 0);
760 self.builder.emit(Op::LoadInt(0), 0);
761 self.builder.emit(Op::NumEq, 0);
762 }
763 CondExpr::StringNonEmpty(w) => {
764 self.compile_word(w);
765 self.builder.emit(Op::StringLen, 0);
766 self.builder.emit(Op::LoadInt(0), 0);
767 self.builder.emit(Op::NumGt, 0);
768 }
769 CondExpr::StringEqual(a, b) => {
770 self.compile_word(a);
771 self.compile_word(b);
772 self.builder.emit(Op::StrEq, 0);
773 }
774 CondExpr::StringNotEqual(a, b) => {
775 self.compile_word(a);
776 self.compile_word(b);
777 self.builder.emit(Op::StrNe, 0);
778 }
779 CondExpr::StringMatch(a, b) => {
780 self.compile_word(a);
782 self.compile_word(b);
783 self.builder.emit(Op::StrEq, 0);
784 }
785 CondExpr::StringLess(a, b) => {
786 self.compile_word(a);
787 self.compile_word(b);
788 self.builder.emit(Op::StrLt, 0);
789 }
790 CondExpr::StringGreater(a, b) => {
791 self.compile_word(a);
792 self.compile_word(b);
793 self.builder.emit(Op::StrGt, 0);
794 }
795
796 CondExpr::NumEqual(a, b) => {
798 self.compile_word(a);
799 self.compile_word(b);
800 self.builder.emit(Op::NumEq, 0);
801 }
802 CondExpr::NumNotEqual(a, b) => {
803 self.compile_word(a);
804 self.compile_word(b);
805 self.builder.emit(Op::NumNe, 0);
806 }
807 CondExpr::NumLess(a, b) => {
808 self.compile_word(a);
809 self.compile_word(b);
810 self.builder.emit(Op::NumLt, 0);
811 }
812 CondExpr::NumLessEqual(a, b) => {
813 self.compile_word(a);
814 self.compile_word(b);
815 self.builder.emit(Op::NumLe, 0);
816 }
817 CondExpr::NumGreater(a, b) => {
818 self.compile_word(a);
819 self.compile_word(b);
820 self.builder.emit(Op::NumGt, 0);
821 }
822 CondExpr::NumGreaterEqual(a, b) => {
823 self.compile_word(a);
824 self.compile_word(b);
825 self.builder.emit(Op::NumGe, 0);
826 }
827
828 CondExpr::Not(inner) => {
830 self.compile_cond(inner);
831 self.builder.emit(Op::LogNot, 0);
832 }
833 CondExpr::And(a, b) => {
834 self.compile_cond(a);
835 let skip = self.builder.emit(Op::JumpIfFalseKeep(0), 0);
836 self.builder.emit(Op::Pop, 0);
837 self.compile_cond(b);
838 self.builder.patch_jump(skip, self.builder.current_pos());
839 }
840 CondExpr::Or(a, b) => {
841 self.compile_cond(a);
842 let skip = self.builder.emit(Op::JumpIfTrueKeep(0), 0);
843 self.builder.emit(Op::Pop, 0);
844 self.compile_cond(b);
845 self.builder.patch_jump(skip, self.builder.current_pos());
846 }
847 }
848 }
849
850 fn compile_word(&mut self, word: &ShellWord) {
852 match word {
853 ShellWord::Literal(s) => {
854 if s.contains('$') {
855 self.compile_string_with_expansions(s);
856 } else {
857 let idx = self.builder.add_constant(Value::str(s.as_str()));
858 self.builder.emit(Op::LoadConst(idx), 0);
859 }
860 }
861 ShellWord::SingleQuoted(s) => {
862 let idx = self.builder.add_constant(Value::str(s.as_str()));
863 self.builder.emit(Op::LoadConst(idx), 0);
864 }
865 ShellWord::Variable(name) => {
866 let var_idx = self.builder.add_name(name);
867 self.builder.emit(Op::GetVar(var_idx), 0);
868 }
869 ShellWord::DoubleQuoted(parts) => {
870 if parts.is_empty() {
871 let idx = self.builder.add_constant(Value::str(""));
872 self.builder.emit(Op::LoadConst(idx), 0);
873 } else if parts.len() == 1 {
874 self.compile_word(&parts[0]);
875 } else {
876 self.compile_word(&parts[0]);
877 for part in &parts[1..] {
878 self.compile_word(part);
879 self.builder.emit(Op::Concat, 0);
880 }
881 }
882 }
883 ShellWord::Concat(parts) => {
884 if parts.is_empty() {
885 let idx = self.builder.add_constant(Value::str(""));
886 self.builder.emit(Op::LoadConst(idx), 0);
887 } else if parts.len() == 1 {
888 self.compile_word(&parts[0]);
889 } else {
890 self.compile_word(&parts[0]);
891 for part in &parts[1..] {
892 self.compile_word(part);
893 self.builder.emit(Op::Concat, 0);
894 }
895 }
896 }
897 _ => {
899 let idx = self.builder.add_constant(Value::str(""));
901 self.builder.emit(Op::LoadConst(idx), 0);
902 }
903 }
904 }
905
906 fn compile_string_with_expansions(&mut self, s: &str) {
907 let chars: Vec<char> = s.chars().collect();
908 let mut i = 0;
909 let mut first = true;
910 while i < chars.len() {
911 if chars[i] == '$' {
912 i += 1;
913 if i >= chars.len() {
914 let idx = self.builder.add_constant(Value::str("$"));
915 self.builder.emit(Op::LoadConst(idx), 0);
916 if !first {
917 self.builder.emit(Op::Concat, 0);
918 }
919 first = false;
920 continue;
921 }
922 if chars[i] == '{' {
923 i += 1;
924 let mut var_name = String::new();
925 while i < chars.len() && chars[i] != '}' {
926 var_name.push(chars[i]);
927 i += 1;
928 }
929 if i < chars.len() {
930 i += 1;
931 }
932 let var_idx = self.builder.add_name(&var_name);
933 self.builder.emit(Op::GetVar(var_idx), 0);
934 if !first {
935 self.builder.emit(Op::Concat, 0);
936 }
937 first = false;
938 } else if chars[i].is_ascii_alphabetic() || chars[i] == '_' {
939 let mut var_name = String::new();
940 while i < chars.len() && (chars[i].is_ascii_alphanumeric() || chars[i] == '_') {
941 var_name.push(chars[i]);
942 i += 1;
943 }
944 let var_idx = self.builder.add_name(&var_name);
945 self.builder.emit(Op::GetVar(var_idx), 0);
946 if !first {
947 self.builder.emit(Op::Concat, 0);
948 }
949 first = false;
950 } else {
951 let idx = self.builder.add_constant(Value::str("$"));
952 self.builder.emit(Op::LoadConst(idx), 0);
953 if !first {
954 self.builder.emit(Op::Concat, 0);
955 }
956 first = false;
957 }
958 } else {
959 let mut literal = String::new();
960 while i < chars.len() && chars[i] != '$' {
961 literal.push(chars[i]);
962 i += 1;
963 }
964 let idx = self.builder.add_constant(Value::str(&literal));
965 self.builder.emit(Op::LoadConst(idx), 0);
966 if !first {
967 self.builder.emit(Op::Concat, 0);
968 }
969 first = false;
970 }
971 }
972 if first {
973 let idx = self.builder.add_constant(Value::str(""));
974 self.builder.emit(Op::LoadConst(idx), 0);
975 }
976 }
977
978 fn compile_while_loop(
980 &mut self,
981 condition: &[ShellCommand],
982 body: &[ShellCommand],
983 is_until: bool,
984 ) {
985 let loop_top = self.builder.current_pos();
986
987 for cmd in condition {
989 self.compile_command(cmd);
990 }
991 self.builder.emit(Op::GetStatus, 0);
992
993 let exit_jump = if is_until {
996 self.builder.emit(Op::JumpIfFalse(0), 0)
997 } else {
998 self.builder.emit(Op::JumpIfTrue(0), 0)
999 };
1000
1001 self.break_patches.push(Vec::new());
1002 self.continue_targets.push(loop_top);
1003
1004 for cmd in body {
1005 self.compile_command(cmd);
1006 }
1007
1008 self.builder.emit(Op::Jump(loop_top), 0);
1009
1010 let loop_exit = self.builder.current_pos();
1011 self.builder.patch_jump(exit_jump, loop_exit);
1012
1013 if let Some(breaks) = self.break_patches.pop() {
1014 for bp in breaks {
1015 self.builder.patch_jump(bp, loop_exit);
1016 }
1017 }
1018 self.continue_targets.pop();
1019 }
1020
1021 fn compile_arith_inline(&mut self, expr: &str) {
1027 let mut ac = ArithCompiler::new(expr);
1028 ac.slots = self.slots.clone();
1030 ac.next_slot = self.next_slot;
1031 ac.expr();
1033 let new_slots = ac.slots.clone();
1034 let new_next = ac.next_slot;
1035 let chunk = ac.builder.build();
1036 self.slots = new_slots;
1038 self.next_slot = new_next;
1039 for op in &chunk.ops {
1041 self.builder.emit(op.clone(), 0);
1042 }
1043 }
1044
1045 fn word_to_string(&self, word: &ShellWord) -> String {
1046 match word {
1047 ShellWord::Literal(s) => s.clone(),
1048 ShellWord::SingleQuoted(s) => s.clone(),
1049 _ => String::new(), }
1051 }
1052}
1053
1054pub struct ArithCompiler<'a> {
1066 pub input: &'a str,
1067 pub pos: usize,
1068 pub builder: ChunkBuilder,
1069 pub slots: HashMap<String, u16>,
1071 pub next_slot: u16,
1072}
1073
1074#[derive(Debug, Clone, Copy, PartialEq)]
1076enum Tok {
1077 Num(i64),
1078 Float(f64),
1079 Ident,
1080 Plus,
1081 Minus,
1082 Mul,
1083 Div,
1084 Mod,
1085 Pow,
1086 BitAnd,
1087 BitOr,
1088 BitXor,
1089 BitNot,
1090 Shl,
1091 Shr,
1092 LogAnd,
1093 LogOr,
1094 LogNot,
1095 Eq,
1096 Neq,
1097 Lt,
1098 Gt,
1099 Leq,
1100 Geq,
1101 Assign,
1102 PlusAssign,
1103 MinusAssign,
1104 MulAssign,
1105 DivAssign,
1106 ModAssign,
1107 PreInc,
1108 PreDec,
1109 PostInc,
1110 PostDec,
1111 LParen,
1112 RParen,
1113 Comma,
1114 Quest,
1115 Colon,
1116 Eoi,
1117}
1118
1119impl<'a> ArithCompiler<'a> {
1120 pub fn new(input: &'a str) -> Self {
1121 Self {
1122 input,
1123 pos: 0,
1124 builder: ChunkBuilder::new(),
1125 slots: HashMap::new(),
1126 next_slot: 0,
1127 }
1128 }
1129
1130 pub fn compile(mut self) -> fusevm::Chunk {
1133 self.builder.set_source("$((...))");
1134 self.builder.emit(Op::PushFrame, 0);
1135 self.expr();
1136 self.builder.emit(Op::ReturnValue, 0);
1137 self.builder.build()
1138 }
1139
1140 fn slot_for(&mut self, name: &str) -> u16 {
1142 if let Some(&slot) = self.slots.get(name) {
1143 return slot;
1144 }
1145 let slot = self.next_slot;
1146 self.next_slot += 1;
1147 self.slots.insert(name.to_string(), slot);
1148 slot
1149 }
1150
1151 fn skip_whitespace(&mut self) {
1154 while self.pos < self.input.len() {
1155 let b = self.input.as_bytes()[self.pos];
1156 if b == b' ' || b == b'\t' || b == b'\n' || b == b'\r' {
1157 self.pos += 1;
1158 } else {
1159 break;
1160 }
1161 }
1162 }
1163
1164 fn peek_char(&self) -> Option<u8> {
1165 self.input.as_bytes().get(self.pos).copied()
1166 }
1167
1168 fn next_char(&mut self) -> Option<u8> {
1169 let c = self.input.as_bytes().get(self.pos).copied();
1170 if c.is_some() {
1171 self.pos += 1;
1172 }
1173 c
1174 }
1175
1176 fn read_ident(&mut self) -> String {
1177 let start = self.pos;
1178 while self.pos < self.input.len() {
1179 let b = self.input.as_bytes()[self.pos];
1180 if b.is_ascii_alphanumeric() || b == b'_' {
1181 self.pos += 1;
1182 } else {
1183 break;
1184 }
1185 }
1186 self.input[start..self.pos].to_string()
1187 }
1188
1189 fn read_number(&mut self) -> Tok {
1190 let start = self.pos;
1191
1192 if self.pos + 1 < self.input.len()
1194 && self.input.as_bytes()[self.pos] == b'0'
1195 && (self.input.as_bytes()[self.pos + 1] == b'x'
1196 || self.input.as_bytes()[self.pos + 1] == b'X')
1197 {
1198 self.pos += 2;
1199 while self.pos < self.input.len() && self.input.as_bytes()[self.pos].is_ascii_hexdigit()
1200 {
1201 self.pos += 1;
1202 }
1203 let val = i64::from_str_radix(&self.input[start + 2..self.pos], 16).unwrap_or(0);
1204 return Tok::Num(val);
1205 }
1206
1207 if self.pos + 1 < self.input.len()
1209 && self.input.as_bytes()[self.pos] == b'0'
1210 && self.input.as_bytes()[self.pos + 1].is_ascii_digit()
1211 {
1212 while self.pos < self.input.len() && self.input.as_bytes()[self.pos].is_ascii_digit() {
1213 self.pos += 1;
1214 }
1215 let val = i64::from_str_radix(&self.input[start + 1..self.pos], 8).unwrap_or(0);
1216 return Tok::Num(val);
1217 }
1218
1219 while self.pos < self.input.len() && self.input.as_bytes()[self.pos].is_ascii_digit() {
1221 self.pos += 1;
1222 }
1223
1224 if self.pos < self.input.len() && self.input.as_bytes()[self.pos] == b'.' {
1226 self.pos += 1;
1227 while self.pos < self.input.len() && self.input.as_bytes()[self.pos].is_ascii_digit() {
1228 self.pos += 1;
1229 }
1230 let val: f64 = self.input[start..self.pos].parse().unwrap_or(0.0);
1231 return Tok::Float(val);
1232 }
1233
1234 let val: i64 = self.input[start..self.pos].parse().unwrap_or(0);
1235 Tok::Num(val)
1236 }
1237
1238 fn next_tok(&mut self) -> (Tok, String) {
1239 self.skip_whitespace();
1240
1241 let Some(c) = self.peek_char() else {
1242 return (Tok::Eoi, String::new());
1243 };
1244
1245 match c {
1246 b'0'..=b'9' => {
1247 let tok = self.read_number();
1248 (tok, String::new())
1249 }
1250 b'a'..=b'z' | b'A'..=b'Z' | b'_' => {
1251 let name = self.read_ident();
1252 (Tok::Ident, name)
1253 }
1254 b'+' => {
1255 self.pos += 1;
1256 match self.peek_char() {
1257 Some(b'+') => {
1258 self.pos += 1;
1259 (Tok::PreInc, String::new())
1260 }
1261 Some(b'=') => {
1262 self.pos += 1;
1263 (Tok::PlusAssign, String::new())
1264 }
1265 _ => (Tok::Plus, String::new()),
1266 }
1267 }
1268 b'-' => {
1269 self.pos += 1;
1270 match self.peek_char() {
1271 Some(b'-') => {
1272 self.pos += 1;
1273 (Tok::PreDec, String::new())
1274 }
1275 Some(b'=') => {
1276 self.pos += 1;
1277 (Tok::MinusAssign, String::new())
1278 }
1279 _ => (Tok::Minus, String::new()),
1280 }
1281 }
1282 b'*' => {
1283 self.pos += 1;
1284 match self.peek_char() {
1285 Some(b'*') => {
1286 self.pos += 1;
1287 if self.peek_char() == Some(b'=') {
1288 self.pos += 1;
1289 (Tok::MulAssign, String::new()) } else {
1291 (Tok::Pow, String::new())
1292 }
1293 }
1294 Some(b'=') => {
1295 self.pos += 1;
1296 (Tok::MulAssign, String::new())
1297 }
1298 _ => (Tok::Mul, String::new()),
1299 }
1300 }
1301 b'/' => {
1302 self.pos += 1;
1303 if self.peek_char() == Some(b'=') {
1304 self.pos += 1;
1305 (Tok::DivAssign, String::new())
1306 } else {
1307 (Tok::Div, String::new())
1308 }
1309 }
1310 b'%' => {
1311 self.pos += 1;
1312 if self.peek_char() == Some(b'=') {
1313 self.pos += 1;
1314 (Tok::ModAssign, String::new())
1315 } else {
1316 (Tok::Mod, String::new())
1317 }
1318 }
1319 b'&' => {
1320 self.pos += 1;
1321 if self.peek_char() == Some(b'&') {
1322 self.pos += 1;
1323 (Tok::LogAnd, String::new())
1324 } else {
1325 (Tok::BitAnd, String::new())
1326 }
1327 }
1328 b'|' => {
1329 self.pos += 1;
1330 if self.peek_char() == Some(b'|') {
1331 self.pos += 1;
1332 (Tok::LogOr, String::new())
1333 } else {
1334 (Tok::BitOr, String::new())
1335 }
1336 }
1337 b'^' => {
1338 self.pos += 1;
1339 (Tok::BitXor, String::new())
1340 }
1341 b'~' => {
1342 self.pos += 1;
1343 (Tok::BitNot, String::new())
1344 }
1345 b'!' => {
1346 self.pos += 1;
1347 if self.peek_char() == Some(b'=') {
1348 self.pos += 1;
1349 (Tok::Neq, String::new())
1350 } else {
1351 (Tok::LogNot, String::new())
1352 }
1353 }
1354 b'<' => {
1355 self.pos += 1;
1356 match self.peek_char() {
1357 Some(b'<') => {
1358 self.pos += 1;
1359 (Tok::Shl, String::new())
1360 }
1361 Some(b'=') => {
1362 self.pos += 1;
1363 (Tok::Leq, String::new())
1364 }
1365 _ => (Tok::Lt, String::new()),
1366 }
1367 }
1368 b'>' => {
1369 self.pos += 1;
1370 match self.peek_char() {
1371 Some(b'>') => {
1372 self.pos += 1;
1373 (Tok::Shr, String::new())
1374 }
1375 Some(b'=') => {
1376 self.pos += 1;
1377 (Tok::Geq, String::new())
1378 }
1379 _ => (Tok::Gt, String::new()),
1380 }
1381 }
1382 b'=' => {
1383 self.pos += 1;
1384 if self.peek_char() == Some(b'=') {
1385 self.pos += 1;
1386 (Tok::Eq, String::new())
1387 } else {
1388 (Tok::Assign, String::new())
1389 }
1390 }
1391 b'(' => {
1392 self.pos += 1;
1393 (Tok::LParen, String::new())
1394 }
1395 b')' => {
1396 self.pos += 1;
1397 (Tok::RParen, String::new())
1398 }
1399 b',' => {
1400 self.pos += 1;
1401 (Tok::Comma, String::new())
1402 }
1403 b'?' => {
1404 self.pos += 1;
1405 (Tok::Quest, String::new())
1406 }
1407 b':' => {
1408 self.pos += 1;
1409 (Tok::Colon, String::new())
1410 }
1411 _ => {
1412 self.pos += 1;
1413 (Tok::Eoi, String::new())
1414 }
1415 }
1416 }
1417
1418 fn expr(&mut self) {
1423 self.assign_expr();
1424 }
1425
1426 fn assign_expr(&mut self) {
1427 let save_pos = self.pos;
1428
1429 self.skip_whitespace();
1431 if let Some(c) = self.peek_char() {
1432 if c.is_ascii_alphabetic() || c == b'_' {
1433 let name = self.read_ident();
1434 self.skip_whitespace();
1435 let (tok, _) = self.peek_tok();
1436 match tok {
1437 Tok::Assign => {
1438 let _ = self.next_tok(); let slot = self.slot_for(&name);
1440 self.assign_expr();
1441 self.builder.emit(Op::Dup, 0);
1442 self.builder.emit(Op::SetSlot(slot), 0);
1443 return;
1444 }
1445 Tok::PlusAssign
1446 | Tok::MinusAssign
1447 | Tok::MulAssign
1448 | Tok::DivAssign
1449 | Tok::ModAssign => {
1450 let _ = self.next_tok(); let slot = self.slot_for(&name);
1452 self.builder.emit(Op::GetSlot(slot), 0);
1453 self.assign_expr();
1454 match tok {
1455 Tok::PlusAssign => self.builder.emit(Op::Add, 0),
1456 Tok::MinusAssign => self.builder.emit(Op::Sub, 0),
1457 Tok::MulAssign => self.builder.emit(Op::Mul, 0),
1458 Tok::DivAssign => self.builder.emit(Op::Div, 0),
1459 Tok::ModAssign => self.builder.emit(Op::Mod, 0),
1460 _ => unreachable!(),
1461 };
1462 self.builder.emit(Op::Dup, 0);
1463 self.builder.emit(Op::SetSlot(slot), 0);
1464 return;
1465 }
1466 _ => {}
1467 }
1468 self.pos = save_pos;
1470 }
1471 }
1472
1473 self.ternary_expr();
1474 }
1475
1476 fn peek_tok(&mut self) -> (Tok, String) {
1477 let save = self.pos;
1478 let tok = self.next_tok();
1479 self.pos = save;
1480 tok
1481 }
1482
1483 fn ternary_expr(&mut self) {
1484 self.logor_expr();
1485 let (tok, _) = self.peek_tok();
1486 if tok == Tok::Quest {
1487 let _ = self.next_tok(); let else_jump = self.builder.emit(Op::JumpIfFalse(0), 0);
1489 self.expr(); let (colon, _) = self.peek_tok();
1491 let end_jump = self.builder.emit(Op::Jump(0), 0);
1492 let else_target = self.builder.current_pos();
1493 self.builder.patch_jump(else_jump, else_target);
1494 if colon == Tok::Colon {
1495 let _ = self.next_tok(); }
1497 self.expr(); let end_target = self.builder.current_pos();
1499 self.builder.patch_jump(end_jump, end_target);
1500 }
1501 }
1502
1503 fn logor_expr(&mut self) {
1504 self.logand_expr();
1505 loop {
1506 let (tok, _) = self.peek_tok();
1507 if tok == Tok::LogOr {
1508 let _ = self.next_tok();
1509 let skip = self.builder.emit(Op::JumpIfTrueKeep(0), 0);
1510 self.builder.emit(Op::Pop, 0);
1511 self.logand_expr();
1512 self.builder.patch_jump(skip, self.builder.current_pos());
1513 } else {
1514 break;
1515 }
1516 }
1517 }
1518
1519 fn logand_expr(&mut self) {
1520 self.bitor_expr();
1521 loop {
1522 let (tok, _) = self.peek_tok();
1523 if tok == Tok::LogAnd {
1524 let _ = self.next_tok();
1525 let skip = self.builder.emit(Op::JumpIfFalseKeep(0), 0);
1526 self.builder.emit(Op::Pop, 0);
1527 self.bitor_expr();
1528 self.builder.patch_jump(skip, self.builder.current_pos());
1529 } else {
1530 break;
1531 }
1532 }
1533 }
1534
1535 fn bitor_expr(&mut self) {
1536 self.bitxor_expr();
1537 loop {
1538 let (tok, _) = self.peek_tok();
1539 if tok == Tok::BitOr {
1540 let _ = self.next_tok();
1541 self.bitxor_expr();
1542 self.builder.emit(Op::BitOr, 0);
1543 } else {
1544 break;
1545 }
1546 }
1547 }
1548
1549 fn bitxor_expr(&mut self) {
1550 self.bitand_expr();
1551 loop {
1552 let (tok, _) = self.peek_tok();
1553 if tok == Tok::BitXor {
1554 let _ = self.next_tok();
1555 self.bitand_expr();
1556 self.builder.emit(Op::BitXor, 0);
1557 } else {
1558 break;
1559 }
1560 }
1561 }
1562
1563 fn bitand_expr(&mut self) {
1564 self.equality_expr();
1565 loop {
1566 let (tok, _) = self.peek_tok();
1567 if tok == Tok::BitAnd {
1568 let _ = self.next_tok();
1569 self.equality_expr();
1570 self.builder.emit(Op::BitAnd, 0);
1571 } else {
1572 break;
1573 }
1574 }
1575 }
1576
1577 fn equality_expr(&mut self) {
1578 self.comparison_expr();
1579 loop {
1580 let (tok, _) = self.peek_tok();
1581 match tok {
1582 Tok::Eq => {
1583 let _ = self.next_tok();
1584 self.comparison_expr();
1585 self.builder.emit(Op::NumEq, 0);
1586 }
1587 Tok::Neq => {
1588 let _ = self.next_tok();
1589 self.comparison_expr();
1590 self.builder.emit(Op::NumNe, 0);
1591 }
1592 _ => break,
1593 }
1594 }
1595 }
1596
1597 fn comparison_expr(&mut self) {
1598 self.shift_expr();
1599 loop {
1600 let (tok, _) = self.peek_tok();
1601 match tok {
1602 Tok::Lt => {
1603 let _ = self.next_tok();
1604 self.shift_expr();
1605 self.builder.emit(Op::NumLt, 0);
1606 }
1607 Tok::Gt => {
1608 let _ = self.next_tok();
1609 self.shift_expr();
1610 self.builder.emit(Op::NumGt, 0);
1611 }
1612 Tok::Leq => {
1613 let _ = self.next_tok();
1614 self.shift_expr();
1615 self.builder.emit(Op::NumLe, 0);
1616 }
1617 Tok::Geq => {
1618 let _ = self.next_tok();
1619 self.shift_expr();
1620 self.builder.emit(Op::NumGe, 0);
1621 }
1622 _ => break,
1623 }
1624 }
1625 }
1626
1627 fn shift_expr(&mut self) {
1628 self.add_expr();
1629 loop {
1630 let (tok, _) = self.peek_tok();
1631 match tok {
1632 Tok::Shl => {
1633 let _ = self.next_tok();
1634 self.add_expr();
1635 self.builder.emit(Op::Shl, 0);
1636 }
1637 Tok::Shr => {
1638 let _ = self.next_tok();
1639 self.add_expr();
1640 self.builder.emit(Op::Shr, 0);
1641 }
1642 _ => break,
1643 }
1644 }
1645 }
1646
1647 fn add_expr(&mut self) {
1648 self.mul_expr();
1649 loop {
1650 let (tok, _) = self.peek_tok();
1651 match tok {
1652 Tok::Plus => {
1653 let _ = self.next_tok();
1654 self.mul_expr();
1655 self.builder.emit(Op::Add, 0);
1656 }
1657 Tok::Minus => {
1658 let _ = self.next_tok();
1659 self.mul_expr();
1660 self.builder.emit(Op::Sub, 0);
1661 }
1662 _ => break,
1663 }
1664 }
1665 }
1666
1667 fn mul_expr(&mut self) {
1668 self.pow_expr();
1669 loop {
1670 let (tok, _) = self.peek_tok();
1671 match tok {
1672 Tok::Mul => {
1673 let _ = self.next_tok();
1674 self.pow_expr();
1675 self.builder.emit(Op::Mul, 0);
1676 }
1677 Tok::Div => {
1678 let _ = self.next_tok();
1679 self.pow_expr();
1680 self.builder.emit(Op::Div, 0);
1681 }
1682 Tok::Mod => {
1683 let _ = self.next_tok();
1684 self.pow_expr();
1685 self.builder.emit(Op::Mod, 0);
1686 }
1687 _ => break,
1688 }
1689 }
1690 }
1691
1692 fn pow_expr(&mut self) {
1693 self.unary_expr();
1694 let (tok, _) = self.peek_tok();
1695 if tok == Tok::Pow {
1696 let _ = self.next_tok();
1697 self.pow_expr(); self.builder.emit(Op::Pow, 0);
1699 }
1700 }
1701
1702 fn unary_expr(&mut self) {
1703 let (tok, name) = self.peek_tok();
1704 match tok {
1705 Tok::Minus => {
1706 let _ = self.next_tok();
1707 self.unary_expr();
1708 self.builder.emit(Op::Negate, 0);
1709 }
1710 Tok::Plus => {
1711 let _ = self.next_tok();
1712 self.unary_expr();
1713 }
1715 Tok::LogNot => {
1716 let _ = self.next_tok();
1717 self.unary_expr();
1718 self.builder.emit(Op::LogNot, 0);
1719 }
1720 Tok::BitNot => {
1721 let _ = self.next_tok();
1722 self.unary_expr();
1723 self.builder.emit(Op::BitNot, 0);
1724 }
1725 Tok::PreInc => {
1726 let _ = self.next_tok();
1727 let (_, var_name) = self.next_tok();
1729 let slot = self.slot_for(&var_name);
1730 self.builder.emit(Op::PreIncSlot(slot), 0);
1731 }
1732 Tok::PreDec => {
1733 let _ = self.next_tok();
1734 let (_, var_name) = self.next_tok();
1735 let slot = self.slot_for(&var_name);
1736 self.builder.emit(Op::GetSlot(slot), 0);
1737 self.builder.emit(Op::Dec, 0);
1738 self.builder.emit(Op::Dup, 0);
1739 self.builder.emit(Op::SetSlot(slot), 0);
1740 }
1741 _ => self.primary_expr(),
1742 }
1743 }
1744
1745 fn primary_expr(&mut self) {
1746 let (tok, name) = self.next_tok();
1747 match tok {
1748 Tok::Num(n) => {
1749 self.builder.emit(Op::LoadInt(n), 0);
1750 }
1751 Tok::Float(f) => {
1752 self.builder.emit(Op::LoadFloat(f), 0);
1753 }
1754 Tok::Ident => {
1755 let slot = self.slot_for(&name);
1756 self.builder.emit(Op::GetSlot(slot), 0);
1757
1758 let (post_tok, _) = self.peek_tok();
1760 match post_tok {
1761 Tok::PreInc => {
1762 let _ = self.next_tok();
1764 self.builder.emit(Op::Dup, 0); self.builder.emit(Op::Inc, 0);
1766 self.builder.emit(Op::SetSlot(slot), 0);
1767 }
1769 Tok::PreDec => {
1770 let _ = self.next_tok();
1771 self.builder.emit(Op::Dup, 0);
1772 self.builder.emit(Op::Dec, 0);
1773 self.builder.emit(Op::SetSlot(slot), 0);
1774 }
1775 _ => {}
1776 }
1777 }
1778 Tok::LParen => {
1779 self.expr();
1780 let _ = self.next_tok(); }
1782 _ => {
1783 self.builder.emit(Op::LoadInt(0), 0);
1785 }
1786 }
1787 }
1788}
1789
1790#[cfg(test)]
1791mod tests {
1792 use super::*;
1793 use fusevm::{VMResult, VM};
1794
1795 fn eval(expr: &str) -> i64 {
1796 let compiler = ArithCompiler::new(expr);
1797 let chunk = compiler.compile();
1798 let mut vm = VM::new(chunk);
1799 match vm.run() {
1800 VMResult::Ok(Value::Int(n)) => n,
1801 VMResult::Ok(Value::Bool(b)) => b as i64,
1802 VMResult::Ok(Value::Float(f)) => f as i64,
1803 VMResult::Ok(v) => v.to_int(),
1804 other => panic!("expected value, got {:?}", other),
1805 }
1806 }
1807
1808 fn eval_float(expr: &str) -> f64 {
1809 let compiler = ArithCompiler::new(expr);
1810 let chunk = compiler.compile();
1811 let mut vm = VM::new(chunk);
1812 match vm.run() {
1813 VMResult::Ok(v) => v.to_float(),
1814 other => panic!("expected value, got {:?}", other),
1815 }
1816 }
1817
1818 #[test]
1819 fn test_basic_arithmetic() {
1820 assert_eq!(eval("2 + 3"), 5);
1821 assert_eq!(eval("10 - 4"), 6);
1822 assert_eq!(eval("6 * 7"), 42);
1823 assert_eq!(eval("100 / 4"), 25);
1824 assert_eq!(eval("17 % 5"), 2);
1825 }
1826
1827 #[test]
1828 fn test_precedence() {
1829 assert_eq!(eval("2 + 3 * 4"), 14);
1830 assert_eq!(eval("(2 + 3) * 4"), 20);
1831 assert_eq!(eval("2 * 3 + 4 * 5"), 26);
1832 assert_eq!(eval("10 - 2 * 3"), 4);
1833 }
1834
1835 #[test]
1836 fn test_power() {
1837 assert_eq!(eval("2 ** 10"), 1024);
1838 assert_eq!(eval("3 ** 3"), 27);
1839 }
1840
1841 #[test]
1842 fn test_unary() {
1843 assert_eq!(eval("-5"), -5);
1844 assert_eq!(eval("-(-3)"), 3);
1845 assert_eq!(eval("!0"), 1);
1846 assert_eq!(eval("!1"), 0);
1847 assert_eq!(eval("~0"), -1);
1848 }
1849
1850 #[test]
1851 fn test_comparison() {
1852 assert_eq!(eval("3 < 5"), 1);
1853 assert_eq!(eval("5 < 3"), 0);
1854 assert_eq!(eval("3 <= 3"), 1);
1855 assert_eq!(eval("3 == 3"), 1);
1856 assert_eq!(eval("3 != 4"), 1);
1857 assert_eq!(eval("5 > 3"), 1);
1858 assert_eq!(eval("5 >= 5"), 1);
1859 }
1860
1861 #[test]
1862 fn test_bitwise() {
1863 assert_eq!(eval("0xFF & 0x0F"), 0x0F);
1864 assert_eq!(eval("0xF0 | 0x0F"), 0xFF);
1865 assert_eq!(eval("0xFF ^ 0x0F"), 0xF0);
1866 assert_eq!(eval("1 << 10"), 1024);
1867 assert_eq!(eval("1024 >> 5"), 32);
1868 }
1869
1870 #[test]
1871 fn test_logical_short_circuit() {
1872 assert_eq!(eval("1 && 2"), 2); assert_eq!(eval("0 && 2"), 0); assert_eq!(eval("0 || 5"), 5); assert_eq!(eval("1 || 0"), 1); }
1878
1879 #[test]
1880 fn test_ternary() {
1881 assert_eq!(eval("1 ? 42 : 99"), 42);
1882 assert_eq!(eval("0 ? 42 : 99"), 99);
1883 assert_eq!(eval("(3 > 2) ? 10 : 20"), 10);
1884 }
1885
1886 #[test]
1887 fn test_assignment() {
1888 assert_eq!(eval("x = 5"), 5);
1889 assert_eq!(eval("x = 5 + 3"), 8);
1890 }
1891
1892 #[test]
1893 fn test_hex_octal() {
1894 assert_eq!(eval("0xFF"), 255);
1895 assert_eq!(eval("0x10"), 16);
1896 assert_eq!(eval("010"), 8); }
1898
1899 #[test]
1900 fn test_complex_expression() {
1901 assert_eq!(eval("(5 + 3) * 2 - 10 / 5"), 14);
1903 assert_eq!(eval("1 ? (0 ? 1 : 2) : 3"), 2);
1905 }
1906
1907 #[test]
1908 fn test_float() {
1909 assert!((eval_float("3.14 * 2.0") - 6.28).abs() < 0.001);
1910 }
1911
1912 fn run_shell(commands: &[ShellCommand]) -> i64 {
1915 let compiler = ShellCompiler::new();
1916 let chunk = compiler.compile(commands);
1917 let mut vm = VM::new(chunk);
1918 match vm.run() {
1919 VMResult::Ok(v) => v.to_int(),
1920 other => panic!("VM error: {:?}", other),
1921 }
1922 }
1923
1924 #[test]
1925 fn test_for_arith_sum() {
1926 use crate::parser::CompoundCommand;
1927 let cmd = ShellCommand::Compound(CompoundCommand::ForArith {
1929 init: "i = 0".to_string(),
1930 cond: "i < 10".to_string(),
1931 step: "i++".to_string(),
1932 body: vec![ShellCommand::Compound(CompoundCommand::Arith(
1933 "sum = sum + i".to_string(),
1934 ))],
1935 });
1936 let compiler = ShellCompiler::new();
1937 let chunk = compiler.compile(&[cmd]);
1938
1939 for (i, op) in chunk.ops.iter().enumerate() {
1941 eprintln!("{:3}: {:?}", i, op);
1942 }
1943
1944 let has_set_slot = chunk.ops.iter().any(|op| matches!(op, Op::SetSlot(_)));
1947 let has_jump = chunk.ops.iter().any(|op| matches!(op, Op::Jump(_)));
1948 let has_jump_if_false = chunk.ops.iter().any(|op| matches!(op, Op::JumpIfFalse(_)));
1949 assert!(has_set_slot, "missing SetSlot for loop variable");
1950 assert!(has_jump, "missing Jump for loop backedge");
1951 assert!(has_jump_if_false, "missing JumpIfFalse for loop exit");
1952 }
1953
1954 #[test]
1955 fn test_arith_compound_status() {
1956 use crate::parser::CompoundCommand;
1957 let cmd = ShellCommand::Compound(CompoundCommand::Arith("5 > 3".to_string()));
1959 let compiler = ShellCompiler::new();
1960 let chunk = compiler.compile(&[cmd]);
1961 let mut vm = VM::new(chunk);
1962 let _ = vm.run();
1963 assert_eq!(vm.last_status, 0); let cmd = ShellCommand::Compound(CompoundCommand::Arith("0".to_string()));
1967 let compiler = ShellCompiler::new();
1968 let chunk = compiler.compile(&[cmd]);
1969 let mut vm = VM::new(chunk);
1970 let _ = vm.run();
1971 assert_eq!(vm.last_status, 1); }
1973
1974 #[test]
1975 fn test_if_arith() {
1976 use crate::parser::CompoundCommand;
1977 let cmd = ShellCommand::Compound(CompoundCommand::If {
1979 conditions: vec![(
1980 vec![ShellCommand::Compound(CompoundCommand::Arith(
1981 "1".to_string(),
1982 ))],
1983 vec![ShellCommand::Compound(CompoundCommand::Arith(
1984 "result = 42".to_string(),
1985 ))],
1986 )],
1987 else_part: None,
1988 });
1989 let compiler = ShellCompiler::new();
1990 let chunk = compiler.compile(&[cmd]);
1991 let mut vm = VM::new(chunk);
1992 let _ = vm.run();
1993 assert_eq!(vm.last_status, 0);
1994 }
1995
1996 #[test]
1997 fn test_repeat_loop() {
1998 use crate::parser::CompoundCommand;
1999 let cmd = ShellCommand::Compound(CompoundCommand::Repeat {
2000 count: "5".to_string(),
2001 body: vec![ShellCommand::Compound(CompoundCommand::Arith(
2002 "count = count + 1".to_string(),
2003 ))],
2004 });
2005 let compiler = ShellCompiler::new();
2006 let chunk = compiler.compile(&[cmd]);
2007 let mut vm = VM::new(chunk);
2008 let _ = vm.run();
2009 assert_eq!(vm.last_status, 0);
2010 }
2011
2012 #[test]
2013 fn test_simple_command_compiles() {
2014 use crate::parser::SimpleCommand;
2015 let cmd = ShellCommand::Simple(SimpleCommand {
2017 assignments: vec![],
2018 words: vec![
2019 ShellWord::Literal("echo".to_string()),
2020 ShellWord::Literal("hello".to_string()),
2021 ShellWord::Literal("world".to_string()),
2022 ],
2023 redirects: vec![],
2024 });
2025 let compiler = ShellCompiler::new();
2026 let chunk = compiler.compile(&[cmd]);
2027 let has_builtin = chunk
2029 .ops
2030 .iter()
2031 .any(|op| matches!(op, Op::CallBuiltin(2, 2))); assert!(
2033 has_builtin,
2034 "expected CallBuiltin(2, 2) for 'echo hello world', got: {:?}",
2035 chunk.ops
2036 );
2037 }
2038
2039 #[test]
2040 fn test_external_command_compiles() {
2041 use crate::parser::SimpleCommand;
2042 let cmd = ShellCommand::Simple(SimpleCommand {
2044 assignments: vec![],
2045 words: vec![
2046 ShellWord::Literal("ls".to_string()),
2047 ShellWord::Literal("-la".to_string()),
2048 ],
2049 redirects: vec![],
2050 });
2051 let compiler = ShellCompiler::new();
2052 let chunk = compiler.compile(&[cmd]);
2053 let has_exec = chunk.ops.iter().any(|op| matches!(op, Op::Exec(2)));
2054 assert!(
2055 has_exec,
2056 "expected Exec(2) for 'ls -la', got: {:?}",
2057 chunk.ops
2058 );
2059 }
2060
2061 #[test]
2062 fn test_assignment_compiles() {
2063 use crate::parser::SimpleCommand;
2064 let cmd = ShellCommand::Simple(SimpleCommand {
2066 assignments: vec![("X".to_string(), ShellWord::Literal("42".to_string()), false)],
2067 words: vec![],
2068 redirects: vec![],
2069 });
2070 let compiler = ShellCompiler::new();
2071 let chunk = compiler.compile(&[cmd]);
2072 let has_set = chunk.ops.iter().any(|op| matches!(op, Op::SetVar(_)));
2073 assert!(has_set, "expected SetVar for assignment");
2074 }
2075
2076 #[test]
2077 fn test_pipeline_compiles() {
2078 use crate::parser::SimpleCommand;
2079 let cmds = vec![
2081 ShellCommand::Simple(SimpleCommand {
2082 assignments: vec![],
2083 words: vec![ShellWord::Literal("ls".to_string())],
2084 redirects: vec![],
2085 }),
2086 ShellCommand::Simple(SimpleCommand {
2087 assignments: vec![],
2088 words: vec![
2089 ShellWord::Literal("grep".to_string()),
2090 ShellWord::Literal("foo".to_string()),
2091 ],
2092 redirects: vec![],
2093 }),
2094 ];
2095 let cmd = ShellCommand::Pipeline(cmds, false);
2096 let compiler = ShellCompiler::new();
2097 let chunk = compiler.compile(&[cmd]);
2098 let has_begin = chunk
2099 .ops
2100 .iter()
2101 .any(|op| matches!(op, Op::PipelineBegin(2)));
2102 let has_end = chunk.ops.iter().any(|op| matches!(op, Op::PipelineEnd));
2103 let has_stage = chunk.ops.iter().any(|op| matches!(op, Op::PipelineStage));
2104 assert!(has_begin, "expected PipelineBegin(2)");
2105 assert!(has_stage, "expected PipelineStage");
2106 assert!(has_end, "expected PipelineEnd");
2107 }
2108
2109 #[test]
2110 fn test_redirect_compiles() {
2111 use crate::parser::{Redirect, RedirectOp, SimpleCommand};
2112 let cmd = ShellCommand::Simple(SimpleCommand {
2114 assignments: vec![],
2115 words: vec![
2116 ShellWord::Literal("echo".to_string()),
2117 ShellWord::Literal("hi".to_string()),
2118 ],
2119 redirects: vec![Redirect {
2120 fd: None,
2121 op: RedirectOp::Write,
2122 target: ShellWord::Literal("/tmp/out".to_string()),
2123 heredoc_content: None,
2124 fd_var: None,
2125 }],
2126 });
2127 let compiler = ShellCompiler::new();
2128 let chunk = compiler.compile(&[cmd]);
2129 let has_redirect = chunk.ops.iter().any(|op| matches!(op, Op::Redirect(1, 0))); assert!(has_redirect, "expected Redirect(1, 0) for > /tmp/out");
2131 }
2132
2133 #[test]
2134 fn test_heredoc_compiles() {
2135 use crate::parser::{Redirect, RedirectOp, SimpleCommand};
2136 let cmd = ShellCommand::Simple(SimpleCommand {
2138 assignments: vec![],
2139 words: vec![ShellWord::Literal("cat".to_string())],
2140 redirects: vec![Redirect {
2141 fd: None,
2142 op: RedirectOp::HereDoc,
2143 target: ShellWord::Literal("EOF".to_string()),
2144 heredoc_content: Some("hello\n".to_string()),
2145 fd_var: None,
2146 }],
2147 });
2148 let compiler = ShellCompiler::new();
2149 let chunk = compiler.compile(&[cmd]);
2150 let has_heredoc = chunk.ops.iter().any(|op| matches!(op, Op::HereDoc(_)));
2151 assert!(has_heredoc, "expected HereDoc op");
2152 }
2153
2154 #[test]
2155 fn test_case_compiles() {
2156 use crate::parser::CompoundCommand;
2157 let cmd = ShellCommand::Compound(CompoundCommand::Case {
2159 word: ShellWord::Literal("hello".to_string()),
2160 cases: vec![
2161 (
2162 vec![ShellWord::Literal("hello".to_string())],
2163 vec![ShellCommand::Compound(CompoundCommand::Arith(
2164 "result = 1".to_string(),
2165 ))],
2166 CaseTerminator::Break,
2167 ),
2168 (
2169 vec![ShellWord::Literal("world".to_string())],
2170 vec![ShellCommand::Compound(CompoundCommand::Arith(
2171 "result = 2".to_string(),
2172 ))],
2173 CaseTerminator::Break,
2174 ),
2175 ],
2176 });
2177 let compiler = ShellCompiler::new();
2178 let chunk = compiler.compile(&[cmd]);
2179 let has_streq = chunk.ops.iter().any(|op| matches!(op, Op::StrEq));
2181 assert!(has_streq, "expected StrEq for case pattern");
2182 }
2183
2184 #[test]
2185 fn test_cond_file_test() {
2186 use crate::parser::CompoundCommand;
2187 let cmd = ShellCommand::Compound(CompoundCommand::Cond(CondExpr::FileRegular(
2189 ShellWord::Literal("/etc/passwd".to_string()),
2190 )));
2191 let compiler = ShellCompiler::new();
2192 let chunk = compiler.compile(&[cmd]);
2193 let has_test = chunk.ops.iter().any(|op| matches!(op, Op::TestFile(0))); assert!(has_test, "expected TestFile(IS_FILE)");
2195 }
2196
2197 #[test]
2198 fn test_cond_string_compare() {
2199 use crate::parser::CompoundCommand;
2200 let cmd = ShellCommand::Compound(CompoundCommand::Cond(CondExpr::StringEqual(
2202 ShellWord::Literal("abc".to_string()),
2203 ShellWord::Literal("abc".to_string()),
2204 )));
2205 let compiler = ShellCompiler::new();
2206 let chunk = compiler.compile(&[cmd]);
2207 let has_streq = chunk.ops.iter().any(|op| matches!(op, Op::StrEq));
2208 assert!(has_streq, "expected StrEq for string comparison");
2209 }
2210
2211 #[test]
2212 fn test_cond_logical() {
2213 use crate::parser::CompoundCommand;
2214 let cmd = ShellCommand::Compound(CompoundCommand::Cond(CondExpr::And(
2216 Box::new(CondExpr::FileRegular(ShellWord::Literal(
2217 "/etc/passwd".to_string(),
2218 ))),
2219 Box::new(CondExpr::FileDirectory(ShellWord::Literal(
2220 "/tmp".to_string(),
2221 ))),
2222 )));
2223 let compiler = ShellCompiler::new();
2224 let chunk = compiler.compile(&[cmd]);
2225 let has_short_circuit = chunk
2226 .ops
2227 .iter()
2228 .any(|op| matches!(op, Op::JumpIfFalseKeep(_)));
2229 assert!(has_short_circuit, "expected short-circuit && in [[ ]]");
2230 }
2231
2232 #[test]
2233 fn test_list_and_or() {
2234 use crate::parser::{ListOp, SimpleCommand};
2235 let items = vec![
2237 (
2238 ShellCommand::Compound(CompoundCommand::Arith("1".to_string())),
2239 ListOp::And,
2240 ),
2241 (
2242 ShellCommand::Simple(SimpleCommand {
2243 assignments: vec![],
2244 words: vec![
2245 ShellWord::Literal("echo".to_string()),
2246 ShellWord::Literal("yes".to_string()),
2247 ],
2248 redirects: vec![],
2249 }),
2250 ListOp::Semi,
2251 ),
2252 ];
2253 let cmd = ShellCommand::List(items);
2254 let compiler = ShellCompiler::new();
2255 let chunk = compiler.compile(&[cmd]);
2256 let has_get_status = chunk.ops.iter().any(|op| matches!(op, Op::GetStatus));
2257 assert!(has_get_status, "expected GetStatus for && list");
2258 }
2259
2260 #[test]
2261 fn test_function_def_compiles() {
2262 let cmd = ShellCommand::FunctionDef(
2264 "myfunc".to_string(),
2265 Box::new(ShellCommand::Compound(CompoundCommand::Arith(
2266 "x = 42".to_string(),
2267 ))),
2268 );
2269 let compiler = ShellCompiler::new();
2270 let chunk = compiler.compile(&[cmd]);
2271 assert!(
2272 !chunk.sub_entries.is_empty(),
2273 "expected sub entry for function"
2274 );
2275 let has_return = chunk.ops.iter().any(|op| matches!(op, Op::Return));
2276 assert!(has_return, "expected Return in function body");
2277 }
2278
2279 fn compile_and_run(commands: &[ShellCommand]) -> VM {
2285 let compiler = ShellCompiler::new();
2286 let chunk = compiler.compile(commands);
2287 let mut vm = VM::new(chunk);
2288 let _ = vm.run();
2289 vm
2290 }
2291
2292 #[test]
2293 fn test_exec_file_test_exists() {
2294 use crate::parser::CompoundCommand;
2295 let cmd = ShellCommand::Compound(CompoundCommand::Cond(CondExpr::FileExists(
2297 ShellWord::Literal("/tmp".to_string()),
2298 )));
2299 let vm = compile_and_run(&[cmd]);
2300 assert_eq!(vm.last_status, 0, "/tmp should exist");
2301 }
2302
2303 #[test]
2304 fn test_exec_file_test_not_exists() {
2305 use crate::parser::CompoundCommand;
2306 let cmd = ShellCommand::Compound(CompoundCommand::Cond(CondExpr::FileExists(
2308 ShellWord::Literal("/nonexistent_path_xyz".to_string()),
2309 )));
2310 let vm = compile_and_run(&[cmd]);
2311 assert_eq!(vm.last_status, 1, "/nonexistent should not exist");
2312 }
2313
2314 #[test]
2315 fn test_exec_file_is_dir() {
2316 use crate::parser::CompoundCommand;
2317 let cmd = ShellCommand::Compound(CompoundCommand::Cond(CondExpr::FileDirectory(
2319 ShellWord::Literal("/tmp".to_string()),
2320 )));
2321 let vm = compile_and_run(&[cmd]);
2322 assert_eq!(vm.last_status, 0, "/tmp should be a directory");
2323 }
2324
2325 #[test]
2326 fn test_exec_file_is_regular() {
2327 use crate::parser::CompoundCommand;
2328 let cmd = ShellCommand::Compound(CompoundCommand::Cond(CondExpr::FileRegular(
2330 ShellWord::Literal("/etc/hosts".to_string()),
2331 )));
2332 let vm = compile_and_run(&[cmd]);
2333 assert_eq!(vm.last_status, 0, "/etc/hosts should be a regular file");
2334 }
2335
2336 #[test]
2337 fn test_exec_string_equal() {
2338 use crate::parser::CompoundCommand;
2339 let cmd = ShellCommand::Compound(CompoundCommand::Cond(CondExpr::StringEqual(
2341 ShellWord::Literal("abc".to_string()),
2342 ShellWord::Literal("abc".to_string()),
2343 )));
2344 let vm = compile_and_run(&[cmd]);
2345 assert_eq!(vm.last_status, 0);
2346 }
2347
2348 #[test]
2349 fn test_exec_string_not_equal() {
2350 use crate::parser::CompoundCommand;
2351 let cmd = ShellCommand::Compound(CompoundCommand::Cond(CondExpr::StringEqual(
2353 ShellWord::Literal("abc".to_string()),
2354 ShellWord::Literal("xyz".to_string()),
2355 )));
2356 let vm = compile_and_run(&[cmd]);
2357 assert_eq!(vm.last_status, 1);
2358 }
2359
2360 #[test]
2361 fn test_exec_string_empty() {
2362 use crate::parser::CompoundCommand;
2363 let cmd = ShellCommand::Compound(CompoundCommand::Cond(CondExpr::StringEmpty(
2365 ShellWord::Literal("".to_string()),
2366 )));
2367 let vm = compile_and_run(&[cmd]);
2368 assert_eq!(vm.last_status, 0);
2369
2370 let cmd = ShellCommand::Compound(CompoundCommand::Cond(CondExpr::StringEmpty(
2372 ShellWord::Literal("notempty".to_string()),
2373 )));
2374 let vm = compile_and_run(&[cmd]);
2375 assert_eq!(vm.last_status, 1);
2376 }
2377
2378 #[test]
2379 fn test_exec_cond_and() {
2380 use crate::parser::CompoundCommand;
2381 let cmd = ShellCommand::Compound(CompoundCommand::Cond(CondExpr::And(
2383 Box::new(CondExpr::FileDirectory(ShellWord::Literal(
2384 "/tmp".to_string(),
2385 ))),
2386 Box::new(CondExpr::FileExists(ShellWord::Literal("/tmp".to_string()))),
2387 )));
2388 let vm = compile_and_run(&[cmd]);
2389 assert_eq!(vm.last_status, 0);
2390 }
2391
2392 #[test]
2393 fn test_exec_cond_and_short_circuit() {
2394 use crate::parser::CompoundCommand;
2395 let cmd = ShellCommand::Compound(CompoundCommand::Cond(CondExpr::And(
2397 Box::new(CondExpr::FileRegular(ShellWord::Literal(
2398 "/nonexistent".to_string(),
2399 ))),
2400 Box::new(CondExpr::FileDirectory(ShellWord::Literal(
2401 "/tmp".to_string(),
2402 ))),
2403 )));
2404 let vm = compile_and_run(&[cmd]);
2405 assert_eq!(vm.last_status, 1);
2406 }
2407
2408 #[test]
2409 fn test_exec_cond_or() {
2410 use crate::parser::CompoundCommand;
2411 let cmd = ShellCommand::Compound(CompoundCommand::Cond(CondExpr::Or(
2413 Box::new(CondExpr::FileRegular(ShellWord::Literal(
2414 "/nonexistent".to_string(),
2415 ))),
2416 Box::new(CondExpr::FileDirectory(ShellWord::Literal(
2417 "/tmp".to_string(),
2418 ))),
2419 )));
2420 let vm = compile_and_run(&[cmd]);
2421 assert_eq!(vm.last_status, 0);
2422 }
2423
2424 #[test]
2425 fn test_exec_cond_not() {
2426 use crate::parser::CompoundCommand;
2427 let cmd = ShellCommand::Compound(CompoundCommand::Cond(CondExpr::Not(Box::new(
2429 CondExpr::FileRegular(ShellWord::Literal("/nonexistent".to_string())),
2430 ))));
2431 let vm = compile_and_run(&[cmd]);
2432 assert_eq!(vm.last_status, 0);
2433 }
2434
2435 #[test]
2436 fn test_exec_if_true_branch() {
2437 use crate::parser::CompoundCommand;
2438 let cmd = ShellCommand::Compound(CompoundCommand::If {
2441 conditions: vec![(
2442 vec![ShellCommand::Compound(CompoundCommand::Arith(
2443 "1".to_string(),
2444 ))],
2445 vec![ShellCommand::Compound(CompoundCommand::Arith(
2446 "result = 42".to_string(),
2447 ))],
2448 )],
2449 else_part: Some(vec![ShellCommand::Compound(CompoundCommand::Arith(
2450 "result = 99".to_string(),
2451 ))]),
2452 });
2453 let vm = compile_and_run(&[cmd]);
2454 assert_eq!(vm.last_status, 0); }
2456
2457 #[test]
2458 fn test_exec_if_false_branch() {
2459 use crate::parser::CompoundCommand;
2460 let cmd = ShellCommand::Compound(CompoundCommand::If {
2462 conditions: vec![(
2463 vec![ShellCommand::Compound(CompoundCommand::Arith(
2464 "0".to_string(),
2465 ))],
2466 vec![ShellCommand::Compound(CompoundCommand::Arith(
2467 "result = 42".to_string(),
2468 ))],
2469 )],
2470 else_part: Some(vec![ShellCommand::Compound(CompoundCommand::Arith(
2471 "result = 99".to_string(),
2472 ))]),
2473 });
2474 let vm = compile_and_run(&[cmd]);
2475 assert_eq!(vm.last_status, 0); }
2477
2478 #[test]
2479 fn test_exec_numeric_comparison() {
2480 use crate::parser::CompoundCommand;
2481 let cmd = ShellCommand::Compound(CompoundCommand::Cond(CondExpr::NumGreater(
2483 ShellWord::Literal("5".to_string()),
2484 ShellWord::Literal("3".to_string()),
2485 )));
2486 let vm = compile_and_run(&[cmd]);
2487 assert_eq!(vm.last_status, 0);
2488
2489 let cmd = ShellCommand::Compound(CompoundCommand::Cond(CondExpr::NumGreater(
2491 ShellWord::Literal("2".to_string()),
2492 ShellWord::Literal("3".to_string()),
2493 )));
2494 let vm = compile_and_run(&[cmd]);
2495 assert_eq!(vm.last_status, 1);
2496 }
2497
2498 #[test]
2499 fn test_exec_arith_zero_is_false() {
2500 use crate::parser::CompoundCommand;
2501 let cmd = ShellCommand::Compound(CompoundCommand::Arith("0".to_string()));
2503 let vm = compile_and_run(&[cmd]);
2504 assert_eq!(vm.last_status, 1);
2505 }
2506
2507 #[test]
2508 fn test_exec_arith_nonzero_is_true() {
2509 use crate::parser::CompoundCommand;
2510 let cmd = ShellCommand::Compound(CompoundCommand::Arith("42".to_string()));
2512 let vm = compile_and_run(&[cmd]);
2513 assert_eq!(vm.last_status, 0);
2514 }
2515
2516 #[test]
2517 fn test_exec_nested_arith_comparison() {
2518 use crate::parser::CompoundCommand;
2519 let cmd = ShellCommand::Compound(CompoundCommand::Arith("5 > 3 && 2 < 10".to_string()));
2521 let vm = compile_and_run(&[cmd]);
2522 assert_eq!(vm.last_status, 0);
2523
2524 let cmd = ShellCommand::Compound(CompoundCommand::Arith("5 > 3 && 2 > 10".to_string()));
2526 let vm = compile_and_run(&[cmd]);
2527 assert_eq!(vm.last_status, 1);
2528 }
2529}