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, Default)]
55pub struct ShellCommand {
56 pub args: Vec<String>,
57 pub input: Option<String>,
58 pub output: Option<String>,
59 pub append: Option<String>,
60 pub here_doc_delimiter: Option<String>, pub here_doc_quoted: bool, pub here_string_content: Option<String>, }
64
65fn is_valid_variable_name(name: &str) -> bool {
68 if let Some(first_char) = name.chars().next() {
69 first_char.is_alphabetic() || first_char == '_'
70 } else {
71 false
72 }
73}
74
75fn create_empty_body_ast() -> Ast {
78 Ast::Pipeline(vec![ShellCommand {
79 args: vec!["true".to_string()],
80 input: None,
81 output: None,
82 append: None,
83 here_doc_delimiter: None,
84 here_doc_quoted: false,
85 here_string_content: None,
86 }])
87}
88
89fn skip_newlines(tokens: &[Token], i: &mut usize) {
92 while *i < tokens.len() && tokens[*i] == Token::Newline {
93 *i += 1;
94 }
95}
96
97fn skip_to_matching_fi(tokens: &[Token], i: &mut usize) {
100 let mut if_depth = 1;
101 *i += 1; while *i < tokens.len() && if_depth > 0 {
103 match tokens[*i] {
104 Token::If => if_depth += 1,
105 Token::Fi => if_depth -= 1,
106 _ => {}
107 }
108 *i += 1;
109 }
110}
111
112fn skip_to_matching_done(tokens: &[Token], i: &mut usize) {
115 let mut loop_depth = 1;
116 *i += 1; while *i < tokens.len() && loop_depth > 0 {
118 match tokens[*i] {
119 Token::For | Token::While => loop_depth += 1,
120 Token::Done => loop_depth -= 1,
121 _ => {}
122 }
123 *i += 1;
124 }
125}
126
127fn skip_to_matching_esac(tokens: &[Token], i: &mut usize) {
129 *i += 1; while *i < tokens.len() {
131 if tokens[*i] == Token::Esac {
132 *i += 1;
133 break;
134 }
135 *i += 1;
136 }
137}
138
139pub fn parse(tokens: Vec<Token>) -> Result<Ast, String> {
140 if tokens.len() >= 4
142 && let (Token::Word(_), Token::LeftParen, Token::RightParen, Token::LeftBrace) =
143 (&tokens[0], &tokens[1], &tokens[2], &tokens[3])
144 {
145 let mut brace_depth = 1; let mut function_end = tokens.len();
149 let mut j = 4; while j < tokens.len() {
152 match &tokens[j] {
153 Token::LeftBrace => {
154 brace_depth += 1;
155 j += 1;
156 }
157 Token::RightBrace => {
158 brace_depth -= 1;
159 if brace_depth == 0 {
160 function_end = j + 1; break;
162 }
163 j += 1;
164 }
165 Token::If => {
166 let mut if_depth = 1;
168 j += 1;
169 while j < tokens.len() && if_depth > 0 {
170 match tokens[j] {
171 Token::If => if_depth += 1,
172 Token::Fi => if_depth -= 1,
173 _ => {}
174 }
175 j += 1;
176 }
177 }
178 Token::For | Token::While => {
179 let mut for_depth = 1;
181 j += 1;
182 while j < tokens.len() && for_depth > 0 {
183 match tokens[j] {
184 Token::For | Token::While => for_depth += 1,
185 Token::Done => for_depth -= 1,
186 _ => {}
187 }
188 j += 1;
189 }
190 }
191 Token::Case => {
192 j += 1;
194 while j < tokens.len() {
195 if tokens[j] == Token::Esac {
196 j += 1;
197 break;
198 }
199 j += 1;
200 }
201 }
202 _ => {
203 j += 1;
204 }
205 }
206 }
207
208 if brace_depth == 0 && function_end <= tokens.len() {
209 let function_tokens = &tokens[0..function_end];
211 let remaining_tokens = &tokens[function_end..];
212
213 let function_ast = parse_function_definition(function_tokens)?;
214
215 return if remaining_tokens.is_empty() {
216 Ok(function_ast)
217 } else {
218 let remaining_ast = parse_commands_sequentially(remaining_tokens)?;
220 Ok(Ast::Sequence(vec![function_ast, remaining_ast]))
221 };
222 }
223 }
224
225 if tokens.len() >= 2
227 && let Token::Word(ref word) = tokens[0]
228 && let Some(paren_pos) = word.find('(')
229 && word.ends_with(')')
230 && paren_pos > 0
231 && tokens[1] == Token::LeftBrace
232 {
233 return parse_function_definition(&tokens);
234 }
235
236 parse_commands_sequentially(&tokens)
238}
239
240fn parse_slice(tokens: &[Token]) -> Result<Ast, String> {
241 if tokens.is_empty() {
242 return Err("No commands found".to_string());
243 }
244
245 if tokens.len() == 2 {
247 if let (Token::Word(var_eq), Token::Word(value)) = (&tokens[0], &tokens[1])
249 && let Some(eq_pos) = var_eq.find('=')
250 && eq_pos > 0
251 && eq_pos < var_eq.len()
252 {
253 let var = var_eq[..eq_pos].to_string();
254 let full_value = format!("{}{}", &var_eq[eq_pos + 1..], value);
255 if is_valid_variable_name(&var) {
257 return Ok(Ast::Assignment {
258 var,
259 value: full_value,
260 });
261 }
262 }
263 }
264
265 if tokens.len() == 2
267 && let (Token::Word(var_eq), Token::Word(value)) = (&tokens[0], &tokens[1])
268 && let Some(eq_pos) = var_eq.find('=')
269 && eq_pos > 0
270 && eq_pos == var_eq.len() - 1
271 {
272 let var = var_eq[..eq_pos].to_string();
273 if is_valid_variable_name(&var) {
275 return Ok(Ast::Assignment {
276 var,
277 value: value.clone(),
278 });
279 }
280 }
281
282 if tokens.len() == 3
284 && let (Token::Local, Token::Word(var), Token::Word(value)) =
285 (&tokens[0], &tokens[1], &tokens[2])
286 {
287 let clean_var = if var.ends_with('=') {
289 &var[..var.len() - 1]
290 } else {
291 var
292 };
293 if is_valid_variable_name(clean_var) {
295 return Ok(Ast::LocalAssignment {
296 var: clean_var.to_string(),
297 value: value.clone(),
298 });
299 }
300 }
301
302 if !tokens.is_empty()
304 && tokens.len() <= 2
305 && let Token::Return = &tokens[0]
306 {
307 if tokens.len() == 1 {
308 return Ok(Ast::Return { value: None });
310 } else if let Token::Word(word) = &tokens[1] {
311 return Ok(Ast::Return {
313 value: Some(word.clone()),
314 });
315 }
316 }
317
318 if tokens.len() == 2
320 && let (Token::Local, Token::Word(var_eq)) = (&tokens[0], &tokens[1])
321 && let Some(eq_pos) = var_eq.find('=')
322 && eq_pos > 0
323 && eq_pos < var_eq.len()
324 {
325 let var = var_eq[..eq_pos].to_string();
326 let value = var_eq[eq_pos + 1..].to_string();
327 if is_valid_variable_name(&var) {
329 return Ok(Ast::LocalAssignment { var, value });
330 }
331 }
332
333 if tokens.len() == 1
335 && let Token::Word(ref word) = tokens[0]
336 && let Some(eq_pos) = word.find('=')
337 && eq_pos > 0
338 && eq_pos < word.len()
339 {
340 let var = word[..eq_pos].to_string();
341 let value = word[eq_pos + 1..].to_string();
342 if is_valid_variable_name(&var) {
344 return Ok(Ast::Assignment { var, value });
345 }
346 }
347
348 if let Token::If = tokens[0] {
350 return parse_if(tokens);
351 }
352
353 if let Token::Case = tokens[0] {
355 return parse_case(tokens);
356 }
357
358 if let Token::For = tokens[0] {
360 return parse_for(tokens);
361 }
362
363 if let Token::While = tokens[0] {
365 return parse_while(tokens);
366 }
367
368 if tokens.len() >= 4
371 && let (Token::Word(word), Token::LeftParen, Token::RightParen, Token::LeftBrace) =
372 (&tokens[0], &tokens[1], &tokens[2], &tokens[3])
373 && is_valid_variable_name(word)
374 {
375 return parse_function_definition(tokens);
376 }
377
378 if tokens.len() >= 2
380 && let Token::Word(ref word) = tokens[0]
381 && let Some(paren_pos) = word.find('(')
382 && word.ends_with(')')
383 && paren_pos > 0
384 {
385 let func_name = &word[..paren_pos];
386 if is_valid_variable_name(func_name) && tokens[1] == Token::LeftBrace {
387 return parse_function_definition(tokens);
388 }
389 }
390
391 parse_pipeline(tokens)
396}
397
398fn parse_commands_sequentially(tokens: &[Token]) -> Result<Ast, String> {
399 let mut i = 0;
400 let mut commands = Vec::new();
401
402 while i < tokens.len() {
403 while i < tokens.len() {
405 match &tokens[i] {
406 Token::Newline => {
407 i += 1;
408 }
409 Token::Word(word) if word.starts_with('#') => {
410 while i < tokens.len() && tokens[i] != Token::Newline {
412 i += 1;
413 }
414 if i < tokens.len() {
415 i += 1; }
417 }
418 _ => break,
419 }
420 }
421
422 if i >= tokens.len() {
423 break;
424 }
425
426 let start = i;
428
429 if tokens[i] == Token::If {
431 let mut depth = 0;
433 while i < tokens.len() {
434 match tokens[i] {
435 Token::If => depth += 1,
436 Token::Fi => {
437 depth -= 1;
438 if depth == 0 {
439 i += 1; break;
441 }
442 }
443 _ => {}
444 }
445 i += 1;
446 }
447
448 } else if tokens[i] == Token::For {
451 let mut depth = 1; i += 1; while i < tokens.len() {
455 match tokens[i] {
456 Token::For | Token::While => depth += 1,
457 Token::Done => {
458 depth -= 1;
459 if depth == 0 {
460 i += 1; break;
462 }
463 }
464 _ => {}
465 }
466 i += 1;
467 }
468 } else if tokens[i] == Token::While {
469 let mut depth = 1; i += 1; while i < tokens.len() {
473 match tokens[i] {
474 Token::While | Token::For => depth += 1,
475 Token::Done => {
476 depth -= 1;
477 if depth == 0 {
478 i += 1; break;
480 }
481 }
482 _ => {}
483 }
484 i += 1;
485 }
486 } else if tokens[i] == Token::Case {
487 while i < tokens.len() {
489 if tokens[i] == Token::Esac {
490 i += 1; break;
492 }
493 i += 1;
494 }
495 } else if i + 3 < tokens.len()
496 && matches!(tokens[i], Token::Word(_))
497 && tokens[i + 1] == Token::LeftParen
498 && tokens[i + 2] == Token::RightParen
499 && tokens[i + 3] == Token::LeftBrace
500 {
501 let mut brace_depth = 1;
503 i += 4; while i < tokens.len() && brace_depth > 0 {
505 match tokens[i] {
506 Token::LeftBrace => brace_depth += 1,
507 Token::RightBrace => brace_depth -= 1,
508 _ => {}
509 }
510 i += 1;
511 }
512 } else {
513 while i < tokens.len() {
516 if tokens[i] == Token::Newline
517 || tokens[i] == Token::Semicolon
518 || tokens[i] == Token::And
519 || tokens[i] == Token::Or
520 {
521 let mut j = i + 1;
523 while j < tokens.len() && tokens[j] == Token::Newline {
524 j += 1;
525 }
526 if j < tokens.len()
528 && (tokens[j] == Token::Else
529 || tokens[j] == Token::Elif
530 || tokens[j] == Token::Fi)
531 {
532 i = j + 1;
534 continue;
535 }
536 break;
537 }
538 i += 1;
539 }
540 }
541
542 let command_tokens = &tokens[start..i];
543 if !command_tokens.is_empty() {
544 if command_tokens.len() == 1 {
546 match command_tokens[0] {
547 Token::Else | Token::Elif | Token::Fi => {
548 if i < tokens.len()
550 && (tokens[i] == Token::Newline || tokens[i] == Token::Semicolon)
551 {
552 i += 1;
553 }
554 continue;
555 }
556 _ => {}
557 }
558 }
559
560 let ast = parse_slice(command_tokens)?;
561
562 if i < tokens.len() && (tokens[i] == Token::And || tokens[i] == Token::Or) {
564 let operator = tokens[i].clone();
565 i += 1; while i < tokens.len() && tokens[i] == Token::Newline {
569 i += 1;
570 }
571
572 let remaining_tokens = &tokens[i..];
574 let right_ast = parse_commands_sequentially(remaining_tokens)?;
575
576 let combined_ast = match operator {
578 Token::And => Ast::And {
579 left: Box::new(ast),
580 right: Box::new(right_ast),
581 },
582 Token::Or => Ast::Or {
583 left: Box::new(ast),
584 right: Box::new(right_ast),
585 },
586 _ => unreachable!(),
587 };
588
589 commands.push(combined_ast);
590 break; } else {
592 commands.push(ast);
593 }
594 }
595
596 if i < tokens.len() && (tokens[i] == Token::Newline || tokens[i] == Token::Semicolon) {
597 i += 1;
598 }
599 }
600
601 if commands.is_empty() {
602 return Err("No commands found".to_string());
603 }
604
605 if commands.len() == 1 {
606 Ok(commands.into_iter().next().unwrap())
607 } else {
608 Ok(Ast::Sequence(commands))
609 }
610}
611
612fn parse_pipeline(tokens: &[Token]) -> Result<Ast, String> {
613 let mut commands = Vec::new();
614 let mut current_cmd = ShellCommand::default();
615
616 let mut i = 0;
617 while i < tokens.len() {
618 let token = &tokens[i];
619 match token {
620 Token::Word(word) => {
621 current_cmd.args.push(word.clone());
622 }
623 Token::Pipe => {
624 if !current_cmd.args.is_empty() {
625 commands.push(current_cmd.clone());
626 current_cmd = ShellCommand::default();
627 }
628 }
629 Token::RedirIn => {
630 i += 1;
631 if i < tokens.len()
632 && let Token::Word(ref file) = tokens[i]
633 {
634 current_cmd.input = Some(file.clone());
635 }
636 }
637 Token::RedirOut => {
638 i += 1;
639 if i < tokens.len()
640 && let Token::Word(ref file) = tokens[i]
641 {
642 current_cmd.output = Some(file.clone());
643 }
644 }
645 Token::RedirAppend => {
646 i += 1;
647 if i < tokens.len()
648 && let Token::Word(ref file) = tokens[i]
649 {
650 current_cmd.append = Some(file.clone());
651 }
652 }
653 Token::RedirHereDoc(delimiter, quoted) => {
654 current_cmd.here_doc_delimiter = Some(delimiter.clone());
655 current_cmd.here_doc_quoted = *quoted;
656 }
657 Token::RedirHereString(content) => {
658 current_cmd.here_string_content = Some(content.clone());
659 }
660 Token::RightParen => {
661 if !current_cmd.args.is_empty()
664 && i > 0
665 && let Token::LeftParen = tokens[i - 1]
666 {
667 break;
671 }
672 return Err("Unexpected ) in pipeline".to_string());
673 }
674 Token::Newline => {
675 i += 1;
677 continue;
678 }
679 Token::Do
680 | Token::Done
681 | Token::Then
682 | Token::Else
683 | Token::Elif
684 | Token::Fi
685 | Token::Esac => {
686 break;
689 }
690 _ => {
691 return Err(format!("Unexpected token in pipeline: {:?}", token));
692 }
693 }
694 i += 1;
695 }
696
697 if !current_cmd.args.is_empty() {
698 commands.push(current_cmd);
699 }
700
701 if commands.is_empty() {
702 return Err("No commands found".to_string());
703 }
704
705 Ok(Ast::Pipeline(commands))
706}
707
708fn parse_if(tokens: &[Token]) -> Result<Ast, String> {
709 let mut i = 1; let mut branches = Vec::new();
711
712 loop {
713 let mut cond_tokens = Vec::new();
715 while i < tokens.len()
716 && tokens[i] != Token::Semicolon
717 && tokens[i] != Token::Newline
718 && tokens[i] != Token::Then
719 {
720 cond_tokens.push(tokens[i].clone());
721 i += 1;
722 }
723
724 if i < tokens.len() && (tokens[i] == Token::Semicolon || tokens[i] == Token::Newline) {
726 i += 1;
727 }
728
729 skip_newlines(tokens, &mut i);
731
732 if i >= tokens.len() || tokens[i] != Token::Then {
733 return Err("Expected then after if/elif condition".to_string());
734 }
735 i += 1; while i < tokens.len() && tokens[i] == Token::Newline {
739 i += 1;
740 }
741
742 let mut then_tokens = Vec::new();
745 let mut depth = 0;
746 while i < tokens.len() {
747 match &tokens[i] {
748 Token::If => {
749 depth += 1;
750 then_tokens.push(tokens[i].clone());
751 }
752 Token::Fi => {
753 if depth > 0 {
754 depth -= 1;
755 then_tokens.push(tokens[i].clone());
756 } else {
757 break; }
759 }
760 Token::Else | Token::Elif if depth == 0 => {
761 break; }
763 Token::Newline => {
764 let mut j = i + 1;
766 while j < tokens.len() && tokens[j] == Token::Newline {
767 j += 1;
768 }
769 if j < tokens.len()
770 && depth == 0
771 && (tokens[j] == Token::Else
772 || tokens[j] == Token::Elif
773 || tokens[j] == Token::Fi)
774 {
775 i = j; break;
777 }
778 then_tokens.push(tokens[i].clone());
780 }
781 _ => {
782 then_tokens.push(tokens[i].clone());
783 }
784 }
785 i += 1;
786 }
787
788 skip_newlines(tokens, &mut i);
790
791 let then_ast = if then_tokens.is_empty() {
792 create_empty_body_ast()
794 } else {
795 parse_commands_sequentially(&then_tokens)?
796 };
797
798 let condition = parse_slice(&cond_tokens)?;
799 branches.push((Box::new(condition), Box::new(then_ast)));
800
801 if i < tokens.len() && tokens[i] == Token::Elif {
803 i += 1; } else {
805 break;
806 }
807 }
808
809 let else_ast = if i < tokens.len() && tokens[i] == Token::Else {
810 i += 1; while i < tokens.len() && tokens[i] == Token::Newline {
814 i += 1;
815 }
816
817 let mut else_tokens = Vec::new();
818 let mut depth = 0;
819 while i < tokens.len() {
820 match &tokens[i] {
821 Token::If => {
822 depth += 1;
823 else_tokens.push(tokens[i].clone());
824 }
825 Token::Fi => {
826 if depth > 0 {
827 depth -= 1;
828 else_tokens.push(tokens[i].clone());
829 } else {
830 break; }
832 }
833 Token::Newline => {
834 let mut j = i + 1;
836 while j < tokens.len() && tokens[j] == Token::Newline {
837 j += 1;
838 }
839 if j < tokens.len() && depth == 0 && tokens[j] == Token::Fi {
840 i = j; break;
842 }
843 else_tokens.push(tokens[i].clone());
845 }
846 _ => {
847 else_tokens.push(tokens[i].clone());
848 }
849 }
850 i += 1;
851 }
852
853 let else_ast = if else_tokens.is_empty() {
854 create_empty_body_ast()
856 } else {
857 parse_commands_sequentially(&else_tokens)?
858 };
859
860 Some(Box::new(else_ast))
861 } else {
862 None
863 };
864
865 if i >= tokens.len() || tokens[i] != Token::Fi {
866 return Err("Expected fi".to_string());
867 }
868
869 Ok(Ast::If {
870 branches,
871 else_branch: else_ast,
872 })
873}
874
875fn parse_case(tokens: &[Token]) -> Result<Ast, String> {
876 let mut i = 1; if i >= tokens.len() || !matches!(tokens[i], Token::Word(_)) {
880 return Err("Expected word after case".to_string());
881 }
882 let word = if let Token::Word(ref w) = tokens[i] {
883 w.clone()
884 } else {
885 unreachable!()
886 };
887 i += 1;
888
889 if i >= tokens.len() || tokens[i] != Token::In {
890 return Err("Expected in after case word".to_string());
891 }
892 i += 1;
893
894 let mut cases = Vec::new();
895 let mut default = None;
896
897 loop {
898 while i < tokens.len() && tokens[i] == Token::Newline {
900 i += 1;
901 }
902
903 if i >= tokens.len() {
904 return Err("Unexpected end in case statement".to_string());
905 }
906
907 if tokens[i] == Token::Esac {
908 break;
909 }
910
911 let mut patterns = Vec::new();
913 while i < tokens.len() && tokens[i] != Token::RightParen {
914 if let Token::Word(ref p) = tokens[i] {
915 for pat in p.split('|') {
917 patterns.push(pat.to_string());
918 }
919 } else if tokens[i] == Token::Pipe {
920 } else if tokens[i] == Token::Newline {
922 } else {
924 return Err(format!("Expected pattern, found {:?}", tokens[i]));
925 }
926 i += 1;
927 }
928
929 if i >= tokens.len() || tokens[i] != Token::RightParen {
930 return Err("Expected ) after patterns".to_string());
931 }
932 i += 1;
933
934 let mut commands_tokens = Vec::new();
936 while i < tokens.len() && tokens[i] != Token::DoubleSemicolon && tokens[i] != Token::Esac {
937 commands_tokens.push(tokens[i].clone());
938 i += 1;
939 }
940
941 let commands_ast = parse_slice(&commands_tokens)?;
942
943 if i >= tokens.len() {
944 return Err("Unexpected end in case statement".to_string());
945 }
946
947 if tokens[i] == Token::DoubleSemicolon {
948 i += 1;
949 if patterns.len() == 1 && patterns[0] == "*" {
951 default = Some(Box::new(commands_ast));
952 } else {
953 cases.push((patterns, commands_ast));
954 }
955 } else if tokens[i] == Token::Esac {
956 if patterns.len() == 1 && patterns[0] == "*" {
958 default = Some(Box::new(commands_ast));
959 } else {
960 cases.push((patterns, commands_ast));
961 }
962 break;
963 } else {
964 return Err("Expected ;; or esac after commands".to_string());
965 }
966 }
967
968 Ok(Ast::Case {
969 word,
970 cases,
971 default,
972 })
973}
974
975fn parse_for(tokens: &[Token]) -> Result<Ast, String> {
976 let mut i = 1; if i >= tokens.len() || !matches!(tokens[i], Token::Word(_)) {
980 return Err("Expected variable name after for".to_string());
981 }
982 let variable = if let Token::Word(ref v) = tokens[i] {
983 v.clone()
984 } else {
985 unreachable!()
986 };
987 i += 1;
988
989 if i >= tokens.len() || tokens[i] != Token::In {
991 return Err("Expected 'in' after for variable".to_string());
992 }
993 i += 1;
994
995 let mut items = Vec::new();
997 while i < tokens.len() {
998 match &tokens[i] {
999 Token::Do => break,
1000 Token::Semicolon | Token::Newline => {
1001 i += 1;
1002 if i < tokens.len() && tokens[i] == Token::Do {
1004 break;
1005 }
1006 }
1007 Token::Word(word) => {
1008 items.push(word.clone());
1009 i += 1;
1010 }
1011 _ => {
1012 return Err(format!("Unexpected token in for items: {:?}", tokens[i]));
1013 }
1014 }
1015 }
1016
1017 while i < tokens.len() && tokens[i] == Token::Newline {
1019 i += 1;
1020 }
1021
1022 if i >= tokens.len() || tokens[i] != Token::Do {
1024 return Err("Expected 'do' in for loop".to_string());
1025 }
1026 i += 1;
1027
1028 while i < tokens.len() && tokens[i] == Token::Newline {
1030 i += 1;
1031 }
1032
1033 let mut body_tokens = Vec::new();
1035 let mut depth = 0;
1036 while i < tokens.len() {
1037 match &tokens[i] {
1038 Token::For => {
1039 depth += 1;
1040 body_tokens.push(tokens[i].clone());
1041 }
1042 Token::Done => {
1043 if depth > 0 {
1044 depth -= 1;
1045 body_tokens.push(tokens[i].clone());
1046 } else {
1047 break; }
1049 }
1050 Token::Newline => {
1051 let mut j = i + 1;
1053 while j < tokens.len() && tokens[j] == Token::Newline {
1054 j += 1;
1055 }
1056 if j < tokens.len() && depth == 0 && tokens[j] == Token::Done {
1057 i = j; break;
1059 }
1060 body_tokens.push(tokens[i].clone());
1062 }
1063 _ => {
1064 body_tokens.push(tokens[i].clone());
1065 }
1066 }
1067 i += 1;
1068 }
1069
1070 if i >= tokens.len() || tokens[i] != Token::Done {
1071 return Err("Expected 'done' to close for loop".to_string());
1072 }
1073
1074 let body_ast = if body_tokens.is_empty() {
1076 create_empty_body_ast()
1078 } else {
1079 parse_commands_sequentially(&body_tokens)?
1080 };
1081
1082 Ok(Ast::For {
1083 variable,
1084 items,
1085 body: Box::new(body_ast),
1086 })
1087}
1088
1089fn parse_while(tokens: &[Token]) -> Result<Ast, String> {
1090 let mut i = 1; let mut cond_tokens = Vec::new();
1094 while i < tokens.len() {
1095 match &tokens[i] {
1096 Token::Do => break,
1097 Token::Semicolon | Token::Newline => {
1098 i += 1;
1099 if i < tokens.len() && tokens[i] == Token::Do {
1101 break;
1102 }
1103 }
1104 _ => {
1105 cond_tokens.push(tokens[i].clone());
1106 i += 1;
1107 }
1108 }
1109 }
1110
1111 if cond_tokens.is_empty() {
1112 return Err("Expected condition after while".to_string());
1113 }
1114
1115 while i < tokens.len() && tokens[i] == Token::Newline {
1117 i += 1;
1118 }
1119
1120 if i >= tokens.len() || tokens[i] != Token::Do {
1122 return Err("Expected 'do' in while loop".to_string());
1123 }
1124 i += 1;
1125
1126 while i < tokens.len() && tokens[i] == Token::Newline {
1128 i += 1;
1129 }
1130
1131 let mut body_tokens = Vec::new();
1133 let mut depth = 0;
1134 while i < tokens.len() {
1135 match &tokens[i] {
1136 Token::While | Token::For => {
1137 depth += 1;
1138 body_tokens.push(tokens[i].clone());
1139 }
1140 Token::Done => {
1141 if depth > 0 {
1142 depth -= 1;
1143 body_tokens.push(tokens[i].clone());
1144 } else {
1145 break; }
1147 }
1148 Token::Newline => {
1149 let mut j = i + 1;
1151 while j < tokens.len() && tokens[j] == Token::Newline {
1152 j += 1;
1153 }
1154 if j < tokens.len() && depth == 0 && tokens[j] == Token::Done {
1155 i = j; break;
1157 }
1158 body_tokens.push(tokens[i].clone());
1160 }
1161 _ => {
1162 body_tokens.push(tokens[i].clone());
1163 }
1164 }
1165 i += 1;
1166 }
1167
1168 if i >= tokens.len() || tokens[i] != Token::Done {
1169 return Err("Expected 'done' to close while loop".to_string());
1170 }
1171
1172 let condition_ast = parse_slice(&cond_tokens)?;
1174
1175 let body_ast = if body_tokens.is_empty() {
1177 create_empty_body_ast()
1179 } else {
1180 parse_commands_sequentially(&body_tokens)?
1181 };
1182
1183 Ok(Ast::While {
1184 condition: Box::new(condition_ast),
1185 body: Box::new(body_ast),
1186 })
1187}
1188
1189fn parse_function_definition(tokens: &[Token]) -> Result<Ast, String> {
1190 if tokens.len() < 2 {
1191 return Err("Function definition too short".to_string());
1192 }
1193
1194 let func_name = if let Token::Word(word) = &tokens[0] {
1196 if let Some(paren_pos) = word.find('(') {
1198 if word.ends_with(')') && paren_pos > 0 {
1199 word[..paren_pos].to_string()
1200 } else {
1201 word.clone()
1202 }
1203 } else {
1204 word.clone()
1205 }
1206 } else {
1207 return Err("Function name must be a word".to_string());
1208 };
1209
1210 let brace_pos =
1212 if tokens.len() >= 4 && tokens[1] == Token::LeftParen && tokens[2] == Token::RightParen {
1213 if tokens[3] != Token::LeftBrace {
1215 return Err("Expected { after function name".to_string());
1216 }
1217 3
1218 } else if tokens.len() >= 2 && tokens[1] == Token::LeftBrace {
1219 1
1221 } else {
1222 return Err("Expected ( after function name or { for legacy format".to_string());
1223 };
1224
1225 let mut brace_depth = 0;
1227 let mut body_end = 0;
1228 let mut found_closing = false;
1229 let mut i = brace_pos + 1;
1230
1231 while i < tokens.len() {
1232 if i + 3 < tokens.len()
1235 && matches!(&tokens[i], Token::Word(_))
1236 && tokens[i + 1] == Token::LeftParen
1237 && tokens[i + 2] == Token::RightParen
1238 && tokens[i + 3] == Token::LeftBrace
1239 {
1240 i += 4;
1243 let mut nested_depth = 1;
1244 while i < tokens.len() && nested_depth > 0 {
1245 match tokens[i] {
1246 Token::LeftBrace => nested_depth += 1,
1247 Token::RightBrace => nested_depth -= 1,
1248 _ => {}
1249 }
1250 i += 1;
1251 }
1252 continue;
1254 }
1255
1256 match &tokens[i] {
1257 Token::LeftBrace => {
1258 brace_depth += 1;
1259 i += 1;
1260 }
1261 Token::RightBrace => {
1262 if brace_depth == 0 {
1263 body_end = i;
1265 found_closing = true;
1266 break;
1267 } else {
1268 brace_depth -= 1;
1269 i += 1;
1270 }
1271 }
1272 Token::If => {
1273 skip_to_matching_fi(tokens, &mut i);
1275 }
1276 Token::For | Token::While => {
1277 skip_to_matching_done(tokens, &mut i);
1279 }
1280 Token::Case => {
1281 skip_to_matching_esac(tokens, &mut i);
1283 }
1284 _ => {
1285 i += 1;
1286 }
1287 }
1288 }
1289
1290 if !found_closing {
1291 return Err("Missing closing } for function definition".to_string());
1292 }
1293
1294 let body_tokens = &tokens[brace_pos + 1..body_end];
1296
1297 let body_ast = if body_tokens.is_empty() {
1299 create_empty_body_ast()
1301 } else {
1302 parse_commands_sequentially(body_tokens)?
1303 };
1304
1305 Ok(Ast::FunctionDefinition {
1306 name: func_name,
1307 body: Box::new(body_ast),
1308 })
1309}
1310
1311#[cfg(test)]
1312mod tests {
1313 use super::super::lexer::Token;
1314 use super::*;
1315
1316 #[test]
1317 fn test_single_command() {
1318 let tokens = vec![Token::Word("ls".to_string())];
1319 let result = parse(tokens).unwrap();
1320 assert_eq!(
1321 result,
1322 Ast::Pipeline(vec![ShellCommand {
1323 args: vec!["ls".to_string()],
1324 input: None,
1325 output: None,
1326 append: None,
1327 here_doc_delimiter: None,
1328 here_doc_quoted: false,
1329 here_string_content: None,
1330 }])
1331 );
1332 }
1333
1334 #[test]
1335 fn test_command_with_args() {
1336 let tokens = vec![
1337 Token::Word("ls".to_string()),
1338 Token::Word("-la".to_string()),
1339 ];
1340 let result = parse(tokens).unwrap();
1341 assert_eq!(
1342 result,
1343 Ast::Pipeline(vec![ShellCommand {
1344 args: vec!["ls".to_string(), "-la".to_string()],
1345 input: None,
1346 output: None,
1347 append: None,
1348 here_doc_delimiter: None,
1349 here_doc_quoted: false,
1350 here_string_content: None,
1351 }])
1352 );
1353 }
1354
1355 #[test]
1356 fn test_pipeline() {
1357 let tokens = vec![
1358 Token::Word("ls".to_string()),
1359 Token::Pipe,
1360 Token::Word("grep".to_string()),
1361 Token::Word("txt".to_string()),
1362 ];
1363 let result = parse(tokens).unwrap();
1364 assert_eq!(
1365 result,
1366 Ast::Pipeline(vec![
1367 ShellCommand {
1368 args: vec!["ls".to_string()],
1369 input: None,
1370 output: None,
1371 append: None,
1372 here_doc_delimiter: None,
1373 here_doc_quoted: false,
1374 here_string_content: None,
1375 },
1376 ShellCommand {
1377 args: vec!["grep".to_string(), "txt".to_string()],
1378 input: None,
1379 output: None,
1380 append: None,
1381 here_doc_delimiter: None,
1382 here_doc_quoted: false,
1383 here_string_content: None,
1384 }
1385 ])
1386 );
1387 }
1388
1389 #[test]
1390 fn test_input_redirection() {
1391 let tokens = vec![
1392 Token::Word("cat".to_string()),
1393 Token::RedirIn,
1394 Token::Word("input.txt".to_string()),
1395 ];
1396 let result = parse(tokens).unwrap();
1397 assert_eq!(
1398 result,
1399 Ast::Pipeline(vec![ShellCommand {
1400 args: vec!["cat".to_string()],
1401 input: Some("input.txt".to_string()),
1402 output: None,
1403 append: None,
1404 here_doc_delimiter: None,
1405 here_doc_quoted: false,
1406 here_string_content: None,
1407 }])
1408 );
1409 }
1410
1411 #[test]
1412 fn test_output_redirection() {
1413 let tokens = vec![
1414 Token::Word("printf".to_string()),
1415 Token::Word("hello".to_string()),
1416 Token::RedirOut,
1417 Token::Word("output.txt".to_string()),
1418 ];
1419 let result = parse(tokens).unwrap();
1420 assert_eq!(
1421 result,
1422 Ast::Pipeline(vec![ShellCommand {
1423 args: vec!["printf".to_string(), "hello".to_string()],
1424 input: None,
1425 output: Some("output.txt".to_string()),
1426 append: None,
1427 here_doc_delimiter: None,
1428 here_doc_quoted: false,
1429 here_string_content: None,
1430 }])
1431 );
1432 }
1433
1434 #[test]
1435 fn test_append_redirection() {
1436 let tokens = vec![
1437 Token::Word("printf".to_string()),
1438 Token::Word("hello".to_string()),
1439 Token::RedirAppend,
1440 Token::Word("output.txt".to_string()),
1441 ];
1442 let result = parse(tokens).unwrap();
1443 assert_eq!(
1444 result,
1445 Ast::Pipeline(vec![ShellCommand {
1446 args: vec!["printf".to_string(), "hello".to_string()],
1447 input: None,
1448 output: None,
1449 append: Some("output.txt".to_string()),
1450 here_doc_delimiter: None,
1451 here_doc_quoted: false,
1452 here_string_content: None,
1453 }])
1454 );
1455 }
1456
1457 #[test]
1458 fn test_complex_pipeline_with_redirections() {
1459 let tokens = vec![
1460 Token::Word("cat".to_string()),
1461 Token::RedirIn,
1462 Token::Word("input.txt".to_string()),
1463 Token::Pipe,
1464 Token::Word("grep".to_string()),
1465 Token::Word("pattern".to_string()),
1466 Token::Pipe,
1467 Token::Word("sort".to_string()),
1468 Token::RedirOut,
1469 Token::Word("output.txt".to_string()),
1470 ];
1471 let result = parse(tokens).unwrap();
1472 assert_eq!(
1473 result,
1474 Ast::Pipeline(vec![
1475 ShellCommand {
1476 args: vec!["cat".to_string()],
1477 input: Some("input.txt".to_string()),
1478 output: None,
1479 append: None,
1480 here_doc_delimiter: None,
1481 here_doc_quoted: false,
1482 here_string_content: None,
1483 },
1484 ShellCommand {
1485 args: vec!["grep".to_string(), "pattern".to_string()],
1486 input: None,
1487 output: None,
1488 append: None,
1489 here_doc_delimiter: None,
1490 here_doc_quoted: false,
1491 here_string_content: None,
1492 },
1493 ShellCommand {
1494 args: vec!["sort".to_string()],
1495 input: None,
1496 output: Some("output.txt".to_string()),
1497 append: None,
1498 here_doc_delimiter: None,
1499 here_doc_quoted: false,
1500 here_string_content: None,
1501 }
1502 ])
1503 );
1504 }
1505
1506 #[test]
1507 fn test_empty_tokens() {
1508 let tokens = vec![];
1509 let result = parse(tokens);
1510 assert!(result.is_err());
1511 assert_eq!(result.unwrap_err(), "No commands found");
1512 }
1513
1514 #[test]
1515 fn test_only_pipe() {
1516 let tokens = vec![Token::Pipe];
1517 let result = parse(tokens);
1518 assert!(result.is_err());
1519 assert_eq!(result.unwrap_err(), "No commands found");
1520 }
1521
1522 #[test]
1523 fn test_redirection_without_file() {
1524 let tokens = vec![Token::Word("cat".to_string()), Token::RedirIn];
1526 let result = parse(tokens).unwrap();
1527 assert_eq!(
1528 result,
1529 Ast::Pipeline(vec![ShellCommand {
1530 args: vec!["cat".to_string()],
1531 input: None,
1532 output: None,
1533 append: None,
1534 here_doc_delimiter: None,
1535 here_doc_quoted: false,
1536 here_string_content: None,
1537 }])
1538 );
1539 }
1540
1541 #[test]
1542 fn test_multiple_redirections() {
1543 let tokens = vec![
1544 Token::Word("cat".to_string()),
1545 Token::RedirIn,
1546 Token::Word("file1.txt".to_string()),
1547 Token::RedirOut,
1548 Token::Word("file2.txt".to_string()),
1549 ];
1550 let result = parse(tokens).unwrap();
1551 assert_eq!(
1552 result,
1553 Ast::Pipeline(vec![ShellCommand {
1554 args: vec!["cat".to_string()],
1555 input: Some("file1.txt".to_string()),
1556 output: Some("file2.txt".to_string()),
1557 append: None,
1558 here_doc_delimiter: None,
1559 here_doc_quoted: false,
1560 here_string_content: None,
1561 }])
1562 );
1563 }
1564
1565 #[test]
1566 fn test_parse_if() {
1567 let tokens = vec![
1568 Token::If,
1569 Token::Word("true".to_string()),
1570 Token::Semicolon,
1571 Token::Then,
1572 Token::Word("printf".to_string()),
1573 Token::Word("yes".to_string()),
1574 Token::Semicolon,
1575 Token::Fi,
1576 ];
1577 let result = parse(tokens).unwrap();
1578 if let Ast::If {
1579 branches,
1580 else_branch,
1581 } = result
1582 {
1583 assert_eq!(branches.len(), 1);
1584 let (condition, then_branch) = &branches[0];
1585 if let Ast::Pipeline(cmds) = &**condition {
1586 assert_eq!(cmds[0].args, vec!["true"]);
1587 } else {
1588 panic!("condition not pipeline");
1589 }
1590 if let Ast::Pipeline(cmds) = &**then_branch {
1591 assert_eq!(cmds[0].args, vec!["printf", "yes"]);
1592 } else {
1593 panic!("then_branch not pipeline");
1594 }
1595 assert!(else_branch.is_none());
1596 } else {
1597 panic!("not if");
1598 }
1599 }
1600
1601 #[test]
1602 fn test_parse_if_elif() {
1603 let tokens = vec![
1604 Token::If,
1605 Token::Word("false".to_string()),
1606 Token::Semicolon,
1607 Token::Then,
1608 Token::Word("printf".to_string()),
1609 Token::Word("no".to_string()),
1610 Token::Semicolon,
1611 Token::Elif,
1612 Token::Word("true".to_string()),
1613 Token::Semicolon,
1614 Token::Then,
1615 Token::Word("printf".to_string()),
1616 Token::Word("yes".to_string()),
1617 Token::Semicolon,
1618 Token::Fi,
1619 ];
1620 let result = parse(tokens).unwrap();
1621 if let Ast::If {
1622 branches,
1623 else_branch,
1624 } = result
1625 {
1626 assert_eq!(branches.len(), 2);
1627 let (condition1, then1) = &branches[0];
1629 if let Ast::Pipeline(cmds) = &**condition1 {
1630 assert_eq!(cmds[0].args, vec!["false"]);
1631 }
1632 if let Ast::Pipeline(cmds) = &**then1 {
1633 assert_eq!(cmds[0].args, vec!["printf", "no"]);
1634 }
1635 let (condition2, then2) = &branches[1];
1637 if let Ast::Pipeline(cmds) = &**condition2 {
1638 assert_eq!(cmds[0].args, vec!["true"]);
1639 }
1640 if let Ast::Pipeline(cmds) = &**then2 {
1641 assert_eq!(cmds[0].args, vec!["printf", "yes"]);
1642 }
1643 assert!(else_branch.is_none());
1644 } else {
1645 panic!("not if");
1646 }
1647 }
1648
1649 #[test]
1650 fn test_parse_assignment() {
1651 let tokens = vec![Token::Word("MY_VAR=test_value".to_string())];
1652 let result = parse(tokens).unwrap();
1653 if let Ast::Assignment { var, value } = result {
1654 assert_eq!(var, "MY_VAR");
1655 assert_eq!(value, "test_value");
1656 } else {
1657 panic!("not assignment");
1658 }
1659 }
1660
1661 #[test]
1662 fn test_parse_assignment_quoted() {
1663 let tokens = vec![Token::Word("MY_VAR=hello world".to_string())];
1664 let result = parse(tokens).unwrap();
1665 if let Ast::Assignment { var, value } = result {
1666 assert_eq!(var, "MY_VAR");
1667 assert_eq!(value, "hello world");
1668 } else {
1669 panic!("not assignment");
1670 }
1671 }
1672
1673 #[test]
1674 fn test_parse_assignment_invalid() {
1675 let tokens = vec![Token::Word("123VAR=value".to_string())];
1677 let result = parse(tokens).unwrap();
1678 if let Ast::Pipeline(cmds) = result {
1679 assert_eq!(cmds[0].args, vec!["123VAR=value"]);
1680 } else {
1681 panic!("should be parsed as pipeline");
1682 }
1683 }
1684
1685 #[test]
1686 fn test_parse_function_definition() {
1687 let tokens = vec![
1688 Token::Word("myfunc".to_string()),
1689 Token::LeftParen,
1690 Token::RightParen,
1691 Token::LeftBrace,
1692 Token::Word("echo".to_string()),
1693 Token::Word("hello".to_string()),
1694 Token::RightBrace,
1695 ];
1696 let result = parse(tokens).unwrap();
1697 if let Ast::FunctionDefinition { name, body } = result {
1698 assert_eq!(name, "myfunc");
1699 if let Ast::Pipeline(cmds) = *body {
1701 assert_eq!(cmds[0].args, vec!["echo", "hello"]);
1702 } else {
1703 panic!("function body should be a pipeline");
1704 }
1705 } else {
1706 panic!("should be parsed as function definition");
1707 }
1708 }
1709
1710 #[test]
1711 fn test_parse_function_definition_empty() {
1712 let tokens = vec![
1713 Token::Word("emptyfunc".to_string()),
1714 Token::LeftParen,
1715 Token::RightParen,
1716 Token::LeftBrace,
1717 Token::RightBrace,
1718 ];
1719 let result = parse(tokens).unwrap();
1720 if let Ast::FunctionDefinition { name, body } = result {
1721 assert_eq!(name, "emptyfunc");
1722 if let Ast::Pipeline(cmds) = *body {
1724 assert_eq!(cmds[0].args, vec!["true"]);
1725 } else {
1726 panic!("function body should be a pipeline");
1727 }
1728 } else {
1729 panic!("should be parsed as function definition");
1730 }
1731 }
1732
1733 #[test]
1734 fn test_parse_function_definition_legacy_format() {
1735 let tokens = vec![
1737 Token::Word("legacyfunc()".to_string()),
1738 Token::LeftBrace,
1739 Token::Word("echo".to_string()),
1740 Token::Word("hello".to_string()),
1741 Token::RightBrace,
1742 ];
1743 let result = parse(tokens).unwrap();
1744 if let Ast::FunctionDefinition { name, body } = result {
1745 assert_eq!(name, "legacyfunc");
1746 if let Ast::Pipeline(cmds) = *body {
1748 assert_eq!(cmds[0].args, vec!["echo", "hello"]);
1749 } else {
1750 panic!("function body should be a pipeline");
1751 }
1752 } else {
1753 panic!("should be parsed as function definition");
1754 }
1755 }
1756
1757 #[test]
1758 fn test_parse_local_assignment() {
1759 let tokens = vec![Token::Local, Token::Word("MY_VAR=test_value".to_string())];
1760 let result = parse(tokens).unwrap();
1761 if let Ast::LocalAssignment { var, value } = result {
1762 assert_eq!(var, "MY_VAR");
1763 assert_eq!(value, "test_value");
1764 } else {
1765 panic!("should be parsed as local assignment");
1766 }
1767 }
1768
1769 #[test]
1770 fn test_parse_local_assignment_separate_tokens() {
1771 let tokens = vec![
1772 Token::Local,
1773 Token::Word("MY_VAR".to_string()),
1774 Token::Word("test_value".to_string()),
1775 ];
1776 let result = parse(tokens).unwrap();
1777 if let Ast::LocalAssignment { var, value } = result {
1778 assert_eq!(var, "MY_VAR");
1779 assert_eq!(value, "test_value");
1780 } else {
1781 panic!("should be parsed as local assignment");
1782 }
1783 }
1784
1785 #[test]
1786 fn test_parse_local_assignment_invalid_var_name() {
1787 let tokens = vec![Token::Local, Token::Word("123VAR=value".to_string())];
1789 let result = parse(tokens);
1790 assert!(result.is_err());
1792 }
1793
1794 #[test]
1795 fn test_parse_here_document_redirection() {
1796 let tokens = vec![
1797 Token::Word("cat".to_string()),
1798 Token::RedirHereDoc("EOF".to_string(), false),
1799 ];
1800 let result = parse(tokens).unwrap();
1801 assert_eq!(
1802 result,
1803 Ast::Pipeline(vec![ShellCommand {
1804 args: vec!["cat".to_string()],
1805 input: None,
1806 output: None,
1807 append: None,
1808 here_doc_delimiter: Some("EOF".to_string()),
1809 here_doc_quoted: false,
1810 here_string_content: None,
1811 }])
1812 );
1813 }
1814
1815 #[test]
1816 fn test_parse_here_string_redirection() {
1817 let tokens = vec![
1818 Token::Word("grep".to_string()),
1819 Token::RedirHereString("pattern".to_string()),
1820 ];
1821 let result = parse(tokens).unwrap();
1822 assert_eq!(
1823 result,
1824 Ast::Pipeline(vec![ShellCommand {
1825 args: vec!["grep".to_string()],
1826 input: None,
1827 output: None,
1828 append: None,
1829 here_doc_delimiter: None,
1830 here_doc_quoted: false,
1831 here_string_content: Some("pattern".to_string()),
1832 }])
1833 );
1834 }
1835
1836 #[test]
1837 fn test_parse_mixed_redirections() {
1838 let tokens = vec![
1839 Token::Word("cat".to_string()),
1840 Token::RedirIn,
1841 Token::Word("file.txt".to_string()),
1842 Token::RedirHereString("fallback".to_string()),
1843 Token::RedirOut,
1844 Token::Word("output.txt".to_string()),
1845 ];
1846 let result = parse(tokens).unwrap();
1847 assert_eq!(
1848 result,
1849 Ast::Pipeline(vec![ShellCommand {
1850 args: vec!["cat".to_string()],
1851 input: Some("file.txt".to_string()),
1852 output: Some("output.txt".to_string()),
1853 append: None,
1854 here_doc_delimiter: None,
1855 here_doc_quoted: false,
1856 here_string_content: Some("fallback".to_string()),
1857 }])
1858 );
1859 }
1860}