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