1use crate::lexer::ZshLexer;
8use crate::tokens::LexTok;
9use serde::{Serialize, Deserialize};
10use std::iter::Peekable;
11use std::str::Chars;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct ZshProgram {
16 pub lists: Vec<ZshList>,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct ZshList {
22 pub sublist: ZshSublist,
23 pub flags: ListFlags,
24}
25
26#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
27pub struct ListFlags {
28 pub async_: bool,
30 pub disown: bool,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct ZshSublist {
37 pub pipe: ZshPipe,
38 pub next: Option<(SublistOp, Box<ZshSublist>)>,
39 pub flags: SublistFlags,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
43pub enum SublistOp {
44 And, Or, }
47
48#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
49pub struct SublistFlags {
50 pub coproc: bool,
52 pub not: bool,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct ZshPipe {
59 pub cmd: ZshCommand,
60 pub next: Option<Box<ZshPipe>>,
61 pub lineno: u64,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub enum ZshCommand {
67 Simple(ZshSimple),
68 Subsh(Box<ZshProgram>), Cursh(Box<ZshProgram>), For(ZshFor),
71 Case(ZshCase),
72 If(ZshIf),
73 While(ZshWhile),
74 Until(ZshWhile),
75 Repeat(ZshRepeat),
76 FuncDef(ZshFuncDef),
77 Time(Option<Box<ZshSublist>>),
78 Cond(ZshCond), Arith(String), Try(ZshTry), }
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct ZshSimple {
86 pub assigns: Vec<ZshAssign>,
87 pub words: Vec<String>,
88 pub redirs: Vec<ZshRedir>,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct ZshAssign {
94 pub name: String,
95 pub value: ZshAssignValue,
96 pub append: bool, }
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub enum ZshAssignValue {
101 Scalar(String),
102 Array(Vec<String>),
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct ZshRedir {
108 pub rtype: RedirType,
109 pub fd: i32,
110 pub name: String,
111 pub heredoc: Option<HereDocInfo>,
112 pub varid: Option<String>, }
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct HereDocInfo {
117 pub content: String,
118 pub terminator: String,
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
123pub enum RedirType {
124 Write, Writenow, Append, Appendnow, Read, ReadWrite, Heredoc, HeredocDash, Herestr, MergeIn, MergeOut, ErrWrite, ErrWritenow, ErrAppend, ErrAppendnow, InPipe, OutPipe, }
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct ZshFor {
146 pub var: String,
147 pub list: ForList,
148 pub body: Box<ZshProgram>,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub enum ForList {
153 Words(Vec<String>),
154 CStyle {
155 init: String,
156 cond: String,
157 step: String,
158 },
159 Positional,
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct ZshCase {
165 pub word: String,
166 pub arms: Vec<CaseArm>,
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct CaseArm {
171 pub patterns: Vec<String>,
172 pub body: ZshProgram,
173 pub terminator: CaseTerm,
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
177pub enum CaseTerm {
178 Break, Continue, TestNext, }
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct ZshIf {
186 pub cond: Box<ZshProgram>,
187 pub then: Box<ZshProgram>,
188 pub elif: Vec<(ZshProgram, ZshProgram)>,
189 pub else_: Option<Box<ZshProgram>>,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct ZshWhile {
195 pub cond: Box<ZshProgram>,
196 pub body: Box<ZshProgram>,
197 pub until: bool,
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize)]
202pub struct ZshRepeat {
203 pub count: String,
204 pub body: Box<ZshProgram>,
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct ZshFuncDef {
210 pub names: Vec<String>,
211 pub body: Box<ZshProgram>,
212 pub tracing: bool,
213}
214
215#[derive(Debug, Clone, Serialize, Deserialize)]
217pub enum ZshCond {
218 Not(Box<ZshCond>),
219 And(Box<ZshCond>, Box<ZshCond>),
220 Or(Box<ZshCond>, Box<ZshCond>),
221 Unary(String, String), Binary(String, String, String), Regex(String, String), }
225
226#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct ZshTry {
229 pub try_block: Box<ZshProgram>,
230 pub always: Box<ZshProgram>,
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize)]
235pub enum ZshParamFlag {
236 Lower, Upper, Capitalize, Join(String), JoinNewline, Split(String), SplitLines, SplitWords, Type, Words, Quote, DoubleQuote, QuoteBackslash, Unique, Reverse, Sort, NumericSort, IndexSort, Keys, Values, Length, CountChars, Expand, PromptExpand, PromptExpandFull, Visible, Directory, Head(usize), Tail(usize), PadLeft(usize, char), PadRight(usize, char), Width(usize), Match, Remove, Subscript, Parameter, Glob, }
274
275#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
277pub enum ListOp {
278 And, Or, Semi, Amp, Newline, }
284
285#[derive(Debug, Clone, Serialize, Deserialize)]
287pub enum ShellWord {
288 Literal(String),
289 SingleQuoted(String),
290 DoubleQuoted(Vec<ShellWord>),
291 Variable(String),
292 VariableBraced(String, Option<Box<VarModifier>>),
293 ArrayVar(String, Box<ShellWord>),
294 CommandSub(Box<ShellCommand>),
295 ProcessSubIn(Box<ShellCommand>),
296 ProcessSubOut(Box<ShellCommand>),
297 ArithSub(String),
298 ArrayLiteral(Vec<ShellWord>),
299 Glob(String),
300 Tilde(Option<String>),
301 Concat(Vec<ShellWord>),
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize)]
306pub enum VarModifier {
307 Default(ShellWord),
308 DefaultAssign(ShellWord),
309 Error(ShellWord),
310 Alternate(ShellWord),
311 Length,
312 ArrayLength,
313 ArrayIndex(String),
314 ArrayAll,
315 Substring(i64, Option<i64>),
316 RemovePrefix(ShellWord),
317 RemovePrefixLong(ShellWord),
318 RemoveSuffix(ShellWord),
319 RemoveSuffixLong(ShellWord),
320 Replace(ShellWord, ShellWord),
321 ReplaceAll(ShellWord, ShellWord),
322 Upper,
323 Lower,
324 ZshFlags(Vec<ZshParamFlag>),
325}
326
327#[derive(Debug, Clone, Serialize, Deserialize)]
329pub enum ShellCommand {
330 Simple(SimpleCommand),
331 Pipeline(Vec<ShellCommand>, bool),
332 List(Vec<(ShellCommand, ListOp)>),
333 Compound(CompoundCommand),
334 FunctionDef(String, Box<ShellCommand>),
335}
336
337#[derive(Debug, Clone, Serialize, Deserialize)]
339pub struct SimpleCommand {
340 pub assignments: Vec<(String, ShellWord, bool)>,
341 pub words: Vec<ShellWord>,
342 pub redirects: Vec<Redirect>,
343}
344
345#[derive(Debug, Clone, Serialize, Deserialize)]
347pub struct Redirect {
348 pub fd: Option<i32>,
349 pub op: RedirectOp,
350 pub target: ShellWord,
351 pub heredoc_content: Option<String>,
352 pub fd_var: Option<String>,
353}
354
355#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
357pub enum RedirectOp {
358 Write,
359 Append,
360 Read,
361 ReadWrite,
362 Clobber,
363 DupRead,
364 DupWrite,
365 HereDoc,
366 HereString,
367 WriteBoth,
368 AppendBoth,
369}
370
371#[derive(Debug, Clone, Serialize, Deserialize)]
373pub enum CompoundCommand {
374 BraceGroup(Vec<ShellCommand>),
375 Subshell(Vec<ShellCommand>),
376 If {
377 conditions: Vec<(Vec<ShellCommand>, Vec<ShellCommand>)>,
378 else_part: Option<Vec<ShellCommand>>,
379 },
380 For {
381 var: String,
382 words: Option<Vec<ShellWord>>,
383 body: Vec<ShellCommand>,
384 },
385 ForArith {
386 init: String,
387 cond: String,
388 step: String,
389 body: Vec<ShellCommand>,
390 },
391 While {
392 condition: Vec<ShellCommand>,
393 body: Vec<ShellCommand>,
394 },
395 Until {
396 condition: Vec<ShellCommand>,
397 body: Vec<ShellCommand>,
398 },
399 Case {
400 word: ShellWord,
401 cases: Vec<(Vec<ShellWord>, Vec<ShellCommand>, CaseTerminator)>,
402 },
403 Select {
404 var: String,
405 words: Option<Vec<ShellWord>>,
406 body: Vec<ShellCommand>,
407 },
408 Coproc {
409 name: Option<String>,
410 body: Box<ShellCommand>,
411 },
412 Repeat {
414 count: String,
415 body: Vec<ShellCommand>,
416 },
417 Try {
419 try_body: Vec<ShellCommand>,
420 always_body: Vec<ShellCommand>,
421 },
422 Cond(CondExpr),
423 Arith(String),
424 WithRedirects(Box<ShellCommand>, Vec<Redirect>),
425}
426
427#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
429pub enum CaseTerminator {
430 Break,
431 Fallthrough,
432 Continue,
433}
434
435#[derive(Debug, Clone, Serialize, Deserialize)]
437pub enum CondExpr {
438 FileExists(ShellWord),
439 FileRegular(ShellWord),
440 FileDirectory(ShellWord),
441 FileSymlink(ShellWord),
442 FileReadable(ShellWord),
443 FileWritable(ShellWord),
444 FileExecutable(ShellWord),
445 FileNonEmpty(ShellWord),
446 StringEmpty(ShellWord),
447 StringNonEmpty(ShellWord),
448 StringEqual(ShellWord, ShellWord),
449 StringNotEqual(ShellWord, ShellWord),
450 StringMatch(ShellWord, ShellWord),
451 StringLess(ShellWord, ShellWord),
452 StringGreater(ShellWord, ShellWord),
453 NumEqual(ShellWord, ShellWord),
454 NumNotEqual(ShellWord, ShellWord),
455 NumLess(ShellWord, ShellWord),
456 NumLessEqual(ShellWord, ShellWord),
457 NumGreater(ShellWord, ShellWord),
458 NumGreaterEqual(ShellWord, ShellWord),
459 Not(Box<CondExpr>),
460 And(Box<CondExpr>, Box<CondExpr>),
461 Or(Box<CondExpr>, Box<CondExpr>),
462}
463
464#[derive(Debug, Clone, Serialize, Deserialize)]
466pub struct ParseError {
467 pub message: String,
468 pub line: u64,
469}
470
471impl std::fmt::Display for ParseError {
472 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
473 write!(f, "parse error at line {}: {}", self.line, self.message)
474 }
475}
476
477impl std::error::Error for ParseError {}
478
479#[derive(Debug, Clone, PartialEq)]
484pub enum ShellToken {
485 Word(String),
486 SingleQuotedWord(String),
487 DoubleQuotedWord(String),
488 Number(i64),
489 Semi,
490 Newline,
491 Amp,
492 AmpAmp,
493 Pipe,
494 PipePipe,
495 LParen,
496 RParen,
497 LBrace,
498 RBrace,
499 LBracket,
500 RBracket,
501 DoubleLBracket,
502 DoubleRBracket,
503 Less,
504 Greater,
505 GreaterGreater,
506 LessGreater,
507 GreaterAmp,
508 LessAmp,
509 GreaterPipe,
510 LessLess,
511 LessLessLess,
512 HereDoc(String, String),
513 AmpGreater,
514 AmpGreaterGreater,
515 DoubleLParen,
516 DoubleRParen,
517 If,
518 Then,
519 Else,
520 Elif,
521 Fi,
522 Case,
523 Esac,
524 For,
525 While,
526 Until,
527 Do,
528 Done,
529 In,
530 Function,
531 Select,
532 Time,
533 Coproc,
534 Typeset(String),
535 Repeat,
536 Always,
537 Bang,
538 DoubleSemi,
539 SemiAmp,
540 SemiSemiAmp,
541 Eof,
542}
543
544pub struct ShellLexer<'a> {
545 input: Peekable<Chars<'a>>,
546 line: usize,
547 col: usize,
548 at_line_start: bool,
549}
550
551impl<'a> ShellLexer<'a> {
552 pub fn new(input: &'a str) -> Self {
553 Self {
554 input: input.chars().peekable(),
555 line: 1,
556 col: 1,
557 at_line_start: true,
558 }
559 }
560
561 fn peek(&mut self) -> Option<char> {
562 self.input.peek().copied()
563 }
564
565 fn next_char(&mut self) -> Option<char> {
566 let c = self.input.next();
567 if let Some(ch) = c {
568 if ch == '\n' {
569 self.line += 1;
570 self.col = 1;
571 self.at_line_start = true;
572 } else {
573 self.col += 1;
574 self.at_line_start = false;
575 }
576 }
577 c
578 }
579
580 fn skip_whitespace(&mut self) -> bool {
581 let mut had_whitespace = false;
582 while let Some(c) = self.peek() {
583 if c == ' ' || c == '\t' {
584 self.next_char();
585 had_whitespace = true;
586 } else if c == '\\' {
587 self.next_char();
588 if self.peek() == Some('\n') {
589 self.next_char();
590 }
591 had_whitespace = true;
592 } else {
593 break;
594 }
595 }
596 had_whitespace
597 }
598
599 fn skip_comment(&mut self, after_whitespace: bool) {
600 if after_whitespace && self.peek() == Some('#') {
601 while let Some(c) = self.peek() {
602 if c == '\n' {
603 break;
604 }
605 self.next_char();
606 }
607 }
608 }
609
610 pub fn next_token(&mut self) -> ShellToken {
611 let was_at_line_start = self.at_line_start;
612 let had_whitespace = self.skip_whitespace();
613 self.skip_comment(had_whitespace || was_at_line_start);
614
615 let c = match self.peek() {
616 Some(c) => c,
617 None => return ShellToken::Eof,
618 };
619
620 if c == '\n' {
621 self.next_char();
622 self.at_line_start = true;
623 return ShellToken::Newline;
624 }
625
626 self.at_line_start = false;
627
628 if c == ';' {
629 self.next_char();
630 if self.peek() == Some(';') {
631 self.next_char();
632 if self.peek() == Some('&') {
633 self.next_char();
634 return ShellToken::SemiSemiAmp;
635 }
636 return ShellToken::DoubleSemi;
637 }
638 if self.peek() == Some('&') {
639 self.next_char();
640 return ShellToken::SemiAmp;
641 }
642 return ShellToken::Semi;
643 }
644
645 if c == '&' {
646 self.next_char();
647 match self.peek() {
648 Some('&') => {
649 self.next_char();
650 return ShellToken::AmpAmp;
651 }
652 Some('>') => {
653 self.next_char();
654 if self.peek() == Some('>') {
655 self.next_char();
656 return ShellToken::AmpGreaterGreater;
657 }
658 return ShellToken::AmpGreater;
659 }
660 _ => return ShellToken::Amp,
661 }
662 }
663
664 if c == '|' {
665 self.next_char();
666 if self.peek() == Some('|') {
667 self.next_char();
668 return ShellToken::PipePipe;
669 }
670 return ShellToken::Pipe;
671 }
672
673 if c == '<' {
674 self.next_char();
675 match self.peek() {
676 Some('(') => {
677 self.next_char();
678 let cmd = self.read_process_sub();
679 return ShellToken::Word(format!("<({}", cmd));
680 }
681 Some('<') => {
682 self.next_char();
683 if self.peek() == Some('<') {
684 self.next_char();
685 return ShellToken::LessLessLess;
686 }
687 return self.read_heredoc();
688 }
689 Some('>') => {
690 self.next_char();
691 return ShellToken::LessGreater;
692 }
693 Some('&') => {
694 self.next_char();
695 return ShellToken::LessAmp;
696 }
697 _ => return ShellToken::Less,
698 }
699 }
700
701 if c == '>' {
702 self.next_char();
703 match self.peek() {
704 Some('(') => {
705 self.next_char();
706 let cmd = self.read_process_sub();
707 return ShellToken::Word(format!(">({}", cmd));
708 }
709 Some('>') => {
710 self.next_char();
711 return ShellToken::GreaterGreater;
712 }
713 Some('&') => {
714 self.next_char();
715 return ShellToken::GreaterAmp;
716 }
717 Some('|') => {
718 self.next_char();
719 return ShellToken::GreaterPipe;
720 }
721 _ => return ShellToken::Greater,
722 }
723 }
724
725 if c == '(' {
726 self.next_char();
727 if self.peek() == Some('(') {
728 self.next_char();
729 return ShellToken::DoubleLParen;
730 }
731 return ShellToken::LParen;
732 }
733
734 if c == ')' {
735 self.next_char();
736 if self.peek() == Some(')') {
737 self.next_char();
738 return ShellToken::DoubleRParen;
739 }
740 return ShellToken::RParen;
741 }
742
743 if c == '[' {
744 self.next_char();
745 if self.peek() == Some('[') {
746 self.next_char();
747 return ShellToken::DoubleLBracket;
748 }
749 if let Some(next_ch) = self.peek() {
750 if !next_ch.is_whitespace() && next_ch != ']' {
751 let mut pattern = String::from("[");
752 while let Some(ch) = self.peek() {
753 pattern.push(self.next_char().unwrap());
754 if ch == ']' {
755 while let Some(c2) = self.peek() {
756 if c2.is_whitespace()
757 || c2 == ';'
758 || c2 == '&'
759 || c2 == '|'
760 || c2 == '<'
761 || c2 == '>'
762 || c2 == ')'
763 || c2 == '\n'
764 {
765 break;
766 }
767 pattern.push(self.next_char().unwrap());
768 }
769 return ShellToken::Word(pattern);
770 }
771 if ch.is_whitespace() {
772 return ShellToken::Word(pattern);
773 }
774 }
775 return ShellToken::Word(pattern);
776 }
777 }
778 return ShellToken::LBracket;
779 }
780
781 if c == ']' {
782 self.next_char();
783 if self.peek() == Some(']') {
784 self.next_char();
785 return ShellToken::DoubleRBracket;
786 }
787 return ShellToken::RBracket;
788 }
789
790 if c == '{' {
791 self.next_char();
792 match self.peek() {
793 Some(' ') | Some('\t') | Some('\n') | None => {
794 return ShellToken::LBrace;
795 }
796 _ => {
797 let mut word = String::from("{");
798 let mut depth = 1;
799 while let Some(ch) = self.peek() {
800 if ch == '{' {
801 depth += 1;
802 word.push(self.next_char().unwrap());
803 } else if ch == '}' {
804 depth -= 1;
805 word.push(self.next_char().unwrap());
806 if depth == 0 {
807 break;
808 }
809 } else if (ch == ' ' || ch == '\t' || ch == '\n') && depth == 1 {
810 break;
811 } else {
812 word.push(self.next_char().unwrap());
813 }
814 }
815 while let Some(ch) = self.peek() {
816 if ch.is_whitespace()
817 || ch == ';'
818 || ch == '&'
819 || ch == '|'
820 || ch == '<'
821 || ch == '>'
822 || ch == '('
823 || ch == ')'
824 {
825 break;
826 }
827 word.push(self.next_char().unwrap());
828 }
829 return ShellToken::Word(word);
830 }
831 }
832 }
833
834 if c == '}' {
835 self.next_char();
836 return ShellToken::RBrace;
837 }
838
839 if c == '!' {
840 self.next_char();
841 if self.peek() == Some('=') {
842 self.next_char();
843 return ShellToken::Word("!=".to_string());
844 }
845 if self.peek() == Some('(') {
846 let mut word = String::from("!(");
847 self.next_char();
848 let mut depth = 1;
849 while let Some(ch) = self.peek() {
850 word.push(self.next_char().unwrap());
851 if ch == '(' {
852 depth += 1;
853 } else if ch == ')' {
854 depth -= 1;
855 if depth == 0 {
856 break;
857 }
858 }
859 }
860 while let Some(ch) = self.peek() {
861 if ch.is_whitespace()
862 || ch == ';'
863 || ch == '&'
864 || ch == '|'
865 || ch == '<'
866 || ch == '>'
867 {
868 break;
869 }
870 word.push(self.next_char().unwrap());
871 }
872 return ShellToken::Word(word);
873 }
874 return ShellToken::Bang;
875 }
876
877 if c.is_alphanumeric()
878 || c == '_'
879 || c == '/'
880 || c == '.'
881 || c == '-'
882 || c == '$'
883 || c == '\''
884 || c == '"'
885 || c == '~'
886 || c == '*'
887 || c == '?'
888 || c == '%'
889 || c == '+'
890 || c == '@'
891 || c == ':'
892 || c == '='
893 || c == '`'
894 {
895 return self.read_word();
896 }
897
898 self.next_char();
899 ShellToken::Word(c.to_string())
900 }
901
902 fn read_process_sub(&mut self) -> String {
903 let mut content = String::new();
904 let mut depth = 1;
905 while let Some(c) = self.next_char() {
906 if c == '(' {
907 depth += 1;
908 content.push(c);
909 } else if c == ')' {
910 depth -= 1;
911 if depth == 0 {
912 content.push(')');
913 break;
914 }
915 content.push(c);
916 } else {
917 content.push(c);
918 }
919 }
920 content
921 }
922
923 fn read_heredoc(&mut self) -> ShellToken {
924 while self.peek() == Some(' ') || self.peek() == Some('\t') {
925 self.next_char();
926 }
927 let quoted = self.peek() == Some('\'') || self.peek() == Some('"');
928 if quoted {
929 self.next_char();
930 }
931 let mut delimiter = String::new();
932 while let Some(c) = self.peek() {
933 if c == '\n' || c == ' ' || c == '\t' {
934 break;
935 }
936 if quoted && (c == '\'' || c == '"') {
937 self.next_char();
938 break;
939 }
940 delimiter.push(self.next_char().unwrap());
941 }
942 while let Some(c) = self.peek() {
943 if c == '\n' {
944 self.next_char();
945 break;
946 }
947 self.next_char();
948 }
949 let mut content = String::new();
950 let mut current_line = String::new();
951 while let Some(c) = self.next_char() {
952 if c == '\n' {
953 if current_line.trim() == delimiter {
954 break;
955 }
956 content.push_str(¤t_line);
957 content.push('\n');
958 current_line.clear();
959 } else {
960 current_line.push(c);
961 }
962 }
963 ShellToken::HereDoc(delimiter, content)
964 }
965
966 fn read_word(&mut self) -> ShellToken {
967 let mut word = String::new();
968 while let Some(c) = self.peek() {
969 match c {
970 ' ' | '\t' | '\n' | ';' | '&' | '<' | '>' => break,
971 '[' => {
972 word.push(self.next_char().unwrap());
973 let mut bracket_depth = 1;
974 while let Some(ch) = self.peek() {
975 word.push(self.next_char().unwrap());
976 if ch == '[' {
977 bracket_depth += 1;
978 } else if ch == ']' {
979 bracket_depth -= 1;
980 if bracket_depth == 0 {
981 break;
982 }
983 }
984 if ch == ' ' || ch == '\t' || ch == '\n' {
985 break;
986 }
987 }
988 }
989 ']' => {
990 if word.is_empty() {
991 break;
992 }
993 word.push(self.next_char().unwrap());
994 }
995 '|' | '(' | ')' => {
996 if c == '(' && !word.is_empty() {
997 let last_char = word.chars().last().unwrap();
998 if matches!(last_char, '?' | '*' | '+' | '@' | '!') {
999 word.push(self.next_char().unwrap());
1000 let mut depth = 1;
1001 while let Some(ch) = self.peek() {
1002 word.push(self.next_char().unwrap());
1003 if ch == '(' {
1004 depth += 1;
1005 } else if ch == ')' {
1006 depth -= 1;
1007 if depth == 0 {
1008 break;
1009 }
1010 }
1011 }
1012 continue;
1013 }
1014 if last_char == '=' {
1015 word.push(self.next_char().unwrap());
1016 let mut depth = 1;
1017 let mut in_sq = false;
1018 let mut in_dq = false;
1019 while let Some(ch) = self.peek() {
1020 if in_sq {
1021 if ch == '\'' {
1022 in_sq = false;
1023 }
1024 word.push(self.next_char().unwrap());
1025 } else if in_dq {
1026 if ch == '"' {
1027 in_dq = false;
1028 } else if ch == '\\' {
1029 word.push(self.next_char().unwrap());
1030 if self.peek().is_some() {
1031 word.push(self.next_char().unwrap());
1032 }
1033 continue;
1034 }
1035 word.push(self.next_char().unwrap());
1036 } else {
1037 match ch {
1038 '\'' => {
1039 in_sq = true;
1040 word.push(self.next_char().unwrap());
1041 }
1042 '"' => {
1043 in_dq = true;
1044 word.push(self.next_char().unwrap());
1045 }
1046 '(' => {
1047 depth += 1;
1048 word.push(self.next_char().unwrap());
1049 }
1050 ')' => {
1051 depth -= 1;
1052 word.push(self.next_char().unwrap());
1053 if depth == 0 {
1054 break;
1055 }
1056 }
1057 '\\' => {
1058 word.push(self.next_char().unwrap());
1059 if self.peek().is_some() {
1060 word.push(self.next_char().unwrap());
1061 }
1062 }
1063 _ => word.push(self.next_char().unwrap()),
1064 }
1065 }
1066 }
1067 continue;
1068 }
1069 }
1070 break;
1071 }
1072 '{' => {
1073 word.push(self.next_char().unwrap());
1074 let mut depth = 1;
1075 while let Some(ch) = self.peek() {
1076 if ch == '{' {
1077 depth += 1;
1078 word.push(self.next_char().unwrap());
1079 } else if ch == '}' {
1080 depth -= 1;
1081 word.push(self.next_char().unwrap());
1082 if depth == 0 {
1083 break;
1084 }
1085 } else if ch == ' ' || ch == '\t' || ch == '\n' {
1086 break;
1087 } else {
1088 word.push(self.next_char().unwrap());
1089 }
1090 }
1091 }
1092 '}' => break,
1093 '$' => {
1094 word.push(self.next_char().unwrap());
1095 if self.peek() == Some('\'') {
1096 word.push(self.next_char().unwrap());
1097 while let Some(ch) = self.peek() {
1098 if ch == '\'' {
1099 word.push(self.next_char().unwrap());
1100 break;
1101 } else if ch == '\\' {
1102 word.push(self.next_char().unwrap());
1103 if self.peek().is_some() {
1104 word.push(self.next_char().unwrap());
1105 }
1106 } else {
1107 word.push(self.next_char().unwrap());
1108 }
1109 }
1110 } else if self.peek() == Some('{') {
1111 word.push(self.next_char().unwrap());
1112 let mut depth = 1;
1113 while let Some(ch) = self.peek() {
1114 if ch == '{' {
1115 depth += 1;
1116 } else if ch == '}' {
1117 depth -= 1;
1118 if depth == 0 {
1119 word.push(self.next_char().unwrap());
1120 break;
1121 }
1122 }
1123 word.push(self.next_char().unwrap());
1124 }
1125 } else if self.peek() == Some('(') {
1126 word.push(self.next_char().unwrap());
1127 let mut depth = 1;
1128 while let Some(ch) = self.peek() {
1129 if ch == '(' {
1130 depth += 1;
1131 } else if ch == ')' {
1132 depth -= 1;
1133 if depth == 0 {
1134 word.push(self.next_char().unwrap());
1135 break;
1136 }
1137 }
1138 word.push(self.next_char().unwrap());
1139 }
1140 }
1141 }
1142 '=' => {
1143 word.push(self.next_char().unwrap());
1144 if self.peek() == Some('(') {
1145 word.push(self.next_char().unwrap());
1146 let mut depth = 1;
1147 while let Some(ch) = self.peek() {
1148 if ch == '(' {
1149 depth += 1;
1150 } else if ch == ')' {
1151 depth -= 1;
1152 if depth == 0 {
1153 word.push(self.next_char().unwrap());
1154 break;
1155 }
1156 }
1157 word.push(self.next_char().unwrap());
1158 }
1159 }
1160 }
1161 '`' => {
1162 word.push(self.next_char().unwrap()); while let Some(ch) = self.peek() {
1166 if ch == '`' {
1167 word.push(self.next_char().unwrap()); break;
1169 }
1170 word.push(self.next_char().unwrap());
1171 }
1172 }
1173 '\'' => {
1174 self.next_char();
1175 while let Some(ch) = self.peek() {
1176 if ch == '\'' {
1177 self.next_char();
1178 break;
1179 }
1180 let c = self.next_char().unwrap();
1181 if matches!(c, '`' | '$' | '(' | ')') {
1182 word.push('\x00');
1183 }
1184 word.push(c);
1185 }
1186 }
1187 '"' => {
1188 self.next_char();
1189 while let Some(ch) = self.peek() {
1190 if ch == '"' {
1191 self.next_char();
1192 break;
1193 }
1194 if ch == '\\' {
1195 self.next_char();
1196 if let Some(escaped) = self.peek() {
1197 match escaped {
1198 '$' | '`' | '"' | '\\' | '\n' => {
1199 word.push(self.next_char().unwrap());
1200 }
1201 _ => {
1202 word.push('\\');
1203 word.push(self.next_char().unwrap());
1204 }
1205 }
1206 } else {
1207 word.push('\\');
1208 }
1209 } else {
1210 word.push(self.next_char().unwrap());
1211 }
1212 }
1213 }
1214 '\\' => {
1215 self.next_char();
1216 if let Some(escaped) = self.next_char() {
1217 word.push(escaped);
1218 }
1219 }
1220 _ => {
1221 word.push(self.next_char().unwrap());
1222 }
1223 }
1224 }
1225
1226 match word.as_str() {
1227 "if" => ShellToken::If,
1228 "then" => ShellToken::Then,
1229 "else" => ShellToken::Else,
1230 "elif" => ShellToken::Elif,
1231 "fi" => ShellToken::Fi,
1232 "case" => ShellToken::Case,
1233 "esac" => ShellToken::Esac,
1234 "for" => ShellToken::For,
1235 "while" => ShellToken::While,
1236 "until" => ShellToken::Until,
1237 "do" => ShellToken::Do,
1238 "done" => ShellToken::Done,
1239 "in" => ShellToken::In,
1240 "function" => ShellToken::Function,
1241 "select" => ShellToken::Select,
1242 "time" => ShellToken::Time,
1243 "coproc" => ShellToken::Coproc,
1244 "repeat" => ShellToken::Repeat,
1245 "typeset" | "local" | "declare" | "export" | "readonly" | "integer" | "float" => {
1248 ShellToken::Typeset(word)
1249 }
1250 _ => ShellToken::Word(word),
1251 }
1252 }
1253}
1254
1255pub struct ShellParser<'a> {
1256 lexer: ShellLexer<'a>,
1257 current: ShellToken,
1258}
1259
1260impl<'a> ShellParser<'a> {
1261 pub fn new(input: &'a str) -> Self {
1262 let mut lexer = ShellLexer::new(input);
1263 let current = lexer.next_token();
1264 Self { lexer, current }
1265 }
1266
1267 fn parse_array_elements(content: &str) -> Vec<ShellWord> {
1268 let mut elements = Vec::new();
1269 let mut current = String::new();
1270 let mut chars = content.chars().peekable();
1271 let mut in_single_quote = false;
1272 let mut in_double_quote = false;
1273
1274 while let Some(c) = chars.next() {
1275 if in_single_quote {
1276 if c == '\'' {
1277 in_single_quote = false;
1278 let marked: String = current
1279 .chars()
1280 .flat_map(|ch| {
1281 if matches!(ch, '`' | '$' | '(' | ')') {
1282 vec!['\x00', ch]
1283 } else {
1284 vec![ch]
1285 }
1286 })
1287 .collect();
1288 elements.push(ShellWord::Literal(marked));
1289 current.clear();
1290 } else {
1291 current.push(c);
1292 }
1293 } else if in_double_quote {
1294 if c == '"' {
1295 in_double_quote = false;
1296 elements.push(ShellWord::Literal(current.clone()));
1297 current.clear();
1298 } else if c == '\\' {
1299 if let Some(&next) = chars.peek() {
1300 if matches!(next, '$' | '`' | '"' | '\\') {
1301 chars.next();
1302 current.push(next);
1303 } else {
1304 current.push(c);
1305 }
1306 } else {
1307 current.push(c);
1308 }
1309 } else {
1310 current.push(c);
1311 }
1312 } else {
1313 match c {
1314 '\'' => in_single_quote = true,
1315 '"' => in_double_quote = true,
1316 '$' if chars.peek() == Some(&'(') => {
1317 if !current.is_empty() {
1319 elements.push(ShellWord::Literal(current.clone()));
1320 current.clear();
1321 }
1322 chars.next(); let mut depth = 1;
1324 let mut cmd = String::new();
1325 while let Some(ch) = chars.next() {
1326 if ch == '(' { depth += 1; }
1327 if ch == ')' { depth -= 1; if depth == 0 { break; } }
1328 cmd.push(ch);
1329 }
1330 let mut sub_parser = ShellParser::new(&cmd);
1332 if let Ok(cmds) = sub_parser.parse_script() {
1333 if cmds.len() == 1 {
1334 elements.push(ShellWord::CommandSub(Box::new(cmds.into_iter().next().unwrap())));
1335 } else if !cmds.is_empty() {
1336 elements.push(ShellWord::CommandSub(Box::new(
1338 ShellCommand::Compound(CompoundCommand::BraceGroup(cmds))
1339 )));
1340 }
1341 }
1342 }
1343 ' ' | '\t' | '\n' => {
1344 if !current.is_empty() {
1345 elements.push(ShellWord::Literal(current.clone()));
1346 current.clear();
1347 }
1348 }
1349 '\\' => {
1350 if let Some(next) = chars.next() {
1351 current.push(next);
1352 }
1353 }
1354 _ => current.push(c),
1355 }
1356 }
1357 }
1358
1359 if !current.is_empty() {
1360 elements.push(ShellWord::Literal(current));
1361 }
1362
1363 elements
1364 }
1365
1366 fn advance(&mut self) -> ShellToken {
1367 std::mem::replace(&mut self.current, self.lexer.next_token())
1368 }
1369
1370 fn expect(&mut self, expected: ShellToken) -> Result<(), String> {
1371 if self.current == expected {
1372 self.advance();
1373 Ok(())
1374 } else {
1375 Err(format!("Expected {:?}, got {:?}", expected, self.current))
1376 }
1377 }
1378
1379 fn skip_newlines(&mut self) {
1380 while self.current == ShellToken::Newline {
1381 self.advance();
1382 }
1383 }
1384
1385 fn skip_separators(&mut self) {
1386 while self.current == ShellToken::Newline || self.current == ShellToken::Semi {
1387 self.advance();
1388 }
1389 }
1390
1391 #[tracing::instrument(level = "trace", skip_all)]
1392 pub fn parse_script(&mut self) -> Result<Vec<ShellCommand>, String> {
1393 let mut commands = Vec::new();
1394 self.skip_newlines();
1395 while self.current != ShellToken::Eof {
1396 if let Some(cmd) = self.parse_complete_command()? {
1397 commands.push(cmd);
1398 }
1399 self.skip_newlines();
1400 }
1401 Ok(commands)
1402 }
1403
1404 fn parse_complete_command(&mut self) -> Result<Option<ShellCommand>, String> {
1405 self.skip_newlines();
1406 if self.current == ShellToken::Eof {
1407 return Ok(None);
1408 }
1409 let cmd = self.parse_list()?;
1410 match &self.current {
1411 ShellToken::Newline | ShellToken::Semi | ShellToken::Amp => {
1412 self.advance();
1413 }
1414 _ => {}
1415 }
1416 Ok(Some(cmd))
1417 }
1418
1419 fn parse_list(&mut self) -> Result<ShellCommand, String> {
1420 let first = self.parse_pipeline()?;
1421 let mut items = vec![(first, ListOp::Semi)];
1422
1423 loop {
1424 let op = match &self.current {
1425 ShellToken::AmpAmp => ListOp::And,
1426 ShellToken::PipePipe => ListOp::Or,
1427 ShellToken::Semi => ListOp::Semi,
1428 ShellToken::Amp => ListOp::Amp,
1429 ShellToken::Newline => break,
1430 _ => break,
1431 };
1432
1433 self.advance();
1434 self.skip_newlines();
1435
1436 if let Some(last) = items.last_mut() {
1437 last.1 = op;
1438 }
1439
1440 if self.current == ShellToken::Eof
1441 || self.current == ShellToken::Then
1442 || self.current == ShellToken::Else
1443 || self.current == ShellToken::Elif
1444 || self.current == ShellToken::Fi
1445 || self.current == ShellToken::Do
1446 || self.current == ShellToken::Done
1447 || self.current == ShellToken::Esac
1448 || self.current == ShellToken::RBrace
1449 || self.current == ShellToken::RParen
1450 {
1451 break;
1452 }
1453
1454 let next = self.parse_pipeline()?;
1455 items.push((next, ListOp::Semi));
1456 }
1457
1458 if items.len() == 1 {
1459 let (cmd, op) = items.pop().unwrap();
1460 if matches!(op, ListOp::Amp) {
1461 Ok(ShellCommand::List(vec![(cmd, op)]))
1462 } else {
1463 Ok(cmd)
1464 }
1465 } else {
1466 Ok(ShellCommand::List(items))
1467 }
1468 }
1469
1470 fn parse_pipeline(&mut self) -> Result<ShellCommand, String> {
1471 let negated = if self.current == ShellToken::Bang {
1472 self.advance();
1473 true
1474 } else {
1475 false
1476 };
1477
1478 let first = self.parse_command()?;
1479 let mut cmds = vec![first];
1480
1481 while self.current == ShellToken::Pipe {
1482 self.advance();
1483 self.skip_newlines();
1484 cmds.push(self.parse_command()?);
1485 }
1486
1487 if cmds.len() == 1 && !negated {
1488 Ok(cmds.pop().unwrap())
1489 } else {
1490 Ok(ShellCommand::Pipeline(cmds, negated))
1491 }
1492 }
1493
1494 fn parse_command(&mut self) -> Result<ShellCommand, String> {
1495 let cmd = match &self.current {
1496 ShellToken::If => self.parse_if(),
1497 ShellToken::For => self.parse_for(),
1498 ShellToken::While => self.parse_while(),
1499 ShellToken::Until => self.parse_until(),
1500 ShellToken::Case => self.parse_case(),
1501 ShellToken::Repeat => self.parse_repeat(),
1502 ShellToken::LBrace => self.parse_brace_group_or_try(),
1503 ShellToken::LParen => {
1504 self.advance();
1505 if self.current == ShellToken::RParen {
1506 self.advance();
1507 self.skip_newlines();
1508 if self.current == ShellToken::LBrace {
1509 let body = self.parse_brace_group()?;
1510 Ok(ShellCommand::FunctionDef(String::new(), Box::new(body)))
1511 } else {
1512 Ok(ShellCommand::Compound(CompoundCommand::Subshell(vec![])))
1513 }
1514 } else {
1515 self.skip_newlines();
1516 let body = self.parse_compound_list()?;
1517 self.expect(ShellToken::RParen)?;
1518 Ok(ShellCommand::Compound(CompoundCommand::Subshell(body)))
1519 }
1520 }
1521 ShellToken::DoubleLBracket => self.parse_cond_command(),
1522 ShellToken::DoubleLParen => self.parse_arith_command(),
1523 ShellToken::Function => self.parse_function(),
1524 ShellToken::Coproc => self.parse_coproc(),
1525 _ => self.parse_simple_command(),
1526 }?;
1527
1528 let mut redirects = Vec::new();
1529 loop {
1530 if let ShellToken::Word(w) = &self.current {
1531 if w.chars().all(|c| c.is_ascii_digit()) {
1532 let fd_str = w.clone();
1533 self.advance();
1534 match &self.current {
1535 ShellToken::Less
1536 | ShellToken::Greater
1537 | ShellToken::GreaterGreater
1538 | ShellToken::LessAmp
1539 | ShellToken::GreaterAmp
1540 | ShellToken::LessLess
1541 | ShellToken::LessLessLess
1542 | ShellToken::LessGreater
1543 | ShellToken::GreaterPipe => {
1544 let fd = fd_str.parse::<i32>().ok();
1545 redirects.push(self.parse_redirect_with_fd(fd)?);
1546 continue;
1547 }
1548 _ => break,
1549 }
1550 }
1551 }
1552
1553 match &self.current {
1554 ShellToken::Less
1555 | ShellToken::Greater
1556 | ShellToken::GreaterGreater
1557 | ShellToken::LessAmp
1558 | ShellToken::GreaterAmp
1559 | ShellToken::LessLess
1560 | ShellToken::LessLessLess
1561 | ShellToken::LessGreater
1562 | ShellToken::GreaterPipe
1563 | ShellToken::AmpGreater
1564 | ShellToken::AmpGreaterGreater => {
1565 redirects.push(self.parse_redirect_with_fd(None)?);
1566 }
1567 _ => break,
1568 }
1569 }
1570
1571 if !redirects.is_empty() {
1572 Ok(ShellCommand::Compound(CompoundCommand::WithRedirects(
1573 Box::new(cmd),
1574 redirects,
1575 )))
1576 } else {
1577 Ok(cmd)
1578 }
1579 }
1580
1581 fn parse_simple_command(&mut self) -> Result<ShellCommand, String> {
1582 let mut cmd = SimpleCommand {
1583 assignments: Vec::new(),
1584 words: Vec::new(),
1585 redirects: Vec::new(),
1586 };
1587
1588 loop {
1589 match &self.current {
1590 ShellToken::Word(w) => {
1591 if w.starts_with('{') && w.ends_with('}') && w.len() > 2 {
1592 let varname = w[1..w.len() - 1].to_string();
1593 if varname.chars().all(|c| c.is_alphanumeric() || c == '_') {
1594 let saved_word = w.clone();
1595 self.advance();
1596 match &self.current {
1597 ShellToken::Less
1598 | ShellToken::Greater
1599 | ShellToken::GreaterGreater
1600 | ShellToken::LessAmp
1601 | ShellToken::GreaterAmp
1602 | ShellToken::LessLess
1603 | ShellToken::LessLessLess
1604 | ShellToken::LessGreater
1605 | ShellToken::GreaterPipe => {
1606 let mut redir = self.parse_redirect_with_fd(None)?;
1607 redir.fd_var = Some(varname);
1608 cmd.redirects.push(redir);
1609 continue;
1610 }
1611 _ => {
1612 cmd.words.push(ShellWord::Literal(saved_word));
1613 continue;
1614 }
1615 }
1616 }
1617 }
1618
1619 if w.chars().all(|c| c.is_ascii_digit()) {
1620 let fd_str = w.clone();
1621 self.advance();
1622 match &self.current {
1623 ShellToken::Less
1624 | ShellToken::Greater
1625 | ShellToken::GreaterGreater
1626 | ShellToken::LessAmp
1627 | ShellToken::GreaterAmp
1628 | ShellToken::LessLess
1629 | ShellToken::LessLessLess
1630 | ShellToken::LessGreater
1631 | ShellToken::GreaterPipe => {
1632 let fd = fd_str.parse::<i32>().ok();
1633 cmd.redirects.push(self.parse_redirect_with_fd(fd)?);
1634 continue;
1635 }
1636 _ => {
1637 cmd.words.push(ShellWord::Literal(fd_str));
1638 continue;
1639 }
1640 }
1641 }
1642
1643 if cmd.words.is_empty() && w.contains('=') && !w.starts_with('=') {
1644 let (eq_pos, is_append) = if let Some(pos) = w.find("+=") {
1645 (pos, true)
1646 } else if let Some(pos) = w.find('=') {
1647 (pos, false)
1648 } else {
1649 (0, false)
1650 };
1651
1652 if eq_pos > 0 {
1653 let var = w[..eq_pos].to_string();
1654 let val_start = if is_append { eq_pos + 2 } else { eq_pos + 1 };
1655 let val = w[val_start..].to_string();
1656
1657 let is_valid_var = if let Some(bracket_pos) = var.find('[') {
1658 let name = &var[..bracket_pos];
1659 let rest = &var[bracket_pos..];
1660 name.chars().all(|c| c.is_alphanumeric() || c == '_')
1661 && rest.ends_with(']')
1662 } else {
1663 var.chars().all(|c| c.is_alphanumeric() || c == '_')
1664 };
1665 if is_valid_var {
1666 if val.starts_with('(') && val.ends_with(')') {
1667 let array_content = &val[1..val.len() - 1];
1668 let elements = Self::parse_array_elements(array_content);
1669 cmd.assignments.push((
1670 var,
1671 ShellWord::ArrayLiteral(elements),
1672 is_append,
1673 ));
1674 } else {
1675 cmd.assignments
1676 .push((var, ShellWord::Literal(val), is_append));
1677 }
1678 self.advance();
1679 continue;
1680 }
1681 }
1682 }
1683
1684 cmd.words.push(self.parse_word()?);
1685 }
1686
1687 ShellToken::LBracket => {
1688 cmd.words.push(ShellWord::Literal("[".to_string()));
1689 self.advance();
1690 }
1691 ShellToken::RBracket => {
1692 if !cmd.words.is_empty() {
1693 cmd.words.push(ShellWord::Literal("]".to_string()));
1694 self.advance();
1695 } else {
1696 break;
1697 }
1698 }
1699 ShellToken::If
1700 | ShellToken::Then
1701 | ShellToken::Else
1702 | ShellToken::Elif
1703 | ShellToken::Fi
1704 | ShellToken::Case
1705 | ShellToken::Esac
1706 | ShellToken::For
1707 | ShellToken::While
1708 | ShellToken::Until
1709 | ShellToken::Do
1710 | ShellToken::Done
1711 | ShellToken::In
1712 | ShellToken::Function
1713 | ShellToken::Select
1714 | ShellToken::Time
1715 | ShellToken::Coproc
1716 | ShellToken::Repeat => {
1717 if !cmd.words.is_empty() {
1718 cmd.words.push(self.parse_word()?);
1719 } else {
1720 break;
1721 }
1722 }
1723
1724 ShellToken::Typeset(ref name) => {
1725 if cmd.words.is_empty() {
1726 let name = name.clone();
1727 cmd.words.push(ShellWord::Literal(name));
1728 self.advance();
1729 } else {
1730 cmd.words.push(self.parse_word()?);
1731 }
1732 }
1733
1734 ShellToken::Less
1735 | ShellToken::Greater
1736 | ShellToken::GreaterGreater
1737 | ShellToken::LessAmp
1738 | ShellToken::GreaterAmp
1739 | ShellToken::LessLess
1740 | ShellToken::LessLessLess
1741 | ShellToken::LessGreater
1742 | ShellToken::GreaterPipe
1743 | ShellToken::AmpGreater
1744 | ShellToken::AmpGreaterGreater
1745 | ShellToken::HereDoc(_, _) => {
1746 cmd.redirects.push(self.parse_redirect_with_fd(None)?);
1747 }
1748
1749 _ => break,
1750 }
1751 }
1752
1753 if cmd.words.len() == 1 && self.current == ShellToken::LParen {
1754 if let ShellWord::Literal(name) = &cmd.words[0] {
1755 let name = name.clone();
1756 self.advance();
1757 self.expect(ShellToken::RParen)?;
1758 self.skip_newlines();
1759 let body = self.parse_command()?;
1760 return Ok(ShellCommand::FunctionDef(name, Box::new(body)));
1761 }
1762 }
1763
1764 Ok(ShellCommand::Simple(cmd))
1765 }
1766
1767 fn parse_word(&mut self) -> Result<ShellWord, String> {
1768 let token = self.advance();
1769 match token {
1770 ShellToken::Word(w) => Ok(ShellWord::Literal(w)),
1771 ShellToken::LBracket => Ok(ShellWord::Literal("[".to_string())),
1772 ShellToken::If => Ok(ShellWord::Literal("if".to_string())),
1773 ShellToken::Then => Ok(ShellWord::Literal("then".to_string())),
1774 ShellToken::Else => Ok(ShellWord::Literal("else".to_string())),
1775 ShellToken::Elif => Ok(ShellWord::Literal("elif".to_string())),
1776 ShellToken::Fi => Ok(ShellWord::Literal("fi".to_string())),
1777 ShellToken::Case => Ok(ShellWord::Literal("case".to_string())),
1778 ShellToken::Esac => Ok(ShellWord::Literal("esac".to_string())),
1779 ShellToken::For => Ok(ShellWord::Literal("for".to_string())),
1780 ShellToken::While => Ok(ShellWord::Literal("while".to_string())),
1781 ShellToken::Until => Ok(ShellWord::Literal("until".to_string())),
1782 ShellToken::Do => Ok(ShellWord::Literal("do".to_string())),
1783 ShellToken::Done => Ok(ShellWord::Literal("done".to_string())),
1784 ShellToken::In => Ok(ShellWord::Literal("in".to_string())),
1785 ShellToken::Function => Ok(ShellWord::Literal("function".to_string())),
1786 ShellToken::Select => Ok(ShellWord::Literal("select".to_string())),
1787 ShellToken::Time => Ok(ShellWord::Literal("time".to_string())),
1788 ShellToken::Coproc => Ok(ShellWord::Literal("coproc".to_string())),
1789 ShellToken::Typeset(name) => Ok(ShellWord::Literal(name)),
1790 ShellToken::Repeat => Ok(ShellWord::Literal("repeat".to_string())),
1791 _ => Err("Expected word".to_string()),
1792 }
1793 }
1794
1795 fn parse_redirect_with_fd(&mut self, fd: Option<i32>) -> Result<Redirect, String> {
1796 if let ShellToken::HereDoc(delimiter, content) = &self.current {
1797 let delimiter = delimiter.clone();
1798 let content = content.clone();
1799 self.advance();
1800 return Ok(Redirect {
1801 fd,
1802 op: RedirectOp::HereDoc,
1803 target: ShellWord::Literal(delimiter),
1804 heredoc_content: Some(content),
1805 fd_var: None,
1806 });
1807 }
1808
1809 let mut fd_var = None;
1810 if let ShellToken::Word(w) = &self.current {
1811 if w.starts_with('{') && w.ends_with('}') && w.len() > 2 {
1812 let varname = w[1..w.len() - 1].to_string();
1813 fd_var = Some(varname);
1814 self.advance();
1815 }
1816 }
1817
1818 let op = match self.advance() {
1819 ShellToken::Less => RedirectOp::Read,
1820 ShellToken::Greater => RedirectOp::Write,
1821 ShellToken::GreaterGreater => RedirectOp::Append,
1822 ShellToken::LessAmp => RedirectOp::DupRead,
1823 ShellToken::GreaterAmp => RedirectOp::DupWrite,
1824 ShellToken::LessLess => RedirectOp::HereDoc,
1825 ShellToken::LessLessLess => RedirectOp::HereString,
1826 ShellToken::LessGreater => RedirectOp::ReadWrite,
1827 ShellToken::GreaterPipe => RedirectOp::Clobber,
1828 ShellToken::AmpGreater => RedirectOp::WriteBoth,
1829 ShellToken::AmpGreaterGreater => RedirectOp::AppendBoth,
1830 _ => return Err("Expected redirect operator".to_string()),
1831 };
1832
1833 let target = self.parse_word()?;
1834
1835 Ok(Redirect {
1836 fd,
1837 op,
1838 target,
1839 heredoc_content: None,
1840 fd_var,
1841 })
1842 }
1843
1844 fn parse_if(&mut self) -> Result<ShellCommand, String> {
1845 let mut conditions = Vec::new();
1846 let mut else_part = None;
1847 let mut usebrace = false;
1848
1849 let mut xtok = self.current.clone();
1850 loop {
1851 if xtok == ShellToken::Fi {
1852 self.advance();
1853 break;
1854 }
1855
1856 self.advance();
1857
1858 if xtok == ShellToken::Else {
1859 break;
1860 }
1861
1862 self.skip_separators();
1863
1864 if xtok != ShellToken::If && xtok != ShellToken::Elif {
1865 return Err(format!("Expected If or Elif, got {:?}", xtok));
1866 }
1867
1868 let cond = self.parse_compound_list_until(&[ShellToken::Then, ShellToken::LBrace])?;
1869 self.skip_separators();
1870 xtok = ShellToken::Fi;
1871
1872 if self.current == ShellToken::Then {
1873 usebrace = false;
1874 self.advance();
1875 let body = self.parse_compound_list()?;
1876 conditions.push((cond, body));
1877 } else if self.current == ShellToken::LBrace {
1878 usebrace = true;
1879 self.advance();
1880 self.skip_separators();
1881 let body = self.parse_compound_list_until(&[ShellToken::RBrace])?;
1882 if self.current != ShellToken::RBrace {
1883 return Err(format!("Expected RBrace, got {:?}", self.current));
1884 }
1885 conditions.push((cond, body));
1886 self.advance();
1887 if self.current == ShellToken::Newline || self.current == ShellToken::Semi {
1888 break;
1889 }
1890 } else {
1891 return Err(format!(
1892 "Expected Then or LBrace after condition, got {:?}",
1893 self.current
1894 ));
1895 }
1896
1897 xtok = self.current.clone();
1898 if xtok != ShellToken::Elif && xtok != ShellToken::Else && xtok != ShellToken::Fi {
1899 break;
1900 }
1901 }
1902
1903 if xtok == ShellToken::Else || self.current == ShellToken::Else {
1904 if self.current == ShellToken::Else {
1905 self.advance();
1906 }
1907 self.skip_separators();
1908
1909 if self.current == ShellToken::LBrace && usebrace {
1910 self.advance();
1911 self.skip_separators();
1912 let body = self.parse_compound_list_until(&[ShellToken::RBrace])?;
1913 if self.current != ShellToken::RBrace {
1914 return Err(format!("Expected RBrace in else, got {:?}", self.current));
1915 }
1916 self.advance();
1917 else_part = Some(body);
1918 } else {
1919 let body = self.parse_compound_list()?;
1920 if self.current != ShellToken::Fi {
1921 return Err(format!("Expected Fi, got {:?}", self.current));
1922 }
1923 self.advance();
1924 else_part = Some(body);
1925 }
1926 }
1927
1928 Ok(ShellCommand::Compound(CompoundCommand::If {
1929 conditions,
1930 else_part,
1931 }))
1932 }
1933
1934 fn parse_for(&mut self) -> Result<ShellCommand, String> {
1935 self.expect(ShellToken::For)?;
1936 self.skip_newlines();
1937
1938 if self.current == ShellToken::DoubleLParen {
1939 return self.parse_for_arith();
1940 }
1941
1942 let var = if let ShellToken::Word(w) = self.advance() {
1943 w
1944 } else {
1945 return Err("Expected variable name after 'for'".to_string());
1946 };
1947
1948 while self.current == ShellToken::Newline {
1949 self.advance();
1950 }
1951
1952 let words = if self.current == ShellToken::In {
1953 self.advance();
1954 let mut words = Vec::new();
1955 while let ShellToken::Word(_) = &self.current {
1956 words.push(self.parse_word()?);
1957 }
1958 Some(words)
1959 } else if self.current == ShellToken::LParen {
1960 self.advance();
1961 let mut words = Vec::new();
1962 while self.current != ShellToken::RParen && self.current != ShellToken::Eof {
1963 if let ShellToken::Word(_) = &self.current {
1964 words.push(self.parse_word()?);
1965 } else if self.current == ShellToken::Newline {
1966 self.advance();
1967 } else {
1968 break;
1969 }
1970 }
1971 self.expect(ShellToken::RParen)?;
1972 Some(words)
1973 } else {
1974 None
1975 };
1976
1977 self.skip_separators();
1978
1979 let body = if self.current == ShellToken::LBrace {
1980 self.advance();
1981 let body = self.parse_compound_list_until(&[ShellToken::RBrace])?;
1982 self.expect(ShellToken::RBrace)?;
1983 body
1984 } else {
1985 self.expect(ShellToken::Do)?;
1986 let body = self.parse_compound_list()?;
1987 self.expect(ShellToken::Done)?;
1988 body
1989 };
1990
1991 Ok(ShellCommand::Compound(CompoundCommand::For {
1992 var,
1993 words,
1994 body,
1995 }))
1996 }
1997
1998 fn parse_for_arith(&mut self) -> Result<ShellCommand, String> {
1999 self.expect(ShellToken::DoubleLParen)?;
2000
2001 let mut parts = Vec::new();
2002 let mut current_part = String::new();
2003 let mut depth = 0;
2004
2005 loop {
2006 match &self.current {
2007 ShellToken::DoubleRParen if depth == 0 => break,
2008 ShellToken::DoubleLParen => {
2009 depth += 1;
2010 current_part.push_str("((");
2011 self.advance();
2012 }
2013 ShellToken::DoubleRParen => {
2014 depth -= 1;
2015 current_part.push_str("))");
2016 self.advance();
2017 }
2018 ShellToken::Semi => {
2019 parts.push(current_part.trim().to_string());
2020 current_part = String::new();
2021 self.advance();
2022 }
2023 ShellToken::Word(w) => {
2024 current_part.push_str(w);
2025 current_part.push(' ');
2026 self.advance();
2027 }
2028 ShellToken::Less => {
2029 current_part.push('<');
2030 self.advance();
2031 }
2032 ShellToken::Greater => {
2033 current_part.push('>');
2034 self.advance();
2035 }
2036 ShellToken::LessLess => {
2037 current_part.push_str("<<");
2038 self.advance();
2039 }
2040 ShellToken::GreaterGreater => {
2041 current_part.push_str(">>");
2042 self.advance();
2043 }
2044 _ => {
2045 self.advance();
2046 }
2047 }
2048 }
2049 parts.push(current_part.trim().to_string());
2050
2051 self.expect(ShellToken::DoubleRParen)?;
2052 self.skip_newlines();
2053
2054 match &self.current {
2055 ShellToken::Semi | ShellToken::Newline => {
2056 self.advance();
2057 }
2058 _ => {}
2059 }
2060 self.skip_newlines();
2061
2062 self.expect(ShellToken::Do)?;
2063 self.skip_newlines();
2064 let body = self.parse_compound_list()?;
2065 self.expect(ShellToken::Done)?;
2066
2067 Ok(ShellCommand::Compound(CompoundCommand::ForArith {
2068 init: parts.first().cloned().unwrap_or_default(),
2069 cond: parts.get(1).cloned().unwrap_or_default(),
2070 step: parts.get(2).cloned().unwrap_or_default(),
2071 body,
2072 }))
2073 }
2074
2075 fn parse_while(&mut self) -> Result<ShellCommand, String> {
2076 self.expect(ShellToken::While)?;
2077 let condition = self.parse_compound_list_until(&[ShellToken::Do, ShellToken::LBrace])?;
2078 self.skip_separators();
2079
2080 let body = if self.current == ShellToken::LBrace {
2081 self.advance();
2082 let body = self.parse_compound_list_until(&[ShellToken::RBrace])?;
2083 self.expect(ShellToken::RBrace)?;
2084 body
2085 } else {
2086 self.expect(ShellToken::Do)?;
2087 let body = self.parse_compound_list()?;
2088 self.expect(ShellToken::Done)?;
2089 body
2090 };
2091
2092 Ok(ShellCommand::Compound(CompoundCommand::While {
2093 condition,
2094 body,
2095 }))
2096 }
2097
2098 fn parse_until(&mut self) -> Result<ShellCommand, String> {
2099 self.expect(ShellToken::Until)?;
2100 let condition = self.parse_compound_list_until(&[ShellToken::Do, ShellToken::LBrace])?;
2101 self.skip_separators();
2102
2103 let body = if self.current == ShellToken::LBrace {
2104 self.advance();
2105 let body = self.parse_compound_list_until(&[ShellToken::RBrace])?;
2106 self.expect(ShellToken::RBrace)?;
2107 body
2108 } else {
2109 self.expect(ShellToken::Do)?;
2110 let body = self.parse_compound_list()?;
2111 self.expect(ShellToken::Done)?;
2112 body
2113 };
2114
2115 Ok(ShellCommand::Compound(CompoundCommand::Until {
2116 condition,
2117 body,
2118 }))
2119 }
2120
2121 fn parse_case(&mut self) -> Result<ShellCommand, String> {
2122 self.expect(ShellToken::Case)?;
2123 self.skip_newlines();
2124 let word = self.parse_word()?;
2125 self.skip_newlines();
2126 self.expect(ShellToken::In)?;
2127 self.skip_newlines();
2128
2129 let mut cases = Vec::new();
2130
2131 while self.current != ShellToken::Esac {
2132 let mut patterns = Vec::new();
2133 if self.current == ShellToken::LParen {
2134 self.advance();
2135 }
2136
2137 loop {
2138 patterns.push(self.parse_word()?);
2139 if self.current == ShellToken::Pipe {
2140 self.advance();
2141 } else {
2142 break;
2143 }
2144 }
2145
2146 self.expect(ShellToken::RParen)?;
2147 self.skip_newlines();
2148
2149 let body = self.parse_compound_list()?;
2150
2151 let term = match &self.current {
2152 ShellToken::DoubleSemi => {
2153 self.advance();
2154 CaseTerminator::Break
2155 }
2156 ShellToken::SemiAmp => {
2157 self.advance();
2158 CaseTerminator::Fallthrough
2159 }
2160 ShellToken::SemiSemiAmp => {
2161 self.advance();
2162 CaseTerminator::Continue
2163 }
2164 _ => CaseTerminator::Break,
2165 };
2166
2167 cases.push((patterns, body, term));
2168 self.skip_newlines();
2169 }
2170
2171 self.expect(ShellToken::Esac)?;
2172
2173 Ok(ShellCommand::Compound(CompoundCommand::Case {
2174 word,
2175 cases,
2176 }))
2177 }
2178
2179 fn parse_repeat(&mut self) -> Result<ShellCommand, String> {
2180 self.expect(ShellToken::Repeat)?;
2181
2182 let count = match &self.current {
2183 ShellToken::Word(w) => {
2184 let c = w.clone();
2185 self.advance();
2186 c
2187 }
2188 _ => return Err("expected count after 'repeat'".to_string()),
2189 };
2190
2191 self.skip_separators();
2192
2193 let body = if self.current == ShellToken::LBrace {
2194 self.advance();
2195 self.skip_newlines();
2196 let body = self.parse_compound_list_until(&[ShellToken::RBrace])?;
2197 self.expect(ShellToken::RBrace)?;
2198 body
2199 } else if self.current == ShellToken::Do {
2200 self.expect(ShellToken::Do)?;
2201 let body = self.parse_compound_list()?;
2202 self.expect(ShellToken::Done)?;
2203 body
2204 } else {
2205 let cmd = self.parse_command()?;
2207 vec![cmd]
2208 };
2209
2210 Ok(ShellCommand::Compound(CompoundCommand::Repeat {
2211 count,
2212 body,
2213 }))
2214 }
2215
2216 fn parse_brace_group_or_try(&mut self) -> Result<ShellCommand, String> {
2217 self.expect(ShellToken::LBrace)?;
2218 self.skip_newlines();
2219 let try_body = self.parse_compound_list()?;
2220 self.expect(ShellToken::RBrace)?;
2221
2222 if matches!(&self.current, ShellToken::Word(w) if w == "always") {
2225 self.advance();
2226 self.expect(ShellToken::LBrace)?;
2227 self.skip_newlines();
2228 let always_body = self.parse_compound_list()?;
2229 self.expect(ShellToken::RBrace)?;
2230
2231 Ok(ShellCommand::Compound(CompoundCommand::Try {
2232 try_body,
2233 always_body,
2234 }))
2235 } else {
2236 Ok(ShellCommand::Compound(CompoundCommand::BraceGroup(
2237 try_body,
2238 )))
2239 }
2240 }
2241
2242 fn parse_brace_group(&mut self) -> Result<ShellCommand, String> {
2243 self.expect(ShellToken::LBrace)?;
2244 self.skip_newlines();
2245 let body = self.parse_compound_list()?;
2246 self.expect(ShellToken::RBrace)?;
2247
2248 Ok(ShellCommand::Compound(CompoundCommand::BraceGroup(body)))
2249 }
2250
2251 fn parse_cond_command(&mut self) -> Result<ShellCommand, String> {
2252 self.expect(ShellToken::DoubleLBracket)?;
2253
2254 let mut tokens: Vec<String> = Vec::new();
2255 while self.current != ShellToken::DoubleRBracket && self.current != ShellToken::Eof {
2256 match &self.current {
2257 ShellToken::Word(w) => tokens.push(w.clone()),
2258 ShellToken::Bang => tokens.push("!".to_string()),
2259 ShellToken::AmpAmp => tokens.push("&&".to_string()),
2260 ShellToken::PipePipe => tokens.push("||".to_string()),
2261 ShellToken::LParen => tokens.push("(".to_string()),
2262 ShellToken::RParen => tokens.push(")".to_string()),
2263 ShellToken::Less => tokens.push("<".to_string()),
2264 ShellToken::Greater => tokens.push(">".to_string()),
2265 _ => {}
2266 }
2267 self.advance();
2268 }
2269
2270 self.expect(ShellToken::DoubleRBracket)?;
2271
2272 let expr = self.parse_cond_tokens(&tokens)?;
2273 Ok(ShellCommand::Compound(CompoundCommand::Cond(expr)))
2274 }
2275
2276 fn parse_cond_tokens(&self, tokens: &[String]) -> Result<CondExpr, String> {
2277 if tokens.is_empty() {
2278 return Ok(CondExpr::StringNonEmpty(ShellWord::Literal(String::new())));
2279 }
2280
2281 if tokens[0] == "!" {
2282 let inner = self.parse_cond_tokens(&tokens[1..])?;
2283 return Ok(CondExpr::Not(Box::new(inner)));
2284 }
2285
2286 for i in (0..tokens.len()).rev() {
2292 if tokens[i] == "||" {
2293 let left = self.parse_cond_tokens(&tokens[..i])?;
2294 let right = self.parse_cond_tokens(&tokens[i + 1..])?;
2295 return Ok(CondExpr::Or(Box::new(left), Box::new(right)));
2296 }
2297 }
2298
2299 for i in (0..tokens.len()).rev() {
2301 if tokens[i] == "&&" {
2302 let left = self.parse_cond_tokens(&tokens[..i])?;
2303 let right = self.parse_cond_tokens(&tokens[i + 1..])?;
2304 return Ok(CondExpr::And(Box::new(left), Box::new(right)));
2305 }
2306 }
2307
2308 for (i, tok) in tokens.iter().enumerate() {
2310 match tok.as_str() {
2311 "=" | "==" => {
2312 let left = tokens[..i].join(" ");
2313 let right = tokens[i + 1..].join(" ");
2314 return Ok(CondExpr::StringEqual(
2315 ShellWord::Literal(left),
2316 ShellWord::Literal(right),
2317 ));
2318 }
2319 "!=" => {
2320 let left = tokens[..i].join(" ");
2321 let right = tokens[i + 1..].join(" ");
2322 return Ok(CondExpr::StringNotEqual(
2323 ShellWord::Literal(left),
2324 ShellWord::Literal(right),
2325 ));
2326 }
2327 "=~" => {
2328 let left = tokens[..i].join(" ");
2329 let right = tokens[i + 1..].join(" ");
2330 return Ok(CondExpr::StringMatch(
2331 ShellWord::Literal(left),
2332 ShellWord::Literal(right),
2333 ));
2334 }
2335 "-eq" => {
2336 let left = tokens[..i].join(" ");
2337 let right = tokens[i + 1..].join(" ");
2338 return Ok(CondExpr::NumEqual(
2339 ShellWord::Literal(left),
2340 ShellWord::Literal(right),
2341 ));
2342 }
2343 "-ne" => {
2344 let left = tokens[..i].join(" ");
2345 let right = tokens[i + 1..].join(" ");
2346 return Ok(CondExpr::NumNotEqual(
2347 ShellWord::Literal(left),
2348 ShellWord::Literal(right),
2349 ));
2350 }
2351 "-lt" => {
2352 let left = tokens[..i].join(" ");
2353 let right = tokens[i + 1..].join(" ");
2354 return Ok(CondExpr::NumLess(
2355 ShellWord::Literal(left),
2356 ShellWord::Literal(right),
2357 ));
2358 }
2359 "-le" => {
2360 let left = tokens[..i].join(" ");
2361 let right = tokens[i + 1..].join(" ");
2362 return Ok(CondExpr::NumLessEqual(
2363 ShellWord::Literal(left),
2364 ShellWord::Literal(right),
2365 ));
2366 }
2367 "-gt" => {
2368 let left = tokens[..i].join(" ");
2369 let right = tokens[i + 1..].join(" ");
2370 return Ok(CondExpr::NumGreater(
2371 ShellWord::Literal(left),
2372 ShellWord::Literal(right),
2373 ));
2374 }
2375 "-ge" => {
2376 let left = tokens[..i].join(" ");
2377 let right = tokens[i + 1..].join(" ");
2378 return Ok(CondExpr::NumGreaterEqual(
2379 ShellWord::Literal(left),
2380 ShellWord::Literal(right),
2381 ));
2382 }
2383 "<" => {
2384 let left = tokens[..i].join(" ");
2385 let right = tokens[i + 1..].join(" ");
2386 return Ok(CondExpr::StringLess(
2387 ShellWord::Literal(left),
2388 ShellWord::Literal(right),
2389 ));
2390 }
2391 ">" => {
2392 let left = tokens[..i].join(" ");
2393 let right = tokens[i + 1..].join(" ");
2394 return Ok(CondExpr::StringGreater(
2395 ShellWord::Literal(left),
2396 ShellWord::Literal(right),
2397 ));
2398 }
2399 _ => {}
2400 }
2401 }
2402
2403 if tokens.len() >= 2 {
2404 let op = &tokens[0];
2405 let arg = tokens[1..].join(" ");
2406 match op.as_str() {
2407 "-e" => return Ok(CondExpr::FileExists(ShellWord::Literal(arg))),
2408 "-f" => return Ok(CondExpr::FileRegular(ShellWord::Literal(arg))),
2409 "-d" => return Ok(CondExpr::FileDirectory(ShellWord::Literal(arg))),
2410 "-L" | "-h" => return Ok(CondExpr::FileSymlink(ShellWord::Literal(arg))),
2411 "-r" => return Ok(CondExpr::FileReadable(ShellWord::Literal(arg))),
2412 "-w" => return Ok(CondExpr::FileWritable(ShellWord::Literal(arg))),
2413 "-x" => return Ok(CondExpr::FileExecutable(ShellWord::Literal(arg))),
2414 "-s" => return Ok(CondExpr::FileNonEmpty(ShellWord::Literal(arg))),
2415 "-z" => return Ok(CondExpr::StringEmpty(ShellWord::Literal(arg))),
2416 "-n" => return Ok(CondExpr::StringNonEmpty(ShellWord::Literal(arg))),
2417 _ => {}
2418 }
2419 }
2420
2421 let expr_str = tokens.join(" ");
2422 Ok(CondExpr::StringNonEmpty(ShellWord::Literal(expr_str)))
2423 }
2424
2425 fn parse_arith_command(&mut self) -> Result<ShellCommand, String> {
2426 self.expect(ShellToken::DoubleLParen)?;
2427
2428 let mut expr = String::new();
2429 let mut depth = 1;
2430
2431 while depth > 0 {
2432 match &self.current {
2433 ShellToken::DoubleLParen => {
2434 depth += 1;
2435 expr.push_str("((");
2436 }
2437 ShellToken::DoubleRParen => {
2438 depth -= 1;
2439 if depth > 0 {
2440 expr.push_str("))");
2441 }
2442 }
2443 ShellToken::Word(w) => {
2444 expr.push_str(w);
2445 expr.push(' ');
2446 }
2447 ShellToken::LParen => expr.push('('),
2448 ShellToken::RParen => expr.push(')'),
2449 ShellToken::LBracket => expr.push('['),
2450 ShellToken::RBracket => expr.push(']'),
2451 ShellToken::Less => expr.push('<'),
2452 ShellToken::Greater => expr.push('>'),
2453 ShellToken::LessLess => expr.push_str("<<"),
2454 ShellToken::GreaterGreater => expr.push_str(">>"),
2455 ShellToken::Bang => expr.push('!'),
2456 ShellToken::Eof => break,
2457 _ => {}
2458 }
2459 self.advance();
2460 }
2461
2462 Ok(ShellCommand::Compound(CompoundCommand::Arith(
2463 expr.trim().to_string(),
2464 )))
2465 }
2466
2467 fn parse_function(&mut self) -> Result<ShellCommand, String> {
2468 self.expect(ShellToken::Function)?;
2469 self.skip_newlines();
2470 let name = if let ShellToken::Word(w) = self.advance() {
2471 w
2472 } else {
2473 return Err("Expected function name".to_string());
2474 };
2475
2476 self.skip_newlines();
2477 if self.current == ShellToken::LParen {
2478 self.advance();
2479 self.expect(ShellToken::RParen)?;
2480 self.skip_newlines();
2481 }
2482
2483 let body = self.parse_command()?;
2484 Ok(ShellCommand::FunctionDef(name, Box::new(body)))
2485 }
2486
2487 fn parse_coproc(&mut self) -> Result<ShellCommand, String> {
2488 self.expect(ShellToken::Coproc)?;
2489 self.skip_newlines();
2490
2491 let name = if let ShellToken::Word(w) = &self.current {
2492 let n = w.clone();
2493 self.advance();
2494 self.skip_newlines();
2495 Some(n)
2496 } else {
2497 None
2498 };
2499
2500 let body = self.parse_command()?;
2501 Ok(ShellCommand::Compound(CompoundCommand::Coproc {
2502 name,
2503 body: Box::new(body),
2504 }))
2505 }
2506
2507 fn parse_compound_list(&mut self) -> Result<Vec<ShellCommand>, String> {
2508 let mut commands = Vec::new();
2509 self.skip_newlines();
2510
2511 while self.current != ShellToken::Eof
2512 && self.current != ShellToken::RBrace
2513 && self.current != ShellToken::RParen
2514 && self.current != ShellToken::Fi
2515 && self.current != ShellToken::Done
2516 && self.current != ShellToken::Esac
2517 && self.current != ShellToken::Elif
2518 && self.current != ShellToken::Else
2519 && self.current != ShellToken::DoubleSemi
2520 && self.current != ShellToken::SemiAmp
2521 && self.current != ShellToken::SemiSemiAmp
2522 {
2523 let cmd = self.parse_list()?;
2524 commands.push(cmd);
2525 match &self.current {
2526 ShellToken::Newline | ShellToken::Semi => {
2527 self.advance();
2528 }
2529 _ => {}
2530 }
2531 self.skip_newlines();
2532 }
2533
2534 Ok(commands)
2535 }
2536
2537 fn parse_compound_list_until(
2538 &mut self,
2539 terminators: &[ShellToken],
2540 ) -> Result<Vec<ShellCommand>, String> {
2541 let mut commands = Vec::new();
2542 self.skip_newlines();
2543
2544 while self.current != ShellToken::Eof && !terminators.contains(&self.current) {
2545 let cmd = self.parse_list()?;
2546 commands.push(cmd);
2547 match &self.current {
2548 ShellToken::Newline | ShellToken::Semi => {
2549 self.advance();
2550 }
2551 _ => {}
2552 }
2553 self.skip_newlines();
2554 }
2555
2556 Ok(commands)
2557 }
2558}
2559
2560pub struct ZshParser<'a> {
2562 lexer: ZshLexer<'a>,
2563 errors: Vec<ParseError>,
2564 global_iterations: usize,
2566 recursion_depth: usize,
2568}
2569
2570const MAX_RECURSION_DEPTH: usize = 500;
2571
2572impl<'a> ZshParser<'a> {
2573 pub fn new(input: &'a str) -> Self {
2575 ZshParser {
2576 lexer: ZshLexer::new(input),
2577 errors: Vec::new(),
2578 global_iterations: 0,
2579 recursion_depth: 0,
2580 }
2581 }
2582
2583 #[inline]
2585 fn check_limit(&mut self) -> bool {
2586 self.global_iterations += 1;
2587 self.global_iterations > 10_000
2588 }
2589
2590 #[inline]
2592 fn check_recursion(&mut self) -> bool {
2593 self.recursion_depth > MAX_RECURSION_DEPTH
2594 }
2595
2596 pub fn parse(&mut self) -> Result<ZshProgram, Vec<ParseError>> {
2598 self.lexer.zshlex();
2599
2600 let program = self.parse_program_until(None);
2601
2602 if !self.errors.is_empty() {
2603 return Err(std::mem::take(&mut self.errors));
2604 }
2605
2606 Ok(program)
2607 }
2608
2609 fn parse_program(&mut self) -> ZshProgram {
2611 self.parse_program_until(None)
2612 }
2613
2614 fn parse_program_until(&mut self, end_tokens: Option<&[LexTok]>) -> ZshProgram {
2616 let mut lists = Vec::new();
2617
2618 loop {
2619 if self.check_limit() {
2620 self.error("parser exceeded global iteration limit");
2621 break;
2622 }
2623
2624 while self.lexer.tok == LexTok::Seper || self.lexer.tok == LexTok::Newlin {
2626 if self.check_limit() {
2627 self.error("parser exceeded global iteration limit");
2628 return ZshProgram { lists };
2629 }
2630 self.lexer.zshlex();
2631 }
2632
2633 if self.lexer.tok == LexTok::Endinput || self.lexer.tok == LexTok::Lexerr {
2634 break;
2635 }
2636
2637 if let Some(end_toks) = end_tokens {
2639 if end_toks.contains(&self.lexer.tok) {
2640 break;
2641 }
2642 }
2643
2644 match self.lexer.tok {
2648 LexTok::Outbrace
2649 | LexTok::Dsemi
2650 | LexTok::Semiamp
2651 | LexTok::Semibar
2652 | LexTok::Done
2653 | LexTok::Fi
2654 | LexTok::Esac
2655 | LexTok::Zend => break,
2656 _ => {}
2657 }
2658
2659 match self.parse_list() {
2660 Some(list) => lists.push(list),
2661 None => break,
2662 }
2663 }
2664
2665 ZshProgram { lists }
2666 }
2667
2668 fn parse_list(&mut self) -> Option<ZshList> {
2670 let sublist = self.parse_sublist()?;
2671
2672 let flags = match self.lexer.tok {
2673 LexTok::Amper => {
2674 self.lexer.zshlex();
2675 ListFlags {
2676 async_: true,
2677 disown: false,
2678 }
2679 }
2680 LexTok::Amperbang => {
2681 self.lexer.zshlex();
2682 ListFlags {
2683 async_: true,
2684 disown: true,
2685 }
2686 }
2687 LexTok::Seper | LexTok::Semi | LexTok::Newlin => {
2688 self.lexer.zshlex();
2689 ListFlags::default()
2690 }
2691 _ => ListFlags::default(),
2692 };
2693
2694 Some(ZshList { sublist, flags })
2695 }
2696
2697 fn parse_sublist(&mut self) -> Option<ZshSublist> {
2699 self.recursion_depth += 1;
2700 if self.check_recursion() {
2701 self.error("parse_sublist: max recursion depth exceeded");
2702 self.recursion_depth -= 1;
2703 return None;
2704 }
2705
2706 let mut flags = SublistFlags::default();
2707
2708 if self.lexer.tok == LexTok::Coproc {
2710 flags.coproc = true;
2711 self.lexer.zshlex();
2712 } else if self.lexer.tok == LexTok::Bang {
2713 flags.not = true;
2714 self.lexer.zshlex();
2715 }
2716
2717 let pipe = match self.parse_pipe() {
2718 Some(p) => p,
2719 None => {
2720 self.recursion_depth -= 1;
2721 return None;
2722 }
2723 };
2724
2725 let next = match self.lexer.tok {
2727 LexTok::Damper => {
2728 self.lexer.zshlex();
2729 self.skip_separators();
2730 self.parse_sublist().map(|s| (SublistOp::And, Box::new(s)))
2731 }
2732 LexTok::Dbar => {
2733 self.lexer.zshlex();
2734 self.skip_separators();
2735 self.parse_sublist().map(|s| (SublistOp::Or, Box::new(s)))
2736 }
2737 _ => None,
2738 };
2739
2740 self.recursion_depth -= 1;
2741 Some(ZshSublist { pipe, next, flags })
2742 }
2743
2744 fn parse_pipe(&mut self) -> Option<ZshPipe> {
2746 self.recursion_depth += 1;
2747 if self.check_recursion() {
2748 self.error("parse_pipe: max recursion depth exceeded");
2749 self.recursion_depth -= 1;
2750 return None;
2751 }
2752
2753 let lineno = self.lexer.toklineno;
2754 let cmd = match self.parse_cmd() {
2755 Some(c) => c,
2756 None => {
2757 self.recursion_depth -= 1;
2758 return None;
2759 }
2760 };
2761
2762 let next = match self.lexer.tok {
2764 LexTok::Bar | LexTok::Baramp => {
2765 let _merge_stderr = self.lexer.tok == LexTok::Baramp;
2766 self.lexer.zshlex();
2767 self.skip_separators();
2768 self.parse_pipe().map(Box::new)
2769 }
2770 _ => None,
2771 };
2772
2773 self.recursion_depth -= 1;
2774 Some(ZshPipe { cmd, next, lineno })
2775 }
2776
2777 fn parse_cmd(&mut self) -> Option<ZshCommand> {
2779 let mut redirs = Vec::new();
2781 while self.lexer.tok.is_redirop() {
2782 if let Some(redir) = self.parse_redir() {
2783 redirs.push(redir);
2784 }
2785 }
2786
2787 let cmd = match self.lexer.tok {
2788 LexTok::For | LexTok::Foreach => self.parse_for(),
2789 LexTok::Select => self.parse_select(),
2790 LexTok::Case => self.parse_case(),
2791 LexTok::If => self.parse_if(),
2792 LexTok::While => self.parse_while(false),
2793 LexTok::Until => self.parse_while(true),
2794 LexTok::Repeat => self.parse_repeat(),
2795 LexTok::Inpar => self.parse_subsh(),
2796 LexTok::Inbrace => self.parse_cursh(),
2797 LexTok::Func => self.parse_funcdef(),
2798 LexTok::Dinbrack => self.parse_cond(),
2799 LexTok::Dinpar => self.parse_arith(),
2800 LexTok::Time => self.parse_time(),
2801 _ => self.parse_simple(redirs),
2802 };
2803
2804 if cmd.is_some() {
2806 while self.lexer.tok.is_redirop() {
2807 if let Some(_redir) = self.parse_redir() {
2808 }
2811 }
2812 }
2813
2814 cmd
2815 }
2816
2817 fn parse_simple(&mut self, mut redirs: Vec<ZshRedir>) -> Option<ZshCommand> {
2819 let mut assigns = Vec::new();
2820 let mut words = Vec::new();
2821 const MAX_ITERATIONS: usize = 10_000;
2822 let mut iterations = 0;
2823
2824 while self.lexer.tok == LexTok::Envstring || self.lexer.tok == LexTok::Envarray {
2826 iterations += 1;
2827 if iterations > MAX_ITERATIONS {
2828 self.error("parse_simple: exceeded max iterations in assignments");
2829 return None;
2830 }
2831 if let Some(assign) = self.parse_assign() {
2832 assigns.push(assign);
2833 }
2834 self.lexer.zshlex();
2835 }
2836
2837 loop {
2839 iterations += 1;
2840 if iterations > MAX_ITERATIONS {
2841 self.error("parse_simple: exceeded max iterations");
2842 return None;
2843 }
2844 match self.lexer.tok {
2845 LexTok::String | LexTok::Typeset => {
2846 let s = self.lexer.tokstr.clone();
2847 if let Some(s) = s {
2848 words.push(s);
2849 }
2850 self.lexer.zshlex();
2851 if words.len() == 1 && self.peek_inoutpar() {
2853 return self.parse_inline_funcdef(words.pop().unwrap());
2854 }
2855 }
2856 _ if self.lexer.tok.is_redirop() => {
2857 match self.parse_redir() {
2858 Some(redir) => redirs.push(redir),
2859 None => break, }
2861 }
2862 LexTok::Inoutpar if !words.is_empty() => {
2863 return self.parse_inline_funcdef(words.pop().unwrap());
2865 }
2866 _ => break,
2867 }
2868 }
2869
2870 if assigns.is_empty() && words.is_empty() && redirs.is_empty() {
2871 return None;
2872 }
2873
2874 Some(ZshCommand::Simple(ZshSimple {
2875 assigns,
2876 words,
2877 redirs,
2878 }))
2879 }
2880
2881 fn parse_assign(&mut self) -> Option<ZshAssign> {
2883 use crate::tokens::char_tokens;
2884
2885 let tokstr = self.lexer.tokstr.as_ref()?;
2886
2887 let (name, value_str, append) = if let Some(pos) = tokstr.find(char_tokens::EQUALS) {
2890 let name_part = &tokstr[..pos];
2891 let (name, append) = if name_part.ends_with('+') {
2892 (&name_part[..name_part.len() - 1], true)
2893 } else {
2894 (name_part, false)
2895 };
2896 (
2897 name.to_string(),
2898 tokstr[pos + char_tokens::EQUALS.len_utf8()..].to_string(),
2899 append,
2900 )
2901 } else if let Some(pos) = tokstr.find('=') {
2902 let name_part = &tokstr[..pos];
2904 let (name, append) = if name_part.ends_with('+') {
2905 (&name_part[..name_part.len() - 1], true)
2906 } else {
2907 (name_part, false)
2908 };
2909 (name.to_string(), tokstr[pos + 1..].to_string(), append)
2910 } else {
2911 return None;
2912 };
2913
2914 let value = if self.lexer.tok == LexTok::Envarray {
2915 let mut elements = Vec::new();
2917 self.lexer.zshlex(); let mut arr_iters = 0;
2920 const MAX_ARRAY_ELEMENTS: usize = 10_000;
2921 while matches!(
2922 self.lexer.tok,
2923 LexTok::String | LexTok::Seper | LexTok::Newlin
2924 ) {
2925 arr_iters += 1;
2926 if arr_iters > MAX_ARRAY_ELEMENTS {
2927 self.error("array assignment exceeded maximum elements");
2928 break;
2929 }
2930 if self.lexer.tok == LexTok::String {
2931 if let Some(ref s) = self.lexer.tokstr {
2932 elements.push(s.clone());
2933 }
2934 }
2935 self.lexer.zshlex();
2936 }
2937
2938 if self.lexer.tok == LexTok::Outpar {
2940 self.lexer.zshlex();
2941 }
2942
2943 ZshAssignValue::Array(elements)
2944 } else {
2945 ZshAssignValue::Scalar(value_str)
2946 };
2947
2948 Some(ZshAssign {
2949 name,
2950 value,
2951 append,
2952 })
2953 }
2954
2955 fn parse_redir(&mut self) -> Option<ZshRedir> {
2957 let rtype = match self.lexer.tok {
2958 LexTok::Outang => RedirType::Write,
2959 LexTok::Outangbang => RedirType::Writenow,
2960 LexTok::Doutang => RedirType::Append,
2961 LexTok::Doutangbang => RedirType::Appendnow,
2962 LexTok::Inang => RedirType::Read,
2963 LexTok::Inoutang => RedirType::ReadWrite,
2964 LexTok::Dinang => RedirType::Heredoc,
2965 LexTok::Dinangdash => RedirType::HeredocDash,
2966 LexTok::Trinang => RedirType::Herestr,
2967 LexTok::Inangamp => RedirType::MergeIn,
2968 LexTok::Outangamp => RedirType::MergeOut,
2969 LexTok::Ampoutang => RedirType::ErrWrite,
2970 LexTok::Outangampbang => RedirType::ErrWritenow,
2971 LexTok::Doutangamp => RedirType::ErrAppend,
2972 LexTok::Doutangampbang => RedirType::ErrAppendnow,
2973 _ => return None,
2974 };
2975
2976 let fd = if self.lexer.tokfd >= 0 {
2977 self.lexer.tokfd
2978 } else if matches!(
2979 rtype,
2980 RedirType::Read
2981 | RedirType::ReadWrite
2982 | RedirType::MergeIn
2983 | RedirType::Heredoc
2984 | RedirType::HeredocDash
2985 | RedirType::Herestr
2986 ) {
2987 0
2988 } else {
2989 1
2990 };
2991
2992 self.lexer.zshlex();
2993
2994 let name = match self.lexer.tok {
2995 LexTok::String | LexTok::Envstring => {
2996 let n = self.lexer.tokstr.clone().unwrap_or_default();
2997 self.lexer.zshlex();
2998 n
2999 }
3000 _ => {
3001 self.error("expected word after redirection");
3002 return None;
3003 }
3004 };
3005
3006 let heredoc = if matches!(rtype, RedirType::Heredoc | RedirType::HeredocDash) {
3008 None } else {
3011 None
3012 };
3013
3014 Some(ZshRedir {
3015 rtype,
3016 fd,
3017 name,
3018 heredoc,
3019 varid: None,
3020 })
3021 }
3022
3023 fn parse_for(&mut self) -> Option<ZshCommand> {
3025 let is_foreach = self.lexer.tok == LexTok::Foreach;
3026 self.lexer.zshlex();
3027
3028 if self.lexer.tok == LexTok::Dinpar {
3030 return self.parse_for_cstyle();
3031 }
3032
3033 let var = match self.lexer.tok {
3035 LexTok::String => {
3036 let v = self.lexer.tokstr.clone().unwrap_or_default();
3037 self.lexer.zshlex();
3038 v
3039 }
3040 _ => {
3041 self.error("expected variable name in for");
3042 return None;
3043 }
3044 };
3045
3046 self.skip_separators();
3048
3049 let list = if self.lexer.tok == LexTok::String {
3051 let s = self.lexer.tokstr.as_ref();
3052 if s.map(|s| s == "in").unwrap_or(false) {
3053 self.lexer.zshlex();
3054 let mut words = Vec::new();
3055 let mut word_count = 0;
3056 while self.lexer.tok == LexTok::String {
3057 word_count += 1;
3058 if word_count > 500 || self.check_limit() {
3059 self.error("for: too many words");
3060 return None;
3061 }
3062 if let Some(ref s) = self.lexer.tokstr {
3063 words.push(s.clone());
3064 }
3065 self.lexer.zshlex();
3066 }
3067 ForList::Words(words)
3068 } else {
3069 ForList::Positional
3070 }
3071 } else if self.lexer.tok == LexTok::Inpar {
3072 self.lexer.zshlex();
3074 let mut words = Vec::new();
3075 let mut word_count = 0;
3076 while self.lexer.tok == LexTok::String || self.lexer.tok == LexTok::Seper {
3077 word_count += 1;
3078 if word_count > 500 || self.check_limit() {
3079 self.error("for: too many words in parens");
3080 return None;
3081 }
3082 if self.lexer.tok == LexTok::String {
3083 if let Some(ref s) = self.lexer.tokstr {
3084 words.push(s.clone());
3085 }
3086 }
3087 self.lexer.zshlex();
3088 }
3089 if self.lexer.tok == LexTok::Outpar {
3090 self.lexer.zshlex();
3091 }
3092 ForList::Words(words)
3093 } else {
3094 ForList::Positional
3095 };
3096
3097 self.skip_separators();
3099
3100 let body = self.parse_loop_body(is_foreach)?;
3102
3103 Some(ZshCommand::For(ZshFor {
3104 var,
3105 list,
3106 body: Box::new(body),
3107 }))
3108 }
3109
3110 fn parse_for_cstyle(&mut self) -> Option<ZshCommand> {
3112 self.lexer.zshlex(); if self.lexer.tok != LexTok::Dinpar {
3122 self.error("expected init expression in for ((");
3123 return None;
3124 }
3125 let init = self.lexer.tokstr.clone().unwrap_or_default();
3126
3127 self.lexer.zshlex(); if self.lexer.tok != LexTok::Dinpar {
3130 self.error("expected condition in for ((");
3131 return None;
3132 }
3133 let cond = self.lexer.tokstr.clone().unwrap_or_default();
3134
3135 self.lexer.zshlex(); if self.lexer.tok != LexTok::Doutpar {
3138 self.error("expected )) in for");
3139 return None;
3140 }
3141 let step = self.lexer.tokstr.clone().unwrap_or_default();
3142
3143 self.lexer.zshlex(); self.skip_separators();
3146 let body = self.parse_loop_body(false)?;
3147
3148 Some(ZshCommand::For(ZshFor {
3149 var: String::new(),
3150 list: ForList::CStyle { init, cond, step },
3151 body: Box::new(body),
3152 }))
3153 }
3154
3155 fn parse_select(&mut self) -> Option<ZshCommand> {
3157 self.parse_for()
3158 }
3159
3160 fn parse_case(&mut self) -> Option<ZshCommand> {
3162 self.lexer.zshlex(); let word = match self.lexer.tok {
3165 LexTok::String => {
3166 let w = self.lexer.tokstr.clone().unwrap_or_default();
3167 self.lexer.zshlex();
3168 w
3169 }
3170 _ => {
3171 self.error("expected word after case");
3172 return None;
3173 }
3174 };
3175
3176 self.skip_separators();
3177
3178 let use_brace = self.lexer.tok == LexTok::Inbrace;
3180 if self.lexer.tok == LexTok::String {
3181 let s = self.lexer.tokstr.as_ref();
3182 if s.map(|s| s != "in").unwrap_or(true) {
3183 self.error("expected 'in' in case");
3184 return None;
3185 }
3186 } else if !use_brace {
3187 self.error("expected 'in' or '{' in case");
3188 return None;
3189 }
3190 self.lexer.zshlex();
3191
3192 let mut arms = Vec::new();
3193 const MAX_ARMS: usize = 10_000;
3194
3195 loop {
3196 if arms.len() > MAX_ARMS {
3197 self.error("parse_case: too many arms");
3198 break;
3199 }
3200
3201 self.lexer.incasepat = 1;
3204
3205 self.skip_separators();
3206
3207 let is_esac = self.lexer.tok == LexTok::Esac
3210 || (self.lexer.tok == LexTok::String
3211 && self
3212 .lexer
3213 .tokstr
3214 .as_ref()
3215 .map(|s| s == "esac")
3216 .unwrap_or(false));
3217 if (use_brace && self.lexer.tok == LexTok::Outbrace) || (!use_brace && is_esac) {
3218 self.lexer.incasepat = 0;
3219 self.lexer.zshlex();
3220 break;
3221 }
3222
3223 if self.lexer.tok == LexTok::Endinput || self.lexer.tok == LexTok::Lexerr {
3225 self.lexer.incasepat = 0;
3226 break;
3227 }
3228
3229 if self.lexer.tok == LexTok::Inpar {
3231 self.lexer.zshlex();
3232 }
3233
3234 let mut patterns = Vec::new();
3236 let mut pattern_iterations = 0;
3237 loop {
3238 pattern_iterations += 1;
3239 if pattern_iterations > 1000 {
3240 self.error("parse_case: too many pattern iterations");
3241 self.lexer.incasepat = 0;
3242 return None;
3243 }
3244
3245 if self.lexer.tok == LexTok::String {
3246 let s = self.lexer.tokstr.as_ref();
3247 if s.map(|s| s == "esac").unwrap_or(false) {
3248 break;
3249 }
3250 patterns.push(self.lexer.tokstr.clone().unwrap_or_default());
3251 self.lexer.incasepat = 2;
3253 self.lexer.zshlex();
3254 } else if self.lexer.tok != LexTok::Bar {
3255 break;
3256 }
3257
3258 if self.lexer.tok == LexTok::Bar {
3259 self.lexer.incasepat = 1;
3261 self.lexer.zshlex();
3262 } else {
3263 break;
3264 }
3265 }
3266 self.lexer.incasepat = 0;
3267
3268 if self.lexer.tok != LexTok::Outpar {
3270 self.error("expected ')' in case pattern");
3271 return None;
3272 }
3273 self.lexer.zshlex();
3274
3275 let body = self.parse_program();
3277
3278 let terminator = match self.lexer.tok {
3280 LexTok::Dsemi => {
3281 self.lexer.zshlex();
3282 CaseTerm::Break
3283 }
3284 LexTok::Semiamp => {
3285 self.lexer.zshlex();
3286 CaseTerm::Continue
3287 }
3288 LexTok::Semibar => {
3289 self.lexer.zshlex();
3290 CaseTerm::TestNext
3291 }
3292 _ => CaseTerm::Break,
3293 };
3294
3295 if !patterns.is_empty() {
3296 arms.push(CaseArm {
3297 patterns,
3298 body,
3299 terminator,
3300 });
3301 }
3302 }
3303
3304 Some(ZshCommand::Case(ZshCase { word, arms }))
3305 }
3306
3307 fn parse_if(&mut self) -> Option<ZshCommand> {
3309 self.lexer.zshlex(); let cond = Box::new(self.parse_program_until(Some(&[LexTok::Then, LexTok::Inbrace])));
3313
3314 self.skip_separators();
3315
3316 let use_brace = self.lexer.tok == LexTok::Inbrace;
3318 if self.lexer.tok != LexTok::Then && !use_brace {
3319 self.error("expected 'then' or '{' after if condition");
3320 return None;
3321 }
3322 self.lexer.zshlex();
3323
3324 let then = if use_brace {
3326 let body = self.parse_program_until(Some(&[LexTok::Outbrace]));
3327 if self.lexer.tok == LexTok::Outbrace {
3328 self.lexer.zshlex();
3329 }
3330 Box::new(body)
3331 } else {
3332 Box::new(self.parse_program_until(Some(&[LexTok::Else, LexTok::Elif, LexTok::Fi])))
3333 };
3334
3335 let mut elif = Vec::new();
3337 let mut else_ = None;
3338
3339 if !use_brace {
3340 loop {
3341 self.skip_separators();
3342
3343 match self.lexer.tok {
3344 LexTok::Elif => {
3345 self.lexer.zshlex();
3346 let econd =
3348 self.parse_program_until(Some(&[LexTok::Then, LexTok::Inbrace]));
3349 self.skip_separators();
3350
3351 let elif_use_brace = self.lexer.tok == LexTok::Inbrace;
3352 if self.lexer.tok != LexTok::Then && !elif_use_brace {
3353 self.error("expected 'then' after elif");
3354 return None;
3355 }
3356 self.lexer.zshlex();
3357
3358 let ebody = if elif_use_brace {
3360 let body = self.parse_program_until(Some(&[LexTok::Outbrace]));
3361 if self.lexer.tok == LexTok::Outbrace {
3362 self.lexer.zshlex();
3363 }
3364 body
3365 } else {
3366 self.parse_program_until(Some(&[
3367 LexTok::Else,
3368 LexTok::Elif,
3369 LexTok::Fi,
3370 ]))
3371 };
3372
3373 elif.push((econd, ebody));
3374 }
3375 LexTok::Else => {
3376 self.lexer.zshlex();
3377 self.skip_separators();
3378
3379 let else_use_brace = self.lexer.tok == LexTok::Inbrace;
3380 if else_use_brace {
3381 self.lexer.zshlex();
3382 }
3383
3384 else_ = Some(Box::new(if else_use_brace {
3386 let body = self.parse_program_until(Some(&[LexTok::Outbrace]));
3387 if self.lexer.tok == LexTok::Outbrace {
3388 self.lexer.zshlex();
3389 }
3390 body
3391 } else {
3392 self.parse_program_until(Some(&[LexTok::Fi]))
3393 }));
3394
3395 if !else_use_brace && self.lexer.tok == LexTok::Fi {
3397 self.lexer.zshlex();
3398 }
3399 break;
3400 }
3401 LexTok::Fi => {
3402 self.lexer.zshlex();
3403 break;
3404 }
3405 _ => break,
3406 }
3407 }
3408 }
3409
3410 Some(ZshCommand::If(ZshIf {
3411 cond,
3412 then,
3413 elif,
3414 else_,
3415 }))
3416 }
3417
3418 fn parse_while(&mut self, until: bool) -> Option<ZshCommand> {
3420 self.lexer.zshlex(); let cond = Box::new(self.parse_program());
3423
3424 self.skip_separators();
3425 let body = self.parse_loop_body(false)?;
3426
3427 Some(ZshCommand::While(ZshWhile {
3428 cond,
3429 body: Box::new(body),
3430 until,
3431 }))
3432 }
3433
3434 fn parse_repeat(&mut self) -> Option<ZshCommand> {
3436 self.lexer.zshlex(); let count = match self.lexer.tok {
3439 LexTok::String => {
3440 let c = self.lexer.tokstr.clone().unwrap_or_default();
3441 self.lexer.zshlex();
3442 c
3443 }
3444 _ => {
3445 self.error("expected count after repeat");
3446 return None;
3447 }
3448 };
3449
3450 self.skip_separators();
3451 let body = self.parse_loop_body(false)?;
3452
3453 Some(ZshCommand::Repeat(ZshRepeat {
3454 count,
3455 body: Box::new(body),
3456 }))
3457 }
3458
3459 fn parse_loop_body(&mut self, foreach_style: bool) -> Option<ZshProgram> {
3461 if self.lexer.tok == LexTok::Doloop {
3462 self.lexer.zshlex();
3463 let body = self.parse_program();
3464 if self.lexer.tok == LexTok::Done {
3465 self.lexer.zshlex();
3466 }
3467 Some(body)
3468 } else if self.lexer.tok == LexTok::Inbrace {
3469 self.lexer.zshlex();
3470 let body = self.parse_program();
3471 if self.lexer.tok == LexTok::Outbrace {
3472 self.lexer.zshlex();
3473 }
3474 Some(body)
3475 } else if foreach_style {
3476 let body = self.parse_program();
3478 if self.lexer.tok == LexTok::Zend {
3479 self.lexer.zshlex();
3480 }
3481 Some(body)
3482 } else {
3483 match self.parse_list() {
3485 Some(list) => Some(ZshProgram { lists: vec![list] }),
3486 None => None,
3487 }
3488 }
3489 }
3490
3491 fn parse_subsh(&mut self) -> Option<ZshCommand> {
3493 self.lexer.zshlex(); let prog = self.parse_program();
3495 if self.lexer.tok == LexTok::Outpar {
3496 self.lexer.zshlex();
3497 }
3498 Some(ZshCommand::Subsh(Box::new(prog)))
3499 }
3500
3501 fn parse_cursh(&mut self) -> Option<ZshCommand> {
3503 self.lexer.zshlex(); let prog = self.parse_program();
3505
3506 if self.lexer.tok == LexTok::Outbrace {
3508 self.lexer.zshlex();
3509
3510 if self.lexer.tok == LexTok::String {
3512 let s = self.lexer.tokstr.as_ref();
3513 if s.map(|s| s == "always").unwrap_or(false) {
3514 self.lexer.zshlex();
3515 self.skip_separators();
3516
3517 if self.lexer.tok == LexTok::Inbrace {
3518 self.lexer.zshlex();
3519 let always = self.parse_program();
3520 if self.lexer.tok == LexTok::Outbrace {
3521 self.lexer.zshlex();
3522 }
3523 return Some(ZshCommand::Try(ZshTry {
3524 try_block: Box::new(prog),
3525 always: Box::new(always),
3526 }));
3527 }
3528 }
3529 }
3530 }
3531
3532 Some(ZshCommand::Cursh(Box::new(prog)))
3533 }
3534
3535 fn parse_funcdef(&mut self) -> Option<ZshCommand> {
3537 self.lexer.zshlex(); let mut names = Vec::new();
3540 let mut tracing = false;
3541
3542 loop {
3544 match self.lexer.tok {
3545 LexTok::String => {
3546 let s = self.lexer.tokstr.as_ref()?;
3547 if s.starts_with('-') {
3548 if s.contains('T') {
3549 tracing = true;
3550 }
3551 self.lexer.zshlex();
3552 continue;
3553 }
3554 names.push(s.clone());
3555 self.lexer.zshlex();
3556 }
3557 LexTok::Inbrace | LexTok::Inoutpar | LexTok::Seper | LexTok::Newlin => break,
3558 _ => break,
3559 }
3560 }
3561
3562 if self.lexer.tok == LexTok::Inoutpar {
3564 self.lexer.zshlex();
3565 }
3566
3567 self.skip_separators();
3568
3569 if self.lexer.tok == LexTok::Inbrace {
3571 self.lexer.zshlex();
3572 let body = self.parse_program();
3573 if self.lexer.tok == LexTok::Outbrace {
3574 self.lexer.zshlex();
3575 }
3576 Some(ZshCommand::FuncDef(ZshFuncDef {
3577 names,
3578 body: Box::new(body),
3579 tracing,
3580 }))
3581 } else {
3582 match self.parse_list() {
3584 Some(list) => Some(ZshCommand::FuncDef(ZshFuncDef {
3585 names,
3586 body: Box::new(ZshProgram { lists: vec![list] }),
3587 tracing,
3588 })),
3589 None => None,
3590 }
3591 }
3592 }
3593
3594 fn parse_inline_funcdef(&mut self, name: String) -> Option<ZshCommand> {
3596 if self.lexer.tok == LexTok::Inoutpar {
3598 self.lexer.zshlex();
3599 }
3600
3601 self.skip_separators();
3602
3603 if self.lexer.tok == LexTok::Inbrace {
3605 self.lexer.zshlex();
3606 let body = self.parse_program();
3607 if self.lexer.tok == LexTok::Outbrace {
3608 self.lexer.zshlex();
3609 }
3610 Some(ZshCommand::FuncDef(ZshFuncDef {
3611 names: vec![name],
3612 body: Box::new(body),
3613 tracing: false,
3614 }))
3615 } else {
3616 match self.parse_cmd() {
3617 Some(cmd) => {
3618 let list = ZshList {
3619 sublist: ZshSublist {
3620 pipe: ZshPipe {
3621 cmd,
3622 next: None,
3623 lineno: self.lexer.lineno,
3624 },
3625 next: None,
3626 flags: SublistFlags::default(),
3627 },
3628 flags: ListFlags::default(),
3629 };
3630 Some(ZshCommand::FuncDef(ZshFuncDef {
3631 names: vec![name],
3632 body: Box::new(ZshProgram { lists: vec![list] }),
3633 tracing: false,
3634 }))
3635 }
3636 None => None,
3637 }
3638 }
3639 }
3640
3641 fn parse_cond(&mut self) -> Option<ZshCommand> {
3643 self.lexer.zshlex(); let cond = self.parse_cond_expr();
3645
3646 if self.lexer.tok == LexTok::Doutbrack {
3647 self.lexer.zshlex();
3648 }
3649
3650 cond.map(ZshCommand::Cond)
3651 }
3652
3653 fn parse_cond_expr(&mut self) -> Option<ZshCond> {
3655 self.parse_cond_or()
3656 }
3657
3658 fn parse_cond_or(&mut self) -> Option<ZshCond> {
3659 self.recursion_depth += 1;
3660 if self.check_recursion() {
3661 self.error("parse_cond_or: max recursion depth exceeded");
3662 self.recursion_depth -= 1;
3663 return None;
3664 }
3665
3666 let left = match self.parse_cond_and() {
3667 Some(l) => l,
3668 None => {
3669 self.recursion_depth -= 1;
3670 return None;
3671 }
3672 };
3673
3674 self.skip_cond_separators();
3675
3676 let result = if self.lexer.tok == LexTok::Dbar {
3677 self.lexer.zshlex();
3678 self.skip_cond_separators();
3679 match self.parse_cond_or() {
3680 Some(right) => Some(ZshCond::Or(Box::new(left), Box::new(right))),
3681 None => None,
3682 }
3683 } else {
3684 Some(left)
3685 };
3686
3687 self.recursion_depth -= 1;
3688 result
3689 }
3690
3691 fn parse_cond_and(&mut self) -> Option<ZshCond> {
3692 self.recursion_depth += 1;
3693 if self.check_recursion() {
3694 self.error("parse_cond_and: max recursion depth exceeded");
3695 self.recursion_depth -= 1;
3696 return None;
3697 }
3698
3699 let left = match self.parse_cond_not() {
3700 Some(l) => l,
3701 None => {
3702 self.recursion_depth -= 1;
3703 return None;
3704 }
3705 };
3706
3707 self.skip_cond_separators();
3708
3709 let result = if self.lexer.tok == LexTok::Damper {
3710 self.lexer.zshlex();
3711 self.skip_cond_separators();
3712 match self.parse_cond_and() {
3713 Some(right) => Some(ZshCond::And(Box::new(left), Box::new(right))),
3714 None => None,
3715 }
3716 } else {
3717 Some(left)
3718 };
3719
3720 self.recursion_depth -= 1;
3721 result
3722 }
3723
3724 fn parse_cond_not(&mut self) -> Option<ZshCond> {
3725 self.recursion_depth += 1;
3726 if self.check_recursion() {
3727 self.error("parse_cond_not: max recursion depth exceeded");
3728 self.recursion_depth -= 1;
3729 return None;
3730 }
3731
3732 self.skip_cond_separators();
3733
3734 let is_not = self.lexer.tok == LexTok::Bang
3736 || (self.lexer.tok == LexTok::String
3737 && self
3738 .lexer
3739 .tokstr
3740 .as_ref()
3741 .map(|s| s == "!")
3742 .unwrap_or(false));
3743 if is_not {
3744 self.lexer.zshlex();
3745 let inner = match self.parse_cond_not() {
3746 Some(i) => i,
3747 None => {
3748 self.recursion_depth -= 1;
3749 return None;
3750 }
3751 };
3752 self.recursion_depth -= 1;
3753 return Some(ZshCond::Not(Box::new(inner)));
3754 }
3755
3756 if self.lexer.tok == LexTok::Inpar {
3757 self.lexer.zshlex();
3758 self.skip_cond_separators();
3759 let inner = match self.parse_cond_expr() {
3760 Some(i) => i,
3761 None => {
3762 self.recursion_depth -= 1;
3763 return None;
3764 }
3765 };
3766 self.skip_cond_separators();
3767 if self.lexer.tok == LexTok::Outpar {
3768 self.lexer.zshlex();
3769 }
3770 self.recursion_depth -= 1;
3771 return Some(inner);
3772 }
3773
3774 let result = self.parse_cond_primary();
3775 self.recursion_depth -= 1;
3776 result
3777 }
3778
3779 fn parse_cond_primary(&mut self) -> Option<ZshCond> {
3780 let s1 = match self.lexer.tok {
3781 LexTok::String => {
3782 let s = self.lexer.tokstr.clone().unwrap_or_default();
3783 self.lexer.zshlex();
3784 s
3785 }
3786 _ => return None,
3787 };
3788
3789 self.skip_cond_separators();
3790
3791 if s1.starts_with('-') && s1.len() == 2 {
3793 let s2 = match self.lexer.tok {
3794 LexTok::String => {
3795 let s = self.lexer.tokstr.clone().unwrap_or_default();
3796 self.lexer.zshlex();
3797 s
3798 }
3799 _ => return Some(ZshCond::Unary("-n".to_string(), s1)),
3800 };
3801 return Some(ZshCond::Unary(s1, s2));
3802 }
3803
3804 let op = match self.lexer.tok {
3806 LexTok::String => {
3807 let s = self.lexer.tokstr.clone().unwrap_or_default();
3808 self.lexer.zshlex();
3809 s
3810 }
3811 LexTok::Inang => {
3812 self.lexer.zshlex();
3813 "<".to_string()
3814 }
3815 LexTok::Outang => {
3816 self.lexer.zshlex();
3817 ">".to_string()
3818 }
3819 _ => return Some(ZshCond::Unary("-n".to_string(), s1)),
3820 };
3821
3822 self.skip_cond_separators();
3823
3824 let s2 = match self.lexer.tok {
3825 LexTok::String => {
3826 let s = self.lexer.tokstr.clone().unwrap_or_default();
3827 self.lexer.zshlex();
3828 s
3829 }
3830 _ => return Some(ZshCond::Binary(s1, op, String::new())),
3831 };
3832
3833 if op == "=~" {
3834 Some(ZshCond::Regex(s1, s2))
3835 } else {
3836 Some(ZshCond::Binary(s1, op, s2))
3837 }
3838 }
3839
3840 fn skip_cond_separators(&mut self) {
3841 while self.lexer.tok == LexTok::Seper && {
3842 let s = self.lexer.tokstr.as_ref();
3843 s.map(|s| !s.contains(';')).unwrap_or(true)
3844 } {
3845 self.lexer.zshlex();
3846 }
3847 }
3848
3849 fn parse_arith(&mut self) -> Option<ZshCommand> {
3851 let expr = self.lexer.tokstr.clone().unwrap_or_default();
3852 self.lexer.zshlex();
3853 Some(ZshCommand::Arith(expr))
3854 }
3855
3856 fn parse_time(&mut self) -> Option<ZshCommand> {
3858 self.lexer.zshlex(); if self.lexer.tok == LexTok::Seper
3862 || self.lexer.tok == LexTok::Newlin
3863 || self.lexer.tok == LexTok::Endinput
3864 {
3865 Some(ZshCommand::Time(None))
3866 } else {
3867 let sublist = self.parse_sublist();
3868 Some(ZshCommand::Time(sublist.map(Box::new)))
3869 }
3870 }
3871
3872 fn peek_inoutpar(&mut self) -> bool {
3874 self.lexer.tok == LexTok::Inoutpar
3875 }
3876
3877 fn skip_separators(&mut self) {
3879 let mut iterations = 0;
3880 while self.lexer.tok == LexTok::Seper || self.lexer.tok == LexTok::Newlin {
3881 iterations += 1;
3882 if iterations > 100_000 {
3883 self.error("skip_separators: too many iterations");
3884 return;
3885 }
3886 self.lexer.zshlex();
3887 }
3888 }
3889
3890 fn error(&mut self, msg: &str) {
3892 self.errors.push(ParseError {
3893 message: msg.to_string(),
3894 line: self.lexer.lineno,
3895 });
3896 }
3897}
3898
3899#[cfg(test)]
3900mod tests {
3901 use super::*;
3902
3903 fn parse(input: &str) -> Result<ZshProgram, Vec<ParseError>> {
3904 let mut parser = ZshParser::new(input);
3905 parser.parse()
3906 }
3907
3908 #[test]
3909 fn test_simple_command() {
3910 let prog = parse("echo hello world").unwrap();
3911 assert_eq!(prog.lists.len(), 1);
3912 match &prog.lists[0].sublist.pipe.cmd {
3913 ZshCommand::Simple(s) => {
3914 assert_eq!(s.words, vec!["echo", "hello", "world"]);
3915 }
3916 _ => panic!("expected simple command"),
3917 }
3918 }
3919
3920 #[test]
3921 fn test_pipeline() {
3922 let prog = parse("ls | grep foo | wc -l").unwrap();
3923 assert_eq!(prog.lists.len(), 1);
3924
3925 let pipe = &prog.lists[0].sublist.pipe;
3926 assert!(pipe.next.is_some());
3927
3928 let pipe2 = pipe.next.as_ref().unwrap();
3929 assert!(pipe2.next.is_some());
3930 }
3931
3932 #[test]
3933 fn test_and_or() {
3934 let prog = parse("cmd1 && cmd2 || cmd3").unwrap();
3935 let sublist = &prog.lists[0].sublist;
3936
3937 assert!(sublist.next.is_some());
3938 let (op, _) = sublist.next.as_ref().unwrap();
3939 assert_eq!(*op, SublistOp::And);
3940 }
3941
3942 #[test]
3943 fn test_if_then() {
3944 let prog = parse("if test -f foo; then echo yes; fi").unwrap();
3945 match &prog.lists[0].sublist.pipe.cmd {
3946 ZshCommand::If(_) => {}
3947 _ => panic!("expected if command"),
3948 }
3949 }
3950
3951 #[test]
3952 fn test_for_loop() {
3953 let prog = parse("for i in a b c; do echo $i; done").unwrap();
3954 match &prog.lists[0].sublist.pipe.cmd {
3955 ZshCommand::For(f) => {
3956 assert_eq!(f.var, "i");
3957 match &f.list {
3958 ForList::Words(w) => assert_eq!(w, &vec!["a", "b", "c"]),
3959 _ => panic!("expected word list"),
3960 }
3961 }
3962 _ => panic!("expected for command"),
3963 }
3964 }
3965
3966 #[test]
3967 fn test_case() {
3968 let prog = parse("case $x in a) echo a;; b) echo b;; esac").unwrap();
3969 match &prog.lists[0].sublist.pipe.cmd {
3970 ZshCommand::Case(c) => {
3971 assert_eq!(c.arms.len(), 2);
3972 }
3973 _ => panic!("expected case command"),
3974 }
3975 }
3976
3977 #[test]
3978 fn test_function() {
3979 let prog = parse("function foo { }").unwrap();
3981 match &prog.lists[0].sublist.pipe.cmd {
3982 ZshCommand::FuncDef(f) => {
3983 assert_eq!(f.names, vec!["foo"]);
3984 }
3985 _ => panic!(
3986 "expected function, got {:?}",
3987 prog.lists[0].sublist.pipe.cmd
3988 ),
3989 }
3990 }
3991
3992 #[test]
3993 fn test_redirection() {
3994 let prog = parse("echo hello > file.txt").unwrap();
3995 match &prog.lists[0].sublist.pipe.cmd {
3996 ZshCommand::Simple(s) => {
3997 assert_eq!(s.redirs.len(), 1);
3998 assert_eq!(s.redirs[0].rtype, RedirType::Write);
3999 }
4000 _ => panic!("expected simple command"),
4001 }
4002 }
4003
4004 #[test]
4005 fn test_assignment() {
4006 let prog = parse("FOO=bar echo $FOO").unwrap();
4007 match &prog.lists[0].sublist.pipe.cmd {
4008 ZshCommand::Simple(s) => {
4009 assert_eq!(s.assigns.len(), 1);
4010 assert_eq!(s.assigns[0].name, "FOO");
4011 }
4012 _ => panic!("expected simple command"),
4013 }
4014 }
4015
4016 #[test]
4017 fn test_parse_completion_function() {
4018 let input = r#"_2to3_fixes() {
4019 local -a fixes
4020 fixes=( ${${(M)${(f)"$(2to3 --list-fixes 2>/dev/null)"}:#*}//[[:space:]]/} )
4021 (( ${#fixes} )) && _describe -t fixes 'fix' fixes
4022}"#;
4023 let result = parse(input);
4024 assert!(
4025 result.is_ok(),
4026 "Failed to parse completion function: {:?}",
4027 result.err()
4028 );
4029 let prog = result.unwrap();
4030 assert!(
4031 !prog.lists.is_empty(),
4032 "Expected at least one list in program"
4033 );
4034 }
4035
4036 #[test]
4037 fn test_parse_array_with_complex_elements() {
4038 let input = r#"arguments=(
4039 '(- * :)'{-h,--help}'[show this help message and exit]'
4040 {-d,--doctests_only}'[fix up doctests only]'
4041 '*:filename:_files'
4042)"#;
4043 let result = parse(input);
4044 assert!(
4045 result.is_ok(),
4046 "Failed to parse array assignment: {:?}",
4047 result.err()
4048 );
4049 }
4050
4051 #[test]
4052 fn test_parse_full_completion_file() {
4053 let input = r##"#compdef 2to3
4054
4055# zsh completions for '2to3'
4056
4057_2to3_fixes() {
4058 local -a fixes
4059 fixes=( ${${(M)${(f)"$(2to3 --list-fixes 2>/dev/null)"}:#*}//[[:space:]]/} )
4060 (( ${#fixes} )) && _describe -t fixes 'fix' fixes
4061}
4062
4063local -a arguments
4064
4065arguments=(
4066 '(- * :)'{-h,--help}'[show this help message and exit]'
4067 {-d,--doctests_only}'[fix up doctests only]'
4068 {-f,--fix}'[each FIX specifies a transformation; default: all]:fix name:_2to3_fixes'
4069 {-j,--processes}'[run 2to3 concurrently]:number: '
4070 {-x,--nofix}'[prevent a transformation from being run]:fix name:_2to3_fixes'
4071 {-l,--list-fixes}'[list available transformations]'
4072 {-p,--print-function}'[modify the grammar so that print() is a function]'
4073 {-v,--verbose}'[more verbose logging]'
4074 '--no-diffs[do not show diffs of the refactoring]'
4075 {-w,--write}'[write back modified files]'
4076 {-n,--nobackups}'[do not write backups for modified files]'
4077 {-o,--output-dir}'[put output files in this directory instead of overwriting]:directory:_directories'
4078 {-W,--write-unchanged-files}'[also write files even if no changes were required]'
4079 '--add-suffix[append this string to all output filenames]:suffix: '
4080 '*:filename:_files'
4081)
4082
4083_arguments -s -S $arguments
4084"##;
4085 let result = parse(input);
4086 assert!(
4087 result.is_ok(),
4088 "Failed to parse full completion file: {:?}",
4089 result.err()
4090 );
4091 let prog = result.unwrap();
4092 assert!(!prog.lists.is_empty(), "Expected at least one list");
4094 }
4095
4096 #[test]
4097 fn test_parse_logs_sh() {
4098 let input = r#"#!/usr/bin/env bash
4099shopt -s globstar
4100
4101if [[ $(uname) == Darwin ]]; then
4102 tail -f /var/log/**/*.log /var/log/**/*.out | lolcat
4103else
4104 if [[ $ZPWR_DISTRO_NAME == raspbian ]]; then
4105 tail -f /var/log/**/*.log | lolcat
4106 else
4107 printf "Unsupported...\n" >&2
4108 fi
4109fi
4110"#;
4111 let result = parse(input);
4112 assert!(
4113 result.is_ok(),
4114 "Failed to parse logs.sh: {:?}",
4115 result.err()
4116 );
4117 }
4118
4119 #[test]
4120 fn test_parse_case_with_glob() {
4121 let input = r#"case "$ZPWR_OS_TYPE" in
4122 darwin*) open_cmd='open'
4123 ;;
4124 cygwin*) open_cmd='cygstart'
4125 ;;
4126 linux*)
4127 open_cmd='xdg-open'
4128 ;;
4129esac"#;
4130 let result = parse(input);
4131 assert!(
4132 result.is_ok(),
4133 "Failed to parse case with glob: {:?}",
4134 result.err()
4135 );
4136 }
4137
4138 #[test]
4139 fn test_parse_case_with_nested_if() {
4140 let input = r##"function zpwrGetOpenCommand(){
4142 local open_cmd
4143 case "$ZPWR_OS_TYPE" in
4144 darwin*) open_cmd='open' ;;
4145 cygwin*) open_cmd='cygstart' ;;
4146 linux*)
4147 if [[ "$_zpwr_uname_r" != *icrosoft* ]];then
4148 open_cmd='nohup xdg-open'
4149 fi
4150 ;;
4151 esac
4152}"##;
4153 let result = parse(input);
4154 assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
4155 }
4156
4157 #[test]
4158 fn test_parse_zpwr_scripts() {
4159 use std::fs;
4160 use std::path::Path;
4161 use std::sync::mpsc;
4162 use std::thread;
4163 use std::time::{Duration, Instant};
4164
4165 let scripts_dir = Path::new("/Users/wizard/.zpwr/scripts");
4166 if !scripts_dir.exists() {
4167 eprintln!("Skipping test: scripts directory not found");
4168 return;
4169 }
4170
4171 let mut total = 0;
4172 let mut passed = 0;
4173 let mut failed_files = Vec::new();
4174 let mut timeout_files = Vec::new();
4175
4176 for ext in &["sh", "zsh"] {
4177 let pattern = scripts_dir.join(format!("*.{}", ext));
4178 if let Ok(entries) = glob::glob(pattern.to_str().unwrap()) {
4179 for entry in entries.flatten() {
4180 total += 1;
4181 let file_path = entry.display().to_string();
4182 let content = match fs::read_to_string(&entry) {
4183 Ok(c) => c,
4184 Err(e) => {
4185 failed_files.push((file_path, format!("read error: {}", e)));
4186 continue;
4187 }
4188 };
4189
4190 let content_clone = content.clone();
4192 let (tx, rx) = mpsc::channel();
4193 let handle = thread::spawn(move || {
4194 let result = parse(&content_clone);
4195 let _ = tx.send(result);
4196 });
4197
4198 match rx.recv_timeout(Duration::from_secs(2)) {
4199 Ok(Ok(_)) => passed += 1,
4200 Ok(Err(errors)) => {
4201 let first_err = errors
4202 .first()
4203 .map(|e| format!("line {}: {}", e.line, e.message))
4204 .unwrap_or_default();
4205 failed_files.push((file_path, first_err));
4206 }
4207 Err(_) => {
4208 timeout_files.push(file_path);
4209 }
4211 }
4212 }
4213 }
4214 }
4215
4216 eprintln!("\n=== ZPWR Scripts Parse Results ===");
4217 eprintln!("Passed: {}/{}", passed, total);
4218
4219 if !timeout_files.is_empty() {
4220 eprintln!("\nTimeout files (>2s):");
4221 for file in &timeout_files {
4222 eprintln!(" {}", file);
4223 }
4224 }
4225
4226 if !failed_files.is_empty() {
4227 eprintln!("\nFailed files:");
4228 for (file, err) in &failed_files {
4229 eprintln!(" {} - {}", file, err);
4230 }
4231 }
4232
4233 let pass_rate = if total > 0 {
4235 (passed as f64 / total as f64) * 100.0
4236 } else {
4237 0.0
4238 };
4239 eprintln!("Pass rate: {:.1}%", pass_rate);
4240
4241 assert!(pass_rate >= 50.0, "Pass rate too low: {:.1}%", pass_rate);
4243 }
4244
4245 #[test]
4246 #[ignore] fn test_parse_zsh_stdlib_functions() {
4248 use std::fs;
4249 use std::path::Path;
4250 use std::sync::mpsc;
4251 use std::thread;
4252 use std::time::Duration;
4253
4254 let functions_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("test_data/zsh_functions");
4255 if !functions_dir.exists() {
4256 eprintln!(
4257 "Skipping test: zsh_functions directory not found at {:?}",
4258 functions_dir
4259 );
4260 return;
4261 }
4262
4263 let mut total = 0;
4264 let mut passed = 0;
4265 let mut failed_files = Vec::new();
4266 let mut timeout_files = Vec::new();
4267
4268 if let Ok(entries) = fs::read_dir(&functions_dir) {
4269 for entry in entries.flatten() {
4270 let path = entry.path();
4271 if !path.is_file() {
4272 continue;
4273 }
4274
4275 total += 1;
4276 let file_path = path.display().to_string();
4277 let content = match fs::read_to_string(&path) {
4278 Ok(c) => c,
4279 Err(e) => {
4280 failed_files.push((file_path, format!("read error: {}", e)));
4281 continue;
4282 }
4283 };
4284
4285 let content_clone = content.clone();
4287 let (tx, rx) = mpsc::channel();
4288 thread::spawn(move || {
4289 let result = parse(&content_clone);
4290 let _ = tx.send(result);
4291 });
4292
4293 match rx.recv_timeout(Duration::from_secs(2)) {
4294 Ok(Ok(_)) => passed += 1,
4295 Ok(Err(errors)) => {
4296 let first_err = errors
4297 .first()
4298 .map(|e| format!("line {}: {}", e.line, e.message))
4299 .unwrap_or_default();
4300 failed_files.push((file_path, first_err));
4301 }
4302 Err(_) => {
4303 timeout_files.push(file_path);
4304 }
4305 }
4306 }
4307 }
4308
4309 eprintln!("\n=== Zsh Stdlib Functions Parse Results ===");
4310 eprintln!("Passed: {}/{}", passed, total);
4311
4312 if !timeout_files.is_empty() {
4313 eprintln!("\nTimeout files (>2s): {}", timeout_files.len());
4314 for file in timeout_files.iter().take(10) {
4315 eprintln!(" {}", file);
4316 }
4317 if timeout_files.len() > 10 {
4318 eprintln!(" ... and {} more", timeout_files.len() - 10);
4319 }
4320 }
4321
4322 if !failed_files.is_empty() {
4323 eprintln!("\nFailed files: {}", failed_files.len());
4324 for (file, err) in failed_files.iter().take(20) {
4325 let filename = Path::new(file)
4326 .file_name()
4327 .unwrap_or_default()
4328 .to_string_lossy();
4329 eprintln!(" {} - {}", filename, err);
4330 }
4331 if failed_files.len() > 20 {
4332 eprintln!(" ... and {} more", failed_files.len() - 20);
4333 }
4334 }
4335
4336 let pass_rate = if total > 0 {
4337 (passed as f64 / total as f64) * 100.0
4338 } else {
4339 0.0
4340 };
4341 eprintln!("Pass rate: {:.1}%", pass_rate);
4342
4343 assert!(pass_rate >= 50.0, "Pass rate too low: {:.1}%", pass_rate);
4345 }
4346}