added tests for the parser
This commit is contained in:
@@ -250,6 +250,7 @@ pub struct LexStream {
|
|||||||
quote_state: QuoteState,
|
quote_state: QuoteState,
|
||||||
brc_grp_depth: usize,
|
brc_grp_depth: usize,
|
||||||
brc_grp_start: Option<usize>,
|
brc_grp_start: Option<usize>,
|
||||||
|
case_depth: usize,
|
||||||
flags: LexFlags,
|
flags: LexFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,7 +272,6 @@ bitflags! {
|
|||||||
/// The lexer has no more tokens to produce
|
/// The lexer has no more tokens to produce
|
||||||
const STALE = 0b0001000000;
|
const STALE = 0b0001000000;
|
||||||
const EXPECTING_IN = 0b0010000000;
|
const EXPECTING_IN = 0b0010000000;
|
||||||
const IN_CASE = 0b0100000000;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,6 +306,7 @@ impl LexStream {
|
|||||||
quote_state: QuoteState::default(),
|
quote_state: QuoteState::default(),
|
||||||
brc_grp_depth: 0,
|
brc_grp_depth: 0,
|
||||||
brc_grp_start: None,
|
brc_grp_start: None,
|
||||||
|
case_depth: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns a slice of the source input using the given range
|
/// Returns a slice of the source input using the given range
|
||||||
@@ -453,7 +454,7 @@ impl LexStream {
|
|||||||
let mut chars = slice.chars().peekable();
|
let mut chars = slice.chars().peekable();
|
||||||
let can_be_subshell = chars.peek() == Some(&'(');
|
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())
|
&& let Some(count) = case_pat_lookahead(chars.clone())
|
||||||
{
|
{
|
||||||
pos += count;
|
pos += count;
|
||||||
@@ -731,7 +732,7 @@ impl LexStream {
|
|||||||
"case" | "select" | "for" => {
|
"case" | "select" | "for" => {
|
||||||
new_tk.mark(TkFlags::KEYWORD);
|
new_tk.mark(TkFlags::KEYWORD);
|
||||||
self.flags |= LexFlags::EXPECTING_IN;
|
self.flags |= LexFlags::EXPECTING_IN;
|
||||||
self.flags |= LexFlags::IN_CASE;
|
self.case_depth += 1;
|
||||||
self.set_next_is_cmd(false);
|
self.set_next_is_cmd(false);
|
||||||
}
|
}
|
||||||
"in" if self.flags.contains(LexFlags::EXPECTING_IN) => {
|
"in" if self.flags.contains(LexFlags::EXPECTING_IN) => {
|
||||||
@@ -739,8 +740,8 @@ impl LexStream {
|
|||||||
self.flags &= !LexFlags::EXPECTING_IN;
|
self.flags &= !LexFlags::EXPECTING_IN;
|
||||||
}
|
}
|
||||||
_ if is_keyword(text) => {
|
_ if is_keyword(text) => {
|
||||||
if text == "esac" && self.flags.contains(LexFlags::IN_CASE) {
|
if text == "esac" && self.case_depth > 0 {
|
||||||
self.flags &= !LexFlags::IN_CASE;
|
self.case_depth -= 1;
|
||||||
}
|
}
|
||||||
new_tk.mark(TkFlags::KEYWORD);
|
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::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::{HashMap, HashSet},
|
||||||
env,
|
env,
|
||||||
os::fd::{AsRawFd, OwnedFd},
|
os::fd::{AsRawFd, OwnedFd},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{self, MutexGuard},
|
sync::{self, Arc, MutexGuard},
|
||||||
};
|
};
|
||||||
|
|
||||||
use nix::{
|
use nix::{
|
||||||
@@ -14,10 +14,7 @@ use nix::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
libsh::error::ShResult,
|
expand::expand_aliases, libsh::error::ShResult, parse::{ParsedSrc, Redir, RedirType, execute::exec_input, lex::LexFlags}, procio::{IoFrame, IoMode, RedirGuard}, state::{MetaTab, SHED, read_logic}
|
||||||
parse::{Redir, RedirType, execute::exec_input},
|
|
||||||
procio::{IoFrame, IoMode, RedirGuard},
|
|
||||||
state::{MetaTab, SHED},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static TEST_MUTEX: sync::Mutex<()> = sync::Mutex::new(());
|
static TEST_MUTEX: sync::Mutex<()> = sync::Mutex::new(());
|
||||||
@@ -153,3 +150,95 @@ impl Drop for TestGuard {
|
|||||||
SHED.with(|s| s.restore());
|
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