From 490ce4571dee189b2f88f6e91ca1fafb8f7c80a2 Mon Sep 17 00:00:00 2001 From: pagedmov Date: Sat, 7 Mar 2026 14:38:07 -0500 Subject: [PATCH] added tests for the parser --- src/parse/lex.rs | 11 +- src/parse/mod.rs | 622 ++++++++++++++++++++++++++++++++++++++++++++++- src/testutil.rs | 101 +++++++- 3 files changed, 721 insertions(+), 13 deletions(-) diff --git a/src/parse/lex.rs b/src/parse/lex.rs index 3fbb0cd..5e0c886 100644 --- a/src/parse/lex.rs +++ b/src/parse/lex.rs @@ -250,6 +250,7 @@ pub struct LexStream { quote_state: QuoteState, brc_grp_depth: usize, brc_grp_start: Option, + case_depth: usize, flags: LexFlags, } @@ -271,7 +272,6 @@ bitflags! { /// The lexer has no more tokens to produce const STALE = 0b0001000000; const EXPECTING_IN = 0b0010000000; - const IN_CASE = 0b0100000000; } } @@ -306,6 +306,7 @@ impl LexStream { quote_state: QuoteState::default(), brc_grp_depth: 0, brc_grp_start: None, + case_depth: 0, } } /// Returns a slice of the source input using the given range @@ -453,7 +454,7 @@ impl LexStream { let mut chars = slice.chars().peekable(); let can_be_subshell = chars.peek() == Some(&'('); - if self.flags.contains(LexFlags::IN_CASE) + if self.case_depth > 0 && let Some(count) = case_pat_lookahead(chars.clone()) { pos += count; @@ -731,7 +732,7 @@ impl LexStream { "case" | "select" | "for" => { new_tk.mark(TkFlags::KEYWORD); self.flags |= LexFlags::EXPECTING_IN; - self.flags |= LexFlags::IN_CASE; + self.case_depth += 1; self.set_next_is_cmd(false); } "in" if self.flags.contains(LexFlags::EXPECTING_IN) => { @@ -739,8 +740,8 @@ impl LexStream { self.flags &= !LexFlags::EXPECTING_IN; } _ if is_keyword(text) => { - if text == "esac" && self.flags.contains(LexFlags::IN_CASE) { - self.flags &= !LexFlags::IN_CASE; + if text == "esac" && self.case_depth > 0 { + self.case_depth -= 1; } new_tk.mark(TkFlags::KEYWORD); } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index b3b3da5..617c829 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -155,7 +155,7 @@ impl Node { Label::new(span).with_color(color).with_message(msg), ) } - fn walk_tree(&mut self, f: &F) { + pub fn walk_tree(&mut self, f: &mut F) { f(self); match self.class { @@ -244,7 +244,7 @@ impl Node { } } pub fn propagate_context(&mut self, ctx: (SpanSource, Label)) { - self.walk_tree(&|nd| nd.context.push_back(ctx.clone())); + self.walk_tree(&mut |nd| nd.context.push_back(ctx.clone())); } pub fn get_span(&self) -> Span { let Some(first_tk) = self.tokens.first() else { @@ -1891,3 +1891,621 @@ where NdRule::Test { cases: _ } => (), } } + +#[cfg(test)] +pub mod tests { + use crate::testutil::{NdKind, get_ast}; + + #[test] + fn parse_hello_world() { + let input = "echo hello world"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_if_statement() { + let input = "if echo foo; then echo bar; fi"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::IfNode, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_pipeline() { + let input = "ls | grep foo | wc -l"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Command, + NdKind::Command, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_conjunction_and() { + let input = "echo foo && echo bar"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Pipeline, + NdKind::Command, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_while_loop() { + let input = "while true; do echo hello; done"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::LoopNode, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_for_loop() { + let input = "for i in a b c; do echo $i; done"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::ForNode, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_case_statement() { + let input = "case foo in bar) echo bar;; baz) echo baz;; esac"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::CaseNode, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_func_def() { + let input = "foo() { echo hello; }"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::FuncDef, + NdKind::BraceGrp, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_assignment() { + let input = "FOO=bar"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Assignment, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_assignment_with_command() { + let input = "FOO=bar echo hello"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Assignment, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_if_elif_else() { + let input = "if true; then echo a; elif false; then echo b; else echo c; fi"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::IfNode, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_brace_group() { + let input = "{ echo hello; echo world; }"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::BraceGrp, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_nested_if_in_while() { + let input = "while true; do if false; then echo no; fi; done"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::LoopNode, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::IfNode, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_test_bracket() { + let input = "[[ -n hello ]]"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Test, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_nested_func_with_if_and_loop() { + let input = "setup() { + for f in a b c; do + if [[ -n $f ]]; then + echo $f + fi + done + }"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::FuncDef, + NdKind::BraceGrp, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::ForNode, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::IfNode, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Test, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_pipeline_with_brace_groups() { + let input = "{ echo foo; echo bar; } | { grep foo; wc -l; }"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::BraceGrp, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::BraceGrp, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_deeply_nested_if() { + let input = "if true; then + if false; then + if true; then + echo deep + fi + fi + fi"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::IfNode, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::IfNode, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::IfNode, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_case_with_multiple_commands() { + let input = "case $1 in + start) + echo starting + run_server + ;; + stop) + echo stopping + kill_server + ;; + *) + echo unknown + ;; + esac"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::CaseNode, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_func_with_case_and_conjunction() { + let input = "dispatch() { + case $1 in + build) + make clean && make all + ;; + test) + make test || echo failed + ;; + esac + }"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::FuncDef, + NdKind::BraceGrp, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::CaseNode, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Pipeline, + NdKind::Command, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_while_with_pipeline_and_assignment() { + let input = "while read line; do + FOO=bar echo $line | grep pattern | wc -l + done"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::LoopNode, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Assignment, + NdKind::Command, + NdKind::Command, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_nested_loops() { + let input = "for i in 1 2 3; do + for j in a b c; do + while true; do + echo $i $j + done + done + done"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::ForNode, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::ForNode, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::LoopNode, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_complex_conjunction_chain() { + let input = "mkdir -p dir && cd dir && touch file || echo failed && echo done"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Pipeline, + NdKind::Command, + NdKind::Pipeline, + NdKind::Command, + NdKind::Pipeline, + NdKind::Command, + NdKind::Pipeline, + NdKind::Command, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_func_defining_inner_func() { + let input = "outer() { + inner() { + echo hello from inner + } + inner + }"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::FuncDef, + NdKind::BraceGrp, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::FuncDef, + NdKind::BraceGrp, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_multiline_if_elif_with_pipelines() { + let input = "if cat /etc/passwd | grep root; then + echo found root + elif ls /tmp | wc -l; then + echo tmp has files + else + echo fallback | tee log.txt + fi"; + let expected = &mut [ + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::IfNode, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Conjunction, + NdKind::Pipeline, + NdKind::Command, + NdKind::Command, + ].into_iter(); + let ast = get_ast(input).unwrap(); + let mut node = ast[0].clone(); + if let Err(e) = node.assert_structure(expected) { + panic!("{}", e); + } + } + + #[test] + fn parse_cursed_input() { + let input = "if if while if if until :; do :; done; then case foo in; foo) :;; bar) :;; biz) :;; esac; elif case foo in; foo) :;; bar) :;; biz) :;; esac; then if :; then :; elif :; then :; elif :; then :; else :; fi; elif if :; then :; elif :; then :; elif :; then :; else :; fi; then while :; do :; done; else while :; do :; done; fi; then if until :; do :; done; then until :; do :; done; elif case foo in; foo) :;; bar) :;; biz) :;; esac; then case foo in; foo) :;; bar) :;; biz) :;; esac; elif if :; then :; elif :; then :; elif :; then :; else :; fi; then if :; then :; elif :; then :; elif :; then :; else :; fi; else while :; do :; done; fi; elif while while :; do :; done; do until :; do :; done; done; then while until :; do :; done; do case foo in; foo) :;; bar) :;; biz) :;; esac; done; elif until case foo in; foo) :;; bar) :;; biz) :;; esac; do if :; then :; elif :; then :; elif :; then :; else :; fi; done; then until if :; then :; elif :; then :; elif :; then :; else :; fi; do while :; do :; done; done; else case foo in; foo) while :; do :; done;; bar) until :; do :; done;; biz) until :; do :; done;; esac; fi; do while case foo in; foo) case foo in; foo) :;; bar) :;; biz) :;; esac;; bar) case foo in; foo) :;; bar) :;; biz) :;; esac;; biz) :;; esac; do if :; then :; elif :; then :; elif :; then :; else :; fi; done; done; then until while if :; then :; elif :; then :; elif :; then :; else :; fi; do while :; do :; done; done; do until while :; do :; done; do until :; do :; done; done; done; elif until until until :; do :; done; do case foo in; foo) :;; bar) :;; biz) :;; esac; done; do case foo in; foo) case foo in; foo) :;; bar) :;; biz) :;; esac;; bar) if :; then :; elif :; then :; elif :; then :; else :; fi;; biz) if :; then :; elif :; then :; elif :; then :; else :; fi;; esac; done; then case foo in; foo) case foo in; foo) while :; do :; done;; bar) while :; do :; done;; biz) until :; do :; done;; esac;; bar) if until :; do :; done; then case foo in; foo) :;; bar) :;; biz) :;; esac; elif case foo in; foo) :;; bar) :;; biz) :;; esac; then if :; then :; elif :; then :; elif :; then :; else :; fi; elif if :; then :; elif :; then :; elif :; then :; else :; fi; then while :; do :; done; else while :; do :; done; fi;; biz) if until :; do :; done; then until :; do :; done; elif case foo in; foo) :;; bar) :;; biz) :;; esac; then case foo in; foo) :;; bar) :;; biz) :;; esac; elif if :; then :; elif :; then :; elif :; then :; else :; fi; then if :; then :; elif :; then :; elif :; then :; else :; fi; else while :; do :; done; fi;; esac; elif case foo in; foo) while while :; do :; done; do until :; do :; done; done;; bar) while until :; do :; done; do case foo in; foo) :;; bar) :;; biz) :;; esac; done;; biz) until case foo in; foo) :;; bar) :;; biz) :;; esac; do if :; then :; elif :; then :; elif :; then :; else :; fi; done;; esac; then if until if :; then :; elif :; then :; elif :; then :; else :; fi; do while :; do :; done; done; then case foo in; foo) while :; do :; done;; bar) until :; do :; done;; biz) until :; do :; done;; esac; elif case foo in; foo) case foo in; foo) :;; bar) :;; biz) :;; esac;; bar) case foo in; foo) :;; bar) :;; biz) :;; esac;; biz) if :; then :; elif :; then :; elif :; then :; else :; fi;; esac; then if if :; then :; elif :; then :; elif :; then :; else :; fi; then while :; do :; done; elif while :; do :; done; then until :; do :; done; elif until :; do :; done; then case foo in; foo) :;; bar) :;; biz) :;; esac; else case foo in; foo) :;; bar) :;; biz) :;; esac; fi; elif if if :; then :; elif :; then :; elif :; then :; else :; fi; then if :; then :; elif :; then :; elif :; then :; else :; fi; elif while :; do :; done; then while :; do :; done; elif until :; do :; done; then until :; do :; done; else case foo in; foo) :;; bar) :;; biz) :;; esac; fi; then while case foo in; foo) :;; bar) :;; biz) :;; esac; do if :; then :; elif :; then :; elif :; then :; else :; fi; done; else while if :; then :; elif :; then :; elif :; then :; else :; fi; do while :; do :; done; done; fi; else if until while :; do :; done; do until :; do :; done; done; then until until :; do :; done; do case foo in; foo) :;; bar) :;; biz) :;; esac; done; elif case foo in; foo) case foo in; foo) :;; bar) :;; biz) :;; esac;; bar) if :; then :; elif :; then :; elif :; then :; else :; fi;; biz) if :; then :; elif :; then :; elif :; then :; else :; fi;; esac; then case foo in; foo) while :; do :; done;; bar) while :; do :; done;; biz) until :; do :; done;; esac; elif if until :; do :; done; then case foo in; foo) :;; bar) :;; biz) :;; esac; elif case foo in; foo) :;; bar) :;; biz) :;; esac; then if :; then :; elif :; then :; elif :; then :; else :; fi; elif if :; then :; elif :; then :; elif :; then :; else :; fi; then while :; do :; done; else while :; do :; done; fi; then if until :; do :; done; then until :; do :; done; elif case foo in; foo) :;; bar) :;; biz) :;; esac; then case foo in; foo) :;; bar) :;; biz) :;; esac; elif if :; then :; elif :; then :; elif :; then :; else :; fi; then if :; then :; elif :; then :; elif :; then :; else :; fi; else while :; do :; done; fi; else while while :; do :; done; do until :; do :; done; done; fi; fi; then while while while until :; do :; done; do case foo in; foo) :;; bar) :;; biz) :;; esac; done; do until case foo in; foo) :;; bar) :;; biz) :;; esac; do if :; then :; elif :; then :; elif :; then :; else :; fi; done; done; do while until if :; then :; elif :; then :; elif :; then :; else :; fi; do while :; do :; done; done; do case foo in; foo) while :; do :; done;; bar) until :; do :; done;; biz) until :; do :; done;; esac; done; done; elif until until case foo in; foo) case foo in; foo) :;; bar) :;; biz) :;; esac;; bar) case foo in; foo) :;; bar) :;; biz) :;; esac;; biz) if :; then :; elif :; then :; elif :; then :; else :; fi;; esac; do if if :; then :; elif :; then :; elif :; then :; else :; fi; then while :; do :; done; elif while :; do :; done; then until :; do :; done; elif until :; do :; done; then case foo in; foo) :;; bar) :;; biz) :;; esac; else case foo in; foo) :;; bar) :;; biz) :;; esac; fi; done; do until if if :; then :; elif :; then :; elif :; then :; else :; fi; then if :; then :; elif :; then :; elif :; then :; else :; fi; elif while :; do :; done; then while :; do :; done; elif until :; do :; done; then until :; do :; done; else case foo in; foo) :;; bar) :;; biz) :;; esac; fi; do while case foo in; foo) :;; bar) :;; biz) :;; esac; do if :; then :; elif :; then :; elif :; then :; else :; fi; done; done; done; then case foo in; foo) case foo in; foo) while if :; then :; elif :; then :; elif :; then :; else :; fi; do while :; do :; done; done;; bar) until while :; do :; done; do until :; do :; done; done;; biz) until until :; do :; done; do case foo in; foo) :;; bar) :;; biz) :;; esac; done;; esac;; bar) case foo in; foo) case foo in; foo) case foo in; foo) :;; bar) :;; biz) :;; esac;; bar) if :; then :; elif :; then :; elif :; then :; else :; fi;; biz) if :; then :; elif :; then :; elif :; then :; else :; fi;; esac;; bar) case foo in; foo) while :; do :; done;; bar) while :; do :; done;; biz) until :; do :; done;; esac;; biz) if until :; do :; done; then case foo in; foo) :;; bar) :;; biz) :;; esac; elif case foo in; foo) :;; bar) :;; biz) :;; esac; then if :; then :; elif :; then :; elif :; then :; else :; fi; elif if :; then :; elif :; then :; elif :; then :; else :; fi; then while :; do :; done; else while :; do :; done; fi;; esac;; biz) if if until :; do :; done; then until :; do :; done; elif case foo in; foo) :;; bar) :;; biz) :;; esac; then case foo in; foo) :;; bar) :;; biz) :;; esac; elif if :; then :; elif :; then :; elif :; then :; else :; fi; then if :; then :; elif :; then :; elif :; then :; else :; fi; else while :; do :; done; fi; then while while :; do :; done; do until :; do :; done; done; elif while until :; do :; done; do case foo in; foo) :;; bar) :;; biz) :;; esac; done; then until case foo in; foo) :;; bar) :;; biz) :;; esac; do if :; then :; elif :; then :; elif :; then :; else :; fi; done; elif until if :; then :; elif :; then :; elif :; then :; else :; fi; do while :; do :; done; done; then case foo in; foo) while :; do :; done;; bar) until :; do :; done;; biz) until :; do :; done;; esac; else case foo in; foo) case foo in; foo) :;; bar) :;; biz) :;; esac;; bar) case foo in; foo) :;; bar) :;; biz) :;; esac;; biz) if :; then :; elif :; then :; elif :; then :; else :; fi;; esac; fi;; esac; elif if if if if :; then :; elif :; then :; elif :; then :; else :; fi; then while :; do :; done; elif while :; do :; done; then until :; do :; done; elif until :; do :; done; then case foo in; foo) :;; bar) :;; biz) :;; esac; else case foo in; foo) :;; bar) :;; biz) :;; esac; fi; then if if :; then :; elif :; then :; elif :; then :; else :; fi; then if :; then :; elif :; then :; elif :; then :; else :; fi; elif while :; do :; done; then while :; do :; done; elif until :; do :; done; then until :; do :; done; else case foo in; foo) :;; bar) :;; biz) :;; esac; fi; elif while case foo in; foo) :;; bar) :;; biz) :;; esac; do if :; then :; elif :; then :; elif :; then :; else :; fi; done; then while if :; then :; elif :; then :; elif :; then :; else :; fi; do while :; do :; done; done; elif until while :; do :; done; do until :; do :; done; done; then until until :; do :; done; do case foo in; foo) :;; bar) :;; biz) :;; esac; done; else case foo in; foo) case foo in; foo) :;; bar) :;; biz) :;; esac;; bar) if :; then :; elif :; then :; elif :; then :; else :; fi;; biz) if :; then :; elif :; then :; elif :; then :; else :; fi;; esac; fi; then while case foo in; foo) while :; do :; done;; bar) while :; do :; done;; biz) until :; do :; done;; esac; do if until :; do :; done; then case foo in; foo) :;; bar) :;; biz) :;; esac; elif case foo in; foo) :;; bar) :;; biz) :;; esac; then if :; then :; elif :; then :; elif :; then :; else :; fi; elif if :; then :; elif :; then :; elif :; then :; else :; fi; then while :; do :; done; else while :; do :; done; fi; done; elif while if until :; do :; done; then until :; do :; done; elif case foo in; foo) :;; bar) :;; biz) :;; esac; then case foo in; foo) :;; bar) :;; biz) :;; esac; elif if :; then :; elif :; then :; elif :; then :; else :; fi; then if :; then :; elif :; then :; elif :; then :; else :; fi; else while :; do :; done; fi; do while while :; do :; done; do until :; do :; done; done; done; then until while until :; do :; done; do case foo in; foo) :;; bar) :;; biz) :;; esac; done; do until case foo in; foo) :;; bar) :;; biz) :;; esac; do if :; then :; elif :; then :; elif :; then :; else :; fi; done; done; elif until until if :; then :; elif :; then :; elif :; then :; else :; fi; do while :; do :; done; done; do case foo in; foo) while :; do :; done;; bar) until :; do :; done;; biz) until :; do :; done;; esac; done; then case foo in; foo) case foo in; foo) case foo in; foo) :;; bar) :;; biz) :;; esac;; bar) case foo in; foo) :;; bar) :;; biz) :;; esac;; biz) if :; then :; elif :; then :; elif :; then :; else :; fi;; esac;; bar) if if :; then :; elif :; then :; elif :; then :; else :; fi; then while :; do :; done; elif while :; do :; done; then until :; do :; done; elif until :; do :; done; then case foo in; foo) :;; bar) :;; biz) :;; esac; else case foo in; foo) :;; bar) :;; biz) :;; esac; fi;; biz) if if :; then :; elif :; then :; elif :; then :; else :; fi; then if :; then :; elif :; then :; elif :; then :; else :; fi; elif while :; do :; done; then while :; do :; done; elif until :; do :; done; then until :; do :; done; else case foo in; foo) :;; bar) :;; biz) :;; esac; fi;; esac; else case foo in; foo) while case foo in; foo) :;; bar) :;; biz) :;; esac; do if :; then :; elif :; then :; elif :; then :; else :; fi; done;; bar) while if :; then :; elif :; then :; elif :; then :; else :; fi; do while :; do :; done; done;; biz) until while :; do :; done; do until :; do :; done; done;; esac; fi; then if if until until :; do :; done; do case foo in; foo) :;; bar) :;; biz) :;; esac; done; then case foo in; foo) case foo in; foo) :;; bar) :;; biz) :;; esac;; bar) if :; then :; elif :; then :; elif :; then :; else :; fi;; biz) if :; then :; elif :; then :; elif :; then :; else :; fi;; esac; elif case foo in; foo) while :; do :; done;; bar) while :; do :; done;; biz) until :; do :; done;; esac; then if until :; do :; done; then case foo in; foo) :;; bar) :;; biz) :;; esac; elif case foo in; foo) :;; bar) :;; biz) :;; esac; then if :; then :; elif :; then :; elif :; then :; else :; fi; elif if :; then :; elif :; then :; elif :; then :; else :; fi; then while :; do :; done; else while :; do :; done; fi; elif if until :; do :; done; then until :; do :; done; elif case foo in; foo) :;; bar) :;; biz) :;; esac; then case foo in; foo) :;; bar) :;; biz) :;; esac; elif if :; then :; elif :; then :; elif :; then :; else :; fi; then if :; then :; elif :; then :; elif :; then :; else :; fi; else while :; do :; done; fi; then while while :; do :; done; do until :; do :; done; done; else while until :; do :; done; do case foo in; foo) :;; bar) :;; biz) :;; esac; done; fi; then if until case foo in; foo) :;; bar) :;; biz) :;; esac; do if :; then :; elif :; then :; elif :; then :; else :; fi; done; then until if :; then :; elif :; then :; elif :; then :; else :; fi; do while :; do :; done; done; elif case foo in; foo) while :; do :; done;; bar) until :; do :; done;; biz) until :; do :; done;; esac; then case foo in; foo) case foo in; foo) :;; bar) :;; biz) :;; esac;; bar) case foo in; foo) :;; bar) :;; biz) :;; esac;; biz) if :; then :; elif :; then :; elif :; then :; else :; fi;; esac; elif if if :; then :; elif :; then :; elif :; then :; else :; fi; then while :; do :; done; elif while :; do :; done; then until :; do :; done; elif until :; do :; done; then case foo in; foo) :;; bar) :;; biz) :;; esac; else case foo in; foo) :;; bar) :;; biz) :;; esac; fi; then if if :; then :; elif :; then :; elif :; then :; else :; fi; then if :; then :; elif :; then :; elif :; then :; else :; fi; elif while :; do :; done; then while :; do :; done; elif until :; do :; done; then until :; do :; done; else case foo in; foo) :;; bar) :;; biz) :;; esac; fi; else while case foo in; foo) :;; bar) :;; biz) :;; esac; do if :; then :; elif :; then :; elif :; then :; else :; fi; done; fi; elif while while if :; then :; elif :; then :; elif :; then :; else :; fi; do while :; do :; done; done; do until while :; do :; done; do until :; do :; done; done; done; then while until until :; do :; done; do case foo in; foo) :;; bar) :;; biz) :;; esac; done; do case foo in; foo) case foo in; foo) :;; bar) :;; biz) :;; esac;; bar) if :; then :; elif :; then :; elif :; then :; else :; fi;; biz) if :; then :; elif :; then :; elif :; then :; else :; fi;; esac; done; elif until case foo in; foo) while :; do :; done;; bar) while :; do :; done;; biz) until :; do :; done;; esac; do if until :; do :; done; then case foo in; foo) :;; bar) :;; biz) :;; esac; elif case foo in; foo) :;; bar) :;; biz) :;; esac; then if :; then :; elif :; then :; elif :; then :; else :; fi; elif if :; then :; elif :; then :; elif :; then :; else :; fi; then while :; do :; done; else while :; do :; done; fi; done; then until if until :; do :; done; then until :; do :; done; elif case foo in; foo) :;; bar) :;; biz) :;; esac; then case foo in; foo) :;; bar) :;; biz) :;; esac; elif if :; then :; elif :; then :; elif :; then :; else :; fi; then if :; then :; elif :; then :; elif :; then :; else :; fi; else while :; do :; done; fi; do while while :; do :; done; do until :; do :; done; done; done; else case foo in; foo) while until :; do :; done; do case foo in; foo) :;; bar) :;; biz) :;; esac; done;; bar) until case foo in; foo) :;; bar) :;; biz) :;; esac; do if :; then :; elif :; then :; elif :; then :; else :; fi; done;; biz) until if :; then :; elif :; then :; elif :; then :; else :; fi; do while :; do :; done; done;; esac; fi; else while case foo in; foo) case foo in; foo) while :; do :; done;; bar) until :; do :; done;; biz) until :; do :; done;; esac;; bar) case foo in; foo) case foo in; foo) :;; bar) :;; biz) :;; esac;; bar) case foo in; foo) :;; bar) :;; biz) :;; esac;; biz) if :; then :; elif :; then :; elif :; then :; else :; fi;; esac;; biz) if if :; then :; elif :; then :; elif :; then :; else :; fi; then while :; do :; done; elif while :; do :; done; then until :; do :; done; elif until :; do :; done; then case foo in; foo) :;; bar) :;; biz) :;; esac; else case foo in; foo) :;; bar) :;; biz) :;; esac; fi;; esac; do if if if :; then :; elif :; then :; elif :; then :; else :; fi; then if :; then :; elif :; then :; elif :; then :; else :; fi; elif while :; do :; done; then while :; do :; done; elif until :; do :; done; then until :; do :; done; else case foo in; foo) :;; bar) :;; biz) :;; esac; fi; then while case foo in; foo) :;; bar) :;; biz) :;; esac; do if :; then :; elif :; then :; elif :; then :; else :; fi; done; elif while if :; then :; elif :; then :; elif :; then :; else :; fi; do while :; do :; done; done; then until while :; do :; done; do until :; do :; done; done; elif until until :; do :; done; do case foo in; foo) :;; bar) :;; biz) :;; esac; done; then case foo in; foo) case foo in; foo) :;; bar) :;; biz) :;; esac;; bar) if :; then :; elif :; then :; elif :; then :; else :; fi;; biz) if :; then :; elif :; then :; elif :; then :; else :; fi;; esac; else case foo in; foo) while :; do :; done;; bar) while :; do :; done;; biz) until :; do :; done;; esac; fi; done; fi"; + assert!(get_ast(input).is_ok()); // lets spare our sanity and just say that "ok" means "it parsed correctly" + } +} diff --git a/src/testutil.rs b/src/testutil.rs index 484264a..7c5ddc4 100644 --- a/src/testutil.rs +++ b/src/testutil.rs @@ -1,9 +1,9 @@ use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, env, os::fd::{AsRawFd, OwnedFd}, path::PathBuf, - sync::{self, MutexGuard}, + sync::{self, Arc, MutexGuard}, }; use nix::{ @@ -14,10 +14,7 @@ use nix::{ }; use crate::{ - libsh::error::ShResult, - parse::{Redir, RedirType, execute::exec_input}, - procio::{IoFrame, IoMode, RedirGuard}, - state::{MetaTab, SHED}, + expand::expand_aliases, libsh::error::ShResult, parse::{ParsedSrc, Redir, RedirType, execute::exec_input, lex::LexFlags}, procio::{IoFrame, IoMode, RedirGuard}, state::{MetaTab, SHED, read_logic} }; static TEST_MUTEX: sync::Mutex<()> = sync::Mutex::new(()); @@ -153,3 +150,95 @@ impl Drop for TestGuard { SHED.with(|s| s.restore()); } } + +pub fn get_ast(input: &str) -> ShResult> { + let log_tab = read_logic(|l| l.clone()); + let input = expand_aliases(input.into(), HashSet::new(), &log_tab); + + let source_name = "test_input".to_string(); + let mut parser = ParsedSrc::new(Arc::new(input)) + .with_lex_flags(LexFlags::empty()) + .with_name(source_name.clone()); + + parser.parse_src().map_err(|e| e.into_iter().next().unwrap())?; + + Ok(parser.extract_nodes()) +} + +impl crate::parse::Node { + pub fn assert_structure(&mut self, expected: &mut impl Iterator) -> Result<(), String> { + let mut full_structure = vec![]; + let mut before = vec![]; + let mut after = vec![]; + let mut offender = None; + + self.walk_tree(&mut |s| { + let expected_rule = expected.next(); + full_structure.push(s.class.as_nd_kind()); + + if offender.is_none() && expected_rule.as_ref().map_or(true, |e| *e != s.class.as_nd_kind()) { + offender = Some((s.class.as_nd_kind(), expected_rule)); + } else if offender.is_none() { + before.push(s.class.as_nd_kind()); + } else { + after.push(s.class.as_nd_kind()); + } + }); + + assert!(expected.next().is_none(), "Expected structure has more nodes than actual structure"); + + if let Some((nd_kind, expected_rule)) = offender { + let expected_rule = expected_rule.map_or("(none — expected array too short)".into(), |e| format!("{e:?}")); + let full_structure_hint = full_structure.into_iter() + .map(|s| format!("\tNdKind::{s:?},")) + .collect::>() + .join("\n"); + let full_structure_hint = format!("let expected = &mut [\n{full_structure_hint}\n].into_iter();"); + + let output = [ + "Structure assertion failed!\n".into(), + format!("Expected node type '{:?}', found '{:?}'", expected_rule, nd_kind), + format!("Before offender: {:?}", before), + format!("After offender: {:?}\n", after), + format!("hint: here is the full structure as an array\n {full_structure_hint}"), + ].join("\n"); + + Err(output) + } else { + Ok(()) + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum NdKind { + IfNode, + LoopNode, + ForNode, + CaseNode, + Command, + Pipeline, + Conjunction, + Assignment, + BraceGrp, + Test, + FuncDef, +} + +impl crate::parse::NdRule { + pub fn as_nd_kind(&self) -> NdKind { + match self { + Self::IfNode { .. } => NdKind::IfNode, + Self::LoopNode { .. } => NdKind::LoopNode, + Self::ForNode { .. } => NdKind::ForNode, + Self::CaseNode { .. } => NdKind::CaseNode, + Self::Command { .. } => NdKind::Command, + Self::Pipeline { .. } => NdKind::Pipeline, + Self::Conjunction { .. } => NdKind::Conjunction, + Self::Assignment { .. } => NdKind::Assignment, + Self::BraceGrp { .. } => NdKind::BraceGrp, + Self::Test { .. } => NdKind::Test, + Self::FuncDef { .. } => NdKind::FuncDef, + } + } +}