1use std::ffi::CString;
8use std::os::fd::RawFd;
9
10use nix::unistd::Pid;
11
12use frost_builtins::BuiltinRegistry;
13use frost_parser::ast::{
14 AlwaysClause, ArithForClause, BraceGroup, CaseClause, CaseTerminator, Command,
15 CompleteCommand, ForClause, FunctionDef, GlobKind, IfClause, List, ListOp, Pipeline, Program,
16 Redirect, RedirectOp, RepeatClause, SelectClause, SimpleCommand, Subshell, UntilClause,
17 WhileClause, Word, WordPart,
18};
19
20use crate::env::ShellEnv;
21use crate::job::JobTable;
22use crate::redirect;
23use crate::sys;
24
25#[derive(Debug, thiserror::Error)]
27pub enum ExecError {
28 #[error("command not found: {0}")]
29 CommandNotFound(String),
30
31 #[error("fork failed: {0}")]
32 Fork(nix::errno::Errno),
33
34 #[error("exec failed: {0}")]
35 Exec(nix::errno::Errno),
36
37 #[error("pipe failed: {0}")]
38 Pipe(nix::errno::Errno),
39
40 #[error("wait failed: {0}")]
41 Wait(nix::errno::Errno),
42
43 #[error("redirect error: {0}")]
44 Redirect(#[from] redirect::RedirectError),
45}
46
47pub type ExecResult = Result<i32, ExecError>;
49
50pub struct Executor<'env> {
55 pub env: &'env mut ShellEnv,
56 pub builtins: BuiltinRegistry,
57 pub jobs: JobTable,
58}
59
60impl<'env> Executor<'env> {
61 pub fn new(env: &'env mut ShellEnv) -> Self {
63 Self {
64 env,
65 builtins: frost_builtins::default_builtins(),
66 jobs: JobTable::new(),
67 }
68 }
69
70 pub fn execute_program(&mut self, program: &Program) -> ExecResult {
74 let mut status = 0;
75 for cmd in &program.commands {
76 status = self.execute_complete_command(cmd)?;
77 }
78 Ok(status)
79 }
80
81 fn execute_complete_command(&mut self, cmd: &CompleteCommand) -> ExecResult {
83 let status = self.execute_list(&cmd.list)?;
84
85 if cmd.is_async {
86 self.env.exit_status = 0;
87 Ok(0)
88 } else {
89 self.env.exit_status = status;
90 Ok(status)
91 }
92 }
93
94 fn execute_list(&mut self, list: &List) -> ExecResult {
96 let mut status = self.execute_pipeline(&list.first)?;
97
98 for (op, pipeline) in &list.rest {
99 match op {
100 ListOp::And if status == 0 => {
101 status = self.execute_pipeline(pipeline)?;
102 }
103 ListOp::Or if status != 0 => {
104 status = self.execute_pipeline(pipeline)?;
105 }
106 _ => {}
107 }
108 }
109
110 Ok(status)
111 }
112
113 pub fn execute_pipeline(&mut self, pipeline: &Pipeline) -> ExecResult {
117 let cmds = &pipeline.commands;
118
119 if cmds.len() == 1 {
120 let status = self.execute_command(&cmds[0])?;
121 return Ok(if pipeline.bang { invert(status) } else { status });
122 }
123
124 let mut pipes = Vec::with_capacity(cmds.len() - 1);
126 for _ in 0..cmds.len() - 1 {
127 let p = sys::pipe().map_err(ExecError::Pipe)?;
128 pipes.push((p.read, p.write));
129 }
130
131 let mut children: Vec<Pid> = Vec::with_capacity(cmds.len());
132
133 for (i, cmd) in cmds.iter().enumerate() {
134 match unsafe { sys::fork() }.map_err(ExecError::Fork)? {
135 sys::ForkOutcome::Child => {
136 if i > 0 {
138 let (rd, _) = pipes[i - 1];
139 sys::dup2(rd, 0).ok();
140 }
141 if i < cmds.len() - 1 {
143 let (_, wr) = pipes[i];
144 sys::dup2(wr, 1).ok();
145
146 if pipeline.pipe_stderr.get(i).copied().unwrap_or(false) {
147 sys::dup2(wr, 2).ok();
148 }
149 }
150 for &(rd, wr) in &pipes {
152 sys::close(rd).ok();
153 sys::close(wr).ok();
154 }
155 let status = self.execute_command(cmd).unwrap_or(127);
156 std::process::exit(status);
157 }
158 sys::ForkOutcome::Parent { child_pid } => {
159 children.push(child_pid);
160 }
161 }
162 }
163
164 for (rd, wr) in pipes {
166 sys::close(rd).ok();
167 sys::close(wr).ok();
168 }
169
170 let mut last_status = 0;
172 for pid in children {
173 match sys::wait_pid(pid).map_err(ExecError::Wait)? {
174 sys::ChildStatus::Exited(code) => last_status = code,
175 sys::ChildStatus::Signaled(code) => last_status = code,
176 _ => {}
177 }
178 }
179
180 Ok(if pipeline.bang {
181 invert(last_status)
182 } else {
183 last_status
184 })
185 }
186
187 pub fn execute_command(&mut self, cmd: &Command) -> ExecResult {
191 match cmd {
192 Command::Simple(simple) => self.execute_simple(simple),
193 Command::Subshell(sub) => self.execute_subshell(sub),
194 Command::BraceGroup(bg) => self.execute_brace_group(bg),
195 Command::If(clause) => self.execute_if(clause),
196 Command::For(clause) => self.execute_for(clause),
197 Command::ArithFor(clause) => self.execute_arith_for(clause),
198 Command::While(clause) => self.execute_while(clause),
199 Command::Until(clause) => self.execute_until(clause),
200 Command::Case(clause) => self.execute_case(clause),
201 Command::Select(clause) => self.execute_select(clause),
202 Command::Repeat(clause) => self.execute_repeat(clause),
203 Command::Always(clause) => self.execute_always(clause),
204 Command::FunctionDef(fdef) => self.execute_function_def(fdef),
205 Command::Coproc(_) => {
206 eprintln!("frost: coproc: not yet implemented");
208 Ok(1)
209 }
210 Command::Time(tc) => {
211 let start = std::time::Instant::now();
212 let status = self.execute_pipeline(&tc.pipeline)?;
213 let elapsed = start.elapsed();
214 eprintln!(
215 "\nreal\t{:.3}s\nuser\t0.000s\nsys\t0.000s",
216 elapsed.as_secs_f64()
217 );
218 Ok(status)
219 }
220 }
221 }
222
223 pub fn execute_simple(&mut self, cmd: &SimpleCommand) -> ExecResult {
227 for assign in &cmd.assignments {
229 let value = assign
230 .value
231 .as_ref()
232 .map(|w| self.expand_word(w))
233 .unwrap_or_default();
234 self.env.set_var(&assign.name, &value);
235 }
236
237 if cmd.words.is_empty() {
238 return Ok(0);
239 }
240
241 let argv: Vec<String> = cmd
242 .words
243 .iter()
244 .flat_map(|w| self.expand_word_glob(w))
245 .filter(|s| !s.is_empty())
246 .collect();
247
248 if argv.is_empty() {
250 self.env.exit_status = 0;
251 return Ok(0);
252 }
253
254 let name = &argv[0];
255
256 if let Some(func) = self.env.functions.get(name).cloned() {
258 return self.call_function(&func, &argv[1..], &cmd.redirects);
259 }
260
261 match name.as_str() {
263 "eval" => return self.execute_eval(&argv[1..], &cmd.redirects),
264 "source" | "." => return self.execute_source(&argv[1..], &cmd.redirects),
265 "shift" => return self.execute_shift(&argv[1..]),
266 "[[" => return self.execute_cond_bracket(&argv[1..]),
267 "((" => return self.execute_arith_command(&argv[1..]),
268 _ => {}
269 }
270
271 if self.builtins.contains(name) {
273 return self.execute_builtin(name, &argv[1..], &cmd.redirects);
274 }
275
276 self.fork_exec(&argv, &cmd.redirects)
278 }
279
280 fn execute_builtin(
282 &mut self,
283 name: &str,
284 args: &[String],
285 redirects: &[Redirect],
286 ) -> ExecResult {
287 let saved_fds = self.save_and_apply_redirects(redirects)?;
288
289 let arg_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
290 let status = self
291 .builtins
292 .get(name)
293 .unwrap()
294 .execute(&arg_refs, self.env);
295 self.env.exit_status = status;
296
297 self.restore_fds(saved_fds);
298 Ok(status)
299 }
300
301 fn execute_eval(&mut self, args: &[String], redirects: &[Redirect]) -> ExecResult {
303 let saved_fds = self.save_and_apply_redirects(redirects)?;
304 let code = args.join(" ");
305 let status = self.eval_string(&code);
306 self.restore_fds(saved_fds);
307 Ok(status)
308 }
309
310 fn execute_source(&mut self, args: &[String], redirects: &[Redirect]) -> ExecResult {
312 if args.is_empty() {
313 eprintln!("frost: source: filename argument required");
314 return Ok(1);
315 }
316 let saved_fds = self.save_and_apply_redirects(redirects)?;
317
318 let path = &args[0];
319 let code = match std::fs::read_to_string(path) {
320 Ok(c) => c,
321 Err(e) => {
322 eprintln!("frost: {path}: {e}");
323 self.restore_fds(saved_fds);
324 return Ok(1);
325 }
326 };
327
328 let saved_params = if args.len() > 1 {
330 Some(std::mem::replace(
331 &mut self.env.positional_params,
332 args[1..].to_vec(),
333 ))
334 } else {
335 None
336 };
337
338 let status = self.eval_string(&code);
339
340 if let Some(params) = saved_params {
341 self.env.positional_params = params;
342 }
343
344 self.restore_fds(saved_fds);
345 Ok(status)
346 }
347
348 fn execute_shift(&mut self, args: &[String]) -> ExecResult {
350 let n = args
351 .first()
352 .and_then(|s| s.parse::<usize>().ok())
353 .unwrap_or(1);
354 if n <= self.env.positional_params.len() {
355 self.env.positional_params.drain(..n);
356 Ok(0)
357 } else {
358 eprintln!("shift: shift count must be <= $#");
359 Ok(1)
360 }
361 }
362
363 fn eval_string(&mut self, code: &str) -> i32 {
365 let tokens = tokenize(code);
366 let mut parser = frost_parser::Parser::new(&tokens);
367 let program = parser.parse();
368 self.execute_program(&program).unwrap_or(1)
369 }
370
371 fn call_function(
373 &mut self,
374 func: &FunctionDef,
375 args: &[String],
376 redirects: &[Redirect],
377 ) -> ExecResult {
378 let saved_fds = self.save_and_apply_redirects(redirects)?;
379
380 let saved_params = std::mem::replace(
382 &mut self.env.positional_params,
383 args.iter().map(|s| s.to_string()).collect(),
384 );
385
386 let status = self.execute_command(&func.body)?;
387
388 self.env.positional_params = saved_params;
390 self.restore_fds(saved_fds);
391 self.env.exit_status = status;
392 Ok(status)
393 }
394
395 fn execute_arith_command(&mut self, args: &[String]) -> ExecResult {
399 let expr: String = args
401 .iter()
402 .filter(|s| s.as_str() != "))")
403 .cloned()
404 .collect::<Vec<_>>()
405 .join(" ");
406
407 let result = self.eval_arith_with_assignment(expr.trim());
409 let status = if result == 0 { 1 } else { 0 }; self.env.exit_status = status;
411 Ok(status)
412 }
413
414 fn eval_arith_with_assignment(&mut self, expr: &str) -> i64 {
418 let expr = expr.trim();
419
420 if expr.ends_with("++") && !expr.ends_with("+++") {
422 let var = expr[..expr.len() - 2].trim();
423 if is_valid_var_name(var) {
424 let val = self.env.get_var(var).and_then(|v| v.parse::<i64>().ok()).unwrap_or(0);
425 self.env.set_var(var, &(val + 1).to_string());
426 return val; }
428 }
429 if expr.ends_with("--") && !expr.ends_with("---") {
430 let var = expr[..expr.len() - 2].trim();
431 if is_valid_var_name(var) {
432 let val = self.env.get_var(var).and_then(|v| v.parse::<i64>().ok()).unwrap_or(0);
433 self.env.set_var(var, &(val - 1).to_string());
434 return val;
435 }
436 }
437
438 if expr.starts_with("++ ") || (expr.starts_with("++") && expr.len() > 2 && expr.as_bytes()[2].is_ascii_alphabetic()) {
440 let var = expr.trim_start_matches("++").trim_start_matches(' ');
441 if is_valid_var_name(var) {
442 let val = self.env.get_var(var).and_then(|v| v.parse::<i64>().ok()).unwrap_or(0);
443 let new_val = val + 1;
444 self.env.set_var(var, &new_val.to_string());
445 return new_val;
446 }
447 }
448 if expr.starts_with("-- ") || (expr.starts_with("--") && expr.len() > 2 && expr.as_bytes()[2].is_ascii_alphabetic()) {
449 let var = expr.trim_start_matches("--").trim_start_matches(' ');
450 if is_valid_var_name(var) {
451 let val = self.env.get_var(var).and_then(|v| v.parse::<i64>().ok()).unwrap_or(0);
452 let new_val = val - 1;
453 self.env.set_var(var, &new_val.to_string());
454 return new_val;
455 }
456 }
457
458 for op in &["+=", "-=", "*=", "/=", "%="] {
460 if let Some(eq_pos) = expr.find(op) {
461 let var = expr[..eq_pos].trim();
462 if is_valid_var_name(var) {
463 let rhs = expr[eq_pos + op.len()..].trim();
464 let rhs_expanded = self.expand_arith_vars(rhs);
465 let var_val = self.env.get_var(var).and_then(|v| v.parse::<i64>().ok()).unwrap_or(0);
466 let rhs_val = eval_arith_expr(&rhs_expanded).unwrap_or(0);
467 let new_val = match *op {
468 "+=" => var_val + rhs_val,
469 "-=" => var_val - rhs_val,
470 "*=" => var_val * rhs_val,
471 "/=" if rhs_val != 0 => var_val / rhs_val,
472 "%=" if rhs_val != 0 => var_val % rhs_val,
473 _ => var_val,
474 };
475 self.env.set_var(var, &new_val.to_string());
476 return new_val;
477 }
478 }
479 }
480
481 if let Some(eq_pos) = find_assignment_eq(expr) {
484 let var = expr[..eq_pos].trim();
485 if is_valid_var_name(var) {
486 let rhs = expr[eq_pos + 1..].trim();
487 let rhs_expanded = self.expand_arith_vars(rhs);
488 let val = eval_arith_expr(&rhs_expanded).unwrap_or(0);
489 self.env.set_var(var, &val.to_string());
490 return val;
491 }
492 }
493
494 let expanded = self.expand_arith_vars(expr);
496 eval_arith_expr(&expanded).unwrap_or(0)
497 }
498
499 fn execute_cond_bracket(&mut self, args: &[String]) -> ExecResult {
503 let args: Vec<&str> = args
505 .iter()
506 .map(|s| s.as_str())
507 .filter(|s| *s != "]]")
508 .collect();
509
510 let result = eval_cond_expr(&args, self);
511 let status = if result { 0 } else { 1 };
512 self.env.exit_status = status;
513 Ok(status)
514 }
515
516 fn execute_subshell(&mut self, sub: &Subshell) -> ExecResult {
519 let saved_fds = self.save_and_apply_redirects(&sub.redirects)?;
520
521 match unsafe { sys::fork() }.map_err(ExecError::Fork)? {
522 sys::ForkOutcome::Child => {
523 let mut status = 0;
524 for cmd in &sub.body {
525 status = self.execute_complete_command(cmd).unwrap_or(1);
526 }
527 std::process::exit(status);
528 }
529 sys::ForkOutcome::Parent { child_pid } => {
530 let status = match sys::wait_pid(child_pid).map_err(ExecError::Wait)? {
531 sys::ChildStatus::Exited(code) => code,
532 sys::ChildStatus::Signaled(code) => code,
533 _ => 0,
534 };
535 self.restore_fds(saved_fds);
536 self.env.exit_status = status;
537 Ok(status)
538 }
539 }
540 }
541
542 fn execute_brace_group(&mut self, bg: &BraceGroup) -> ExecResult {
543 let saved_fds = self.save_and_apply_redirects(&bg.redirects)?;
544
545 let mut status = 0;
546 for cmd in &bg.body {
547 status = self.execute_complete_command(cmd)?;
548 }
549
550 self.restore_fds(saved_fds);
551 self.env.exit_status = status;
552 Ok(status)
553 }
554
555 fn execute_if(&mut self, clause: &IfClause) -> ExecResult {
556 let saved_fds = self.save_and_apply_redirects(&clause.redirects)?;
557
558 let mut cond_status = 0;
560 for cmd in &clause.condition {
561 cond_status = self.execute_complete_command(cmd)?;
562 }
563
564 let status = if cond_status == 0 {
565 let mut s = 0;
567 for cmd in &clause.then_body {
568 s = self.execute_complete_command(cmd)?;
569 }
570 s
571 } else {
572 let mut found = false;
574 let mut s = 0;
575 for (elif_cond, elif_body) in &clause.elifs {
576 let mut elif_status = 0;
577 for cmd in elif_cond {
578 elif_status = self.execute_complete_command(cmd)?;
579 }
580 if elif_status == 0 {
581 for cmd in elif_body {
582 s = self.execute_complete_command(cmd)?;
583 }
584 found = true;
585 break;
586 }
587 }
588 if !found {
589 if let Some(else_body) = &clause.else_body {
590 for cmd in else_body {
591 s = self.execute_complete_command(cmd)?;
592 }
593 }
594 }
595 s
596 };
597
598 self.restore_fds(saved_fds);
599 self.env.exit_status = status;
600 Ok(status)
601 }
602
603 fn execute_for(&mut self, clause: &ForClause) -> ExecResult {
604 let saved_fds = self.save_and_apply_redirects(&clause.redirects)?;
605
606 let words = if let Some(word_list) = &clause.words {
607 word_list.iter().map(|w| self.expand_word(w)).collect()
608 } else {
609 self.env.positional_params.clone()
610 };
611
612 let mut status = 0;
613 for word in &words {
614 self.env.set_var(&clause.var, word);
615 for cmd in &clause.body {
616 status = self.execute_complete_command(cmd)?;
617 }
618 }
619
620 self.restore_fds(saved_fds);
621 self.env.exit_status = status;
622 Ok(status)
623 }
624
625 fn execute_arith_for(&mut self, clause: &ArithForClause) -> ExecResult {
626 let saved_fds = self.save_and_apply_redirects(&clause.redirects)?;
627
628 if !clause.init.is_empty() {
630 self.eval_arith_with_assignment(&clause.init);
631 }
632
633 let mut status = 0;
634 loop {
635 if !clause.condition.is_empty() {
637 let cond_val = self.eval_arith_with_assignment(&clause.condition);
638 if cond_val == 0 {
639 break;
640 }
641 }
642
643 for cmd in &clause.body {
645 status = self.execute_complete_command(cmd)?;
646 }
647
648 if !clause.step.is_empty() {
650 self.eval_arith_with_assignment(&clause.step);
651 }
652 }
653
654 self.restore_fds(saved_fds);
655 self.env.exit_status = status;
656 Ok(status)
657 }
658
659 fn execute_while(&mut self, clause: &WhileClause) -> ExecResult {
660 let saved_fds = self.save_and_apply_redirects(&clause.redirects)?;
661
662 let mut status = 0;
663 loop {
664 let mut cond_status = 0;
665 for cmd in &clause.condition {
666 cond_status = self.execute_complete_command(cmd)?;
667 }
668 if cond_status != 0 {
669 break;
670 }
671 for cmd in &clause.body {
672 status = self.execute_complete_command(cmd)?;
673 }
674 }
675
676 self.restore_fds(saved_fds);
677 self.env.exit_status = status;
678 Ok(status)
679 }
680
681 fn execute_until(&mut self, clause: &UntilClause) -> ExecResult {
682 let saved_fds = self.save_and_apply_redirects(&clause.redirects)?;
683
684 let mut status = 0;
685 loop {
686 let mut cond_status = 0;
687 for cmd in &clause.condition {
688 cond_status = self.execute_complete_command(cmd)?;
689 }
690 if cond_status == 0 {
691 break;
692 }
693 for cmd in &clause.body {
694 status = self.execute_complete_command(cmd)?;
695 }
696 }
697
698 self.restore_fds(saved_fds);
699 self.env.exit_status = status;
700 Ok(status)
701 }
702
703 fn execute_repeat(&mut self, clause: &RepeatClause) -> ExecResult {
704 let saved_fds = self.save_and_apply_redirects(&clause.redirects)?;
705
706 let count_str = self.expand_word(&clause.count);
707 let count: i64 = count_str.trim().parse().unwrap_or(0);
708
709 let mut status = 0;
710 for _ in 0..count {
711 for cmd in &clause.body {
712 status = self.execute_complete_command(cmd)?;
713 }
714 }
715
716 self.restore_fds(saved_fds);
717 self.env.exit_status = status;
718 Ok(status)
719 }
720
721 fn execute_always(&mut self, clause: &AlwaysClause) -> ExecResult {
722 let saved_fds = self.save_and_apply_redirects(&clause.redirects)?;
723
724 let try_result = (|| {
726 let mut status = 0;
727 for cmd in &clause.try_body {
728 status = self.execute_complete_command(cmd)?;
729 }
730 Ok(status)
731 })();
732
733 let mut always_status = 0;
735 for cmd in &clause.always_body {
736 always_status = self.execute_complete_command(cmd)?;
737 }
738
739 self.restore_fds(saved_fds);
740
741 match try_result {
744 Ok(_try_status) => {
745 self.env.exit_status = always_status;
746 Ok(always_status)
747 }
748 Err(e) => {
749 self.env.exit_status = always_status;
751 Err(e)
752 }
753 }
754 }
755
756 fn execute_case(&mut self, clause: &CaseClause) -> ExecResult {
757 let saved_fds = self.save_and_apply_redirects(&clause.redirects)?;
758
759 let word = self.expand_word(&clause.word);
760 let mut status = 0;
761
762 let mut i = 0;
763 while i < clause.items.len() {
764 let item = &clause.items[i];
765 let matched = item
766 .patterns
767 .iter()
768 .any(|p| glob_match_word(&self.expand_word(p), &word));
769
770 if matched {
771 for cmd in &item.body {
772 status = self.execute_complete_command(cmd)?;
773 }
774 match item.terminator {
775 CaseTerminator::DoubleSemi => break,
776 CaseTerminator::SemiAnd => {
777 i += 1;
779 if i < clause.items.len() {
780 for cmd in &clause.items[i].body {
781 status = self.execute_complete_command(cmd)?;
782 }
783 }
784 break;
785 }
786 CaseTerminator::SemiPipe => {
787 i += 1;
789 continue;
790 }
791 }
792 }
793 i += 1;
794 }
795
796 self.restore_fds(saved_fds);
797 self.env.exit_status = status;
798 Ok(status)
799 }
800
801 fn execute_select(&mut self, clause: &SelectClause) -> ExecResult {
802 let saved_fds = self.save_and_apply_redirects(&clause.redirects)?;
803
804 let words: Vec<String> = if let Some(word_list) = &clause.words {
805 word_list.iter().map(|w| self.expand_word(w)).collect()
806 } else {
807 self.env.positional_params.clone()
808 };
809
810 let mut status = 0;
812 for (i, word) in words.iter().enumerate() {
813 eprintln!("{}) {word}", i + 1);
814 }
815 self.env.set_var(&clause.var, "");
817 for cmd in &clause.body {
818 status = self.execute_complete_command(cmd)?;
819 }
820
821 self.restore_fds(saved_fds);
822 self.env.exit_status = status;
823 Ok(status)
824 }
825
826 fn execute_function_def(&mut self, fdef: &FunctionDef) -> ExecResult {
827 self.env.functions.insert(fdef.name.to_string(), fdef.clone());
828 Ok(0)
829 }
830
831 pub fn expand_word(&mut self, word: &Word) -> String {
835 let mut out = String::new();
836 for part in &word.parts {
837 self.expand_word_part(part, &mut out);
838 }
839 out
840 }
841
842 fn expand_word_glob(&mut self, word: &Word) -> Vec<String> {
845 let has_glob = word.parts.iter().any(|p| matches!(p, WordPart::Glob(_)));
846 let expanded = self.expand_word(word);
847 if has_glob {
848 let mut matches: Vec<String> = glob::glob(&expanded)
849 .ok()
850 .map(|paths| {
851 paths
852 .filter_map(|p| p.ok())
853 .map(|p| p.to_string_lossy().into_owned())
854 .collect()
855 })
856 .unwrap_or_default();
857 if matches.is_empty() {
858 vec![expanded]
860 } else {
861 matches.sort();
862 matches
863 }
864 } else {
865 vec![expanded]
866 }
867 }
868
869 fn expand_word_part(&mut self, part: &WordPart, out: &mut String) {
870 match part {
871 WordPart::Literal(s) => out.push_str(s),
872 WordPart::SingleQuoted(s) => out.push_str(s),
873 WordPart::DoubleQuoted(parts) => {
874 for inner in parts {
875 self.expand_word_part(inner, out);
876 }
877 }
878 WordPart::DollarVar(name) => {
879 out.push_str(&self.expand_special_var(name));
880 }
881 WordPart::DollarBrace {
882 param,
883 operator,
884 arg,
885 } => {
886 let base = self.expand_special_var(param);
887 if let Some(op) = operator {
888 let arg_val = arg
889 .as_ref()
890 .map(|w| self.expand_word(w))
891 .unwrap_or_default();
892 out.push_str(&apply_param_op(&base, op, &arg_val));
893 } else {
894 out.push_str(&base);
895 }
896 }
897 WordPart::CommandSub(program) => {
898 out.push_str(&self.execute_command_sub(program));
899 }
900 WordPart::ArithSub(expr) => {
901 out.push_str(&self.eval_arith(expr));
903 }
904 WordPart::Tilde(user) => {
905 if user.is_empty() {
906 if let Some(home) = self.env.get_var("HOME") {
907 out.push_str(home);
908 } else {
909 out.push('~');
910 }
911 } else {
912 out.push('~');
913 out.push_str(user);
914 }
915 }
916 WordPart::Glob(_) => {
917 match part {
920 WordPart::Glob(frost_parser::ast::GlobKind::Star) => out.push('*'),
921 WordPart::Glob(frost_parser::ast::GlobKind::Question) => out.push('?'),
922 WordPart::Glob(frost_parser::ast::GlobKind::At) => out.push('@'),
923 _ => {}
924 }
925 }
926 }
927 }
928
929 fn expand_special_var(&self, name: &str) -> String {
931 match name {
932 "?" => self.env.exit_status.to_string(),
933 "$" => self.env.pid.to_string(),
934 "#" => self.env.positional_params.len().to_string(),
935 "@" | "*" => self.env.positional_params.join(" "),
936 "0" => "frost".to_string(),
937 "LINENO" => "0".to_string(),
938 _ => {
939 if let Ok(n) = name.parse::<usize>() {
941 if n >= 1 {
942 return self
943 .env
944 .positional_params
945 .get(n - 1)
946 .cloned()
947 .unwrap_or_default();
948 }
949 }
950 self.env.get_var(name).unwrap_or("").to_string()
951 }
952 }
953 }
954
955 fn execute_command_sub(&mut self, program: &Program) -> String {
957 let code = reconstruct_program(program);
964 if code.is_empty() {
965 return String::new();
966 }
967
968 let frost_path = std::env::current_exe().unwrap_or_else(|_| "frost".into());
969 let mut cmd = std::process::Command::new(&frost_path);
970 cmd.arg("-c").arg(&code);
971 cmd.stdout(std::process::Stdio::piped());
972 cmd.stderr(std::process::Stdio::piped());
973 cmd.stdin(std::process::Stdio::null());
974
975 cmd.env_clear();
977 for (name, var) in &self.env.variables {
978 if var.export {
979 cmd.env(name, &var.value);
980 }
981 }
982
983 match cmd.output() {
984 Ok(output) => {
985 let mut stdout = String::from_utf8_lossy(&output.stdout).into_owned();
986 while stdout.ends_with('\n') {
988 stdout.pop();
989 }
990 stdout
991 }
992 Err(_) => String::new(),
993 }
994 }
995
996 fn eval_arith(&self, expr: &str) -> String {
998 let expanded = self.expand_arith_vars(expr);
1000 match eval_arith_expr(&expanded) {
1002 Some(val) => val.to_string(),
1003 None => "0".to_string(),
1004 }
1005 }
1006
1007 fn expand_arith_vars(&self, expr: &str) -> String {
1009 let mut out = String::new();
1010 let chars: Vec<char> = expr.chars().collect();
1011 let mut i = 0;
1012 while i < chars.len() {
1013 if chars[i] == '$' {
1014 i += 1;
1015 let start = i;
1016 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
1017 i += 1;
1018 }
1019 let name: String = chars[start..i].iter().collect();
1020 out.push_str(&self.expand_special_var(&name));
1021 } else if chars[i].is_alphabetic() || chars[i] == '_' {
1022 let start = i;
1023 while i < chars.len() && (chars[i].is_alphanumeric() || chars[i] == '_') {
1024 i += 1;
1025 }
1026 let name: String = chars[start..i].iter().collect();
1027 if let Some(val) = self.env.get_var(&name) {
1029 out.push_str(val);
1030 } else {
1031 out.push('0');
1032 }
1033 } else {
1034 out.push(chars[i]);
1035 i += 1;
1036 }
1037 }
1038 out
1039 }
1040
1041 fn save_and_apply_redirects(
1045 &mut self,
1046 redirects: &[Redirect],
1047 ) -> Result<Vec<(RawFd, RawFd)>, ExecError> {
1048 if redirects.is_empty() {
1049 return Ok(Vec::new());
1050 }
1051
1052 let expanded_targets: Vec<String> = redirects
1054 .iter()
1055 .map(|r| self.expand_word(&r.target))
1056 .collect();
1057
1058 let mut saved = Vec::new();
1059 for redir in redirects {
1060 let target_fd = redirect::target_fd_for(redir);
1061 if let Ok(saved_fd) = nix::unistd::dup(target_fd) {
1063 use std::os::fd::IntoRawFd;
1064 saved.push((target_fd, saved_fd.into_raw_fd()));
1065 }
1066 }
1067 redirect::apply_redirects_expanded(redirects, &expanded_targets)?;
1068 Ok(saved)
1069 }
1070
1071 fn restore_fds(&self, saved: Vec<(RawFd, RawFd)>) {
1073 for (target_fd, saved_fd) in saved {
1074 sys::dup2(saved_fd, target_fd).ok();
1075 sys::close(saved_fd).ok();
1076 }
1077 }
1078
1079 fn fork_exec(
1081 &mut self,
1082 argv: &[String],
1083 redirects: &[Redirect],
1084 ) -> ExecResult {
1085 let c_argv: Vec<CString> = argv
1086 .iter()
1087 .filter_map(|a| CString::new(a.as_bytes()).ok())
1088 .collect();
1089
1090 let c_envp = self.env.to_env_vec();
1091
1092 let expanded_targets: Vec<String> = redirects
1094 .iter()
1095 .map(|r| self.expand_word(&r.target))
1096 .collect();
1097
1098 match unsafe { sys::fork() }.map_err(ExecError::Fork)? {
1099 sys::ForkOutcome::Child => {
1100 if let Err(e) = redirect::apply_redirects_expanded(redirects, &expanded_targets) {
1101 eprintln!("frost: {e}");
1102 std::process::exit(1);
1103 }
1104
1105 let err = sys::exec(&c_argv, &c_envp);
1106 eprintln!("frost: {}: {err}", argv[0]);
1107 std::process::exit(if err == nix::errno::Errno::ENOENT {
1108 127
1109 } else {
1110 126
1111 });
1112 }
1113 sys::ForkOutcome::Parent { child_pid } => {
1114 match sys::wait_pid(child_pid).map_err(ExecError::Wait)? {
1115 sys::ChildStatus::Exited(code) => {
1116 self.env.exit_status = code;
1117 Ok(code)
1118 }
1119 sys::ChildStatus::Signaled(code) => {
1120 self.env.exit_status = code;
1121 Ok(code)
1122 }
1123 _ => Ok(0),
1124 }
1125 }
1126 }
1127 }
1128}
1129
1130fn invert(status: i32) -> i32 {
1134 if status == 0 { 1 } else { 0 }
1135}
1136
1137fn is_valid_var_name(s: &str) -> bool {
1139 !s.is_empty()
1140 && (s.as_bytes()[0].is_ascii_alphabetic() || s.as_bytes()[0] == b'_')
1141 && s.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_')
1142}
1143
1144fn find_assignment_eq(expr: &str) -> Option<usize> {
1147 let bytes = expr.as_bytes();
1148 for i in 0..bytes.len() {
1149 if bytes[i] == b'=' {
1150 if i + 1 < bytes.len() && bytes[i + 1] == b'=' {
1152 continue;
1153 }
1154 if i > 0
1156 && matches!(
1157 bytes[i - 1],
1158 b'!' | b'<' | b'>' | b'+' | b'-' | b'*' | b'/' | b'%'
1159 )
1160 {
1161 continue;
1162 }
1163 return Some(i);
1164 }
1165 }
1166 None
1167}
1168
1169fn eval_cond_expr(args: &[&str], exec: &Executor<'_>) -> bool {
1176 if args.is_empty() {
1177 return false;
1178 }
1179
1180 let mut pos = 0;
1182 eval_cond_or(args, &mut pos, exec)
1183}
1184
1185fn eval_cond_or(args: &[&str], pos: &mut usize, exec: &Executor<'_>) -> bool {
1186 let mut result = eval_cond_and(args, pos, exec);
1187 while *pos < args.len() && args[*pos] == "||" {
1188 *pos += 1;
1189 let right = eval_cond_and(args, pos, exec);
1190 result = result || right;
1191 }
1192 result
1193}
1194
1195fn eval_cond_and(args: &[&str], pos: &mut usize, exec: &Executor<'_>) -> bool {
1196 let mut result = eval_cond_not(args, pos, exec);
1197 while *pos < args.len() && args[*pos] == "&&" {
1198 *pos += 1;
1199 let right = eval_cond_not(args, pos, exec);
1200 result = result && right;
1201 }
1202 result
1203}
1204
1205fn eval_cond_not(args: &[&str], pos: &mut usize, exec: &Executor<'_>) -> bool {
1206 if *pos < args.len() && args[*pos] == "!" {
1207 *pos += 1;
1208 return !eval_cond_primary(args, pos, exec);
1209 }
1210 eval_cond_primary(args, pos, exec)
1211}
1212
1213fn eval_cond_primary(args: &[&str], pos: &mut usize, exec: &Executor<'_>) -> bool {
1214 if *pos >= args.len() {
1215 return false;
1216 }
1217
1218 if args[*pos] == "(" {
1220 *pos += 1;
1221 let result = eval_cond_or(args, pos, exec);
1222 if *pos < args.len() && args[*pos] == ")" {
1223 *pos += 1;
1224 }
1225 return result;
1226 }
1227
1228 if args[*pos].starts_with('-') && args[*pos].len() == 2 && *pos + 1 < args.len() {
1230 let op = args[*pos];
1231 if *pos + 2 < args.len() {
1233 let next_next = args[*pos + 2];
1234 if is_binary_cond_op(args[*pos + 1]) {
1236 } else {
1238 return eval_unary_cond(op, args, pos);
1239 }
1240 } else {
1241 return eval_unary_cond(op, args, pos);
1242 }
1243 }
1244
1245 if *pos + 1 >= args.len() || !is_binary_cond_op_or_end(args.get(*pos + 1).copied()) {
1247 let val = args[*pos];
1248 *pos += 1;
1249 return !val.is_empty();
1250 }
1251
1252 let left = args[*pos];
1254 *pos += 1;
1255 let op = args[*pos];
1256 *pos += 1;
1257 let right = if *pos < args.len() {
1258 let r = args[*pos];
1259 *pos += 1;
1260 r
1261 } else {
1262 ""
1263 };
1264
1265 match op {
1266 "=" | "==" => left == right,
1267 "!=" => left != right,
1268 "-eq" => left.parse::<i64>().unwrap_or(0) == right.parse::<i64>().unwrap_or(0),
1269 "-ne" => left.parse::<i64>().unwrap_or(0) != right.parse::<i64>().unwrap_or(0),
1270 "-lt" => left.parse::<i64>().unwrap_or(0) < right.parse::<i64>().unwrap_or(0),
1271 "-le" => left.parse::<i64>().unwrap_or(0) <= right.parse::<i64>().unwrap_or(0),
1272 "-gt" => left.parse::<i64>().unwrap_or(0) > right.parse::<i64>().unwrap_or(0),
1273 "-ge" => left.parse::<i64>().unwrap_or(0) >= right.parse::<i64>().unwrap_or(0),
1274 "<" => left < right,
1275 ">" => left > right,
1276 "=~" => {
1277 glob_match_word(right, left)
1279 }
1280 _ => false,
1281 }
1282}
1283
1284fn eval_unary_cond(op: &str, args: &[&str], pos: &mut usize) -> bool {
1285 *pos += 1; let arg = if *pos < args.len() {
1287 let a = args[*pos];
1288 *pos += 1;
1289 a
1290 } else {
1291 return false;
1292 };
1293
1294 match op {
1295 "-n" => !arg.is_empty(),
1296 "-z" => arg.is_empty(),
1297 "-f" => std::path::Path::new(arg).is_file(),
1298 "-d" => std::path::Path::new(arg).is_dir(),
1299 "-e" => std::path::Path::new(arg).exists(),
1300 "-s" => std::fs::metadata(arg).map(|m| m.len() > 0).unwrap_or(false),
1301 "-r" | "-w" | "-x" => std::path::Path::new(arg).exists(), "-L" | "-h" => std::fs::symlink_metadata(arg)
1303 .map(|m| m.file_type().is_symlink())
1304 .unwrap_or(false),
1305 "-b" | "-c" | "-p" | "-S" => false, "-t" => false, "-o" => false, "-v" => false, _ => false,
1310 }
1311}
1312
1313fn is_binary_cond_op(s: &str) -> bool {
1314 matches!(
1315 s,
1316 "=" | "==" | "!=" | "-eq" | "-ne" | "-lt" | "-le" | "-gt" | "-ge" | "<" | ">" | "=~"
1317 )
1318}
1319
1320fn is_binary_cond_op_or_end(s: Option<&str>) -> bool {
1321 match s {
1322 Some(s) => is_binary_cond_op(s) || s == "&&" || s == "||" || s == ")" || s == "]]",
1323 None => true,
1324 }
1325}
1326
1327fn apply_param_op(value: &str, op: &str, arg: &str) -> String {
1329 match op {
1330 ":-" => {
1331 if value.is_empty() {
1332 arg.to_string()
1333 } else {
1334 value.to_string()
1335 }
1336 }
1337 "-" => {
1338 if value.is_empty() {
1341 arg.to_string()
1342 } else {
1343 value.to_string()
1344 }
1345 }
1346 ":+" => {
1347 if value.is_empty() {
1348 String::new()
1349 } else {
1350 arg.to_string()
1351 }
1352 }
1353 "+" => {
1354 if value.is_empty() {
1355 String::new()
1356 } else {
1357 arg.to_string()
1358 }
1359 }
1360 "#" => {
1361 for i in 0..=value.len() {
1363 if glob_match_word(arg, &value[..i]) {
1364 return value[i..].to_string();
1365 }
1366 }
1367 value.to_string()
1368 }
1369 "##" => {
1370 for i in (0..=value.len()).rev() {
1372 if glob_match_word(arg, &value[..i]) {
1373 return value[i..].to_string();
1374 }
1375 }
1376 value.to_string()
1377 }
1378 "%" => {
1379 for i in (0..=value.len()).rev() {
1381 if glob_match_word(arg, &value[i..]) {
1382 return value[..i].to_string();
1383 }
1384 }
1385 value.to_string()
1386 }
1387 "%%" => {
1388 for i in 0..=value.len() {
1390 if glob_match_word(arg, &value[i..]) {
1391 return value[..i].to_string();
1392 }
1393 }
1394 value.to_string()
1395 }
1396 "length" => {
1397 value.len().to_string()
1399 }
1400 "/" => {
1401 if let Some(slash_pos) = arg.find('/') {
1403 let pattern = &arg[..slash_pos];
1404 let replacement = &arg[slash_pos + 1..];
1405 if let Some(pos) = find_glob_match(value, pattern) {
1406 let match_len = glob_match_length(value, pos, pattern);
1407 let mut result = String::new();
1408 result.push_str(&value[..pos]);
1409 result.push_str(replacement);
1410 result.push_str(&value[pos + match_len..]);
1411 result
1412 } else {
1413 value.to_string()
1414 }
1415 } else {
1416 if let Some(pos) = find_glob_match(value, arg) {
1418 let match_len = glob_match_length(value, pos, arg);
1419 let mut result = String::new();
1420 result.push_str(&value[..pos]);
1421 result.push_str(&value[pos + match_len..]);
1422 result
1423 } else {
1424 value.to_string()
1425 }
1426 }
1427 }
1428 "//" => {
1429 if let Some(slash_pos) = arg.find('/') {
1431 let pattern = &arg[..slash_pos];
1432 let replacement = &arg[slash_pos + 1..];
1433 let mut result = String::new();
1434 let mut i = 0;
1435 let chars: Vec<char> = value.chars().collect();
1436 while i < chars.len() {
1437 let remaining: String = chars[i..].iter().collect();
1438 if glob_match_word(pattern, &remaining[..glob_match_length(&remaining, 0, pattern).max(1).min(remaining.len())]) {
1439 let mlen = glob_match_length(&remaining, 0, pattern);
1440 result.push_str(replacement);
1441 i += mlen.max(1);
1442 } else {
1443 result.push(chars[i]);
1444 i += 1;
1445 }
1446 }
1447 result
1448 } else {
1449 value.to_string()
1450 }
1451 }
1452 ":="|"=" => {
1453 if value.is_empty() {
1455 arg.to_string()
1456 } else {
1457 value.to_string()
1458 }
1459 }
1460 ":?" | "?" => {
1461 if value.is_empty() {
1463 let msg = if arg.is_empty() { "parameter not set" } else { arg };
1464 eprintln!("frost: {msg}");
1465 value.to_string()
1466 } else {
1467 value.to_string()
1468 }
1469 }
1470 _ => value.to_string(),
1471 }
1472}
1473
1474fn find_glob_match(text: &str, pattern: &str) -> Option<usize> {
1476 for i in 0..text.len() {
1477 for j in (i + 1)..=text.len() {
1478 if glob_match_word(pattern, &text[i..j]) {
1479 return Some(i);
1480 }
1481 }
1482 }
1483 None
1484}
1485
1486fn glob_match_length(text: &str, start: usize, pattern: &str) -> usize {
1488 let mut longest = 1;
1489 for j in (start + 1)..=text.len() {
1490 if glob_match_word(pattern, &text[start..j]) {
1491 longest = j - start;
1492 }
1493 }
1494 longest
1495}
1496
1497fn glob_match_word(pattern: &str, text: &str) -> bool {
1499 let pat: Vec<char> = pattern.chars().collect();
1500 let txt: Vec<char> = text.chars().collect();
1501 glob_match_chars(&pat, &txt)
1502}
1503
1504fn glob_match_chars(pat: &[char], txt: &[char]) -> bool {
1505 if pat.is_empty() {
1506 return txt.is_empty();
1507 }
1508 match pat[0] {
1509 '*' => {
1510 for i in 0..=txt.len() {
1511 if glob_match_chars(&pat[1..], &txt[i..]) {
1512 return true;
1513 }
1514 }
1515 false
1516 }
1517 '?' => {
1518 if txt.is_empty() {
1519 false
1520 } else {
1521 glob_match_chars(&pat[1..], &txt[1..])
1522 }
1523 }
1524 '\\' if pat.len() > 1 => {
1525 if txt.is_empty() || txt[0] != pat[1] {
1526 false
1527 } else {
1528 glob_match_chars(&pat[2..], &txt[1..])
1529 }
1530 }
1531 c => {
1532 if txt.is_empty() || txt[0] != c {
1533 false
1534 } else {
1535 glob_match_chars(&pat[1..], &txt[1..])
1536 }
1537 }
1538 }
1539}
1540
1541fn eval_arith_expr(expr: &str) -> Option<i64> {
1543 let expr = expr.trim();
1544 if expr.is_empty() {
1545 return Some(0);
1546 }
1547
1548 if let Ok(n) = expr.parse::<i64>() {
1550 return Some(n);
1551 }
1552
1553 for &op in &["==", "!=", "<=", ">=", "<", ">"] {
1555 if let Some(pos) = find_binary_op(expr, op) {
1556 let left = eval_arith_expr(&expr[..pos])?;
1557 let right = eval_arith_expr(&expr[pos + op.len()..])?;
1558 return Some(match op {
1559 "==" => (left == right) as i64,
1560 "!=" => (left != right) as i64,
1561 "<=" => (left <= right) as i64,
1562 ">=" => (left >= right) as i64,
1563 "<" => (left < right) as i64,
1564 ">" => (left > right) as i64,
1565 _ => 0,
1566 });
1567 }
1568 }
1569
1570 if let Some(pos) = find_last_additive(expr) {
1572 let left = eval_arith_expr(&expr[..pos])?;
1573 let right = eval_arith_expr(&expr[pos + 1..])?;
1574 return Some(if expr.as_bytes()[pos] == b'+' {
1575 left + right
1576 } else {
1577 left - right
1578 });
1579 }
1580
1581 if let Some(pos) = find_last_multiplicative(expr) {
1583 let left = eval_arith_expr(&expr[..pos])?;
1584 let right = eval_arith_expr(&expr[pos + 1..])?;
1585 return Some(match expr.as_bytes()[pos] {
1586 b'*' => left * right,
1587 b'/' if right != 0 => left / right,
1588 b'%' if right != 0 => left % right,
1589 _ => 0,
1590 });
1591 }
1592
1593 if expr.starts_with('(') && expr.ends_with(')') {
1595 return eval_arith_expr(&expr[1..expr.len() - 1]);
1596 }
1597
1598 if expr.starts_with('-') {
1600 return eval_arith_expr(&expr[1..]).map(|v| -v);
1601 }
1602
1603 if expr.starts_with('+') {
1605 return eval_arith_expr(&expr[1..]);
1606 }
1607
1608 if expr.starts_with('!') {
1610 return eval_arith_expr(&expr[1..]).map(|v| if v == 0 { 1 } else { 0 });
1611 }
1612
1613 None
1614}
1615
1616fn find_last_additive(expr: &str) -> Option<usize> {
1618 let bytes = expr.as_bytes();
1619 let mut depth = 0i32;
1620 let mut last = None;
1621 for (i, &b) in bytes.iter().enumerate() {
1622 match b {
1623 b'(' => depth += 1,
1624 b')' => depth -= 1,
1625 b'+' | b'-' if depth == 0 && i > 0 => {
1626 let prev = bytes[i - 1];
1628 if prev != b'*' && prev != b'/' && prev != b'%' && prev != b'('
1629 && prev != b'+' && prev != b'-'
1630 {
1631 last = Some(i);
1632 }
1633 }
1634 _ => {}
1635 }
1636 }
1637 last
1638}
1639
1640fn find_last_multiplicative(expr: &str) -> Option<usize> {
1642 let bytes = expr.as_bytes();
1643 let mut depth = 0i32;
1644 let mut last = None;
1645 for (i, &b) in bytes.iter().enumerate() {
1646 match b {
1647 b'(' => depth += 1,
1648 b')' => depth -= 1,
1649 b'*' | b'/' | b'%' if depth == 0 => {
1650 if b == b'*' && i + 1 < bytes.len() && bytes[i + 1] == b'*' {
1652 continue;
1653 }
1654 last = Some(i);
1655 }
1656 _ => {}
1657 }
1658 }
1659 last
1660}
1661
1662fn find_binary_op(expr: &str, op: &str) -> Option<usize> {
1664 let bytes = expr.as_bytes();
1665 let op_bytes = op.as_bytes();
1666 let mut depth = 0i32;
1667 for i in 0..bytes.len() {
1668 match bytes[i] {
1669 b'(' => depth += 1,
1670 b')' => depth -= 1,
1671 _ if depth == 0 && i + op_bytes.len() <= bytes.len() => {
1672 if &bytes[i..i + op_bytes.len()] == op_bytes && i > 0 {
1673 return Some(i);
1674 }
1675 }
1676 _ => {}
1677 }
1678 }
1679 None
1680}
1681
1682fn reconstruct_program(program: &Program) -> String {
1686 let mut parts = Vec::new();
1687 for cmd in &program.commands {
1688 parts.push(reconstruct_complete_command(cmd));
1689 }
1690 parts.join("; ")
1691}
1692
1693fn reconstruct_complete_command(cmd: &CompleteCommand) -> String {
1694 let mut s = reconstruct_list(&cmd.list);
1695 if cmd.is_async {
1696 s.push_str(" &");
1697 }
1698 s
1699}
1700
1701fn reconstruct_list(list: &List) -> String {
1702 let mut s = reconstruct_pipeline(&list.first);
1703 for (op, pipeline) in &list.rest {
1704 match op {
1705 ListOp::And => s.push_str(" && "),
1706 ListOp::Or => s.push_str(" || "),
1707 }
1708 s.push_str(&reconstruct_pipeline(pipeline));
1709 }
1710 s
1711}
1712
1713fn reconstruct_pipeline(pipeline: &Pipeline) -> String {
1714 let mut s = String::new();
1715 if pipeline.bang {
1716 s.push_str("! ");
1717 }
1718 let cmd_strs: Vec<String> = pipeline.commands.iter().map(reconstruct_command).collect();
1719 s.push_str(&cmd_strs.join(" | "));
1720 s
1721}
1722
1723fn reconstruct_command(cmd: &Command) -> String {
1724 match cmd {
1725 Command::Simple(simple) => {
1726 let mut parts = Vec::new();
1727 for assign in &simple.assignments {
1728 let val = assign.value.as_ref().map(reconstruct_word).unwrap_or_default();
1729 parts.push(format!("{}={}", assign.name, val));
1730 }
1731 for word in &simple.words {
1732 parts.push(reconstruct_word(word));
1733 }
1734 for redir in &simple.redirects {
1735 parts.push(reconstruct_redirect(redir));
1736 }
1737 parts.join(" ")
1738 }
1739 Command::Subshell(sub) => {
1740 let body: Vec<String> = sub.body.iter().map(reconstruct_complete_command).collect();
1741 format!("( {} )", body.join("; "))
1742 }
1743 Command::BraceGroup(bg) => {
1744 let body: Vec<String> = bg.body.iter().map(reconstruct_complete_command).collect();
1745 format!("{{ {} }}", body.join("; "))
1746 }
1747 Command::If(clause) => {
1748 let mut s = String::from("if ");
1749 let cond: Vec<String> = clause.condition.iter().map(reconstruct_complete_command).collect();
1750 s.push_str(&cond.join("; "));
1751 s.push_str("; then ");
1752 let body: Vec<String> = clause.then_body.iter().map(reconstruct_complete_command).collect();
1753 s.push_str(&body.join("; "));
1754 for (elif_cond, elif_body) in &clause.elifs {
1755 s.push_str("; elif ");
1756 let ec: Vec<String> = elif_cond.iter().map(reconstruct_complete_command).collect();
1757 s.push_str(&ec.join("; "));
1758 s.push_str("; then ");
1759 let eb: Vec<String> = elif_body.iter().map(reconstruct_complete_command).collect();
1760 s.push_str(&eb.join("; "));
1761 }
1762 if let Some(else_body) = &clause.else_body {
1763 s.push_str("; else ");
1764 let eb: Vec<String> = else_body.iter().map(reconstruct_complete_command).collect();
1765 s.push_str(&eb.join("; "));
1766 }
1767 s.push_str("; fi");
1768 s
1769 }
1770 Command::For(clause) => {
1771 let mut s = format!("for {} ", clause.var);
1772 if let Some(words) = &clause.words {
1773 s.push_str("in ");
1774 let w: Vec<String> = words.iter().map(reconstruct_word).collect();
1775 s.push_str(&w.join(" "));
1776 }
1777 s.push_str("; do ");
1778 let body: Vec<String> = clause.body.iter().map(reconstruct_complete_command).collect();
1779 s.push_str(&body.join("; "));
1780 s.push_str("; done");
1781 s
1782 }
1783 Command::ArithFor(clause) => {
1784 let mut s = format!(
1785 "for (( {}; {}; {} )); do ",
1786 clause.init, clause.condition, clause.step
1787 );
1788 let body: Vec<String> = clause.body.iter().map(reconstruct_complete_command).collect();
1789 s.push_str(&body.join("; "));
1790 s.push_str("; done");
1791 s
1792 }
1793 Command::While(clause) => {
1794 let mut s = String::from("while ");
1795 let cond: Vec<String> = clause.condition.iter().map(reconstruct_complete_command).collect();
1796 s.push_str(&cond.join("; "));
1797 s.push_str("; do ");
1798 let body: Vec<String> = clause.body.iter().map(reconstruct_complete_command).collect();
1799 s.push_str(&body.join("; "));
1800 s.push_str("; done");
1801 s
1802 }
1803 Command::Until(clause) => {
1804 let mut s = String::from("until ");
1805 let cond: Vec<String> = clause.condition.iter().map(reconstruct_complete_command).collect();
1806 s.push_str(&cond.join("; "));
1807 s.push_str("; do ");
1808 let body: Vec<String> = clause.body.iter().map(reconstruct_complete_command).collect();
1809 s.push_str(&body.join("; "));
1810 s.push_str("; done");
1811 s
1812 }
1813 Command::Case(clause) => {
1814 let mut s = format!("case {} in ", reconstruct_word(&clause.word));
1815 for item in &clause.items {
1816 let pats: Vec<String> = item.patterns.iter().map(reconstruct_word).collect();
1817 s.push_str(&pats.join("|"));
1818 s.push_str(") ");
1819 let body: Vec<String> = item.body.iter().map(reconstruct_complete_command).collect();
1820 s.push_str(&body.join("; "));
1821 s.push_str(";; ");
1822 }
1823 s.push_str("esac");
1824 s
1825 }
1826 Command::FunctionDef(fdef) => {
1827 format!("{} () {}", fdef.name, reconstruct_command(&fdef.body))
1828 }
1829 Command::Repeat(clause) => {
1830 let mut s = format!("repeat {} do ", reconstruct_word(&clause.count));
1831 let body: Vec<String> = clause.body.iter().map(reconstruct_complete_command).collect();
1832 s.push_str(&body.join("; "));
1833 s.push_str("; done");
1834 s
1835 }
1836 Command::Always(clause) => {
1837 let try_body: Vec<String> = clause.try_body.iter().map(reconstruct_complete_command).collect();
1838 let always_body: Vec<String> = clause.always_body.iter().map(reconstruct_complete_command).collect();
1839 format!("{{ {} }} always {{ {} }}", try_body.join("; "), always_body.join("; "))
1840 }
1841 Command::Select(_) | Command::Coproc(_) | Command::Time(_) => {
1842 String::new()
1844 }
1845 }
1846}
1847
1848fn reconstruct_word(word: &Word) -> String {
1849 let mut s = String::new();
1850 for part in &word.parts {
1851 reconstruct_word_part(part, &mut s);
1852 }
1853 s
1854}
1855
1856fn reconstruct_word_part(part: &WordPart, out: &mut String) {
1857 match part {
1858 WordPart::Literal(s) => out.push_str(s),
1859 WordPart::SingleQuoted(s) => {
1860 out.push('\'');
1861 out.push_str(s);
1862 out.push('\'');
1863 }
1864 WordPart::DoubleQuoted(parts) => {
1865 out.push('"');
1866 for p in parts {
1867 reconstruct_word_part(p, out);
1868 }
1869 out.push('"');
1870 }
1871 WordPart::DollarVar(name) => {
1872 out.push('$');
1873 out.push_str(name);
1874 }
1875 WordPart::DollarBrace { param, operator, arg } => {
1876 out.push_str("${");
1877 out.push_str(param);
1878 if let Some(op) = operator {
1879 out.push_str(op);
1880 if let Some(a) = arg {
1881 out.push_str(&reconstruct_word(a));
1882 }
1883 }
1884 out.push('}');
1885 }
1886 WordPart::CommandSub(prog) => {
1887 out.push_str("$(");
1888 out.push_str(&reconstruct_program(prog));
1889 out.push(')');
1890 }
1891 WordPart::ArithSub(expr) => {
1892 out.push_str("$((");
1893 out.push_str(expr);
1894 out.push_str("))");
1895 }
1896 WordPart::Glob(kind) => match kind {
1897 GlobKind::Star => out.push('*'),
1898 GlobKind::Question => out.push('?'),
1899 GlobKind::At => out.push('@'),
1900 },
1901 WordPart::Tilde(user) => {
1902 out.push('~');
1903 out.push_str(user);
1904 }
1905 }
1906}
1907
1908fn reconstruct_redirect(redir: &Redirect) -> String {
1909 let mut s = String::new();
1910 if let Some(fd) = redir.fd {
1911 s.push_str(&fd.to_string());
1912 }
1913 match redir.op {
1914 RedirectOp::Less => s.push('<'),
1915 RedirectOp::Greater => s.push('>'),
1916 RedirectOp::DoubleGreater => s.push_str(">>"),
1917 RedirectOp::AmpGreater => s.push_str("&>"),
1918 RedirectOp::AmpDoubleGreater => s.push_str("&>>"),
1919 RedirectOp::FdDup => s.push_str(">&"),
1920 _ => s.push('>'),
1921 }
1922 s.push_str(&reconstruct_word(&redir.target));
1923 s
1924}
1925
1926fn tokenize(input: &str) -> Vec<frost_lexer::Token> {
1928 let mut lexer = frost_lexer::Lexer::new(input.as_bytes());
1929 let mut tokens = Vec::new();
1930 loop {
1931 let tok = lexer.next_token();
1932 let eof = tok.kind == frost_lexer::TokenKind::Eof;
1933 tokens.push(tok);
1934 if eof {
1935 break;
1936 }
1937 }
1938 tokens
1939}
1940
1941#[cfg(test)]
1942mod tests {
1943 use super::*;
1944 use frost_lexer::Span;
1945 use frost_parser::ast::{Assignment, AssignOp, CompleteCommand, List, Pipeline, SimpleCommand, Word, WordPart};
1946 use pretty_assertions::assert_eq;
1947
1948 fn literal_word(s: &str) -> Word {
1949 Word {
1950 parts: vec![WordPart::Literal(s.into())],
1951 span: Span::new(0, s.len() as u32),
1952 }
1953 }
1954
1955 fn simple_program(words: Vec<&str>) -> Program {
1956 Program {
1957 commands: vec![CompleteCommand {
1958 list: List {
1959 first: Pipeline {
1960 bang: false,
1961 commands: vec![Command::Simple(SimpleCommand {
1962 assignments: vec![],
1963 words: words.into_iter().map(literal_word).collect(),
1964 redirects: vec![],
1965 })],
1966 pipe_stderr: vec![],
1967 },
1968 rest: vec![],
1969 },
1970 is_async: false,
1971 }],
1972 }
1973 }
1974
1975 #[test]
1976 fn execute_true_builtin() {
1977 let mut env = ShellEnv::new();
1978 let mut exec = Executor::new(&mut env);
1979 let program = simple_program(vec!["true"]);
1980 let status = exec.execute_program(&program).unwrap();
1981 assert_eq!(status, 0);
1982 }
1983
1984 #[test]
1985 fn execute_false_builtin() {
1986 let mut env = ShellEnv::new();
1987 let mut exec = Executor::new(&mut env);
1988 let program = simple_program(vec!["false"]);
1989 let status = exec.execute_program(&program).unwrap();
1990 assert_eq!(status, 1);
1991 }
1992
1993 #[test]
1994 fn invert_status() {
1995 assert_eq!(invert(0), 1);
1996 assert_eq!(invert(1), 0);
1997 assert_eq!(invert(42), 0);
1998 }
1999
2000 #[test]
2001 fn bare_assignment() {
2002 let mut env = ShellEnv::new();
2003 let mut exec = Executor::new(&mut env);
2004 let program = Program {
2005 commands: vec![CompleteCommand {
2006 list: List {
2007 first: Pipeline {
2008 bang: false,
2009 commands: vec![Command::Simple(SimpleCommand {
2010 assignments: vec![Assignment {
2011 name: "MY_VAR".into(),
2012 op: AssignOp::Assign,
2013 value: Some(literal_word("hello")),
2014 span: Span::new(0, 12),
2015 }],
2016 words: vec![],
2017 redirects: vec![],
2018 })],
2019 pipe_stderr: vec![],
2020 },
2021 rest: vec![],
2022 },
2023 is_async: false,
2024 }],
2025 };
2026 let status = exec.execute_program(&program).unwrap();
2027 assert_eq!(status, 0);
2028 assert_eq!(exec.env.get_var("MY_VAR"), Some("hello"));
2029 }
2030
2031 #[test]
2032 fn expand_dollar_var() {
2033 let mut env = ShellEnv::new();
2034 env.set_var("FOO", "bar");
2035 let exec = Executor::new(&mut env);
2036 let word = Word {
2037 parts: vec![WordPart::DollarVar("FOO".into())],
2038 span: Span::new(0, 4),
2039 };
2040 assert_eq!(exec.expand_special_var("FOO"), "bar");
2041 drop(exec);
2042 }
2043
2044 #[test]
2045 fn expand_special_vars() {
2046 let mut env = ShellEnv::new();
2047 env.exit_status = 42;
2048 env.positional_params = vec!["a".into(), "b".into()];
2049 let exec = Executor::new(&mut env);
2050 assert_eq!(exec.expand_special_var("?"), "42");
2051 assert_eq!(exec.expand_special_var("#"), "2");
2052 assert_eq!(exec.expand_special_var("@"), "a b");
2053 assert_eq!(exec.expand_special_var("1"), "a");
2054 assert_eq!(exec.expand_special_var("2"), "b");
2055 assert_eq!(exec.expand_special_var("3"), "");
2056 drop(exec);
2057 }
2058
2059 #[test]
2060 fn arith_basic() {
2061 assert_eq!(eval_arith_expr("1+2"), Some(3));
2062 assert_eq!(eval_arith_expr("10-3"), Some(7));
2063 assert_eq!(eval_arith_expr("4*5"), Some(20));
2064 assert_eq!(eval_arith_expr("10/3"), Some(3));
2065 assert_eq!(eval_arith_expr("10%3"), Some(1));
2066 }
2067
2068 #[test]
2069 fn arith_comparison() {
2070 assert_eq!(eval_arith_expr("1==1"), Some(1));
2071 assert_eq!(eval_arith_expr("1!=2"), Some(1));
2072 assert_eq!(eval_arith_expr("1<2"), Some(1));
2073 assert_eq!(eval_arith_expr("2>1"), Some(1));
2074 }
2075
2076 #[test]
2077 fn glob_match_basic() {
2078 assert!(glob_match_word("hello", "hello"));
2079 assert!(!glob_match_word("hello", "world"));
2080 assert!(glob_match_word("*", "anything"));
2081 assert!(glob_match_word("hel*", "hello"));
2082 assert!(glob_match_word("h?llo", "hello"));
2083 }
2084
2085 #[test]
2086 fn param_op_default() {
2087 assert_eq!(apply_param_op("", ":-", "default"), "default");
2088 assert_eq!(apply_param_op("val", ":-", "default"), "val");
2089 }
2090
2091 #[test]
2092 fn param_op_alternate() {
2093 assert_eq!(apply_param_op("", ":+", "alt"), "");
2094 assert_eq!(apply_param_op("val", ":+", "alt"), "alt");
2095 }
2096}