1use crate::parser::*;
14use fusevm::{ChunkBuilder, Op, Value};
15
16pub fn compile_script(commands: &[ShellCommand], source: &str) -> fusevm::Chunk {
18 let mut c = Compiler::new(source);
19 for cmd in commands {
20 c.compile_command(cmd);
21 }
22 c.finish()
23}
24
25pub fn compile_function(name: &str, body: &ShellCommand) -> fusevm::Chunk {
27 let mut c = Compiler::new(name);
28 c.compile_command(body);
29 c.emit(Op::Return, 0);
30 c.finish()
31}
32
33struct Compiler {
34 builder: ChunkBuilder,
35 line: u32,
36 loop_stack: Vec<LoopCtx>,
38}
39
40struct LoopCtx {
41 start: usize, breaks: Vec<usize>, }
44
45impl Compiler {
46 fn new(source: &str) -> Self {
47 let mut builder = ChunkBuilder::new();
48 builder.set_source(source);
49 Self {
50 builder,
51 line: 1,
52 loop_stack: Vec::new(),
53 }
54 }
55
56 fn emit(&mut self, op: Op, line: u32) -> usize {
57 self.builder.emit(op, line)
58 }
59
60 fn pos(&self) -> usize {
61 self.builder.current_pos()
62 }
63
64 fn name(&mut self, s: &str) -> u16 {
65 self.builder.add_name(s)
66 }
67
68 fn constant_str(&mut self, s: &str) -> u16 {
69 self.builder.add_constant(Value::str(s))
70 }
71
72 fn finish(self) -> fusevm::Chunk {
73 self.builder.build()
74 }
75
76 fn compile_command(&mut self, cmd: &ShellCommand) {
79 match cmd {
80 ShellCommand::Simple(simple) => self.compile_simple(simple),
81 ShellCommand::Pipeline(cmds, negated) => self.compile_pipeline(cmds, *negated),
82 ShellCommand::List(items) => self.compile_list(items),
83 ShellCommand::Compound(compound) => self.compile_compound(compound),
84 ShellCommand::FunctionDef(name, body) => self.compile_funcdef(name, body),
85 }
86 }
87
88 fn compile_simple(&mut self, cmd: &SimpleCommand) {
91 for (var, val, is_append) in &cmd.assignments {
93 self.compile_word(val);
94 if *is_append {
95 let idx = self.name(var);
97 let tmp = self.name(var);
98 self.emit(Op::GetVar(idx), self.line);
99 self.emit(Op::Swap, self.line);
100 self.emit(Op::Concat, self.line);
101 self.emit(Op::SetVar(tmp), self.line);
102 } else {
103 let idx = self.name(var);
104 self.emit(Op::SetVar(idx), self.line);
105 }
106 }
107
108 if cmd.words.is_empty() {
109 return;
110 }
111
112 for redir in &cmd.redirects {
114 self.compile_redirect(redir);
115 }
116
117 if let ShellWord::Literal(name) = &cmd.words[0] {
119 match name.as_str() {
120 "echo" => return self.compile_echo(&cmd.words[1..]),
121 "print" => return self.compile_print(&cmd.words[1..]),
122 "true" => {
123 self.emit(Op::LoadInt(0), self.line);
124 self.emit(Op::SetStatus, self.line);
125 return;
126 }
127 "false" => {
128 self.emit(Op::LoadInt(1), self.line);
129 self.emit(Op::SetStatus, self.line);
130 return;
131 }
132 "return" => {
133 if cmd.words.len() > 1 {
134 self.compile_word(&cmd.words[1]);
135 } else {
136 self.emit(Op::GetStatus, self.line);
137 }
138 self.emit(Op::ReturnValue, self.line);
139 return;
140 }
141 "break" => {
142 let j = self.emit(Op::Jump(0), self.line); if let Some(ctx) = self.loop_stack.last_mut() {
144 ctx.breaks.push(j);
145 }
146 return;
147 }
148 "continue" => {
149 if let Some(ctx) = self.loop_stack.last() {
150 let target = ctx.start;
151 self.emit(Op::Jump(target), self.line);
152 }
153 return;
154 }
155 _ => {}
156 }
157 }
158
159 let argc = cmd.words.len() as u8;
161 for word in &cmd.words {
162 self.compile_word(word);
163 }
164 self.emit(Op::Exec(argc), self.line);
165 self.emit(Op::SetStatus, self.line);
166 }
167
168 fn compile_echo(&mut self, args: &[ShellWord]) {
171 if args.is_empty() {
172 let idx = self.constant_str("");
173 self.emit(Op::LoadConst(idx), self.line);
174 self.emit(Op::PrintLn(1), self.line);
175 return;
176 }
177 for arg in args {
178 self.compile_word(arg);
179 }
180 self.emit(Op::PrintLn(args.len() as u8), self.line);
181 }
182
183 fn compile_print(&mut self, args: &[ShellWord]) {
184 self.compile_echo(args);
186 }
187
188 fn compile_pipeline(&mut self, cmds: &[ShellCommand], negated: bool) {
191 let n = cmds.len() as u8;
192 self.emit(Op::PipelineBegin(n), self.line);
193 for (i, cmd) in cmds.iter().enumerate() {
194 self.compile_command(cmd);
195 if i < cmds.len() - 1 {
196 self.emit(Op::PipelineStage, self.line);
197 }
198 }
199 self.emit(Op::PipelineEnd, self.line);
200 if negated {
201 self.emit(Op::GetStatus, self.line);
203 self.emit(Op::LogNot, self.line);
204 self.emit(Op::SetStatus, self.line);
205 }
206 }
207
208 fn compile_list(&mut self, items: &[(ShellCommand, ListOp)]) {
211 for (i, (cmd, op)) in items.iter().enumerate() {
212 self.compile_command(cmd);
213
214 if i + 1 >= items.len() {
216 break;
217 }
218
219 match op {
220 ListOp::And => {
221 self.emit(Op::GetStatus, self.line);
223 let j = self.emit(Op::JumpIfTrue(0), self.line); self.builder.patch_jump(j, self.pos());
231 }
236 ListOp::Or => {
237 self.emit(Op::GetStatus, self.line);
239 let j = self.emit(Op::JumpIfFalse(0), self.line);
240 self.builder.patch_jump(j, self.pos());
241 }
242 ListOp::Semi | ListOp::Amp | ListOp::Newline => {
243 }
246 }
247 }
248 }
249
250 fn compile_compound(&mut self, compound: &CompoundCommand) {
253 match compound {
254 CompoundCommand::BraceGroup(cmds) => {
255 for cmd in cmds {
256 self.compile_command(cmd);
257 }
258 }
259 CompoundCommand::Subshell(cmds) => {
260 self.emit(Op::SubshellBegin, self.line);
261 for cmd in cmds {
262 self.compile_command(cmd);
263 }
264 self.emit(Op::SubshellEnd, self.line);
265 }
266 CompoundCommand::If {
267 conditions,
268 else_part,
269 } => {
270 self.compile_if(conditions, else_part);
271 }
272 CompoundCommand::For { var, words, body } => {
273 self.compile_for(var, words, body);
274 }
275 CompoundCommand::ForArith {
276 init,
277 cond,
278 step,
279 body,
280 } => {
281 self.compile_for_arith(init, cond, step, body);
282 }
283 CompoundCommand::While { condition, body } => {
284 self.compile_while(condition, body, false);
285 }
286 CompoundCommand::Until { condition, body } => {
287 self.compile_while(condition, body, true);
288 }
289 CompoundCommand::Case { word, cases } => {
290 self.compile_case(word, cases);
291 }
292 CompoundCommand::Try {
293 try_body,
294 always_body,
295 } => {
296 for cmd in try_body {
298 self.compile_command(cmd);
299 }
300 for cmd in always_body {
301 self.compile_command(cmd);
302 }
303 }
304 CompoundCommand::Repeat { count, body } => {
305 self.compile_repeat(count, body);
306 }
307 _ => {
308 }
311 }
312 }
313
314 fn compile_if(
315 &mut self,
316 conditions: &[(Vec<ShellCommand>, Vec<ShellCommand>)],
317 else_part: &Option<Vec<ShellCommand>>,
318 ) {
319 let mut end_jumps = Vec::new();
320
321 for (cond_cmds, body_cmds) in conditions {
322 for cmd in cond_cmds {
324 self.compile_command(cmd);
325 }
326 self.emit(Op::GetStatus, self.line);
328 let skip = self.emit(Op::JumpIfTrue(0), self.line); for cmd in body_cmds {
332 self.compile_command(cmd);
333 }
334 let end_j = self.emit(Op::Jump(0), self.line);
335 end_jumps.push(end_j);
336
337 self.builder.patch_jump(skip, self.pos());
339 }
340
341 if let Some(else_cmds) = else_part {
343 for cmd in else_cmds {
344 self.compile_command(cmd);
345 }
346 }
347
348 let end = self.pos();
350 for j in end_jumps {
351 self.builder.patch_jump(j, end);
352 }
353 }
354
355 fn compile_for(&mut self, var: &str, words: &Option<Vec<ShellWord>>, body: &[ShellCommand]) {
356 let var_idx = self.name(var);
357
358 if let Some(ws) = words {
360 for w in ws {
361 self.compile_word(w);
362 }
363 self.emit(Op::MakeArray(ws.len() as u16), self.line);
364 } else {
365 let empty = self.constant_str("");
368 self.emit(Op::LoadConst(empty), self.line);
369 return;
370 }
371
372 let iter_idx = self.name("__for_arr");
374 let i_idx = self.name("__for_i");
375 let len_idx = self.name("__for_len");
376
377 self.emit(Op::SetVar(iter_idx), self.line); self.emit(Op::ArrayLen(iter_idx), self.line); self.emit(Op::SetVar(len_idx), self.line); self.emit(Op::LoadInt(0), self.line);
381 self.emit(Op::SetVar(i_idx), self.line); let loop_top = self.pos();
384 self.loop_stack.push(LoopCtx {
385 start: loop_top,
386 breaks: Vec::new(),
387 });
388
389 self.emit(Op::GetVar(i_idx), self.line);
391 self.emit(Op::GetVar(len_idx), self.line);
392 self.emit(Op::NumLt, self.line);
393 let exit_jump = self.emit(Op::JumpIfFalse(0), self.line);
394
395 self.emit(Op::GetVar(i_idx), self.line);
397 self.emit(Op::ArrayGet(iter_idx), self.line);
398 self.emit(Op::SetVar(var_idx), self.line);
399
400 for cmd in body {
401 self.compile_command(cmd);
402 }
403
404 self.emit(Op::GetVar(i_idx), self.line);
406 self.emit(Op::LoadInt(1), self.line);
407 self.emit(Op::Add, self.line);
408 self.emit(Op::SetVar(i_idx), self.line);
409 self.emit(Op::Jump(loop_top), self.line);
410
411 let exit_pos = self.pos();
413 self.builder.patch_jump(exit_jump, exit_pos);
414
415 let ctx = self.loop_stack.pop().unwrap();
417 for b in ctx.breaks {
418 self.builder.patch_jump(b, exit_pos);
419 }
420 }
421
422 fn compile_for_arith(&mut self, init: &str, cond: &str, step: &str, body: &[ShellCommand]) {
423 let init_c = self.constant_str(init);
427 let cond_c = self.constant_str(cond);
428 let step_c = self.constant_str(step);
429
430 self.emit(Op::LoadConst(init_c), self.line);
432 self.emit(Op::Extended(0, 0), self.line); let loop_top = self.pos();
435 self.loop_stack.push(LoopCtx {
436 start: loop_top,
437 breaks: Vec::new(),
438 });
439
440 self.emit(Op::LoadConst(cond_c), self.line);
442 self.emit(Op::Extended(1, 0), self.line); let exit_jump = self.emit(Op::JumpIfFalse(0), self.line);
444
445 for cmd in body {
446 self.compile_command(cmd);
447 }
448
449 self.emit(Op::LoadConst(step_c), self.line);
451 self.emit(Op::Extended(0, 0), self.line);
452 self.emit(Op::Jump(loop_top), self.line);
453
454 let exit_pos = self.pos();
455 self.builder.patch_jump(exit_jump, exit_pos);
456
457 let ctx = self.loop_stack.pop().unwrap();
458 for b in ctx.breaks {
459 self.builder.patch_jump(b, exit_pos);
460 }
461 }
462
463 fn compile_while(&mut self, condition: &[ShellCommand], body: &[ShellCommand], negate: bool) {
464 let loop_top = self.pos();
465 self.loop_stack.push(LoopCtx {
466 start: loop_top,
467 breaks: Vec::new(),
468 });
469
470 for cmd in condition {
471 self.compile_command(cmd);
472 }
473 self.emit(Op::GetStatus, self.line);
474
475 let exit_jump = if negate {
476 self.emit(Op::JumpIfFalse(0), self.line) } else {
478 self.emit(Op::JumpIfTrue(0), self.line) };
480
481 for cmd in body {
482 self.compile_command(cmd);
483 }
484 self.emit(Op::Jump(loop_top), self.line);
485
486 let exit_pos = self.pos();
487 self.builder.patch_jump(exit_jump, exit_pos);
488
489 let ctx = self.loop_stack.pop().unwrap();
490 for b in ctx.breaks {
491 self.builder.patch_jump(b, exit_pos);
492 }
493 }
494
495 fn compile_case(
496 &mut self,
497 word: &ShellWord,
498 cases: &[(Vec<ShellWord>, Vec<ShellCommand>, CaseTerminator)],
499 ) {
500 self.compile_word(word); let mut end_jumps = Vec::new();
503
504 for (patterns, cmds, _terminator) in cases {
505 let mut pattern_match_jumps = Vec::new();
507
508 for pat in patterns {
509 self.emit(Op::Dup, self.line); self.compile_word(pat);
511 self.emit(Op::StrEq, self.line);
512 let j = self.emit(Op::JumpIfTrue(0), self.line);
513 pattern_match_jumps.push(j);
514 }
515
516 let skip = self.emit(Op::Jump(0), self.line);
518
519 let body_start = self.pos();
521 for j in pattern_match_jumps {
522 self.builder.patch_jump(j, body_start);
523 }
524
525 for cmd in cmds {
526 self.compile_command(cmd);
527 }
528
529 let end_j = self.emit(Op::Jump(0), self.line);
530 end_jumps.push(end_j);
531
532 self.builder.patch_jump(skip, self.pos());
534 }
535
536 let end = self.pos();
537 for j in end_jumps {
538 self.builder.patch_jump(j, end);
539 }
540 self.emit(Op::Pop, self.line); }
542
543 fn compile_repeat(&mut self, count: &str, body: &[ShellCommand]) {
544 let count_c = self.constant_str(count);
546 let i_idx = self.name("__repeat_i");
547
548 self.emit(Op::LoadConst(count_c), self.line);
549 self.emit(Op::SetVar(i_idx), self.line);
551
552 let loop_top = self.pos();
553 self.loop_stack.push(LoopCtx {
554 start: loop_top,
555 breaks: Vec::new(),
556 });
557
558 self.emit(Op::GetVar(i_idx), self.line);
559 self.emit(Op::LoadInt(0), self.line);
560 self.emit(Op::NumGt, self.line);
561 let exit_jump = self.emit(Op::JumpIfFalse(0), self.line);
562
563 for cmd in body {
564 self.compile_command(cmd);
565 }
566
567 self.emit(Op::GetVar(i_idx), self.line);
569 self.emit(Op::LoadInt(1), self.line);
570 self.emit(Op::Sub, self.line);
571 self.emit(Op::SetVar(i_idx), self.line);
572 self.emit(Op::Jump(loop_top), self.line);
573
574 let exit_pos = self.pos();
575 self.builder.patch_jump(exit_jump, exit_pos);
576 let ctx = self.loop_stack.pop().unwrap();
577 for b in ctx.breaks {
578 self.builder.patch_jump(b, exit_pos);
579 }
580 }
581
582 fn compile_funcdef(&mut self, name: &str, body: &ShellCommand) {
585 let skip = self.emit(Op::Jump(0), self.line);
587
588 let name_idx = self.name(name);
589 let entry = self.pos();
590 self.emit(Op::PushFrame, self.line);
591 self.compile_command(body);
592 self.emit(Op::PopFrame, self.line);
593 self.emit(Op::Return, self.line);
594
595 self.builder.add_sub_entry(name_idx, entry);
596 self.builder.patch_jump(skip, self.pos());
597 }
598
599 fn compile_word(&mut self, word: &ShellWord) {
602 match word {
603 ShellWord::Literal(s) => {
604 let idx = self.constant_str(s);
605 self.emit(Op::LoadConst(idx), self.line);
606 }
607 ShellWord::SingleQuoted(s) => {
608 let idx = self.constant_str(s);
609 self.emit(Op::LoadConst(idx), self.line);
610 }
611 ShellWord::DoubleQuoted(parts) => {
612 if parts.is_empty() {
613 let idx = self.constant_str("");
614 self.emit(Op::LoadConst(idx), self.line);
615 } else {
616 for (i, p) in parts.iter().enumerate() {
617 self.compile_word(p);
618 if i > 0 {
619 self.emit(Op::Concat, self.line);
620 }
621 }
622 }
623 }
624 ShellWord::Variable(name) => {
625 let idx = self.name(name);
626 self.emit(Op::GetVar(idx), self.line);
627 }
628 ShellWord::VariableBraced(name, modifier) => {
629 let idx = self.name(name);
630 self.emit(Op::GetVar(idx), self.line);
631 if let Some(m) = modifier {
632 self.compile_var_modifier(idx, m);
633 }
634 }
635 ShellWord::ArithSub(expr) => {
636 let idx = self.constant_str(expr);
639 self.emit(Op::LoadConst(idx), self.line);
640 self.emit(Op::Extended(2, 0), self.line); }
642 ShellWord::CommandSub(cmd) => {
643 self.compile_command(cmd);
647 }
649 ShellWord::Glob(pattern) => {
650 let idx = self.constant_str(pattern);
651 self.emit(Op::LoadConst(idx), self.line);
652 self.emit(Op::Glob, self.line);
653 }
654 ShellWord::Tilde(user) => {
655 if let Some(u) = user {
656 let idx = self.constant_str(&format!("~{}", u));
657 self.emit(Op::LoadConst(idx), self.line);
658 } else {
659 let idx = self.constant_str("~");
660 self.emit(Op::LoadConst(idx), self.line);
661 }
662 self.emit(Op::TildeExpand, self.line);
663 }
664 ShellWord::Concat(parts) => {
665 for (i, p) in parts.iter().enumerate() {
666 self.compile_word(p);
667 if i > 0 {
668 self.emit(Op::Concat, self.line);
669 }
670 }
671 }
672 ShellWord::ArrayLiteral(elements) => {
673 for e in elements {
674 self.compile_word(e);
675 }
676 self.emit(Op::MakeArray(elements.len() as u16), self.line);
677 }
678 ShellWord::ArrayVar(name, index) => {
679 let idx = self.name(name);
680 self.compile_word(index);
681 self.emit(Op::ArrayGet(idx), self.line);
682 }
683 ShellWord::ProcessSubIn(cmd) => {
684 self.compile_command(cmd);
686 }
687 ShellWord::ProcessSubOut(cmd) => {
688 self.compile_command(cmd);
689 }
690 }
691 }
692
693 fn compile_var_modifier(&mut self, _var_idx: u16, modifier: &VarModifier) {
694 match modifier {
695 VarModifier::Default(word) => {
696 self.emit(Op::Dup, self.line);
698 self.emit(Op::StringLen, self.line);
699 self.emit(Op::LoadInt(0), self.line);
700 self.emit(Op::NumEq, self.line);
701 let skip = self.emit(Op::JumpIfFalse(0), self.line);
702 self.emit(Op::Pop, self.line); self.compile_word(word); self.builder.patch_jump(skip, self.pos());
705 }
706 VarModifier::Length => {
707 self.emit(Op::StringLen, self.line);
708 }
709 _ => {
710 }
713 }
714 }
715
716 fn compile_redirect(&mut self, redir: &Redirect) {
717 let fd = redir.fd.unwrap_or(match redir.op {
718 RedirectOp::Read | RedirectOp::HereDoc | RedirectOp::HereString => 0,
719 _ => 1,
720 }) as u8;
721
722 let op_byte = match redir.op {
723 RedirectOp::Write => fusevm::op::redirect_op::WRITE,
724 RedirectOp::Append => fusevm::op::redirect_op::APPEND,
725 RedirectOp::Read => fusevm::op::redirect_op::READ,
726 RedirectOp::ReadWrite => fusevm::op::redirect_op::READ_WRITE,
727 RedirectOp::Clobber => fusevm::op::redirect_op::CLOBBER,
728 RedirectOp::DupRead => fusevm::op::redirect_op::DUP_READ,
729 RedirectOp::DupWrite => fusevm::op::redirect_op::DUP_WRITE,
730 RedirectOp::WriteBoth => fusevm::op::redirect_op::WRITE_BOTH,
731 RedirectOp::AppendBoth => fusevm::op::redirect_op::APPEND_BOTH,
732 RedirectOp::HereDoc => {
733 if let Some(ref content) = redir.heredoc_content {
734 let idx = self.constant_str(content);
735 self.emit(Op::HereDoc(idx), self.line);
736 }
737 return;
738 }
739 RedirectOp::HereString => {
740 self.compile_word(&redir.target);
741 self.emit(Op::HereString, self.line);
742 return;
743 }
744 };
745
746 self.compile_word(&redir.target);
747 self.emit(Op::Redirect(fd, op_byte), self.line);
748 }
749}
750
751#[cfg(test)]
752mod tests {
753 use super::*;
754
755 #[test]
756 fn test_compile_echo() {
757 let cmd = ShellCommand::Simple(SimpleCommand {
758 assignments: vec![],
759 words: vec![
760 ShellWord::Literal("echo".to_string()),
761 ShellWord::Literal("hello".to_string()),
762 ],
763 redirects: vec![],
764 });
765 let chunk = compile_script(&[cmd], "test");
766 assert!(chunk.ops.len() >= 2);
768 assert!(matches!(chunk.ops.last(), Some(Op::PrintLn(1))));
769 }
770
771 #[test]
772 fn test_compile_assignment() {
773 let cmd = ShellCommand::Simple(SimpleCommand {
774 assignments: vec![("X".to_string(), ShellWord::Literal("42".to_string()), false)],
775 words: vec![],
776 redirects: vec![],
777 });
778 let chunk = compile_script(&[cmd], "test");
779 assert!(chunk.ops.iter().any(|op| matches!(op, Op::SetVar(_))));
780 }
781
782 #[test]
783 fn test_compile_for_loop() {
784 let cmd = ShellCommand::Compound(CompoundCommand::For {
785 var: "i".to_string(),
786 words: Some(vec![
787 ShellWord::Literal("a".to_string()),
788 ShellWord::Literal("b".to_string()),
789 ]),
790 body: vec![ShellCommand::Simple(SimpleCommand {
791 assignments: vec![],
792 words: vec![
793 ShellWord::Literal("echo".to_string()),
794 ShellWord::Variable("i".to_string()),
795 ],
796 redirects: vec![],
797 })],
798 });
799 let chunk = compile_script(&[cmd], "test");
800 assert!(chunk.ops.iter().any(|op| matches!(op, Op::Jump(_))));
802 assert!(chunk.ops.iter().any(|op| matches!(op, Op::JumpIfFalse(_))));
803 }
804
805 #[test]
806 fn test_compile_if() {
807 let cmd = ShellCommand::Compound(CompoundCommand::If {
808 conditions: vec![(
809 vec![ShellCommand::Simple(SimpleCommand {
810 assignments: vec![],
811 words: vec![ShellWord::Literal("true".to_string())],
812 redirects: vec![],
813 })],
814 vec![ShellCommand::Simple(SimpleCommand {
815 assignments: vec![],
816 words: vec![
817 ShellWord::Literal("echo".to_string()),
818 ShellWord::Literal("yes".to_string()),
819 ],
820 redirects: vec![],
821 })],
822 )],
823 else_part: None,
824 });
825 let chunk = compile_script(&[cmd], "test");
826 assert!(chunk.ops.iter().any(|op| matches!(op, Op::JumpIfTrue(_))));
827 }
828}