1use crate::lexer::ZshLexer;
8use crate::tokens::LexTok;
9use serde::{Deserialize, Serialize};
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 == '(' {
1327 depth += 1;
1328 }
1329 if ch == ')' {
1330 depth -= 1;
1331 if depth == 0 {
1332 break;
1333 }
1334 }
1335 cmd.push(ch);
1336 }
1337 let mut sub_parser = ShellParser::new(&cmd);
1339 if let Ok(cmds) = sub_parser.parse_script() {
1340 if cmds.len() == 1 {
1341 elements.push(ShellWord::CommandSub(Box::new(
1342 cmds.into_iter().next().unwrap(),
1343 )));
1344 } else if !cmds.is_empty() {
1345 elements.push(ShellWord::CommandSub(Box::new(
1347 ShellCommand::Compound(CompoundCommand::BraceGroup(cmds)),
1348 )));
1349 }
1350 }
1351 }
1352 ' ' | '\t' | '\n' => {
1353 if !current.is_empty() {
1354 elements.push(ShellWord::Literal(current.clone()));
1355 current.clear();
1356 }
1357 }
1358 '\\' => {
1359 if let Some(next) = chars.next() {
1360 current.push(next);
1361 }
1362 }
1363 _ => current.push(c),
1364 }
1365 }
1366 }
1367
1368 if !current.is_empty() {
1369 elements.push(ShellWord::Literal(current));
1370 }
1371
1372 elements
1373 }
1374
1375 fn advance(&mut self) -> ShellToken {
1376 std::mem::replace(&mut self.current, self.lexer.next_token())
1377 }
1378
1379 fn expect(&mut self, expected: ShellToken) -> Result<(), String> {
1380 if self.current == expected {
1381 self.advance();
1382 Ok(())
1383 } else {
1384 Err(format!("Expected {:?}, got {:?}", expected, self.current))
1385 }
1386 }
1387
1388 fn skip_newlines(&mut self) {
1389 while self.current == ShellToken::Newline {
1390 self.advance();
1391 }
1392 }
1393
1394 fn skip_separators(&mut self) {
1395 while self.current == ShellToken::Newline || self.current == ShellToken::Semi {
1396 self.advance();
1397 }
1398 }
1399
1400 #[tracing::instrument(level = "trace", skip_all)]
1401 pub fn parse_script(&mut self) -> Result<Vec<ShellCommand>, String> {
1402 let mut commands = Vec::new();
1403 self.skip_newlines();
1404 while self.current != ShellToken::Eof {
1405 if let Some(cmd) = self.parse_complete_command()? {
1406 commands.push(cmd);
1407 }
1408 self.skip_newlines();
1409 }
1410 Ok(commands)
1411 }
1412
1413 fn parse_complete_command(&mut self) -> Result<Option<ShellCommand>, String> {
1414 self.skip_newlines();
1415 if self.current == ShellToken::Eof {
1416 return Ok(None);
1417 }
1418 let cmd = self.parse_list()?;
1419 match &self.current {
1420 ShellToken::Newline | ShellToken::Semi | ShellToken::Amp => {
1421 self.advance();
1422 }
1423 _ => {}
1424 }
1425 Ok(Some(cmd))
1426 }
1427
1428 fn parse_list(&mut self) -> Result<ShellCommand, String> {
1429 let first = self.parse_pipeline()?;
1430 let mut items = vec![(first, ListOp::Semi)];
1431
1432 loop {
1433 let op = match &self.current {
1434 ShellToken::AmpAmp => ListOp::And,
1435 ShellToken::PipePipe => ListOp::Or,
1436 ShellToken::Semi => ListOp::Semi,
1437 ShellToken::Amp => ListOp::Amp,
1438 ShellToken::Newline => break,
1439 _ => break,
1440 };
1441
1442 self.advance();
1443 self.skip_newlines();
1444
1445 if let Some(last) = items.last_mut() {
1446 last.1 = op;
1447 }
1448
1449 if self.current == ShellToken::Eof
1450 || self.current == ShellToken::Then
1451 || self.current == ShellToken::Else
1452 || self.current == ShellToken::Elif
1453 || self.current == ShellToken::Fi
1454 || self.current == ShellToken::Do
1455 || self.current == ShellToken::Done
1456 || self.current == ShellToken::Esac
1457 || self.current == ShellToken::RBrace
1458 || self.current == ShellToken::RParen
1459 {
1460 break;
1461 }
1462
1463 let next = self.parse_pipeline()?;
1464 items.push((next, ListOp::Semi));
1465 }
1466
1467 if items.len() == 1 {
1468 let (cmd, op) = items.pop().unwrap();
1469 if matches!(op, ListOp::Amp) {
1470 Ok(ShellCommand::List(vec![(cmd, op)]))
1471 } else {
1472 Ok(cmd)
1473 }
1474 } else {
1475 Ok(ShellCommand::List(items))
1476 }
1477 }
1478
1479 fn parse_pipeline(&mut self) -> Result<ShellCommand, String> {
1480 let negated = if self.current == ShellToken::Bang {
1481 self.advance();
1482 true
1483 } else {
1484 false
1485 };
1486
1487 let first = self.parse_command()?;
1488 let mut cmds = vec![first];
1489
1490 while self.current == ShellToken::Pipe {
1491 self.advance();
1492 self.skip_newlines();
1493 cmds.push(self.parse_command()?);
1494 }
1495
1496 if cmds.len() == 1 && !negated {
1497 Ok(cmds.pop().unwrap())
1498 } else {
1499 Ok(ShellCommand::Pipeline(cmds, negated))
1500 }
1501 }
1502
1503 fn parse_command(&mut self) -> Result<ShellCommand, String> {
1504 let cmd = match &self.current {
1505 ShellToken::If => self.parse_if(),
1506 ShellToken::For => self.parse_for(),
1507 ShellToken::While => self.parse_while(),
1508 ShellToken::Until => self.parse_until(),
1509 ShellToken::Case => self.parse_case(),
1510 ShellToken::Repeat => self.parse_repeat(),
1511 ShellToken::LBrace => self.parse_brace_group_or_try(),
1512 ShellToken::LParen => {
1513 self.advance();
1514 if self.current == ShellToken::RParen {
1515 self.advance();
1516 self.skip_newlines();
1517 if self.current == ShellToken::LBrace {
1518 let body = self.parse_brace_group()?;
1519 Ok(ShellCommand::FunctionDef(String::new(), Box::new(body)))
1520 } else {
1521 Ok(ShellCommand::Compound(CompoundCommand::Subshell(vec![])))
1522 }
1523 } else {
1524 self.skip_newlines();
1525 let body = self.parse_compound_list()?;
1526 self.expect(ShellToken::RParen)?;
1527 Ok(ShellCommand::Compound(CompoundCommand::Subshell(body)))
1528 }
1529 }
1530 ShellToken::DoubleLBracket => self.parse_cond_command(),
1531 ShellToken::DoubleLParen => self.parse_arith_command(),
1532 ShellToken::Function => self.parse_function(),
1533 ShellToken::Coproc => self.parse_coproc(),
1534 _ => self.parse_simple_command(),
1535 }?;
1536
1537 let mut redirects = Vec::new();
1538 loop {
1539 if let ShellToken::Word(w) = &self.current {
1540 if w.chars().all(|c| c.is_ascii_digit()) {
1541 let fd_str = w.clone();
1542 self.advance();
1543 match &self.current {
1544 ShellToken::Less
1545 | ShellToken::Greater
1546 | ShellToken::GreaterGreater
1547 | ShellToken::LessAmp
1548 | ShellToken::GreaterAmp
1549 | ShellToken::LessLess
1550 | ShellToken::LessLessLess
1551 | ShellToken::LessGreater
1552 | ShellToken::GreaterPipe => {
1553 let fd = fd_str.parse::<i32>().ok();
1554 redirects.push(self.parse_redirect_with_fd(fd)?);
1555 continue;
1556 }
1557 _ => break,
1558 }
1559 }
1560 }
1561
1562 match &self.current {
1563 ShellToken::Less
1564 | ShellToken::Greater
1565 | ShellToken::GreaterGreater
1566 | ShellToken::LessAmp
1567 | ShellToken::GreaterAmp
1568 | ShellToken::LessLess
1569 | ShellToken::LessLessLess
1570 | ShellToken::LessGreater
1571 | ShellToken::GreaterPipe
1572 | ShellToken::AmpGreater
1573 | ShellToken::AmpGreaterGreater => {
1574 redirects.push(self.parse_redirect_with_fd(None)?);
1575 }
1576 _ => break,
1577 }
1578 }
1579
1580 if !redirects.is_empty() {
1581 Ok(ShellCommand::Compound(CompoundCommand::WithRedirects(
1582 Box::new(cmd),
1583 redirects,
1584 )))
1585 } else {
1586 Ok(cmd)
1587 }
1588 }
1589
1590 fn parse_simple_command(&mut self) -> Result<ShellCommand, String> {
1591 let mut cmd = SimpleCommand {
1592 assignments: Vec::new(),
1593 words: Vec::new(),
1594 redirects: Vec::new(),
1595 };
1596
1597 loop {
1598 match &self.current {
1599 ShellToken::Word(w) => {
1600 if w.starts_with('{') && w.ends_with('}') && w.len() > 2 {
1601 let varname = w[1..w.len() - 1].to_string();
1602 if varname.chars().all(|c| c.is_alphanumeric() || c == '_') {
1603 let saved_word = w.clone();
1604 self.advance();
1605 match &self.current {
1606 ShellToken::Less
1607 | ShellToken::Greater
1608 | ShellToken::GreaterGreater
1609 | ShellToken::LessAmp
1610 | ShellToken::GreaterAmp
1611 | ShellToken::LessLess
1612 | ShellToken::LessLessLess
1613 | ShellToken::LessGreater
1614 | ShellToken::GreaterPipe => {
1615 let mut redir = self.parse_redirect_with_fd(None)?;
1616 redir.fd_var = Some(varname);
1617 cmd.redirects.push(redir);
1618 continue;
1619 }
1620 _ => {
1621 cmd.words.push(ShellWord::Literal(saved_word));
1622 continue;
1623 }
1624 }
1625 }
1626 }
1627
1628 if w.chars().all(|c| c.is_ascii_digit()) {
1629 let fd_str = w.clone();
1630 self.advance();
1631 match &self.current {
1632 ShellToken::Less
1633 | ShellToken::Greater
1634 | ShellToken::GreaterGreater
1635 | ShellToken::LessAmp
1636 | ShellToken::GreaterAmp
1637 | ShellToken::LessLess
1638 | ShellToken::LessLessLess
1639 | ShellToken::LessGreater
1640 | ShellToken::GreaterPipe => {
1641 let fd = fd_str.parse::<i32>().ok();
1642 cmd.redirects.push(self.parse_redirect_with_fd(fd)?);
1643 continue;
1644 }
1645 _ => {
1646 cmd.words.push(ShellWord::Literal(fd_str));
1647 continue;
1648 }
1649 }
1650 }
1651
1652 if cmd.words.is_empty() && w.contains('=') && !w.starts_with('=') {
1653 let (eq_pos, is_append) = if let Some(pos) = w.find("+=") {
1654 (pos, true)
1655 } else if let Some(pos) = w.find('=') {
1656 (pos, false)
1657 } else {
1658 (0, false)
1659 };
1660
1661 if eq_pos > 0 {
1662 let var = w[..eq_pos].to_string();
1663 let val_start = if is_append { eq_pos + 2 } else { eq_pos + 1 };
1664 let val = w[val_start..].to_string();
1665
1666 let is_valid_var = if let Some(bracket_pos) = var.find('[') {
1667 let name = &var[..bracket_pos];
1668 let rest = &var[bracket_pos..];
1669 name.chars().all(|c| c.is_alphanumeric() || c == '_')
1670 && rest.ends_with(']')
1671 } else {
1672 var.chars().all(|c| c.is_alphanumeric() || c == '_')
1673 };
1674 if is_valid_var {
1675 if val.starts_with('(') && val.ends_with(')') {
1676 let array_content = &val[1..val.len() - 1];
1677 let elements = Self::parse_array_elements(array_content);
1678 cmd.assignments.push((
1679 var,
1680 ShellWord::ArrayLiteral(elements),
1681 is_append,
1682 ));
1683 } else {
1684 cmd.assignments
1685 .push((var, ShellWord::Literal(val), is_append));
1686 }
1687 self.advance();
1688 continue;
1689 }
1690 }
1691 }
1692
1693 cmd.words.push(self.parse_word()?);
1694 }
1695
1696 ShellToken::SingleQuotedWord(_) | ShellToken::DoubleQuotedWord(_) => {
1697 cmd.words.push(self.parse_word()?);
1698 }
1699
1700 ShellToken::LBracket => {
1701 cmd.words.push(ShellWord::Literal("[".to_string()));
1702 self.advance();
1703 }
1704 ShellToken::RBracket => {
1705 if !cmd.words.is_empty() {
1706 cmd.words.push(ShellWord::Literal("]".to_string()));
1707 self.advance();
1708 } else {
1709 break;
1710 }
1711 }
1712 ShellToken::If
1713 | ShellToken::Then
1714 | ShellToken::Else
1715 | ShellToken::Elif
1716 | ShellToken::Fi
1717 | ShellToken::Case
1718 | ShellToken::Esac
1719 | ShellToken::For
1720 | ShellToken::While
1721 | ShellToken::Until
1722 | ShellToken::Do
1723 | ShellToken::Done
1724 | ShellToken::In
1725 | ShellToken::Function
1726 | ShellToken::Select
1727 | ShellToken::Time
1728 | ShellToken::Coproc
1729 | ShellToken::Repeat => {
1730 if !cmd.words.is_empty() {
1731 cmd.words.push(self.parse_word()?);
1732 } else {
1733 break;
1734 }
1735 }
1736
1737 ShellToken::Typeset(ref name) => {
1738 if cmd.words.is_empty() {
1739 let name = name.clone();
1740 cmd.words.push(ShellWord::Literal(name));
1741 self.advance();
1742 } else {
1743 cmd.words.push(self.parse_word()?);
1744 }
1745 }
1746
1747 ShellToken::Less
1748 | ShellToken::Greater
1749 | ShellToken::GreaterGreater
1750 | ShellToken::LessAmp
1751 | ShellToken::GreaterAmp
1752 | ShellToken::LessLess
1753 | ShellToken::LessLessLess
1754 | ShellToken::LessGreater
1755 | ShellToken::GreaterPipe
1756 | ShellToken::AmpGreater
1757 | ShellToken::AmpGreaterGreater
1758 | ShellToken::HereDoc(_, _) => {
1759 cmd.redirects.push(self.parse_redirect_with_fd(None)?);
1760 }
1761
1762 _ => break,
1763 }
1764 }
1765
1766 if cmd.words.len() == 1 && self.current == ShellToken::LParen {
1767 if let ShellWord::Literal(name) = &cmd.words[0] {
1768 let name = name.clone();
1769 self.advance();
1770 self.expect(ShellToken::RParen)?;
1771 self.skip_newlines();
1772 let body = self.parse_command()?;
1773 return Ok(ShellCommand::FunctionDef(name, Box::new(body)));
1774 }
1775 }
1776
1777 Ok(ShellCommand::Simple(cmd))
1778 }
1779
1780 fn parse_word(&mut self) -> Result<ShellWord, String> {
1781 let token = self.advance();
1782 match token {
1783 ShellToken::Word(w) => Ok(ShellWord::Literal(w)),
1784 ShellToken::SingleQuotedWord(s) => Ok(ShellWord::SingleQuoted(s)),
1785 ShellToken::DoubleQuotedWord(s) => Ok(self.parse_double_quoted_contents(&s)),
1786 ShellToken::LBracket => Ok(ShellWord::Literal("[".to_string())),
1787 ShellToken::If => Ok(ShellWord::Literal("if".to_string())),
1788 ShellToken::Then => Ok(ShellWord::Literal("then".to_string())),
1789 ShellToken::Else => Ok(ShellWord::Literal("else".to_string())),
1790 ShellToken::Elif => Ok(ShellWord::Literal("elif".to_string())),
1791 ShellToken::Fi => Ok(ShellWord::Literal("fi".to_string())),
1792 ShellToken::Case => Ok(ShellWord::Literal("case".to_string())),
1793 ShellToken::Esac => Ok(ShellWord::Literal("esac".to_string())),
1794 ShellToken::For => Ok(ShellWord::Literal("for".to_string())),
1795 ShellToken::While => Ok(ShellWord::Literal("while".to_string())),
1796 ShellToken::Until => Ok(ShellWord::Literal("until".to_string())),
1797 ShellToken::Do => Ok(ShellWord::Literal("do".to_string())),
1798 ShellToken::Done => Ok(ShellWord::Literal("done".to_string())),
1799 ShellToken::In => Ok(ShellWord::Literal("in".to_string())),
1800 ShellToken::Function => Ok(ShellWord::Literal("function".to_string())),
1801 ShellToken::Select => Ok(ShellWord::Literal("select".to_string())),
1802 ShellToken::Time => Ok(ShellWord::Literal("time".to_string())),
1803 ShellToken::Coproc => Ok(ShellWord::Literal("coproc".to_string())),
1804 ShellToken::Typeset(name) => Ok(ShellWord::Literal(name)),
1805 ShellToken::Repeat => Ok(ShellWord::Literal("repeat".to_string())),
1806 _ => Err("Expected word".to_string()),
1807 }
1808 }
1809
1810 fn parse_double_quoted_contents(&self, s: &str) -> ShellWord {
1811 let mut parts = Vec::new();
1812 let mut literal = String::new();
1813 let chars: Vec<char> = s.chars().collect();
1814 let mut i = 0;
1815 while i < chars.len() {
1816 if chars[i] == '$' {
1817 if !literal.is_empty() {
1818 parts.push(ShellWord::Literal(std::mem::take(&mut literal)));
1819 }
1820 i += 1;
1821 if i >= chars.len() {
1822 literal.push('$');
1823 continue;
1824 }
1825 if chars[i] == '{' {
1826 i += 1;
1827 let mut var_name = String::new();
1828 while i < chars.len() && chars[i] != '}' {
1829 var_name.push(chars[i]);
1830 i += 1;
1831 }
1832 if i < chars.len() {
1833 i += 1;
1834 }
1835 parts.push(ShellWord::VariableBraced(var_name, None));
1836 } else if chars[i].is_ascii_alphabetic() || chars[i] == '_' {
1837 let mut var_name = String::new();
1838 while i < chars.len() && (chars[i].is_ascii_alphanumeric() || chars[i] == '_') {
1839 var_name.push(chars[i]);
1840 i += 1;
1841 }
1842 parts.push(ShellWord::Variable(var_name));
1843 } else {
1844 literal.push('$');
1845 }
1846 } else if chars[i] == '\\' && i + 1 < chars.len() {
1847 i += 1;
1848 match chars[i] {
1849 'n' => literal.push('\n'),
1850 't' => literal.push('\t'),
1851 'r' => literal.push('\r'),
1852 '\\' => literal.push('\\'),
1853 '"' => literal.push('"'),
1854 '$' => literal.push('$'),
1855 '`' => literal.push('`'),
1856 c => {
1857 literal.push('\\');
1858 literal.push(c);
1859 }
1860 }
1861 i += 1;
1862 } else {
1863 literal.push(chars[i]);
1864 i += 1;
1865 }
1866 }
1867 if !literal.is_empty() {
1868 parts.push(ShellWord::Literal(literal));
1869 }
1870 if parts.is_empty() {
1871 ShellWord::Literal(String::new())
1872 } else if parts.len() == 1 {
1873 parts.pop().unwrap()
1874 } else {
1875 ShellWord::DoubleQuoted(parts)
1876 }
1877 }
1878
1879 fn parse_redirect_with_fd(&mut self, fd: Option<i32>) -> Result<Redirect, String> {
1880 if let ShellToken::HereDoc(delimiter, content) = &self.current {
1881 let delimiter = delimiter.clone();
1882 let content = content.clone();
1883 self.advance();
1884 return Ok(Redirect {
1885 fd,
1886 op: RedirectOp::HereDoc,
1887 target: ShellWord::Literal(delimiter),
1888 heredoc_content: Some(content),
1889 fd_var: None,
1890 });
1891 }
1892
1893 let mut fd_var = None;
1894 if let ShellToken::Word(w) = &self.current {
1895 if w.starts_with('{') && w.ends_with('}') && w.len() > 2 {
1896 let varname = w[1..w.len() - 1].to_string();
1897 fd_var = Some(varname);
1898 self.advance();
1899 }
1900 }
1901
1902 let op = match self.advance() {
1903 ShellToken::Less => RedirectOp::Read,
1904 ShellToken::Greater => RedirectOp::Write,
1905 ShellToken::GreaterGreater => RedirectOp::Append,
1906 ShellToken::LessAmp => RedirectOp::DupRead,
1907 ShellToken::GreaterAmp => RedirectOp::DupWrite,
1908 ShellToken::LessLess => RedirectOp::HereDoc,
1909 ShellToken::LessLessLess => RedirectOp::HereString,
1910 ShellToken::LessGreater => RedirectOp::ReadWrite,
1911 ShellToken::GreaterPipe => RedirectOp::Clobber,
1912 ShellToken::AmpGreater => RedirectOp::WriteBoth,
1913 ShellToken::AmpGreaterGreater => RedirectOp::AppendBoth,
1914 _ => return Err("Expected redirect operator".to_string()),
1915 };
1916
1917 let target = self.parse_word()?;
1918
1919 Ok(Redirect {
1920 fd,
1921 op,
1922 target,
1923 heredoc_content: None,
1924 fd_var,
1925 })
1926 }
1927
1928 fn parse_if(&mut self) -> Result<ShellCommand, String> {
1929 let mut conditions = Vec::new();
1930 let mut else_part = None;
1931 let mut usebrace = false;
1932
1933 let mut xtok = self.current.clone();
1934 loop {
1935 if xtok == ShellToken::Fi {
1936 self.advance();
1937 break;
1938 }
1939
1940 self.advance();
1941
1942 if xtok == ShellToken::Else {
1943 break;
1944 }
1945
1946 self.skip_separators();
1947
1948 if xtok != ShellToken::If && xtok != ShellToken::Elif {
1949 return Err(format!("Expected If or Elif, got {:?}", xtok));
1950 }
1951
1952 let cond = self.parse_compound_list_until(&[ShellToken::Then, ShellToken::LBrace])?;
1953 self.skip_separators();
1954 xtok = ShellToken::Fi;
1955
1956 if self.current == ShellToken::Then {
1957 usebrace = false;
1958 self.advance();
1959 let body = self.parse_compound_list()?;
1960 conditions.push((cond, body));
1961 } else if self.current == ShellToken::LBrace {
1962 usebrace = true;
1963 self.advance();
1964 self.skip_separators();
1965 let body = self.parse_compound_list_until(&[ShellToken::RBrace])?;
1966 if self.current != ShellToken::RBrace {
1967 return Err(format!("Expected RBrace, got {:?}", self.current));
1968 }
1969 conditions.push((cond, body));
1970 self.advance();
1971 if self.current == ShellToken::Newline || self.current == ShellToken::Semi {
1972 break;
1973 }
1974 } else {
1975 return Err(format!(
1976 "Expected Then or LBrace after condition, got {:?}",
1977 self.current
1978 ));
1979 }
1980
1981 xtok = self.current.clone();
1982 if xtok != ShellToken::Elif && xtok != ShellToken::Else && xtok != ShellToken::Fi {
1983 break;
1984 }
1985 }
1986
1987 if xtok == ShellToken::Else || self.current == ShellToken::Else {
1988 if self.current == ShellToken::Else {
1989 self.advance();
1990 }
1991 self.skip_separators();
1992
1993 if self.current == ShellToken::LBrace && usebrace {
1994 self.advance();
1995 self.skip_separators();
1996 let body = self.parse_compound_list_until(&[ShellToken::RBrace])?;
1997 if self.current != ShellToken::RBrace {
1998 return Err(format!("Expected RBrace in else, got {:?}", self.current));
1999 }
2000 self.advance();
2001 else_part = Some(body);
2002 } else {
2003 let body = self.parse_compound_list()?;
2004 if self.current != ShellToken::Fi {
2005 return Err(format!("Expected Fi, got {:?}", self.current));
2006 }
2007 self.advance();
2008 else_part = Some(body);
2009 }
2010 }
2011
2012 Ok(ShellCommand::Compound(CompoundCommand::If {
2013 conditions,
2014 else_part,
2015 }))
2016 }
2017
2018 fn parse_for(&mut self) -> Result<ShellCommand, String> {
2019 self.expect(ShellToken::For)?;
2020 self.skip_newlines();
2021
2022 if self.current == ShellToken::DoubleLParen {
2023 return self.parse_for_arith();
2024 }
2025
2026 let var = if let ShellToken::Word(w) = self.advance() {
2027 w
2028 } else {
2029 return Err("Expected variable name after 'for'".to_string());
2030 };
2031
2032 while self.current == ShellToken::Newline {
2033 self.advance();
2034 }
2035
2036 let words = if self.current == ShellToken::In {
2037 self.advance();
2038 let mut words = Vec::new();
2039 while let ShellToken::Word(_) = &self.current {
2040 words.push(self.parse_word()?);
2041 }
2042 Some(words)
2043 } else if self.current == ShellToken::LParen {
2044 self.advance();
2045 let mut words = Vec::new();
2046 while self.current != ShellToken::RParen && self.current != ShellToken::Eof {
2047 if let ShellToken::Word(_) = &self.current {
2048 words.push(self.parse_word()?);
2049 } else if self.current == ShellToken::Newline {
2050 self.advance();
2051 } else {
2052 break;
2053 }
2054 }
2055 self.expect(ShellToken::RParen)?;
2056 Some(words)
2057 } else {
2058 None
2059 };
2060
2061 self.skip_separators();
2062
2063 let body = if self.current == ShellToken::LBrace {
2064 self.advance();
2065 let body = self.parse_compound_list_until(&[ShellToken::RBrace])?;
2066 self.expect(ShellToken::RBrace)?;
2067 body
2068 } else {
2069 self.expect(ShellToken::Do)?;
2070 let body = self.parse_compound_list()?;
2071 self.expect(ShellToken::Done)?;
2072 body
2073 };
2074
2075 Ok(ShellCommand::Compound(CompoundCommand::For {
2076 var,
2077 words,
2078 body,
2079 }))
2080 }
2081
2082 fn parse_for_arith(&mut self) -> Result<ShellCommand, String> {
2083 self.expect(ShellToken::DoubleLParen)?;
2084
2085 let mut parts = Vec::new();
2086 let mut current_part = String::new();
2087 let mut depth = 0;
2088
2089 loop {
2090 match &self.current {
2091 ShellToken::DoubleRParen if depth == 0 => break,
2092 ShellToken::DoubleLParen => {
2093 depth += 1;
2094 current_part.push_str("((");
2095 self.advance();
2096 }
2097 ShellToken::DoubleRParen => {
2098 depth -= 1;
2099 current_part.push_str("))");
2100 self.advance();
2101 }
2102 ShellToken::Semi => {
2103 parts.push(current_part.trim().to_string());
2104 current_part = String::new();
2105 self.advance();
2106 }
2107 ShellToken::Word(w) => {
2108 current_part.push_str(w);
2109 current_part.push(' ');
2110 self.advance();
2111 }
2112 ShellToken::Less => {
2113 current_part.push('<');
2114 self.advance();
2115 }
2116 ShellToken::Greater => {
2117 current_part.push('>');
2118 self.advance();
2119 }
2120 ShellToken::LessLess => {
2121 current_part.push_str("<<");
2122 self.advance();
2123 }
2124 ShellToken::GreaterGreater => {
2125 current_part.push_str(">>");
2126 self.advance();
2127 }
2128 _ => {
2129 self.advance();
2130 }
2131 }
2132 }
2133 parts.push(current_part.trim().to_string());
2134
2135 self.expect(ShellToken::DoubleRParen)?;
2136 self.skip_newlines();
2137
2138 match &self.current {
2139 ShellToken::Semi | ShellToken::Newline => {
2140 self.advance();
2141 }
2142 _ => {}
2143 }
2144 self.skip_newlines();
2145
2146 self.expect(ShellToken::Do)?;
2147 self.skip_newlines();
2148 let body = self.parse_compound_list()?;
2149 self.expect(ShellToken::Done)?;
2150
2151 Ok(ShellCommand::Compound(CompoundCommand::ForArith {
2152 init: parts.first().cloned().unwrap_or_default(),
2153 cond: parts.get(1).cloned().unwrap_or_default(),
2154 step: parts.get(2).cloned().unwrap_or_default(),
2155 body,
2156 }))
2157 }
2158
2159 fn parse_while(&mut self) -> Result<ShellCommand, String> {
2160 self.expect(ShellToken::While)?;
2161 let condition = self.parse_compound_list_until(&[ShellToken::Do, ShellToken::LBrace])?;
2162 self.skip_separators();
2163
2164 let body = if self.current == ShellToken::LBrace {
2165 self.advance();
2166 let body = self.parse_compound_list_until(&[ShellToken::RBrace])?;
2167 self.expect(ShellToken::RBrace)?;
2168 body
2169 } else {
2170 self.expect(ShellToken::Do)?;
2171 let body = self.parse_compound_list()?;
2172 self.expect(ShellToken::Done)?;
2173 body
2174 };
2175
2176 Ok(ShellCommand::Compound(CompoundCommand::While {
2177 condition,
2178 body,
2179 }))
2180 }
2181
2182 fn parse_until(&mut self) -> Result<ShellCommand, String> {
2183 self.expect(ShellToken::Until)?;
2184 let condition = self.parse_compound_list_until(&[ShellToken::Do, ShellToken::LBrace])?;
2185 self.skip_separators();
2186
2187 let body = if self.current == ShellToken::LBrace {
2188 self.advance();
2189 let body = self.parse_compound_list_until(&[ShellToken::RBrace])?;
2190 self.expect(ShellToken::RBrace)?;
2191 body
2192 } else {
2193 self.expect(ShellToken::Do)?;
2194 let body = self.parse_compound_list()?;
2195 self.expect(ShellToken::Done)?;
2196 body
2197 };
2198
2199 Ok(ShellCommand::Compound(CompoundCommand::Until {
2200 condition,
2201 body,
2202 }))
2203 }
2204
2205 fn parse_case(&mut self) -> Result<ShellCommand, String> {
2206 self.expect(ShellToken::Case)?;
2207 self.skip_newlines();
2208 let word = self.parse_word()?;
2209 self.skip_newlines();
2210 self.expect(ShellToken::In)?;
2211 self.skip_newlines();
2212
2213 let mut cases = Vec::new();
2214
2215 while self.current != ShellToken::Esac {
2216 let mut patterns = Vec::new();
2217 if self.current == ShellToken::LParen {
2218 self.advance();
2219 }
2220
2221 loop {
2222 patterns.push(self.parse_word()?);
2223 if self.current == ShellToken::Pipe {
2224 self.advance();
2225 } else {
2226 break;
2227 }
2228 }
2229
2230 self.expect(ShellToken::RParen)?;
2231 self.skip_newlines();
2232
2233 let body = self.parse_compound_list()?;
2234
2235 let term = match &self.current {
2236 ShellToken::DoubleSemi => {
2237 self.advance();
2238 CaseTerminator::Break
2239 }
2240 ShellToken::SemiAmp => {
2241 self.advance();
2242 CaseTerminator::Fallthrough
2243 }
2244 ShellToken::SemiSemiAmp => {
2245 self.advance();
2246 CaseTerminator::Continue
2247 }
2248 _ => CaseTerminator::Break,
2249 };
2250
2251 cases.push((patterns, body, term));
2252 self.skip_newlines();
2253 }
2254
2255 self.expect(ShellToken::Esac)?;
2256
2257 Ok(ShellCommand::Compound(CompoundCommand::Case {
2258 word,
2259 cases,
2260 }))
2261 }
2262
2263 fn parse_repeat(&mut self) -> Result<ShellCommand, String> {
2264 self.expect(ShellToken::Repeat)?;
2265
2266 let count = match &self.current {
2267 ShellToken::Word(w) => {
2268 let c = w.clone();
2269 self.advance();
2270 c
2271 }
2272 _ => return Err("expected count after 'repeat'".to_string()),
2273 };
2274
2275 self.skip_separators();
2276
2277 let body = if self.current == ShellToken::LBrace {
2278 self.advance();
2279 self.skip_newlines();
2280 let body = self.parse_compound_list_until(&[ShellToken::RBrace])?;
2281 self.expect(ShellToken::RBrace)?;
2282 body
2283 } else if self.current == ShellToken::Do {
2284 self.expect(ShellToken::Do)?;
2285 let body = self.parse_compound_list()?;
2286 self.expect(ShellToken::Done)?;
2287 body
2288 } else {
2289 let cmd = self.parse_command()?;
2291 vec![cmd]
2292 };
2293
2294 Ok(ShellCommand::Compound(CompoundCommand::Repeat {
2295 count,
2296 body,
2297 }))
2298 }
2299
2300 fn parse_brace_group_or_try(&mut self) -> Result<ShellCommand, String> {
2301 self.expect(ShellToken::LBrace)?;
2302 self.skip_newlines();
2303 let try_body = self.parse_compound_list()?;
2304 self.expect(ShellToken::RBrace)?;
2305
2306 if matches!(&self.current, ShellToken::Word(w) if w == "always") {
2309 self.advance();
2310 self.expect(ShellToken::LBrace)?;
2311 self.skip_newlines();
2312 let always_body = self.parse_compound_list()?;
2313 self.expect(ShellToken::RBrace)?;
2314
2315 Ok(ShellCommand::Compound(CompoundCommand::Try {
2316 try_body,
2317 always_body,
2318 }))
2319 } else {
2320 Ok(ShellCommand::Compound(CompoundCommand::BraceGroup(
2321 try_body,
2322 )))
2323 }
2324 }
2325
2326 fn parse_brace_group(&mut self) -> Result<ShellCommand, String> {
2327 self.expect(ShellToken::LBrace)?;
2328 self.skip_newlines();
2329 let body = self.parse_compound_list()?;
2330 self.expect(ShellToken::RBrace)?;
2331
2332 Ok(ShellCommand::Compound(CompoundCommand::BraceGroup(body)))
2333 }
2334
2335 fn parse_cond_command(&mut self) -> Result<ShellCommand, String> {
2336 self.expect(ShellToken::DoubleLBracket)?;
2337
2338 let mut tokens: Vec<String> = Vec::new();
2339 while self.current != ShellToken::DoubleRBracket && self.current != ShellToken::Eof {
2340 match &self.current {
2341 ShellToken::Word(w) => tokens.push(w.clone()),
2342 ShellToken::Bang => tokens.push("!".to_string()),
2343 ShellToken::AmpAmp => tokens.push("&&".to_string()),
2344 ShellToken::PipePipe => tokens.push("||".to_string()),
2345 ShellToken::LParen => tokens.push("(".to_string()),
2346 ShellToken::RParen => tokens.push(")".to_string()),
2347 ShellToken::Less => tokens.push("<".to_string()),
2348 ShellToken::Greater => tokens.push(">".to_string()),
2349 _ => {}
2350 }
2351 self.advance();
2352 }
2353
2354 self.expect(ShellToken::DoubleRBracket)?;
2355
2356 let expr = self.parse_cond_tokens(&tokens)?;
2357 Ok(ShellCommand::Compound(CompoundCommand::Cond(expr)))
2358 }
2359
2360 fn parse_cond_tokens(&self, tokens: &[String]) -> Result<CondExpr, String> {
2361 if tokens.is_empty() {
2362 return Ok(CondExpr::StringNonEmpty(ShellWord::Literal(String::new())));
2363 }
2364
2365 if tokens[0] == "!" {
2366 let inner = self.parse_cond_tokens(&tokens[1..])?;
2367 return Ok(CondExpr::Not(Box::new(inner)));
2368 }
2369
2370 for i in (0..tokens.len()).rev() {
2376 if tokens[i] == "||" {
2377 let left = self.parse_cond_tokens(&tokens[..i])?;
2378 let right = self.parse_cond_tokens(&tokens[i + 1..])?;
2379 return Ok(CondExpr::Or(Box::new(left), Box::new(right)));
2380 }
2381 }
2382
2383 for i in (0..tokens.len()).rev() {
2385 if tokens[i] == "&&" {
2386 let left = self.parse_cond_tokens(&tokens[..i])?;
2387 let right = self.parse_cond_tokens(&tokens[i + 1..])?;
2388 return Ok(CondExpr::And(Box::new(left), Box::new(right)));
2389 }
2390 }
2391
2392 for (i, tok) in tokens.iter().enumerate() {
2394 match tok.as_str() {
2395 "=" | "==" => {
2396 let left = tokens[..i].join(" ");
2397 let right = tokens[i + 1..].join(" ");
2398 return Ok(CondExpr::StringEqual(
2399 ShellWord::Literal(left),
2400 ShellWord::Literal(right),
2401 ));
2402 }
2403 "!=" => {
2404 let left = tokens[..i].join(" ");
2405 let right = tokens[i + 1..].join(" ");
2406 return Ok(CondExpr::StringNotEqual(
2407 ShellWord::Literal(left),
2408 ShellWord::Literal(right),
2409 ));
2410 }
2411 "=~" => {
2412 let left = tokens[..i].join(" ");
2413 let right = tokens[i + 1..].join(" ");
2414 return Ok(CondExpr::StringMatch(
2415 ShellWord::Literal(left),
2416 ShellWord::Literal(right),
2417 ));
2418 }
2419 "-eq" => {
2420 let left = tokens[..i].join(" ");
2421 let right = tokens[i + 1..].join(" ");
2422 return Ok(CondExpr::NumEqual(
2423 ShellWord::Literal(left),
2424 ShellWord::Literal(right),
2425 ));
2426 }
2427 "-ne" => {
2428 let left = tokens[..i].join(" ");
2429 let right = tokens[i + 1..].join(" ");
2430 return Ok(CondExpr::NumNotEqual(
2431 ShellWord::Literal(left),
2432 ShellWord::Literal(right),
2433 ));
2434 }
2435 "-lt" => {
2436 let left = tokens[..i].join(" ");
2437 let right = tokens[i + 1..].join(" ");
2438 return Ok(CondExpr::NumLess(
2439 ShellWord::Literal(left),
2440 ShellWord::Literal(right),
2441 ));
2442 }
2443 "-le" => {
2444 let left = tokens[..i].join(" ");
2445 let right = tokens[i + 1..].join(" ");
2446 return Ok(CondExpr::NumLessEqual(
2447 ShellWord::Literal(left),
2448 ShellWord::Literal(right),
2449 ));
2450 }
2451 "-gt" => {
2452 let left = tokens[..i].join(" ");
2453 let right = tokens[i + 1..].join(" ");
2454 return Ok(CondExpr::NumGreater(
2455 ShellWord::Literal(left),
2456 ShellWord::Literal(right),
2457 ));
2458 }
2459 "-ge" => {
2460 let left = tokens[..i].join(" ");
2461 let right = tokens[i + 1..].join(" ");
2462 return Ok(CondExpr::NumGreaterEqual(
2463 ShellWord::Literal(left),
2464 ShellWord::Literal(right),
2465 ));
2466 }
2467 "<" => {
2468 let left = tokens[..i].join(" ");
2469 let right = tokens[i + 1..].join(" ");
2470 return Ok(CondExpr::StringLess(
2471 ShellWord::Literal(left),
2472 ShellWord::Literal(right),
2473 ));
2474 }
2475 ">" => {
2476 let left = tokens[..i].join(" ");
2477 let right = tokens[i + 1..].join(" ");
2478 return Ok(CondExpr::StringGreater(
2479 ShellWord::Literal(left),
2480 ShellWord::Literal(right),
2481 ));
2482 }
2483 _ => {}
2484 }
2485 }
2486
2487 if tokens.len() >= 2 {
2488 let op = &tokens[0];
2489 let arg = tokens[1..].join(" ");
2490 match op.as_str() {
2491 "-e" => return Ok(CondExpr::FileExists(ShellWord::Literal(arg))),
2492 "-f" => return Ok(CondExpr::FileRegular(ShellWord::Literal(arg))),
2493 "-d" => return Ok(CondExpr::FileDirectory(ShellWord::Literal(arg))),
2494 "-L" | "-h" => return Ok(CondExpr::FileSymlink(ShellWord::Literal(arg))),
2495 "-r" => return Ok(CondExpr::FileReadable(ShellWord::Literal(arg))),
2496 "-w" => return Ok(CondExpr::FileWritable(ShellWord::Literal(arg))),
2497 "-x" => return Ok(CondExpr::FileExecutable(ShellWord::Literal(arg))),
2498 "-s" => return Ok(CondExpr::FileNonEmpty(ShellWord::Literal(arg))),
2499 "-z" => return Ok(CondExpr::StringEmpty(ShellWord::Literal(arg))),
2500 "-n" => return Ok(CondExpr::StringNonEmpty(ShellWord::Literal(arg))),
2501 _ => {}
2502 }
2503 }
2504
2505 let expr_str = tokens.join(" ");
2506 Ok(CondExpr::StringNonEmpty(ShellWord::Literal(expr_str)))
2507 }
2508
2509 fn parse_arith_command(&mut self) -> Result<ShellCommand, String> {
2510 self.expect(ShellToken::DoubleLParen)?;
2511
2512 let mut expr = String::new();
2513 let mut depth = 1;
2514
2515 while depth > 0 {
2516 match &self.current {
2517 ShellToken::DoubleLParen => {
2518 depth += 1;
2519 expr.push_str("((");
2520 }
2521 ShellToken::DoubleRParen => {
2522 depth -= 1;
2523 if depth > 0 {
2524 expr.push_str("))");
2525 }
2526 }
2527 ShellToken::Word(w) => {
2528 expr.push_str(w);
2529 expr.push(' ');
2530 }
2531 ShellToken::LParen => expr.push('('),
2532 ShellToken::RParen => expr.push(')'),
2533 ShellToken::LBracket => expr.push('['),
2534 ShellToken::RBracket => expr.push(']'),
2535 ShellToken::Less => expr.push('<'),
2536 ShellToken::Greater => expr.push('>'),
2537 ShellToken::LessLess => expr.push_str("<<"),
2538 ShellToken::GreaterGreater => expr.push_str(">>"),
2539 ShellToken::Bang => expr.push('!'),
2540 ShellToken::Eof => break,
2541 _ => {}
2542 }
2543 self.advance();
2544 }
2545
2546 Ok(ShellCommand::Compound(CompoundCommand::Arith(
2547 expr.trim().to_string(),
2548 )))
2549 }
2550
2551 fn parse_function(&mut self) -> Result<ShellCommand, String> {
2552 self.expect(ShellToken::Function)?;
2553 self.skip_newlines();
2554 let name = if let ShellToken::Word(w) = self.advance() {
2555 w
2556 } else {
2557 return Err("Expected function name".to_string());
2558 };
2559
2560 self.skip_newlines();
2561 if self.current == ShellToken::LParen {
2562 self.advance();
2563 self.expect(ShellToken::RParen)?;
2564 self.skip_newlines();
2565 }
2566
2567 let body = self.parse_command()?;
2568 Ok(ShellCommand::FunctionDef(name, Box::new(body)))
2569 }
2570
2571 fn parse_coproc(&mut self) -> Result<ShellCommand, String> {
2572 self.expect(ShellToken::Coproc)?;
2573 self.skip_newlines();
2574
2575 let name = if let ShellToken::Word(w) = &self.current {
2576 let n = w.clone();
2577 self.advance();
2578 self.skip_newlines();
2579 Some(n)
2580 } else {
2581 None
2582 };
2583
2584 let body = self.parse_command()?;
2585 Ok(ShellCommand::Compound(CompoundCommand::Coproc {
2586 name,
2587 body: Box::new(body),
2588 }))
2589 }
2590
2591 fn parse_compound_list(&mut self) -> Result<Vec<ShellCommand>, String> {
2592 let mut commands = Vec::new();
2593 self.skip_newlines();
2594
2595 while self.current != ShellToken::Eof
2596 && self.current != ShellToken::RBrace
2597 && self.current != ShellToken::RParen
2598 && self.current != ShellToken::Fi
2599 && self.current != ShellToken::Done
2600 && self.current != ShellToken::Esac
2601 && self.current != ShellToken::Elif
2602 && self.current != ShellToken::Else
2603 && self.current != ShellToken::DoubleSemi
2604 && self.current != ShellToken::SemiAmp
2605 && self.current != ShellToken::SemiSemiAmp
2606 {
2607 let cmd = self.parse_list()?;
2608 commands.push(cmd);
2609 match &self.current {
2610 ShellToken::Newline | ShellToken::Semi => {
2611 self.advance();
2612 }
2613 _ => {}
2614 }
2615 self.skip_newlines();
2616 }
2617
2618 Ok(commands)
2619 }
2620
2621 fn parse_compound_list_until(
2622 &mut self,
2623 terminators: &[ShellToken],
2624 ) -> Result<Vec<ShellCommand>, String> {
2625 let mut commands = Vec::new();
2626 self.skip_newlines();
2627
2628 while self.current != ShellToken::Eof && !terminators.contains(&self.current) {
2629 let cmd = self.parse_list()?;
2630 commands.push(cmd);
2631 match &self.current {
2632 ShellToken::Newline | ShellToken::Semi => {
2633 self.advance();
2634 }
2635 _ => {}
2636 }
2637 self.skip_newlines();
2638 }
2639
2640 Ok(commands)
2641 }
2642}
2643
2644pub struct ZshParser<'a> {
2646 lexer: ZshLexer<'a>,
2647 errors: Vec<ParseError>,
2648 global_iterations: usize,
2650 recursion_depth: usize,
2652}
2653
2654const MAX_RECURSION_DEPTH: usize = 500;
2655
2656impl<'a> ZshParser<'a> {
2657 pub fn new(input: &'a str) -> Self {
2659 ZshParser {
2660 lexer: ZshLexer::new(input),
2661 errors: Vec::new(),
2662 global_iterations: 0,
2663 recursion_depth: 0,
2664 }
2665 }
2666
2667 #[inline]
2669 fn check_limit(&mut self) -> bool {
2670 self.global_iterations += 1;
2671 self.global_iterations > 10_000
2672 }
2673
2674 #[inline]
2676 fn check_recursion(&mut self) -> bool {
2677 self.recursion_depth > MAX_RECURSION_DEPTH
2678 }
2679
2680 pub fn parse(&mut self) -> Result<ZshProgram, Vec<ParseError>> {
2682 self.lexer.zshlex();
2683
2684 let program = self.parse_program_until(None);
2685
2686 if !self.errors.is_empty() {
2687 return Err(std::mem::take(&mut self.errors));
2688 }
2689
2690 Ok(program)
2691 }
2692
2693 fn parse_program(&mut self) -> ZshProgram {
2695 self.parse_program_until(None)
2696 }
2697
2698 fn parse_program_until(&mut self, end_tokens: Option<&[LexTok]>) -> ZshProgram {
2700 let mut lists = Vec::new();
2701
2702 loop {
2703 if self.check_limit() {
2704 self.error("parser exceeded global iteration limit");
2705 break;
2706 }
2707
2708 while self.lexer.tok == LexTok::Seper || self.lexer.tok == LexTok::Newlin {
2710 if self.check_limit() {
2711 self.error("parser exceeded global iteration limit");
2712 return ZshProgram { lists };
2713 }
2714 self.lexer.zshlex();
2715 }
2716
2717 if self.lexer.tok == LexTok::Endinput || self.lexer.tok == LexTok::Lexerr {
2718 break;
2719 }
2720
2721 if let Some(end_toks) = end_tokens {
2723 if end_toks.contains(&self.lexer.tok) {
2724 break;
2725 }
2726 }
2727
2728 match self.lexer.tok {
2732 LexTok::Outbrace
2733 | LexTok::Dsemi
2734 | LexTok::Semiamp
2735 | LexTok::Semibar
2736 | LexTok::Done
2737 | LexTok::Fi
2738 | LexTok::Esac
2739 | LexTok::Zend => break,
2740 _ => {}
2741 }
2742
2743 match self.parse_list() {
2744 Some(list) => lists.push(list),
2745 None => break,
2746 }
2747 }
2748
2749 ZshProgram { lists }
2750 }
2751
2752 fn parse_list(&mut self) -> Option<ZshList> {
2754 let sublist = self.parse_sublist()?;
2755
2756 let flags = match self.lexer.tok {
2757 LexTok::Amper => {
2758 self.lexer.zshlex();
2759 ListFlags {
2760 async_: true,
2761 disown: false,
2762 }
2763 }
2764 LexTok::Amperbang => {
2765 self.lexer.zshlex();
2766 ListFlags {
2767 async_: true,
2768 disown: true,
2769 }
2770 }
2771 LexTok::Seper | LexTok::Semi | LexTok::Newlin => {
2772 self.lexer.zshlex();
2773 ListFlags::default()
2774 }
2775 _ => ListFlags::default(),
2776 };
2777
2778 Some(ZshList { sublist, flags })
2779 }
2780
2781 fn parse_sublist(&mut self) -> Option<ZshSublist> {
2783 self.recursion_depth += 1;
2784 if self.check_recursion() {
2785 self.error("parse_sublist: max recursion depth exceeded");
2786 self.recursion_depth -= 1;
2787 return None;
2788 }
2789
2790 let mut flags = SublistFlags::default();
2791
2792 if self.lexer.tok == LexTok::Coproc {
2794 flags.coproc = true;
2795 self.lexer.zshlex();
2796 } else if self.lexer.tok == LexTok::Bang {
2797 flags.not = true;
2798 self.lexer.zshlex();
2799 }
2800
2801 let pipe = match self.parse_pipe() {
2802 Some(p) => p,
2803 None => {
2804 self.recursion_depth -= 1;
2805 return None;
2806 }
2807 };
2808
2809 let next = match self.lexer.tok {
2811 LexTok::Damper => {
2812 self.lexer.zshlex();
2813 self.skip_separators();
2814 self.parse_sublist().map(|s| (SublistOp::And, Box::new(s)))
2815 }
2816 LexTok::Dbar => {
2817 self.lexer.zshlex();
2818 self.skip_separators();
2819 self.parse_sublist().map(|s| (SublistOp::Or, Box::new(s)))
2820 }
2821 _ => None,
2822 };
2823
2824 self.recursion_depth -= 1;
2825 Some(ZshSublist { pipe, next, flags })
2826 }
2827
2828 fn parse_pipe(&mut self) -> Option<ZshPipe> {
2830 self.recursion_depth += 1;
2831 if self.check_recursion() {
2832 self.error("parse_pipe: max recursion depth exceeded");
2833 self.recursion_depth -= 1;
2834 return None;
2835 }
2836
2837 let lineno = self.lexer.toklineno;
2838 let cmd = match self.parse_cmd() {
2839 Some(c) => c,
2840 None => {
2841 self.recursion_depth -= 1;
2842 return None;
2843 }
2844 };
2845
2846 let next = match self.lexer.tok {
2848 LexTok::Bar | LexTok::Baramp => {
2849 let _merge_stderr = self.lexer.tok == LexTok::Baramp;
2850 self.lexer.zshlex();
2851 self.skip_separators();
2852 self.parse_pipe().map(Box::new)
2853 }
2854 _ => None,
2855 };
2856
2857 self.recursion_depth -= 1;
2858 Some(ZshPipe { cmd, next, lineno })
2859 }
2860
2861 fn parse_cmd(&mut self) -> Option<ZshCommand> {
2863 let mut redirs = Vec::new();
2865 while self.lexer.tok.is_redirop() {
2866 if let Some(redir) = self.parse_redir() {
2867 redirs.push(redir);
2868 }
2869 }
2870
2871 let cmd = match self.lexer.tok {
2872 LexTok::For | LexTok::Foreach => self.parse_for(),
2873 LexTok::Select => self.parse_select(),
2874 LexTok::Case => self.parse_case(),
2875 LexTok::If => self.parse_if(),
2876 LexTok::While => self.parse_while(false),
2877 LexTok::Until => self.parse_while(true),
2878 LexTok::Repeat => self.parse_repeat(),
2879 LexTok::Inpar => self.parse_subsh(),
2880 LexTok::Inbrace => self.parse_cursh(),
2881 LexTok::Func => self.parse_funcdef(),
2882 LexTok::Dinbrack => self.parse_cond(),
2883 LexTok::Dinpar => self.parse_arith(),
2884 LexTok::Time => self.parse_time(),
2885 _ => self.parse_simple(redirs),
2886 };
2887
2888 if cmd.is_some() {
2890 while self.lexer.tok.is_redirop() {
2891 if let Some(_redir) = self.parse_redir() {
2892 }
2895 }
2896 }
2897
2898 cmd
2899 }
2900
2901 fn parse_simple(&mut self, mut redirs: Vec<ZshRedir>) -> Option<ZshCommand> {
2903 let mut assigns = Vec::new();
2904 let mut words = Vec::new();
2905 const MAX_ITERATIONS: usize = 10_000;
2906 let mut iterations = 0;
2907
2908 while self.lexer.tok == LexTok::Envstring || self.lexer.tok == LexTok::Envarray {
2910 iterations += 1;
2911 if iterations > MAX_ITERATIONS {
2912 self.error("parse_simple: exceeded max iterations in assignments");
2913 return None;
2914 }
2915 if let Some(assign) = self.parse_assign() {
2916 assigns.push(assign);
2917 }
2918 self.lexer.zshlex();
2919 }
2920
2921 loop {
2923 iterations += 1;
2924 if iterations > MAX_ITERATIONS {
2925 self.error("parse_simple: exceeded max iterations");
2926 return None;
2927 }
2928 match self.lexer.tok {
2929 LexTok::String | LexTok::Typeset => {
2930 let s = self.lexer.tokstr.clone();
2931 if let Some(s) = s {
2932 words.push(s);
2933 }
2934 self.lexer.zshlex();
2935 if words.len() == 1 && self.peek_inoutpar() {
2937 return self.parse_inline_funcdef(words.pop().unwrap());
2938 }
2939 }
2940 _ if self.lexer.tok.is_redirop() => {
2941 match self.parse_redir() {
2942 Some(redir) => redirs.push(redir),
2943 None => break, }
2945 }
2946 LexTok::Inoutpar if !words.is_empty() => {
2947 return self.parse_inline_funcdef(words.pop().unwrap());
2949 }
2950 _ => break,
2951 }
2952 }
2953
2954 if assigns.is_empty() && words.is_empty() && redirs.is_empty() {
2955 return None;
2956 }
2957
2958 Some(ZshCommand::Simple(ZshSimple {
2959 assigns,
2960 words,
2961 redirs,
2962 }))
2963 }
2964
2965 fn parse_assign(&mut self) -> Option<ZshAssign> {
2967 use crate::tokens::char_tokens;
2968
2969 let tokstr = self.lexer.tokstr.as_ref()?;
2970
2971 let (name, value_str, append) = if let Some(pos) = tokstr.find(char_tokens::EQUALS) {
2974 let name_part = &tokstr[..pos];
2975 let (name, append) = if name_part.ends_with('+') {
2976 (&name_part[..name_part.len() - 1], true)
2977 } else {
2978 (name_part, false)
2979 };
2980 (
2981 name.to_string(),
2982 tokstr[pos + char_tokens::EQUALS.len_utf8()..].to_string(),
2983 append,
2984 )
2985 } else if let Some(pos) = tokstr.find('=') {
2986 let name_part = &tokstr[..pos];
2988 let (name, append) = if name_part.ends_with('+') {
2989 (&name_part[..name_part.len() - 1], true)
2990 } else {
2991 (name_part, false)
2992 };
2993 (name.to_string(), tokstr[pos + 1..].to_string(), append)
2994 } else {
2995 return None;
2996 };
2997
2998 let value = if self.lexer.tok == LexTok::Envarray {
2999 let mut elements = Vec::new();
3001 self.lexer.zshlex(); let mut arr_iters = 0;
3004 const MAX_ARRAY_ELEMENTS: usize = 10_000;
3005 while matches!(
3006 self.lexer.tok,
3007 LexTok::String | LexTok::Seper | LexTok::Newlin
3008 ) {
3009 arr_iters += 1;
3010 if arr_iters > MAX_ARRAY_ELEMENTS {
3011 self.error("array assignment exceeded maximum elements");
3012 break;
3013 }
3014 if self.lexer.tok == LexTok::String {
3015 if let Some(ref s) = self.lexer.tokstr {
3016 elements.push(s.clone());
3017 }
3018 }
3019 self.lexer.zshlex();
3020 }
3021
3022 if self.lexer.tok == LexTok::Outpar {
3024 self.lexer.zshlex();
3025 }
3026
3027 ZshAssignValue::Array(elements)
3028 } else {
3029 ZshAssignValue::Scalar(value_str)
3030 };
3031
3032 Some(ZshAssign {
3033 name,
3034 value,
3035 append,
3036 })
3037 }
3038
3039 fn parse_redir(&mut self) -> Option<ZshRedir> {
3041 let rtype = match self.lexer.tok {
3042 LexTok::Outang => RedirType::Write,
3043 LexTok::Outangbang => RedirType::Writenow,
3044 LexTok::Doutang => RedirType::Append,
3045 LexTok::Doutangbang => RedirType::Appendnow,
3046 LexTok::Inang => RedirType::Read,
3047 LexTok::Inoutang => RedirType::ReadWrite,
3048 LexTok::Dinang => RedirType::Heredoc,
3049 LexTok::Dinangdash => RedirType::HeredocDash,
3050 LexTok::Trinang => RedirType::Herestr,
3051 LexTok::Inangamp => RedirType::MergeIn,
3052 LexTok::Outangamp => RedirType::MergeOut,
3053 LexTok::Ampoutang => RedirType::ErrWrite,
3054 LexTok::Outangampbang => RedirType::ErrWritenow,
3055 LexTok::Doutangamp => RedirType::ErrAppend,
3056 LexTok::Doutangampbang => RedirType::ErrAppendnow,
3057 _ => return None,
3058 };
3059
3060 let fd = if self.lexer.tokfd >= 0 {
3061 self.lexer.tokfd
3062 } else if matches!(
3063 rtype,
3064 RedirType::Read
3065 | RedirType::ReadWrite
3066 | RedirType::MergeIn
3067 | RedirType::Heredoc
3068 | RedirType::HeredocDash
3069 | RedirType::Herestr
3070 ) {
3071 0
3072 } else {
3073 1
3074 };
3075
3076 self.lexer.zshlex();
3077
3078 let name = match self.lexer.tok {
3079 LexTok::String | LexTok::Envstring => {
3080 let n = self.lexer.tokstr.clone().unwrap_or_default();
3081 self.lexer.zshlex();
3082 n
3083 }
3084 _ => {
3085 self.error("expected word after redirection");
3086 return None;
3087 }
3088 };
3089
3090 let heredoc = if matches!(rtype, RedirType::Heredoc | RedirType::HeredocDash) {
3092 None } else {
3095 None
3096 };
3097
3098 Some(ZshRedir {
3099 rtype,
3100 fd,
3101 name,
3102 heredoc,
3103 varid: None,
3104 })
3105 }
3106
3107 fn parse_for(&mut self) -> Option<ZshCommand> {
3109 let is_foreach = self.lexer.tok == LexTok::Foreach;
3110 self.lexer.zshlex();
3111
3112 if self.lexer.tok == LexTok::Dinpar {
3114 return self.parse_for_cstyle();
3115 }
3116
3117 let var = match self.lexer.tok {
3119 LexTok::String => {
3120 let v = self.lexer.tokstr.clone().unwrap_or_default();
3121 self.lexer.zshlex();
3122 v
3123 }
3124 _ => {
3125 self.error("expected variable name in for");
3126 return None;
3127 }
3128 };
3129
3130 self.skip_separators();
3132
3133 let list = if self.lexer.tok == LexTok::String {
3135 let s = self.lexer.tokstr.as_ref();
3136 if s.map(|s| s == "in").unwrap_or(false) {
3137 self.lexer.zshlex();
3138 let mut words = Vec::new();
3139 let mut word_count = 0;
3140 while self.lexer.tok == LexTok::String {
3141 word_count += 1;
3142 if word_count > 500 || self.check_limit() {
3143 self.error("for: too many words");
3144 return None;
3145 }
3146 if let Some(ref s) = self.lexer.tokstr {
3147 words.push(s.clone());
3148 }
3149 self.lexer.zshlex();
3150 }
3151 ForList::Words(words)
3152 } else {
3153 ForList::Positional
3154 }
3155 } else if self.lexer.tok == LexTok::Inpar {
3156 self.lexer.zshlex();
3158 let mut words = Vec::new();
3159 let mut word_count = 0;
3160 while self.lexer.tok == LexTok::String || self.lexer.tok == LexTok::Seper {
3161 word_count += 1;
3162 if word_count > 500 || self.check_limit() {
3163 self.error("for: too many words in parens");
3164 return None;
3165 }
3166 if self.lexer.tok == LexTok::String {
3167 if let Some(ref s) = self.lexer.tokstr {
3168 words.push(s.clone());
3169 }
3170 }
3171 self.lexer.zshlex();
3172 }
3173 if self.lexer.tok == LexTok::Outpar {
3174 self.lexer.zshlex();
3175 }
3176 ForList::Words(words)
3177 } else {
3178 ForList::Positional
3179 };
3180
3181 self.skip_separators();
3183
3184 let body = self.parse_loop_body(is_foreach)?;
3186
3187 Some(ZshCommand::For(ZshFor {
3188 var,
3189 list,
3190 body: Box::new(body),
3191 }))
3192 }
3193
3194 fn parse_for_cstyle(&mut self) -> Option<ZshCommand> {
3196 self.lexer.zshlex(); if self.lexer.tok != LexTok::Dinpar {
3206 self.error("expected init expression in for ((");
3207 return None;
3208 }
3209 let init = self.lexer.tokstr.clone().unwrap_or_default();
3210
3211 self.lexer.zshlex(); if self.lexer.tok != LexTok::Dinpar {
3214 self.error("expected condition in for ((");
3215 return None;
3216 }
3217 let cond = self.lexer.tokstr.clone().unwrap_or_default();
3218
3219 self.lexer.zshlex(); if self.lexer.tok != LexTok::Doutpar {
3222 self.error("expected )) in for");
3223 return None;
3224 }
3225 let step = self.lexer.tokstr.clone().unwrap_or_default();
3226
3227 self.lexer.zshlex(); self.skip_separators();
3230 let body = self.parse_loop_body(false)?;
3231
3232 Some(ZshCommand::For(ZshFor {
3233 var: String::new(),
3234 list: ForList::CStyle { init, cond, step },
3235 body: Box::new(body),
3236 }))
3237 }
3238
3239 fn parse_select(&mut self) -> Option<ZshCommand> {
3241 self.parse_for()
3242 }
3243
3244 fn parse_case(&mut self) -> Option<ZshCommand> {
3246 self.lexer.zshlex(); let word = match self.lexer.tok {
3249 LexTok::String => {
3250 let w = self.lexer.tokstr.clone().unwrap_or_default();
3251 self.lexer.zshlex();
3252 w
3253 }
3254 _ => {
3255 self.error("expected word after case");
3256 return None;
3257 }
3258 };
3259
3260 self.skip_separators();
3261
3262 let use_brace = self.lexer.tok == LexTok::Inbrace;
3264 if self.lexer.tok == LexTok::String {
3265 let s = self.lexer.tokstr.as_ref();
3266 if s.map(|s| s != "in").unwrap_or(true) {
3267 self.error("expected 'in' in case");
3268 return None;
3269 }
3270 } else if !use_brace {
3271 self.error("expected 'in' or '{' in case");
3272 return None;
3273 }
3274 self.lexer.zshlex();
3275
3276 let mut arms = Vec::new();
3277 const MAX_ARMS: usize = 10_000;
3278
3279 loop {
3280 if arms.len() > MAX_ARMS {
3281 self.error("parse_case: too many arms");
3282 break;
3283 }
3284
3285 self.lexer.incasepat = 1;
3288
3289 self.skip_separators();
3290
3291 let is_esac = self.lexer.tok == LexTok::Esac
3294 || (self.lexer.tok == LexTok::String
3295 && self
3296 .lexer
3297 .tokstr
3298 .as_ref()
3299 .map(|s| s == "esac")
3300 .unwrap_or(false));
3301 if (use_brace && self.lexer.tok == LexTok::Outbrace) || (!use_brace && is_esac) {
3302 self.lexer.incasepat = 0;
3303 self.lexer.zshlex();
3304 break;
3305 }
3306
3307 if self.lexer.tok == LexTok::Endinput || self.lexer.tok == LexTok::Lexerr {
3309 self.lexer.incasepat = 0;
3310 break;
3311 }
3312
3313 if self.lexer.tok == LexTok::Inpar {
3315 self.lexer.zshlex();
3316 }
3317
3318 let mut patterns = Vec::new();
3320 let mut pattern_iterations = 0;
3321 loop {
3322 pattern_iterations += 1;
3323 if pattern_iterations > 1000 {
3324 self.error("parse_case: too many pattern iterations");
3325 self.lexer.incasepat = 0;
3326 return None;
3327 }
3328
3329 if self.lexer.tok == LexTok::String {
3330 let s = self.lexer.tokstr.as_ref();
3331 if s.map(|s| s == "esac").unwrap_or(false) {
3332 break;
3333 }
3334 patterns.push(self.lexer.tokstr.clone().unwrap_or_default());
3335 self.lexer.incasepat = 2;
3337 self.lexer.zshlex();
3338 } else if self.lexer.tok != LexTok::Bar {
3339 break;
3340 }
3341
3342 if self.lexer.tok == LexTok::Bar {
3343 self.lexer.incasepat = 1;
3345 self.lexer.zshlex();
3346 } else {
3347 break;
3348 }
3349 }
3350 self.lexer.incasepat = 0;
3351
3352 if self.lexer.tok != LexTok::Outpar {
3354 self.error("expected ')' in case pattern");
3355 return None;
3356 }
3357 self.lexer.zshlex();
3358
3359 let body = self.parse_program();
3361
3362 let terminator = match self.lexer.tok {
3364 LexTok::Dsemi => {
3365 self.lexer.zshlex();
3366 CaseTerm::Break
3367 }
3368 LexTok::Semiamp => {
3369 self.lexer.zshlex();
3370 CaseTerm::Continue
3371 }
3372 LexTok::Semibar => {
3373 self.lexer.zshlex();
3374 CaseTerm::TestNext
3375 }
3376 _ => CaseTerm::Break,
3377 };
3378
3379 if !patterns.is_empty() {
3380 arms.push(CaseArm {
3381 patterns,
3382 body,
3383 terminator,
3384 });
3385 }
3386 }
3387
3388 Some(ZshCommand::Case(ZshCase { word, arms }))
3389 }
3390
3391 fn parse_if(&mut self) -> Option<ZshCommand> {
3393 self.lexer.zshlex(); let cond = Box::new(self.parse_program_until(Some(&[LexTok::Then, LexTok::Inbrace])));
3397
3398 self.skip_separators();
3399
3400 let use_brace = self.lexer.tok == LexTok::Inbrace;
3402 if self.lexer.tok != LexTok::Then && !use_brace {
3403 self.error("expected 'then' or '{' after if condition");
3404 return None;
3405 }
3406 self.lexer.zshlex();
3407
3408 let then = if use_brace {
3410 let body = self.parse_program_until(Some(&[LexTok::Outbrace]));
3411 if self.lexer.tok == LexTok::Outbrace {
3412 self.lexer.zshlex();
3413 }
3414 Box::new(body)
3415 } else {
3416 Box::new(self.parse_program_until(Some(&[LexTok::Else, LexTok::Elif, LexTok::Fi])))
3417 };
3418
3419 let mut elif = Vec::new();
3421 let mut else_ = None;
3422
3423 if !use_brace {
3424 loop {
3425 self.skip_separators();
3426
3427 match self.lexer.tok {
3428 LexTok::Elif => {
3429 self.lexer.zshlex();
3430 let econd =
3432 self.parse_program_until(Some(&[LexTok::Then, LexTok::Inbrace]));
3433 self.skip_separators();
3434
3435 let elif_use_brace = self.lexer.tok == LexTok::Inbrace;
3436 if self.lexer.tok != LexTok::Then && !elif_use_brace {
3437 self.error("expected 'then' after elif");
3438 return None;
3439 }
3440 self.lexer.zshlex();
3441
3442 let ebody = if elif_use_brace {
3444 let body = self.parse_program_until(Some(&[LexTok::Outbrace]));
3445 if self.lexer.tok == LexTok::Outbrace {
3446 self.lexer.zshlex();
3447 }
3448 body
3449 } else {
3450 self.parse_program_until(Some(&[
3451 LexTok::Else,
3452 LexTok::Elif,
3453 LexTok::Fi,
3454 ]))
3455 };
3456
3457 elif.push((econd, ebody));
3458 }
3459 LexTok::Else => {
3460 self.lexer.zshlex();
3461 self.skip_separators();
3462
3463 let else_use_brace = self.lexer.tok == LexTok::Inbrace;
3464 if else_use_brace {
3465 self.lexer.zshlex();
3466 }
3467
3468 else_ = Some(Box::new(if else_use_brace {
3470 let body = self.parse_program_until(Some(&[LexTok::Outbrace]));
3471 if self.lexer.tok == LexTok::Outbrace {
3472 self.lexer.zshlex();
3473 }
3474 body
3475 } else {
3476 self.parse_program_until(Some(&[LexTok::Fi]))
3477 }));
3478
3479 if !else_use_brace && self.lexer.tok == LexTok::Fi {
3481 self.lexer.zshlex();
3482 }
3483 break;
3484 }
3485 LexTok::Fi => {
3486 self.lexer.zshlex();
3487 break;
3488 }
3489 _ => break,
3490 }
3491 }
3492 }
3493
3494 Some(ZshCommand::If(ZshIf {
3495 cond,
3496 then,
3497 elif,
3498 else_,
3499 }))
3500 }
3501
3502 fn parse_while(&mut self, until: bool) -> Option<ZshCommand> {
3504 self.lexer.zshlex(); let cond = Box::new(self.parse_program());
3507
3508 self.skip_separators();
3509 let body = self.parse_loop_body(false)?;
3510
3511 Some(ZshCommand::While(ZshWhile {
3512 cond,
3513 body: Box::new(body),
3514 until,
3515 }))
3516 }
3517
3518 fn parse_repeat(&mut self) -> Option<ZshCommand> {
3520 self.lexer.zshlex(); let count = match self.lexer.tok {
3523 LexTok::String => {
3524 let c = self.lexer.tokstr.clone().unwrap_or_default();
3525 self.lexer.zshlex();
3526 c
3527 }
3528 _ => {
3529 self.error("expected count after repeat");
3530 return None;
3531 }
3532 };
3533
3534 self.skip_separators();
3535 let body = self.parse_loop_body(false)?;
3536
3537 Some(ZshCommand::Repeat(ZshRepeat {
3538 count,
3539 body: Box::new(body),
3540 }))
3541 }
3542
3543 fn parse_loop_body(&mut self, foreach_style: bool) -> Option<ZshProgram> {
3545 if self.lexer.tok == LexTok::Doloop {
3546 self.lexer.zshlex();
3547 let body = self.parse_program();
3548 if self.lexer.tok == LexTok::Done {
3549 self.lexer.zshlex();
3550 }
3551 Some(body)
3552 } else if self.lexer.tok == LexTok::Inbrace {
3553 self.lexer.zshlex();
3554 let body = self.parse_program();
3555 if self.lexer.tok == LexTok::Outbrace {
3556 self.lexer.zshlex();
3557 }
3558 Some(body)
3559 } else if foreach_style {
3560 let body = self.parse_program();
3562 if self.lexer.tok == LexTok::Zend {
3563 self.lexer.zshlex();
3564 }
3565 Some(body)
3566 } else {
3567 match self.parse_list() {
3569 Some(list) => Some(ZshProgram { lists: vec![list] }),
3570 None => None,
3571 }
3572 }
3573 }
3574
3575 fn parse_subsh(&mut self) -> Option<ZshCommand> {
3577 self.lexer.zshlex(); let prog = self.parse_program();
3579 if self.lexer.tok == LexTok::Outpar {
3580 self.lexer.zshlex();
3581 }
3582 Some(ZshCommand::Subsh(Box::new(prog)))
3583 }
3584
3585 fn parse_cursh(&mut self) -> Option<ZshCommand> {
3587 self.lexer.zshlex(); let prog = self.parse_program();
3589
3590 if self.lexer.tok == LexTok::Outbrace {
3592 self.lexer.zshlex();
3593
3594 if self.lexer.tok == LexTok::String {
3596 let s = self.lexer.tokstr.as_ref();
3597 if s.map(|s| s == "always").unwrap_or(false) {
3598 self.lexer.zshlex();
3599 self.skip_separators();
3600
3601 if self.lexer.tok == LexTok::Inbrace {
3602 self.lexer.zshlex();
3603 let always = self.parse_program();
3604 if self.lexer.tok == LexTok::Outbrace {
3605 self.lexer.zshlex();
3606 }
3607 return Some(ZshCommand::Try(ZshTry {
3608 try_block: Box::new(prog),
3609 always: Box::new(always),
3610 }));
3611 }
3612 }
3613 }
3614 }
3615
3616 Some(ZshCommand::Cursh(Box::new(prog)))
3617 }
3618
3619 fn parse_funcdef(&mut self) -> Option<ZshCommand> {
3621 self.lexer.zshlex(); let mut names = Vec::new();
3624 let mut tracing = false;
3625
3626 loop {
3628 match self.lexer.tok {
3629 LexTok::String => {
3630 let s = self.lexer.tokstr.as_ref()?;
3631 if s.starts_with('-') {
3632 if s.contains('T') {
3633 tracing = true;
3634 }
3635 self.lexer.zshlex();
3636 continue;
3637 }
3638 names.push(s.clone());
3639 self.lexer.zshlex();
3640 }
3641 LexTok::Inbrace | LexTok::Inoutpar | LexTok::Seper | LexTok::Newlin => break,
3642 _ => break,
3643 }
3644 }
3645
3646 if self.lexer.tok == LexTok::Inoutpar {
3648 self.lexer.zshlex();
3649 }
3650
3651 self.skip_separators();
3652
3653 if self.lexer.tok == LexTok::Inbrace {
3655 self.lexer.zshlex();
3656 let body = self.parse_program();
3657 if self.lexer.tok == LexTok::Outbrace {
3658 self.lexer.zshlex();
3659 }
3660 Some(ZshCommand::FuncDef(ZshFuncDef {
3661 names,
3662 body: Box::new(body),
3663 tracing,
3664 }))
3665 } else {
3666 match self.parse_list() {
3668 Some(list) => Some(ZshCommand::FuncDef(ZshFuncDef {
3669 names,
3670 body: Box::new(ZshProgram { lists: vec![list] }),
3671 tracing,
3672 })),
3673 None => None,
3674 }
3675 }
3676 }
3677
3678 fn parse_inline_funcdef(&mut self, name: String) -> Option<ZshCommand> {
3680 if self.lexer.tok == LexTok::Inoutpar {
3682 self.lexer.zshlex();
3683 }
3684
3685 self.skip_separators();
3686
3687 if self.lexer.tok == LexTok::Inbrace {
3689 self.lexer.zshlex();
3690 let body = self.parse_program();
3691 if self.lexer.tok == LexTok::Outbrace {
3692 self.lexer.zshlex();
3693 }
3694 Some(ZshCommand::FuncDef(ZshFuncDef {
3695 names: vec![name],
3696 body: Box::new(body),
3697 tracing: false,
3698 }))
3699 } else {
3700 match self.parse_cmd() {
3701 Some(cmd) => {
3702 let list = ZshList {
3703 sublist: ZshSublist {
3704 pipe: ZshPipe {
3705 cmd,
3706 next: None,
3707 lineno: self.lexer.lineno,
3708 },
3709 next: None,
3710 flags: SublistFlags::default(),
3711 },
3712 flags: ListFlags::default(),
3713 };
3714 Some(ZshCommand::FuncDef(ZshFuncDef {
3715 names: vec![name],
3716 body: Box::new(ZshProgram { lists: vec![list] }),
3717 tracing: false,
3718 }))
3719 }
3720 None => None,
3721 }
3722 }
3723 }
3724
3725 fn parse_cond(&mut self) -> Option<ZshCommand> {
3727 self.lexer.zshlex(); let cond = self.parse_cond_expr();
3729
3730 if self.lexer.tok == LexTok::Doutbrack {
3731 self.lexer.zshlex();
3732 }
3733
3734 cond.map(ZshCommand::Cond)
3735 }
3736
3737 fn parse_cond_expr(&mut self) -> Option<ZshCond> {
3739 self.parse_cond_or()
3740 }
3741
3742 fn parse_cond_or(&mut self) -> Option<ZshCond> {
3743 self.recursion_depth += 1;
3744 if self.check_recursion() {
3745 self.error("parse_cond_or: max recursion depth exceeded");
3746 self.recursion_depth -= 1;
3747 return None;
3748 }
3749
3750 let left = match self.parse_cond_and() {
3751 Some(l) => l,
3752 None => {
3753 self.recursion_depth -= 1;
3754 return None;
3755 }
3756 };
3757
3758 self.skip_cond_separators();
3759
3760 let result = if self.lexer.tok == LexTok::Dbar {
3761 self.lexer.zshlex();
3762 self.skip_cond_separators();
3763 match self.parse_cond_or() {
3764 Some(right) => Some(ZshCond::Or(Box::new(left), Box::new(right))),
3765 None => None,
3766 }
3767 } else {
3768 Some(left)
3769 };
3770
3771 self.recursion_depth -= 1;
3772 result
3773 }
3774
3775 fn parse_cond_and(&mut self) -> Option<ZshCond> {
3776 self.recursion_depth += 1;
3777 if self.check_recursion() {
3778 self.error("parse_cond_and: max recursion depth exceeded");
3779 self.recursion_depth -= 1;
3780 return None;
3781 }
3782
3783 let left = match self.parse_cond_not() {
3784 Some(l) => l,
3785 None => {
3786 self.recursion_depth -= 1;
3787 return None;
3788 }
3789 };
3790
3791 self.skip_cond_separators();
3792
3793 let result = if self.lexer.tok == LexTok::Damper {
3794 self.lexer.zshlex();
3795 self.skip_cond_separators();
3796 match self.parse_cond_and() {
3797 Some(right) => Some(ZshCond::And(Box::new(left), Box::new(right))),
3798 None => None,
3799 }
3800 } else {
3801 Some(left)
3802 };
3803
3804 self.recursion_depth -= 1;
3805 result
3806 }
3807
3808 fn parse_cond_not(&mut self) -> Option<ZshCond> {
3809 self.recursion_depth += 1;
3810 if self.check_recursion() {
3811 self.error("parse_cond_not: max recursion depth exceeded");
3812 self.recursion_depth -= 1;
3813 return None;
3814 }
3815
3816 self.skip_cond_separators();
3817
3818 let is_not = self.lexer.tok == LexTok::Bang
3820 || (self.lexer.tok == LexTok::String
3821 && self
3822 .lexer
3823 .tokstr
3824 .as_ref()
3825 .map(|s| s == "!")
3826 .unwrap_or(false));
3827 if is_not {
3828 self.lexer.zshlex();
3829 let inner = match self.parse_cond_not() {
3830 Some(i) => i,
3831 None => {
3832 self.recursion_depth -= 1;
3833 return None;
3834 }
3835 };
3836 self.recursion_depth -= 1;
3837 return Some(ZshCond::Not(Box::new(inner)));
3838 }
3839
3840 if self.lexer.tok == LexTok::Inpar {
3841 self.lexer.zshlex();
3842 self.skip_cond_separators();
3843 let inner = match self.parse_cond_expr() {
3844 Some(i) => i,
3845 None => {
3846 self.recursion_depth -= 1;
3847 return None;
3848 }
3849 };
3850 self.skip_cond_separators();
3851 if self.lexer.tok == LexTok::Outpar {
3852 self.lexer.zshlex();
3853 }
3854 self.recursion_depth -= 1;
3855 return Some(inner);
3856 }
3857
3858 let result = self.parse_cond_primary();
3859 self.recursion_depth -= 1;
3860 result
3861 }
3862
3863 fn parse_cond_primary(&mut self) -> Option<ZshCond> {
3864 let s1 = match self.lexer.tok {
3865 LexTok::String => {
3866 let s = self.lexer.tokstr.clone().unwrap_or_default();
3867 self.lexer.zshlex();
3868 s
3869 }
3870 _ => return None,
3871 };
3872
3873 self.skip_cond_separators();
3874
3875 if s1.starts_with('-') && s1.len() == 2 {
3877 let s2 = match self.lexer.tok {
3878 LexTok::String => {
3879 let s = self.lexer.tokstr.clone().unwrap_or_default();
3880 self.lexer.zshlex();
3881 s
3882 }
3883 _ => return Some(ZshCond::Unary("-n".to_string(), s1)),
3884 };
3885 return Some(ZshCond::Unary(s1, s2));
3886 }
3887
3888 let op = match self.lexer.tok {
3890 LexTok::String => {
3891 let s = self.lexer.tokstr.clone().unwrap_or_default();
3892 self.lexer.zshlex();
3893 s
3894 }
3895 LexTok::Inang => {
3896 self.lexer.zshlex();
3897 "<".to_string()
3898 }
3899 LexTok::Outang => {
3900 self.lexer.zshlex();
3901 ">".to_string()
3902 }
3903 _ => return Some(ZshCond::Unary("-n".to_string(), s1)),
3904 };
3905
3906 self.skip_cond_separators();
3907
3908 let s2 = match self.lexer.tok {
3909 LexTok::String => {
3910 let s = self.lexer.tokstr.clone().unwrap_or_default();
3911 self.lexer.zshlex();
3912 s
3913 }
3914 _ => return Some(ZshCond::Binary(s1, op, String::new())),
3915 };
3916
3917 if op == "=~" {
3918 Some(ZshCond::Regex(s1, s2))
3919 } else {
3920 Some(ZshCond::Binary(s1, op, s2))
3921 }
3922 }
3923
3924 fn skip_cond_separators(&mut self) {
3925 while self.lexer.tok == LexTok::Seper && {
3926 let s = self.lexer.tokstr.as_ref();
3927 s.map(|s| !s.contains(';')).unwrap_or(true)
3928 } {
3929 self.lexer.zshlex();
3930 }
3931 }
3932
3933 fn parse_arith(&mut self) -> Option<ZshCommand> {
3935 let expr = self.lexer.tokstr.clone().unwrap_or_default();
3936 self.lexer.zshlex();
3937 Some(ZshCommand::Arith(expr))
3938 }
3939
3940 fn parse_time(&mut self) -> Option<ZshCommand> {
3942 self.lexer.zshlex(); if self.lexer.tok == LexTok::Seper
3946 || self.lexer.tok == LexTok::Newlin
3947 || self.lexer.tok == LexTok::Endinput
3948 {
3949 Some(ZshCommand::Time(None))
3950 } else {
3951 let sublist = self.parse_sublist();
3952 Some(ZshCommand::Time(sublist.map(Box::new)))
3953 }
3954 }
3955
3956 fn peek_inoutpar(&mut self) -> bool {
3958 self.lexer.tok == LexTok::Inoutpar
3959 }
3960
3961 fn skip_separators(&mut self) {
3963 let mut iterations = 0;
3964 while self.lexer.tok == LexTok::Seper || self.lexer.tok == LexTok::Newlin {
3965 iterations += 1;
3966 if iterations > 100_000 {
3967 self.error("skip_separators: too many iterations");
3968 return;
3969 }
3970 self.lexer.zshlex();
3971 }
3972 }
3973
3974 fn error(&mut self, msg: &str) {
3976 self.errors.push(ParseError {
3977 message: msg.to_string(),
3978 line: self.lexer.lineno,
3979 });
3980 }
3981}
3982
3983#[cfg(test)]
3984mod tests {
3985 use super::*;
3986
3987 fn parse(input: &str) -> Result<ZshProgram, Vec<ParseError>> {
3988 let mut parser = ZshParser::new(input);
3989 parser.parse()
3990 }
3991
3992 #[test]
3993 fn test_simple_command() {
3994 let prog = parse("echo hello world").unwrap();
3995 assert_eq!(prog.lists.len(), 1);
3996 match &prog.lists[0].sublist.pipe.cmd {
3997 ZshCommand::Simple(s) => {
3998 assert_eq!(s.words, vec!["echo", "hello", "world"]);
3999 }
4000 _ => panic!("expected simple command"),
4001 }
4002 }
4003
4004 #[test]
4005 fn test_pipeline() {
4006 let prog = parse("ls | grep foo | wc -l").unwrap();
4007 assert_eq!(prog.lists.len(), 1);
4008
4009 let pipe = &prog.lists[0].sublist.pipe;
4010 assert!(pipe.next.is_some());
4011
4012 let pipe2 = pipe.next.as_ref().unwrap();
4013 assert!(pipe2.next.is_some());
4014 }
4015
4016 #[test]
4017 fn test_and_or() {
4018 let prog = parse("cmd1 && cmd2 || cmd3").unwrap();
4019 let sublist = &prog.lists[0].sublist;
4020
4021 assert!(sublist.next.is_some());
4022 let (op, _) = sublist.next.as_ref().unwrap();
4023 assert_eq!(*op, SublistOp::And);
4024 }
4025
4026 #[test]
4027 fn test_if_then() {
4028 let prog = parse("if test -f foo; then echo yes; fi").unwrap();
4029 match &prog.lists[0].sublist.pipe.cmd {
4030 ZshCommand::If(_) => {}
4031 _ => panic!("expected if command"),
4032 }
4033 }
4034
4035 #[test]
4036 fn test_for_loop() {
4037 let prog = parse("for i in a b c; do echo $i; done").unwrap();
4038 match &prog.lists[0].sublist.pipe.cmd {
4039 ZshCommand::For(f) => {
4040 assert_eq!(f.var, "i");
4041 match &f.list {
4042 ForList::Words(w) => assert_eq!(w, &vec!["a", "b", "c"]),
4043 _ => panic!("expected word list"),
4044 }
4045 }
4046 _ => panic!("expected for command"),
4047 }
4048 }
4049
4050 #[test]
4051 fn test_case() {
4052 let prog = parse("case $x in a) echo a;; b) echo b;; esac").unwrap();
4053 match &prog.lists[0].sublist.pipe.cmd {
4054 ZshCommand::Case(c) => {
4055 assert_eq!(c.arms.len(), 2);
4056 }
4057 _ => panic!("expected case command"),
4058 }
4059 }
4060
4061 #[test]
4062 fn test_function() {
4063 let prog = parse("function foo { }").unwrap();
4065 match &prog.lists[0].sublist.pipe.cmd {
4066 ZshCommand::FuncDef(f) => {
4067 assert_eq!(f.names, vec!["foo"]);
4068 }
4069 _ => panic!(
4070 "expected function, got {:?}",
4071 prog.lists[0].sublist.pipe.cmd
4072 ),
4073 }
4074 }
4075
4076 #[test]
4077 fn test_redirection() {
4078 let prog = parse("echo hello > file.txt").unwrap();
4079 match &prog.lists[0].sublist.pipe.cmd {
4080 ZshCommand::Simple(s) => {
4081 assert_eq!(s.redirs.len(), 1);
4082 assert_eq!(s.redirs[0].rtype, RedirType::Write);
4083 }
4084 _ => panic!("expected simple command"),
4085 }
4086 }
4087
4088 #[test]
4089 fn test_assignment() {
4090 let prog = parse("FOO=bar echo $FOO").unwrap();
4091 match &prog.lists[0].sublist.pipe.cmd {
4092 ZshCommand::Simple(s) => {
4093 assert_eq!(s.assigns.len(), 1);
4094 assert_eq!(s.assigns[0].name, "FOO");
4095 }
4096 _ => panic!("expected simple command"),
4097 }
4098 }
4099
4100 #[test]
4101 fn test_parse_completion_function() {
4102 let input = r#"_2to3_fixes() {
4103 local -a fixes
4104 fixes=( ${${(M)${(f)"$(2to3 --list-fixes 2>/dev/null)"}:#*}//[[:space:]]/} )
4105 (( ${#fixes} )) && _describe -t fixes 'fix' fixes
4106}"#;
4107 let result = parse(input);
4108 assert!(
4109 result.is_ok(),
4110 "Failed to parse completion function: {:?}",
4111 result.err()
4112 );
4113 let prog = result.unwrap();
4114 assert!(
4115 !prog.lists.is_empty(),
4116 "Expected at least one list in program"
4117 );
4118 }
4119
4120 #[test]
4121 fn test_parse_array_with_complex_elements() {
4122 let input = r#"arguments=(
4123 '(- * :)'{-h,--help}'[show this help message and exit]'
4124 {-d,--doctests_only}'[fix up doctests only]'
4125 '*:filename:_files'
4126)"#;
4127 let result = parse(input);
4128 assert!(
4129 result.is_ok(),
4130 "Failed to parse array assignment: {:?}",
4131 result.err()
4132 );
4133 }
4134
4135 #[test]
4136 fn test_parse_full_completion_file() {
4137 let input = r##"#compdef 2to3
4138
4139# zsh completions for '2to3'
4140
4141_2to3_fixes() {
4142 local -a fixes
4143 fixes=( ${${(M)${(f)"$(2to3 --list-fixes 2>/dev/null)"}:#*}//[[:space:]]/} )
4144 (( ${#fixes} )) && _describe -t fixes 'fix' fixes
4145}
4146
4147local -a arguments
4148
4149arguments=(
4150 '(- * :)'{-h,--help}'[show this help message and exit]'
4151 {-d,--doctests_only}'[fix up doctests only]'
4152 {-f,--fix}'[each FIX specifies a transformation; default: all]:fix name:_2to3_fixes'
4153 {-j,--processes}'[run 2to3 concurrently]:number: '
4154 {-x,--nofix}'[prevent a transformation from being run]:fix name:_2to3_fixes'
4155 {-l,--list-fixes}'[list available transformations]'
4156 {-p,--print-function}'[modify the grammar so that print() is a function]'
4157 {-v,--verbose}'[more verbose logging]'
4158 '--no-diffs[do not show diffs of the refactoring]'
4159 {-w,--write}'[write back modified files]'
4160 {-n,--nobackups}'[do not write backups for modified files]'
4161 {-o,--output-dir}'[put output files in this directory instead of overwriting]:directory:_directories'
4162 {-W,--write-unchanged-files}'[also write files even if no changes were required]'
4163 '--add-suffix[append this string to all output filenames]:suffix: '
4164 '*:filename:_files'
4165)
4166
4167_arguments -s -S $arguments
4168"##;
4169 let result = parse(input);
4170 assert!(
4171 result.is_ok(),
4172 "Failed to parse full completion file: {:?}",
4173 result.err()
4174 );
4175 let prog = result.unwrap();
4176 assert!(!prog.lists.is_empty(), "Expected at least one list");
4178 }
4179
4180 #[test]
4181 fn test_parse_logs_sh() {
4182 let input = r#"#!/usr/bin/env bash
4183shopt -s globstar
4184
4185if [[ $(uname) == Darwin ]]; then
4186 tail -f /var/log/**/*.log /var/log/**/*.out | lolcat
4187else
4188 if [[ $ZPWR_DISTRO_NAME == raspbian ]]; then
4189 tail -f /var/log/**/*.log | lolcat
4190 else
4191 printf "Unsupported...\n" >&2
4192 fi
4193fi
4194"#;
4195 let result = parse(input);
4196 assert!(
4197 result.is_ok(),
4198 "Failed to parse logs.sh: {:?}",
4199 result.err()
4200 );
4201 }
4202
4203 #[test]
4204 fn test_parse_case_with_glob() {
4205 let input = r#"case "$ZPWR_OS_TYPE" in
4206 darwin*) open_cmd='open'
4207 ;;
4208 cygwin*) open_cmd='cygstart'
4209 ;;
4210 linux*)
4211 open_cmd='xdg-open'
4212 ;;
4213esac"#;
4214 let result = parse(input);
4215 assert!(
4216 result.is_ok(),
4217 "Failed to parse case with glob: {:?}",
4218 result.err()
4219 );
4220 }
4221
4222 #[test]
4223 fn test_parse_case_with_nested_if() {
4224 let input = r##"function zpwrGetOpenCommand(){
4226 local open_cmd
4227 case "$ZPWR_OS_TYPE" in
4228 darwin*) open_cmd='open' ;;
4229 cygwin*) open_cmd='cygstart' ;;
4230 linux*)
4231 if [[ "$_zpwr_uname_r" != *icrosoft* ]];then
4232 open_cmd='nohup xdg-open'
4233 fi
4234 ;;
4235 esac
4236}"##;
4237 let result = parse(input);
4238 assert!(result.is_ok(), "Failed to parse: {:?}", result.err());
4239 }
4240
4241 #[test]
4242 fn test_parse_zpwr_scripts() {
4243 use std::fs;
4244 use std::path::Path;
4245 use std::sync::mpsc;
4246 use std::thread;
4247 use std::time::{Duration, Instant};
4248
4249 let scripts_dir = Path::new("/Users/wizard/.zpwr/scripts");
4250 if !scripts_dir.exists() {
4251 eprintln!("Skipping test: scripts directory not found");
4252 return;
4253 }
4254
4255 let mut total = 0;
4256 let mut passed = 0;
4257 let mut failed_files = Vec::new();
4258 let mut timeout_files = Vec::new();
4259
4260 for ext in &["sh", "zsh"] {
4261 let pattern = scripts_dir.join(format!("*.{}", ext));
4262 if let Ok(entries) = glob::glob(pattern.to_str().unwrap()) {
4263 for entry in entries.flatten() {
4264 total += 1;
4265 let file_path = entry.display().to_string();
4266 let content = match fs::read_to_string(&entry) {
4267 Ok(c) => c,
4268 Err(e) => {
4269 failed_files.push((file_path, format!("read error: {}", e)));
4270 continue;
4271 }
4272 };
4273
4274 let content_clone = content.clone();
4276 let (tx, rx) = mpsc::channel();
4277 let handle = thread::spawn(move || {
4278 let result = parse(&content_clone);
4279 let _ = tx.send(result);
4280 });
4281
4282 match rx.recv_timeout(Duration::from_secs(2)) {
4283 Ok(Ok(_)) => passed += 1,
4284 Ok(Err(errors)) => {
4285 let first_err = errors
4286 .first()
4287 .map(|e| format!("line {}: {}", e.line, e.message))
4288 .unwrap_or_default();
4289 failed_files.push((file_path, first_err));
4290 }
4291 Err(_) => {
4292 timeout_files.push(file_path);
4293 }
4295 }
4296 }
4297 }
4298 }
4299
4300 eprintln!("\n=== ZPWR Scripts Parse Results ===");
4301 eprintln!("Passed: {}/{}", passed, total);
4302
4303 if !timeout_files.is_empty() {
4304 eprintln!("\nTimeout files (>2s):");
4305 for file in &timeout_files {
4306 eprintln!(" {}", file);
4307 }
4308 }
4309
4310 if !failed_files.is_empty() {
4311 eprintln!("\nFailed files:");
4312 for (file, err) in &failed_files {
4313 eprintln!(" {} - {}", file, err);
4314 }
4315 }
4316
4317 let pass_rate = if total > 0 {
4319 (passed as f64 / total as f64) * 100.0
4320 } else {
4321 0.0
4322 };
4323 eprintln!("Pass rate: {:.1}%", pass_rate);
4324
4325 assert!(pass_rate >= 50.0, "Pass rate too low: {:.1}%", pass_rate);
4327 }
4328
4329 #[test]
4330 #[ignore] fn test_parse_zsh_stdlib_functions() {
4332 use std::fs;
4333 use std::path::Path;
4334 use std::sync::mpsc;
4335 use std::thread;
4336 use std::time::Duration;
4337
4338 let functions_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("test_data/zsh_functions");
4339 if !functions_dir.exists() {
4340 eprintln!(
4341 "Skipping test: zsh_functions directory not found at {:?}",
4342 functions_dir
4343 );
4344 return;
4345 }
4346
4347 let mut total = 0;
4348 let mut passed = 0;
4349 let mut failed_files = Vec::new();
4350 let mut timeout_files = Vec::new();
4351
4352 if let Ok(entries) = fs::read_dir(&functions_dir) {
4353 for entry in entries.flatten() {
4354 let path = entry.path();
4355 if !path.is_file() {
4356 continue;
4357 }
4358
4359 total += 1;
4360 let file_path = path.display().to_string();
4361 let content = match fs::read_to_string(&path) {
4362 Ok(c) => c,
4363 Err(e) => {
4364 failed_files.push((file_path, format!("read error: {}", e)));
4365 continue;
4366 }
4367 };
4368
4369 let content_clone = content.clone();
4371 let (tx, rx) = mpsc::channel();
4372 thread::spawn(move || {
4373 let result = parse(&content_clone);
4374 let _ = tx.send(result);
4375 });
4376
4377 match rx.recv_timeout(Duration::from_secs(2)) {
4378 Ok(Ok(_)) => passed += 1,
4379 Ok(Err(errors)) => {
4380 let first_err = errors
4381 .first()
4382 .map(|e| format!("line {}: {}", e.line, e.message))
4383 .unwrap_or_default();
4384 failed_files.push((file_path, first_err));
4385 }
4386 Err(_) => {
4387 timeout_files.push(file_path);
4388 }
4389 }
4390 }
4391 }
4392
4393 eprintln!("\n=== Zsh Stdlib Functions Parse Results ===");
4394 eprintln!("Passed: {}/{}", passed, total);
4395
4396 if !timeout_files.is_empty() {
4397 eprintln!("\nTimeout files (>2s): {}", timeout_files.len());
4398 for file in timeout_files.iter().take(10) {
4399 eprintln!(" {}", file);
4400 }
4401 if timeout_files.len() > 10 {
4402 eprintln!(" ... and {} more", timeout_files.len() - 10);
4403 }
4404 }
4405
4406 if !failed_files.is_empty() {
4407 eprintln!("\nFailed files: {}", failed_files.len());
4408 for (file, err) in failed_files.iter().take(20) {
4409 let filename = Path::new(file)
4410 .file_name()
4411 .unwrap_or_default()
4412 .to_string_lossy();
4413 eprintln!(" {} - {}", filename, err);
4414 }
4415 if failed_files.len() > 20 {
4416 eprintln!(" ... and {} more", failed_files.len() - 20);
4417 }
4418 }
4419
4420 let pass_rate = if total > 0 {
4421 (passed as f64 / total as f64) * 100.0
4422 } else {
4423 0.0
4424 };
4425 eprintln!("Pass rate: {:.1}%", pass_rate);
4426
4427 assert!(pass_rate >= 50.0, "Pass rate too low: {:.1}%", pass_rate);
4429 }
4430}