added tests for the parser
This commit is contained in:
@@ -250,6 +250,7 @@ pub struct LexStream {
|
||||
quote_state: QuoteState,
|
||||
brc_grp_depth: usize,
|
||||
brc_grp_start: Option<usize>,
|
||||
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);
|
||||
}
|
||||
|
||||
622
src/parse/mod.rs
622
src/parse/mod.rs
File diff suppressed because one or more lines are too long
101
src/testutil.rs
101
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<Vec<crate::parse::Node>> {
|
||||
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<Item = NdKind>) -> 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::<Vec<String>>()
|
||||
.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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user