1use super::lexer::Token;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub enum Ast {
5 Pipeline(Vec<ShellCommand>),
6 Sequence(Vec<Ast>),
7 Assignment {
8 var: String,
9 value: String,
10 },
11 LocalAssignment {
12 var: String,
13 value: String,
14 },
15 If {
16 branches: Vec<(Box<Ast>, Box<Ast>)>, else_branch: Option<Box<Ast>>,
18 },
19 Case {
20 word: String,
21 cases: Vec<(Vec<String>, Ast)>,
22 default: Option<Box<Ast>>,
23 },
24 For {
25 variable: String,
26 items: Vec<String>,
27 body: Box<Ast>,
28 },
29 While {
30 condition: Box<Ast>,
31 body: Box<Ast>,
32 },
33 FunctionDefinition {
34 name: String,
35 body: Box<Ast>,
36 },
37 FunctionCall {
38 name: String,
39 args: Vec<String>,
40 },
41 Return {
42 value: Option<String>,
43 },
44 And {
45 left: Box<Ast>,
46 right: Box<Ast>,
47 },
48 Or {
49 left: Box<Ast>,
50 right: Box<Ast>,
51 },
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct ShellCommand {
56 pub args: Vec<String>,
57 pub input: Option<String>,
58 pub output: Option<String>,
59 pub append: Option<String>,
60}
61
62pub fn parse(tokens: Vec<Token>) -> Result<Ast, String> {
63 if tokens.len() >= 4
65 && let (Token::Word(_), Token::LeftParen, Token::RightParen, Token::LeftBrace) =
66 (&tokens[0], &tokens[1], &tokens[2], &tokens[3])
67 {
68 let mut brace_depth = 1; let mut function_end = tokens.len();
72 let mut j = 4; while j < tokens.len() {
75 match &tokens[j] {
76 Token::LeftBrace => {
77 brace_depth += 1;
78 j += 1;
79 }
80 Token::RightBrace => {
81 brace_depth -= 1;
82 if brace_depth == 0 {
83 function_end = j + 1; break;
85 }
86 j += 1;
87 }
88 Token::If => {
89 let mut if_depth = 1;
91 j += 1;
92 while j < tokens.len() && if_depth > 0 {
93 match tokens[j] {
94 Token::If => if_depth += 1,
95 Token::Fi => if_depth -= 1,
96 _ => {}
97 }
98 j += 1;
99 }
100 }
101 Token::For | Token::While => {
102 let mut for_depth = 1;
104 j += 1;
105 while j < tokens.len() && for_depth > 0 {
106 match tokens[j] {
107 Token::For | Token::While => for_depth += 1,
108 Token::Done => for_depth -= 1,
109 _ => {}
110 }
111 j += 1;
112 }
113 }
114 Token::Case => {
115 j += 1;
117 while j < tokens.len() {
118 if tokens[j] == Token::Esac {
119 j += 1;
120 break;
121 }
122 j += 1;
123 }
124 }
125 _ => {
126 j += 1;
127 }
128 }
129 }
130
131 if brace_depth == 0 && function_end <= tokens.len() {
132 let function_tokens = &tokens[0..function_end];
134 let remaining_tokens = &tokens[function_end..];
135
136 let function_ast = parse_function_definition(function_tokens)?;
137
138 if remaining_tokens.is_empty() {
139 return Ok(function_ast);
140 } else {
141 let remaining_ast = parse_commands_sequentially(remaining_tokens)?;
143 return Ok(Ast::Sequence(vec![function_ast, remaining_ast]));
144 }
145 }
146 }
147
148 if tokens.len() >= 2
150 && let Token::Word(ref word) = tokens[0]
151 && let Some(paren_pos) = word.find('(')
152 && word.ends_with(')')
153 && paren_pos > 0
154 && tokens[1] == Token::LeftBrace
155 {
156 return parse_function_definition(&tokens);
157 }
158
159 parse_commands_sequentially(&tokens)
161}
162
163fn parse_slice(tokens: &[Token]) -> Result<Ast, String> {
164 if tokens.is_empty() {
165 return Err("No commands found".to_string());
166 }
167
168 if tokens.len() == 2 {
170 if let (Token::Word(var_eq), Token::Word(value)) = (&tokens[0], &tokens[1])
172 && let Some(eq_pos) = var_eq.find('=')
173 && eq_pos > 0
174 && eq_pos < var_eq.len()
175 {
176 let var = var_eq[..eq_pos].to_string();
177 let full_value = format!("{}{}", &var_eq[eq_pos + 1..], value);
178 if var.chars().next().unwrap().is_alphabetic() || var.starts_with('_') {
180 return Ok(Ast::Assignment {
181 var,
182 value: full_value,
183 });
184 }
185 }
186 }
187
188 if tokens.len() == 2
190 && let (Token::Word(var_eq), Token::Word(value)) = (&tokens[0], &tokens[1])
191 && let Some(eq_pos) = var_eq.find('=')
192 && eq_pos > 0
193 && eq_pos == var_eq.len() - 1
194 {
195 let var = var_eq[..eq_pos].to_string();
196 if var.chars().next().unwrap().is_alphabetic() || var.starts_with('_') {
198 return Ok(Ast::Assignment {
199 var,
200 value: value.clone(),
201 });
202 }
203 }
204
205 if tokens.len() == 3
207 && let (Token::Local, Token::Word(var), Token::Word(value)) =
208 (&tokens[0], &tokens[1], &tokens[2])
209 {
210 let clean_var = if var.ends_with('=') {
212 &var[..var.len() - 1]
213 } else {
214 var
215 };
216 if clean_var.chars().next().unwrap().is_alphabetic() || clean_var.starts_with('_') {
218 return Ok(Ast::LocalAssignment {
219 var: clean_var.to_string(),
220 value: value.clone(),
221 });
222 }
223 }
224
225 if !tokens.is_empty()
227 && tokens.len() <= 2
228 && let Token::Return = &tokens[0]
229 {
230 if tokens.len() == 1 {
231 return Ok(Ast::Return { value: None });
233 } else if let Token::Word(word) = &tokens[1] {
234 return Ok(Ast::Return {
236 value: Some(word.clone()),
237 });
238 }
239 }
240
241 if tokens.len() == 2
243 && let (Token::Local, Token::Word(var_eq)) = (&tokens[0], &tokens[1])
244 && let Some(eq_pos) = var_eq.find('=')
245 && eq_pos > 0
246 && eq_pos < var_eq.len()
247 {
248 let var = var_eq[..eq_pos].to_string();
249 let value = var_eq[eq_pos + 1..].to_string();
250 if var.chars().next().unwrap().is_alphabetic() || var.starts_with('_') {
252 return Ok(Ast::LocalAssignment { var, value });
253 }
254 }
255
256 if tokens.len() == 1
258 && let Token::Word(ref word) = tokens[0]
259 && let Some(eq_pos) = word.find('=')
260 && eq_pos > 0
261 && eq_pos < word.len()
262 {
263 let var = word[..eq_pos].to_string();
264 let value = word[eq_pos + 1..].to_string();
265 if var.chars().next().unwrap().is_alphabetic() || var.starts_with('_') {
267 return Ok(Ast::Assignment { var, value });
268 }
269 }
270
271 if let Token::If = tokens[0] {
273 return parse_if(tokens);
274 }
275
276 if let Token::Case = tokens[0] {
278 return parse_case(tokens);
279 }
280
281 if let Token::For = tokens[0] {
283 return parse_for(tokens);
284 }
285
286 if let Token::While = tokens[0] {
288 return parse_while(tokens);
289 }
290
291 if tokens.len() >= 4
294 && let (Token::Word(word), Token::LeftParen, Token::RightParen, Token::LeftBrace) =
295 (&tokens[0], &tokens[1], &tokens[2], &tokens[3])
296 && (word.chars().next().unwrap().is_alphabetic() || word.starts_with('_'))
297 {
298 return parse_function_definition(tokens);
299 }
300
301 if tokens.len() >= 2
303 && let Token::Word(ref word) = tokens[0]
304 && let Some(paren_pos) = word.find('(')
305 && word.ends_with(')')
306 && paren_pos > 0
307 {
308 let func_name = &word[..paren_pos];
309 if (func_name.chars().next().unwrap().is_alphabetic() || func_name.starts_with('_'))
310 && tokens[1] == Token::LeftBrace
311 {
312 return parse_function_definition(tokens);
313 }
314 }
315
316 parse_pipeline(tokens)
321}
322
323fn parse_commands_sequentially(tokens: &[Token]) -> Result<Ast, String> {
324 let mut i = 0;
325 let mut commands = Vec::new();
326
327 while i < tokens.len() {
328 while i < tokens.len() {
330 match &tokens[i] {
331 Token::Newline => {
332 i += 1;
333 }
334 Token::Word(word) if word.starts_with('#') => {
335 while i < tokens.len() && tokens[i] != Token::Newline {
337 i += 1;
338 }
339 if i < tokens.len() {
340 i += 1; }
342 }
343 _ => break,
344 }
345 }
346
347 if i >= tokens.len() {
348 break;
349 }
350
351 let start = i;
353
354 if tokens[i] == Token::If {
356 let mut depth = 0;
358 while i < tokens.len() {
359 match tokens[i] {
360 Token::If => depth += 1,
361 Token::Fi => {
362 depth -= 1;
363 if depth == 0 {
364 i += 1; break;
366 }
367 }
368 _ => {}
369 }
370 i += 1;
371 }
372
373 } else if tokens[i] == Token::For {
376 let mut depth = 1; i += 1; while i < tokens.len() {
380 match tokens[i] {
381 Token::For | Token::While => depth += 1,
382 Token::Done => {
383 depth -= 1;
384 if depth == 0 {
385 i += 1; break;
387 }
388 }
389 _ => {}
390 }
391 i += 1;
392 }
393 } else if tokens[i] == Token::While {
394 let mut depth = 1; i += 1; while i < tokens.len() {
398 match tokens[i] {
399 Token::While | Token::For => depth += 1,
400 Token::Done => {
401 depth -= 1;
402 if depth == 0 {
403 i += 1; break;
405 }
406 }
407 _ => {}
408 }
409 i += 1;
410 }
411 } else if tokens[i] == Token::Case {
412 while i < tokens.len() {
414 if tokens[i] == Token::Esac {
415 i += 1; break;
417 }
418 i += 1;
419 }
420 } else if i + 3 < tokens.len()
421 && matches!(tokens[i], Token::Word(_))
422 && tokens[i + 1] == Token::LeftParen
423 && tokens[i + 2] == Token::RightParen
424 && tokens[i + 3] == Token::LeftBrace
425 {
426 let mut brace_depth = 1;
428 i += 4; while i < tokens.len() && brace_depth > 0 {
430 match tokens[i] {
431 Token::LeftBrace => brace_depth += 1,
432 Token::RightBrace => brace_depth -= 1,
433 _ => {}
434 }
435 i += 1;
436 }
437 } else {
438 while i < tokens.len() {
441 if tokens[i] == Token::Newline
442 || tokens[i] == Token::Semicolon
443 || tokens[i] == Token::And
444 || tokens[i] == Token::Or
445 {
446 let mut j = i + 1;
448 while j < tokens.len() && tokens[j] == Token::Newline {
449 j += 1;
450 }
451 if j < tokens.len()
453 && (tokens[j] == Token::Else
454 || tokens[j] == Token::Elif
455 || tokens[j] == Token::Fi)
456 {
457 i = j + 1;
459 continue;
460 }
461 break;
462 }
463 i += 1;
464 }
465 }
466
467 let command_tokens = &tokens[start..i];
468 if !command_tokens.is_empty() {
469 if command_tokens.len() == 1 {
471 match command_tokens[0] {
472 Token::Else | Token::Elif | Token::Fi => {
473 if i < tokens.len()
475 && (tokens[i] == Token::Newline || tokens[i] == Token::Semicolon)
476 {
477 i += 1;
478 }
479 continue;
480 }
481 _ => {}
482 }
483 }
484
485 let ast = parse_slice(command_tokens)?;
486
487 if i < tokens.len() && (tokens[i] == Token::And || tokens[i] == Token::Or) {
489 let operator = tokens[i].clone();
490 i += 1; while i < tokens.len() && tokens[i] == Token::Newline {
494 i += 1;
495 }
496
497 let remaining_tokens = &tokens[i..];
499 let right_ast = parse_commands_sequentially(remaining_tokens)?;
500
501 let combined_ast = match operator {
503 Token::And => Ast::And {
504 left: Box::new(ast),
505 right: Box::new(right_ast),
506 },
507 Token::Or => Ast::Or {
508 left: Box::new(ast),
509 right: Box::new(right_ast),
510 },
511 _ => unreachable!(),
512 };
513
514 commands.push(combined_ast);
515 break; } else {
517 commands.push(ast);
518 }
519 }
520
521 if i < tokens.len() && (tokens[i] == Token::Newline || tokens[i] == Token::Semicolon) {
522 i += 1;
523 }
524 }
525
526 if commands.is_empty() {
527 return Err("No commands found".to_string());
528 }
529
530 if commands.len() == 1 {
531 Ok(commands.into_iter().next().unwrap())
532 } else {
533 Ok(Ast::Sequence(commands))
534 }
535}
536
537fn parse_pipeline(tokens: &[Token]) -> Result<Ast, String> {
538 let mut commands = Vec::new();
539 let mut current_cmd = ShellCommand {
540 args: Vec::new(),
541 input: None,
542 output: None,
543 append: None,
544 };
545
546 let mut i = 0;
547 while i < tokens.len() {
548 let token = &tokens[i];
549 match token {
550 Token::Word(word) => {
551 current_cmd.args.push(word.clone());
552 }
553 Token::Pipe => {
554 if !current_cmd.args.is_empty() {
555 commands.push(current_cmd.clone());
556 current_cmd = ShellCommand {
557 args: Vec::new(),
558 input: None,
559 output: None,
560 append: None,
561 };
562 }
563 }
564 Token::RedirIn => {
565 i += 1;
566 if i < tokens.len()
567 && let Token::Word(ref file) = tokens[i]
568 {
569 current_cmd.input = Some(file.clone());
570 }
571 }
572 Token::RedirOut => {
573 i += 1;
574 if i < tokens.len()
575 && let Token::Word(ref file) = tokens[i]
576 {
577 current_cmd.output = Some(file.clone());
578 }
579 }
580 Token::RedirAppend => {
581 i += 1;
582 if i < tokens.len()
583 && let Token::Word(ref file) = tokens[i]
584 {
585 current_cmd.append = Some(file.clone());
586 }
587 }
588 Token::RightParen => {
589 if !current_cmd.args.is_empty()
592 && i > 0
593 && let Token::LeftParen = tokens[i - 1]
594 {
595 break;
599 }
600 return Err("Unexpected ) in pipeline".to_string());
601 }
602 Token::Newline => {
603 i += 1;
605 continue;
606 }
607 Token::Do
608 | Token::Done
609 | Token::Then
610 | Token::Else
611 | Token::Elif
612 | Token::Fi
613 | Token::Esac => {
614 break;
617 }
618 _ => {
619 return Err(format!("Unexpected token in pipeline: {:?}", token));
620 }
621 }
622 i += 1;
623 }
624
625 if !current_cmd.args.is_empty() {
626 commands.push(current_cmd);
627 }
628
629 if commands.is_empty() {
630 return Err("No commands found".to_string());
631 }
632
633 Ok(Ast::Pipeline(commands))
634}
635
636fn parse_if(tokens: &[Token]) -> Result<Ast, String> {
637 let mut i = 1; let mut branches = Vec::new();
639
640 loop {
641 let mut cond_tokens = Vec::new();
643 while i < tokens.len()
644 && tokens[i] != Token::Semicolon
645 && tokens[i] != Token::Newline
646 && tokens[i] != Token::Then
647 {
648 cond_tokens.push(tokens[i].clone());
649 i += 1;
650 }
651
652 if i < tokens.len() && (tokens[i] == Token::Semicolon || tokens[i] == Token::Newline) {
654 i += 1;
655 }
656
657 while i < tokens.len() && tokens[i] == Token::Newline {
659 i += 1;
660 }
661
662 if i >= tokens.len() || tokens[i] != Token::Then {
663 return Err("Expected then after if/elif condition".to_string());
664 }
665 i += 1; while i < tokens.len() && tokens[i] == Token::Newline {
669 i += 1;
670 }
671
672 let mut then_tokens = Vec::new();
675 let mut depth = 0;
676 while i < tokens.len() {
677 match &tokens[i] {
678 Token::If => {
679 depth += 1;
680 then_tokens.push(tokens[i].clone());
681 }
682 Token::Fi => {
683 if depth > 0 {
684 depth -= 1;
685 then_tokens.push(tokens[i].clone());
686 } else {
687 break; }
689 }
690 Token::Else | Token::Elif if depth == 0 => {
691 break; }
693 Token::Newline => {
694 let mut j = i + 1;
696 while j < tokens.len() && tokens[j] == Token::Newline {
697 j += 1;
698 }
699 if j < tokens.len()
700 && depth == 0
701 && (tokens[j] == Token::Else
702 || tokens[j] == Token::Elif
703 || tokens[j] == Token::Fi)
704 {
705 i = j; break;
707 }
708 then_tokens.push(tokens[i].clone());
710 }
711 _ => {
712 then_tokens.push(tokens[i].clone());
713 }
714 }
715 i += 1;
716 }
717
718 while i < tokens.len() && tokens[i] == Token::Newline {
720 i += 1;
721 }
722
723 let then_ast = if then_tokens.is_empty() {
724 Ast::Pipeline(vec![ShellCommand {
726 args: vec!["true".to_string()],
727 input: None,
728 output: None,
729 append: None,
730 }])
731 } else {
732 parse_commands_sequentially(&then_tokens)?
733 };
734
735 let condition = parse_slice(&cond_tokens)?;
736 branches.push((Box::new(condition), Box::new(then_ast)));
737
738 if i < tokens.len() && tokens[i] == Token::Elif {
740 i += 1; } else {
742 break;
743 }
744 }
745
746 let else_ast = if i < tokens.len() && tokens[i] == Token::Else {
747 i += 1; while i < tokens.len() && tokens[i] == Token::Newline {
751 i += 1;
752 }
753
754 let mut else_tokens = Vec::new();
755 let mut depth = 0;
756 while i < tokens.len() {
757 match &tokens[i] {
758 Token::If => {
759 depth += 1;
760 else_tokens.push(tokens[i].clone());
761 }
762 Token::Fi => {
763 if depth > 0 {
764 depth -= 1;
765 else_tokens.push(tokens[i].clone());
766 } else {
767 break; }
769 }
770 Token::Newline => {
771 let mut j = i + 1;
773 while j < tokens.len() && tokens[j] == Token::Newline {
774 j += 1;
775 }
776 if j < tokens.len() && depth == 0 && tokens[j] == Token::Fi {
777 i = j; break;
779 }
780 else_tokens.push(tokens[i].clone());
782 }
783 _ => {
784 else_tokens.push(tokens[i].clone());
785 }
786 }
787 i += 1;
788 }
789
790 let else_ast = if else_tokens.is_empty() {
791 Ast::Pipeline(vec![ShellCommand {
793 args: vec!["true".to_string()],
794 input: None,
795 output: None,
796 append: None,
797 }])
798 } else {
799 parse_commands_sequentially(&else_tokens)?
800 };
801
802 Some(Box::new(else_ast))
803 } else {
804 None
805 };
806
807 if i >= tokens.len() || tokens[i] != Token::Fi {
808 return Err("Expected fi".to_string());
809 }
810
811 Ok(Ast::If {
812 branches,
813 else_branch: else_ast,
814 })
815}
816
817fn parse_case(tokens: &[Token]) -> Result<Ast, String> {
818 let mut i = 1; if i >= tokens.len() || !matches!(tokens[i], Token::Word(_)) {
822 return Err("Expected word after case".to_string());
823 }
824 let word = if let Token::Word(ref w) = tokens[i] {
825 w.clone()
826 } else {
827 unreachable!()
828 };
829 i += 1;
830
831 if i >= tokens.len() || tokens[i] != Token::In {
832 return Err("Expected in after case word".to_string());
833 }
834 i += 1;
835
836 let mut cases = Vec::new();
837 let mut default = None;
838
839 loop {
840 while i < tokens.len() && tokens[i] == Token::Newline {
842 i += 1;
843 }
844
845 if i >= tokens.len() {
846 return Err("Unexpected end in case statement".to_string());
847 }
848
849 if tokens[i] == Token::Esac {
850 break;
851 }
852
853 let mut patterns = Vec::new();
855 while i < tokens.len() && tokens[i] != Token::RightParen {
856 if let Token::Word(ref p) = tokens[i] {
857 for pat in p.split('|') {
859 patterns.push(pat.to_string());
860 }
861 } else if tokens[i] == Token::Pipe {
862 } else if tokens[i] == Token::Newline {
864 } else {
866 return Err(format!("Expected pattern, found {:?}", tokens[i]));
867 }
868 i += 1;
869 }
870
871 if i >= tokens.len() || tokens[i] != Token::RightParen {
872 return Err("Expected ) after patterns".to_string());
873 }
874 i += 1;
875
876 let mut commands_tokens = Vec::new();
878 while i < tokens.len() && tokens[i] != Token::DoubleSemicolon && tokens[i] != Token::Esac {
879 commands_tokens.push(tokens[i].clone());
880 i += 1;
881 }
882
883 let commands_ast = parse_slice(&commands_tokens)?;
884
885 if i >= tokens.len() {
886 return Err("Unexpected end in case statement".to_string());
887 }
888
889 if tokens[i] == Token::DoubleSemicolon {
890 i += 1;
891 if patterns.len() == 1 && patterns[0] == "*" {
893 default = Some(Box::new(commands_ast));
894 } else {
895 cases.push((patterns, commands_ast));
896 }
897 } else if tokens[i] == Token::Esac {
898 if patterns.len() == 1 && patterns[0] == "*" {
900 default = Some(Box::new(commands_ast));
901 } else {
902 cases.push((patterns, commands_ast));
903 }
904 break;
905 } else {
906 return Err("Expected ;; or esac after commands".to_string());
907 }
908 }
909
910 Ok(Ast::Case {
911 word,
912 cases,
913 default,
914 })
915}
916
917fn parse_for(tokens: &[Token]) -> Result<Ast, String> {
918 let mut i = 1; if i >= tokens.len() || !matches!(tokens[i], Token::Word(_)) {
922 return Err("Expected variable name after for".to_string());
923 }
924 let variable = if let Token::Word(ref v) = tokens[i] {
925 v.clone()
926 } else {
927 unreachable!()
928 };
929 i += 1;
930
931 if i >= tokens.len() || tokens[i] != Token::In {
933 return Err("Expected 'in' after for variable".to_string());
934 }
935 i += 1;
936
937 let mut items = Vec::new();
939 while i < tokens.len() {
940 match &tokens[i] {
941 Token::Do => break,
942 Token::Semicolon | Token::Newline => {
943 i += 1;
944 if i < tokens.len() && tokens[i] == Token::Do {
946 break;
947 }
948 }
949 Token::Word(word) => {
950 items.push(word.clone());
951 i += 1;
952 }
953 _ => {
954 return Err(format!("Unexpected token in for items: {:?}", tokens[i]));
955 }
956 }
957 }
958
959 while i < tokens.len() && tokens[i] == Token::Newline {
961 i += 1;
962 }
963
964 if i >= tokens.len() || tokens[i] != Token::Do {
966 return Err("Expected 'do' in for loop".to_string());
967 }
968 i += 1;
969
970 while i < tokens.len() && tokens[i] == Token::Newline {
972 i += 1;
973 }
974
975 let mut body_tokens = Vec::new();
977 let mut depth = 0;
978 while i < tokens.len() {
979 match &tokens[i] {
980 Token::For => {
981 depth += 1;
982 body_tokens.push(tokens[i].clone());
983 }
984 Token::Done => {
985 if depth > 0 {
986 depth -= 1;
987 body_tokens.push(tokens[i].clone());
988 } else {
989 break; }
991 }
992 Token::Newline => {
993 let mut j = i + 1;
995 while j < tokens.len() && tokens[j] == Token::Newline {
996 j += 1;
997 }
998 if j < tokens.len() && depth == 0 && tokens[j] == Token::Done {
999 i = j; break;
1001 }
1002 body_tokens.push(tokens[i].clone());
1004 }
1005 _ => {
1006 body_tokens.push(tokens[i].clone());
1007 }
1008 }
1009 i += 1;
1010 }
1011
1012 if i >= tokens.len() || tokens[i] != Token::Done {
1013 return Err("Expected 'done' to close for loop".to_string());
1014 }
1015
1016 let body_ast = if body_tokens.is_empty() {
1018 Ast::Pipeline(vec![ShellCommand {
1020 args: vec!["true".to_string()],
1021 input: None,
1022 output: None,
1023 append: None,
1024 }])
1025 } else {
1026 parse_commands_sequentially(&body_tokens)?
1027 };
1028
1029 Ok(Ast::For {
1030 variable,
1031 items,
1032 body: Box::new(body_ast),
1033 })
1034}
1035
1036fn parse_while(tokens: &[Token]) -> Result<Ast, String> {
1037 let mut i = 1; let mut cond_tokens = Vec::new();
1041 while i < tokens.len() {
1042 match &tokens[i] {
1043 Token::Do => break,
1044 Token::Semicolon | Token::Newline => {
1045 i += 1;
1046 if i < tokens.len() && tokens[i] == Token::Do {
1048 break;
1049 }
1050 }
1051 _ => {
1052 cond_tokens.push(tokens[i].clone());
1053 i += 1;
1054 }
1055 }
1056 }
1057
1058 if cond_tokens.is_empty() {
1059 return Err("Expected condition after while".to_string());
1060 }
1061
1062 while i < tokens.len() && tokens[i] == Token::Newline {
1064 i += 1;
1065 }
1066
1067 if i >= tokens.len() || tokens[i] != Token::Do {
1069 return Err("Expected 'do' in while loop".to_string());
1070 }
1071 i += 1;
1072
1073 while i < tokens.len() && tokens[i] == Token::Newline {
1075 i += 1;
1076 }
1077
1078 let mut body_tokens = Vec::new();
1080 let mut depth = 0;
1081 while i < tokens.len() {
1082 match &tokens[i] {
1083 Token::While | Token::For => {
1084 depth += 1;
1085 body_tokens.push(tokens[i].clone());
1086 }
1087 Token::Done => {
1088 if depth > 0 {
1089 depth -= 1;
1090 body_tokens.push(tokens[i].clone());
1091 } else {
1092 break; }
1094 }
1095 Token::Newline => {
1096 let mut j = i + 1;
1098 while j < tokens.len() && tokens[j] == Token::Newline {
1099 j += 1;
1100 }
1101 if j < tokens.len() && depth == 0 && tokens[j] == Token::Done {
1102 i = j; break;
1104 }
1105 body_tokens.push(tokens[i].clone());
1107 }
1108 _ => {
1109 body_tokens.push(tokens[i].clone());
1110 }
1111 }
1112 i += 1;
1113 }
1114
1115 if i >= tokens.len() || tokens[i] != Token::Done {
1116 return Err("Expected 'done' to close while loop".to_string());
1117 }
1118
1119 let condition_ast = parse_slice(&cond_tokens)?;
1121
1122 let body_ast = if body_tokens.is_empty() {
1124 Ast::Pipeline(vec![ShellCommand {
1126 args: vec!["true".to_string()],
1127 input: None,
1128 output: None,
1129 append: None,
1130 }])
1131 } else {
1132 parse_commands_sequentially(&body_tokens)?
1133 };
1134
1135 Ok(Ast::While {
1136 condition: Box::new(condition_ast),
1137 body: Box::new(body_ast),
1138 })
1139}
1140
1141fn parse_function_definition(tokens: &[Token]) -> Result<Ast, String> {
1142 if tokens.len() < 2 {
1143 return Err("Function definition too short".to_string());
1144 }
1145
1146 let func_name = if let Token::Word(word) = &tokens[0] {
1148 if let Some(paren_pos) = word.find('(') {
1150 if word.ends_with(')') && paren_pos > 0 {
1151 word[..paren_pos].to_string()
1152 } else {
1153 word.clone()
1154 }
1155 } else {
1156 word.clone()
1157 }
1158 } else {
1159 return Err("Function name must be a word".to_string());
1160 };
1161
1162 let brace_pos =
1164 if tokens.len() >= 4 && tokens[1] == Token::LeftParen && tokens[2] == Token::RightParen {
1165 if tokens[3] != Token::LeftBrace {
1167 return Err("Expected { after function name".to_string());
1168 }
1169 3
1170 } else if tokens.len() >= 2 && tokens[1] == Token::LeftBrace {
1171 1
1173 } else {
1174 return Err("Expected ( after function name or { for legacy format".to_string());
1175 };
1176
1177 let mut brace_depth = 0;
1179 let mut body_end = 0;
1180 let mut found_closing = false;
1181 let mut i = brace_pos + 1;
1182
1183 while i < tokens.len() {
1184 if i + 3 < tokens.len()
1187 && matches!(&tokens[i], Token::Word(_))
1188 && tokens[i + 1] == Token::LeftParen
1189 && tokens[i + 2] == Token::RightParen
1190 && tokens[i + 3] == Token::LeftBrace
1191 {
1192 i += 4;
1195 let mut nested_depth = 1;
1196 while i < tokens.len() && nested_depth > 0 {
1197 match tokens[i] {
1198 Token::LeftBrace => nested_depth += 1,
1199 Token::RightBrace => nested_depth -= 1,
1200 _ => {}
1201 }
1202 i += 1;
1203 }
1204 continue;
1206 }
1207
1208 match &tokens[i] {
1209 Token::LeftBrace => {
1210 brace_depth += 1;
1211 i += 1;
1212 }
1213 Token::RightBrace => {
1214 if brace_depth == 0 {
1215 body_end = i;
1217 found_closing = true;
1218 break;
1219 } else {
1220 brace_depth -= 1;
1221 i += 1;
1222 }
1223 }
1224 Token::If => {
1225 let mut if_depth = 1;
1227 i += 1;
1228 while i < tokens.len() && if_depth > 0 {
1229 match tokens[i] {
1230 Token::If => if_depth += 1,
1231 Token::Fi => if_depth -= 1,
1232 _ => {}
1233 }
1234 i += 1;
1235 }
1236 }
1237 Token::For | Token::While => {
1238 let mut for_depth = 1;
1240 i += 1;
1241 while i < tokens.len() && for_depth > 0 {
1242 match tokens[i] {
1243 Token::For | Token::While => for_depth += 1,
1244 Token::Done => for_depth -= 1,
1245 _ => {}
1246 }
1247 i += 1;
1248 }
1249 }
1250 Token::Case => {
1251 i += 1;
1253 while i < tokens.len() {
1254 if tokens[i] == Token::Esac {
1255 i += 1;
1256 break;
1257 }
1258 i += 1;
1259 }
1260 }
1261 _ => {
1262 i += 1;
1263 }
1264 }
1265 }
1266
1267 if !found_closing {
1268 return Err("Missing closing } for function definition".to_string());
1269 }
1270
1271 let body_tokens = &tokens[brace_pos + 1..body_end];
1273
1274 let body_ast = if body_tokens.is_empty() {
1276 Ast::Pipeline(vec![ShellCommand {
1278 args: vec!["true".to_string()],
1279 input: None,
1280 output: None,
1281 append: None,
1282 }])
1283 } else {
1284 parse_commands_sequentially(body_tokens)?
1285 };
1286
1287 Ok(Ast::FunctionDefinition {
1288 name: func_name,
1289 body: Box::new(body_ast),
1290 })
1291}
1292
1293#[cfg(test)]
1294mod tests {
1295 use super::super::lexer::Token;
1296 use super::*;
1297
1298 #[test]
1299 fn test_single_command() {
1300 let tokens = vec![Token::Word("ls".to_string())];
1301 let result = parse(tokens).unwrap();
1302 assert_eq!(
1303 result,
1304 Ast::Pipeline(vec![ShellCommand {
1305 args: vec!["ls".to_string()],
1306 input: None,
1307 output: None,
1308 append: None,
1309 }])
1310 );
1311 }
1312
1313 #[test]
1314 fn test_command_with_args() {
1315 let tokens = vec![
1316 Token::Word("ls".to_string()),
1317 Token::Word("-la".to_string()),
1318 ];
1319 let result = parse(tokens).unwrap();
1320 assert_eq!(
1321 result,
1322 Ast::Pipeline(vec![ShellCommand {
1323 args: vec!["ls".to_string(), "-la".to_string()],
1324 input: None,
1325 output: None,
1326 append: None,
1327 }])
1328 );
1329 }
1330
1331 #[test]
1332 fn test_pipeline() {
1333 let tokens = vec![
1334 Token::Word("ls".to_string()),
1335 Token::Pipe,
1336 Token::Word("grep".to_string()),
1337 Token::Word("txt".to_string()),
1338 ];
1339 let result = parse(tokens).unwrap();
1340 assert_eq!(
1341 result,
1342 Ast::Pipeline(vec![
1343 ShellCommand {
1344 args: vec!["ls".to_string()],
1345 input: None,
1346 output: None,
1347 append: None,
1348 },
1349 ShellCommand {
1350 args: vec!["grep".to_string(), "txt".to_string()],
1351 input: None,
1352 output: None,
1353 append: None,
1354 }
1355 ])
1356 );
1357 }
1358
1359 #[test]
1360 fn test_input_redirection() {
1361 let tokens = vec![
1362 Token::Word("cat".to_string()),
1363 Token::RedirIn,
1364 Token::Word("input.txt".to_string()),
1365 ];
1366 let result = parse(tokens).unwrap();
1367 assert_eq!(
1368 result,
1369 Ast::Pipeline(vec![ShellCommand {
1370 args: vec!["cat".to_string()],
1371 input: Some("input.txt".to_string()),
1372 output: None,
1373 append: None,
1374 }])
1375 );
1376 }
1377
1378 #[test]
1379 fn test_output_redirection() {
1380 let tokens = vec![
1381 Token::Word("printf".to_string()),
1382 Token::Word("hello".to_string()),
1383 Token::RedirOut,
1384 Token::Word("output.txt".to_string()),
1385 ];
1386 let result = parse(tokens).unwrap();
1387 assert_eq!(
1388 result,
1389 Ast::Pipeline(vec![ShellCommand {
1390 args: vec!["printf".to_string(), "hello".to_string()],
1391 input: None,
1392 output: Some("output.txt".to_string()),
1393 append: None,
1394 }])
1395 );
1396 }
1397
1398 #[test]
1399 fn test_append_redirection() {
1400 let tokens = vec![
1401 Token::Word("printf".to_string()),
1402 Token::Word("hello".to_string()),
1403 Token::RedirAppend,
1404 Token::Word("output.txt".to_string()),
1405 ];
1406 let result = parse(tokens).unwrap();
1407 assert_eq!(
1408 result,
1409 Ast::Pipeline(vec![ShellCommand {
1410 args: vec!["printf".to_string(), "hello".to_string()],
1411 input: None,
1412 output: None,
1413 append: Some("output.txt".to_string()),
1414 }])
1415 );
1416 }
1417
1418 #[test]
1419 fn test_complex_pipeline_with_redirections() {
1420 let tokens = vec![
1421 Token::Word("cat".to_string()),
1422 Token::RedirIn,
1423 Token::Word("input.txt".to_string()),
1424 Token::Pipe,
1425 Token::Word("grep".to_string()),
1426 Token::Word("pattern".to_string()),
1427 Token::Pipe,
1428 Token::Word("sort".to_string()),
1429 Token::RedirOut,
1430 Token::Word("output.txt".to_string()),
1431 ];
1432 let result = parse(tokens).unwrap();
1433 assert_eq!(
1434 result,
1435 Ast::Pipeline(vec![
1436 ShellCommand {
1437 args: vec!["cat".to_string()],
1438 input: Some("input.txt".to_string()),
1439 output: None,
1440 append: None,
1441 },
1442 ShellCommand {
1443 args: vec!["grep".to_string(), "pattern".to_string()],
1444 input: None,
1445 output: None,
1446 append: None,
1447 },
1448 ShellCommand {
1449 args: vec!["sort".to_string()],
1450 input: None,
1451 output: Some("output.txt".to_string()),
1452 append: None,
1453 }
1454 ])
1455 );
1456 }
1457
1458 #[test]
1459 fn test_empty_tokens() {
1460 let tokens = vec![];
1461 let result = parse(tokens);
1462 assert!(result.is_err());
1463 assert_eq!(result.unwrap_err(), "No commands found");
1464 }
1465
1466 #[test]
1467 fn test_only_pipe() {
1468 let tokens = vec![Token::Pipe];
1469 let result = parse(tokens);
1470 assert!(result.is_err());
1471 assert_eq!(result.unwrap_err(), "No commands found");
1472 }
1473
1474 #[test]
1475 fn test_redirection_without_file() {
1476 let tokens = vec![Token::Word("cat".to_string()), Token::RedirIn];
1478 let result = parse(tokens).unwrap();
1479 assert_eq!(
1480 result,
1481 Ast::Pipeline(vec![ShellCommand {
1482 args: vec!["cat".to_string()],
1483 input: None,
1484 output: None,
1485 append: None,
1486 }])
1487 );
1488 }
1489
1490 #[test]
1491 fn test_multiple_redirections() {
1492 let tokens = vec![
1493 Token::Word("cat".to_string()),
1494 Token::RedirIn,
1495 Token::Word("file1.txt".to_string()),
1496 Token::RedirOut,
1497 Token::Word("file2.txt".to_string()),
1498 ];
1499 let result = parse(tokens).unwrap();
1500 assert_eq!(
1501 result,
1502 Ast::Pipeline(vec![ShellCommand {
1503 args: vec!["cat".to_string()],
1504 input: Some("file1.txt".to_string()),
1505 output: Some("file2.txt".to_string()),
1506 append: None,
1507 }])
1508 );
1509 }
1510
1511 #[test]
1512 fn test_parse_if() {
1513 let tokens = vec![
1514 Token::If,
1515 Token::Word("true".to_string()),
1516 Token::Semicolon,
1517 Token::Then,
1518 Token::Word("printf".to_string()),
1519 Token::Word("yes".to_string()),
1520 Token::Semicolon,
1521 Token::Fi,
1522 ];
1523 let result = parse(tokens).unwrap();
1524 if let Ast::If {
1525 branches,
1526 else_branch,
1527 } = result
1528 {
1529 assert_eq!(branches.len(), 1);
1530 let (condition, then_branch) = &branches[0];
1531 if let Ast::Pipeline(cmds) = &**condition {
1532 assert_eq!(cmds[0].args, vec!["true"]);
1533 } else {
1534 panic!("condition not pipeline");
1535 }
1536 if let Ast::Pipeline(cmds) = &**then_branch {
1537 assert_eq!(cmds[0].args, vec!["printf", "yes"]);
1538 } else {
1539 panic!("then_branch not pipeline");
1540 }
1541 assert!(else_branch.is_none());
1542 } else {
1543 panic!("not if");
1544 }
1545 }
1546
1547 #[test]
1548 fn test_parse_if_elif() {
1549 let tokens = vec![
1550 Token::If,
1551 Token::Word("false".to_string()),
1552 Token::Semicolon,
1553 Token::Then,
1554 Token::Word("printf".to_string()),
1555 Token::Word("no".to_string()),
1556 Token::Semicolon,
1557 Token::Elif,
1558 Token::Word("true".to_string()),
1559 Token::Semicolon,
1560 Token::Then,
1561 Token::Word("printf".to_string()),
1562 Token::Word("yes".to_string()),
1563 Token::Semicolon,
1564 Token::Fi,
1565 ];
1566 let result = parse(tokens).unwrap();
1567 if let Ast::If {
1568 branches,
1569 else_branch,
1570 } = result
1571 {
1572 assert_eq!(branches.len(), 2);
1573 let (condition1, then1) = &branches[0];
1575 if let Ast::Pipeline(cmds) = &**condition1 {
1576 assert_eq!(cmds[0].args, vec!["false"]);
1577 }
1578 if let Ast::Pipeline(cmds) = &**then1 {
1579 assert_eq!(cmds[0].args, vec!["printf", "no"]);
1580 }
1581 let (condition2, then2) = &branches[1];
1583 if let Ast::Pipeline(cmds) = &**condition2 {
1584 assert_eq!(cmds[0].args, vec!["true"]);
1585 }
1586 if let Ast::Pipeline(cmds) = &**then2 {
1587 assert_eq!(cmds[0].args, vec!["printf", "yes"]);
1588 }
1589 assert!(else_branch.is_none());
1590 } else {
1591 panic!("not if");
1592 }
1593 }
1594
1595 #[test]
1596 fn test_parse_assignment() {
1597 let tokens = vec![Token::Word("MY_VAR=test_value".to_string())];
1598 let result = parse(tokens).unwrap();
1599 if let Ast::Assignment { var, value } = result {
1600 assert_eq!(var, "MY_VAR");
1601 assert_eq!(value, "test_value");
1602 } else {
1603 panic!("not assignment");
1604 }
1605 }
1606
1607 #[test]
1608 fn test_parse_assignment_quoted() {
1609 let tokens = vec![Token::Word("MY_VAR=hello world".to_string())];
1610 let result = parse(tokens).unwrap();
1611 if let Ast::Assignment { var, value } = result {
1612 assert_eq!(var, "MY_VAR");
1613 assert_eq!(value, "hello world");
1614 } else {
1615 panic!("not assignment");
1616 }
1617 }
1618
1619 #[test]
1620 fn test_parse_assignment_invalid() {
1621 let tokens = vec![Token::Word("123VAR=value".to_string())];
1623 let result = parse(tokens).unwrap();
1624 if let Ast::Pipeline(cmds) = result {
1625 assert_eq!(cmds[0].args, vec!["123VAR=value"]);
1626 } else {
1627 panic!("should be parsed as pipeline");
1628 }
1629 }
1630
1631 #[test]
1632 fn test_parse_function_definition() {
1633 let tokens = vec![
1634 Token::Word("myfunc".to_string()),
1635 Token::LeftParen,
1636 Token::RightParen,
1637 Token::LeftBrace,
1638 Token::Word("echo".to_string()),
1639 Token::Word("hello".to_string()),
1640 Token::RightBrace,
1641 ];
1642 let result = parse(tokens).unwrap();
1643 if let Ast::FunctionDefinition { name, body } = result {
1644 assert_eq!(name, "myfunc");
1645 if let Ast::Pipeline(cmds) = *body {
1647 assert_eq!(cmds[0].args, vec!["echo", "hello"]);
1648 } else {
1649 panic!("function body should be a pipeline");
1650 }
1651 } else {
1652 panic!("should be parsed as function definition");
1653 }
1654 }
1655
1656 #[test]
1657 fn test_parse_function_definition_empty() {
1658 let tokens = vec![
1659 Token::Word("emptyfunc".to_string()),
1660 Token::LeftParen,
1661 Token::RightParen,
1662 Token::LeftBrace,
1663 Token::RightBrace,
1664 ];
1665 let result = parse(tokens).unwrap();
1666 if let Ast::FunctionDefinition { name, body } = result {
1667 assert_eq!(name, "emptyfunc");
1668 if let Ast::Pipeline(cmds) = *body {
1670 assert_eq!(cmds[0].args, vec!["true"]);
1671 } else {
1672 panic!("function body should be a pipeline");
1673 }
1674 } else {
1675 panic!("should be parsed as function definition");
1676 }
1677 }
1678
1679 #[test]
1680 fn test_parse_function_definition_legacy_format() {
1681 let tokens = vec![
1683 Token::Word("legacyfunc()".to_string()),
1684 Token::LeftBrace,
1685 Token::Word("echo".to_string()),
1686 Token::Word("hello".to_string()),
1687 Token::RightBrace,
1688 ];
1689 let result = parse(tokens).unwrap();
1690 if let Ast::FunctionDefinition { name, body } = result {
1691 assert_eq!(name, "legacyfunc");
1692 if let Ast::Pipeline(cmds) = *body {
1694 assert_eq!(cmds[0].args, vec!["echo", "hello"]);
1695 } else {
1696 panic!("function body should be a pipeline");
1697 }
1698 } else {
1699 panic!("should be parsed as function definition");
1700 }
1701 }
1702
1703 #[test]
1704 fn test_parse_local_assignment() {
1705 let tokens = vec![Token::Local, Token::Word("MY_VAR=test_value".to_string())];
1706 let result = parse(tokens).unwrap();
1707 if let Ast::LocalAssignment { var, value } = result {
1708 assert_eq!(var, "MY_VAR");
1709 assert_eq!(value, "test_value");
1710 } else {
1711 panic!("should be parsed as local assignment");
1712 }
1713 }
1714
1715 #[test]
1716 fn test_parse_local_assignment_separate_tokens() {
1717 let tokens = vec![
1718 Token::Local,
1719 Token::Word("MY_VAR".to_string()),
1720 Token::Word("test_value".to_string()),
1721 ];
1722 let result = parse(tokens).unwrap();
1723 if let Ast::LocalAssignment { var, value } = result {
1724 assert_eq!(var, "MY_VAR");
1725 assert_eq!(value, "test_value");
1726 } else {
1727 panic!("should be parsed as local assignment");
1728 }
1729 }
1730
1731 #[test]
1732 fn test_parse_local_assignment_invalid_var_name() {
1733 let tokens = vec![Token::Local, Token::Word("123VAR=value".to_string())];
1735 let result = parse(tokens);
1736 assert!(result.is_err());
1738 }
1739}