Skip to content

Commit d71b532

Browse files
committed
Add test_files support for nimbleparse.
1 parent 6e857da commit d71b532

File tree

8 files changed

+270
-27
lines changed

8 files changed

+270
-27
lines changed

cfgrammar/src/lib/header.rs

+49
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,55 @@ impl From<Value<Span>> for Value<Location> {
185185
}
186186
}
187187

188+
impl<T> Value<T> {
189+
pub fn expect_string_with_context(&self, ctxt: &str) -> Result<&str, Box<dyn Error>> {
190+
let found = match self {
191+
Value::Flag(_, _) => "bool".to_string(),
192+
Value::Setting(Setting::String(s, _)) => {
193+
return Ok(s);
194+
}
195+
Value::Setting(Setting::Num(_, _)) => "numeric".to_string(),
196+
Value::Setting(Setting::Unitary(Namespaced {
197+
namespace,
198+
member: (member, _),
199+
})) => {
200+
if let Some((ns, _)) = namespace {
201+
format!("'{ns}::{member}'")
202+
} else {
203+
format!("'{member}'")
204+
}
205+
}
206+
Value::Setting(Setting::Constructor {
207+
ctor:
208+
Namespaced {
209+
namespace: ctor_ns,
210+
member: (ctor_memb, _),
211+
},
212+
arg:
213+
Namespaced {
214+
namespace: arg_ns,
215+
member: (arg_memb, _),
216+
},
217+
}) => {
218+
format!(
219+
"'{}({})'",
220+
if let Some((ns, _)) = ctor_ns {
221+
format!("{ns}::{ctor_memb}")
222+
} else {
223+
arg_memb.to_string()
224+
},
225+
if let Some((ns, _)) = arg_ns {
226+
format!("{ns}::{arg_memb}")
227+
} else {
228+
arg_memb.to_string()
229+
}
230+
)
231+
}
232+
};
233+
Err(format!("Expected 'String' value, found {}, at {ctxt}", found).into())
234+
}
235+
}
236+
188237
lazy_static! {
189238
static ref RE_LEADING_WS: Regex = Regex::new(r"^[\p{Pattern_White_Space}]*").unwrap();
190239
static ref RE_NAME: Regex = RegexBuilder::new(r"^[A-Z][A-Z_]*")

lrpar/examples/calc_actions/src/calc.y

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
%grmtools {
22
yacckind: Grmtools,
3-
test_files: "input.txt",
3+
test_files: "input*.txt",
44
}
55
%start Expr
66
%avoid_insert "INT"

lrpar/examples/calc_ast/src/calc.y

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
%grmtools {
22
yacckind: Grmtools,
3-
test_files: "input.txt",
3+
test_files: "input*.txt",
44
}
55
%start Expr
66
%avoid_insert "INT"

lrpar/examples/calc_parsetree/src/calc.y

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
%grmtools{
22
yacckind: Original(GenericParseTree),
3-
test_files: "input.txt",
3+
test_files: "input*.txt",
44
}
55
%start Expr
66
%avoid_insert "INT"

lrpar/examples/clone_param/src/param.y

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
%grmtools {
22
yacckind: Grmtools,
3-
test_files: "input.txt",
3+
test_files: "input*.txt",
44
}
55
%expect-unused Unmatched "UNMATCHED"
66
%token Incr Decr

lrpar/examples/start_states/src/comment.y

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
%grmtools{
22
yacckind: Original(GenericParseTree),
3-
test_files: "input.txt",
3+
test_files: "input*.txt",
44
}
55
%start Expr
66
%%

nimbleparse/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ lrtable = { path="../lrtable", version="0.13" }
2121
getopts.workspace = true
2222
num-traits.workspace = true
2323
unicode-width.workspace = true
24+
glob.workspace = true

nimbleparse/src/main.rs

+215-22
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,18 @@ use cfgrammar::{
77
Location, Span,
88
};
99
use getopts::Options;
10-
use lrlex::{DefaultLexerTypes, LRNonStreamingLexerDef, LexerDef};
10+
use lrlex::{DefaultLexerTypes, LRLexError, LRNonStreamingLexerDef, LexerDef};
1111
use lrpar::{
1212
parser::{RTParserBuilder, RecoveryKind},
1313
LexerTypes,
1414
};
15-
use lrtable::{from_yacc, Minimiser};
15+
use lrtable::{from_yacc, Minimiser, StateTable};
16+
use num_traits::AsPrimitive;
1617
use num_traits::ToPrimitive as _;
1718
use std::{
1819
env,
20+
error::Error,
21+
fmt,
1922
fs::File,
2023
io::Read,
2124
path::{Path, PathBuf},
@@ -34,7 +37,7 @@ fn usage(prog: &str, msg: &str) -> ! {
3437
if !msg.is_empty() {
3538
eprintln!("{}", msg);
3639
}
37-
eprintln!("Usage: {} [-r <cpctplus|none>] [-y <eco|grmtools|original>] [-dq] <lexer.l> <parser.y> <input file>", leaf);
40+
eprintln!("Usage: {} [-r <cpctplus|none>] [-y <eco|grmtools|original>] [-dq] <lexer.l> <parser.y> <input files> ...", leaf);
3841
process::exit(1);
3942
}
4043

@@ -155,8 +158,8 @@ fn main() {
155158
));
156159
}
157160
};
158-
159-
if matches.free.len() != 3 {
161+
let args_len = matches.free.len();
162+
if args_len < 2 {
160163
usage(prog, "Too few arguments given.");
161164
}
162165

@@ -319,8 +322,8 @@ fn main() {
319322
)
320323
);
321324
}
325+
eprintln!();
322326
}
323-
eprintln!();
324327
if let Some(token_spans) = missing_from_parser {
325328
let formatter = SpannedDiagnosticFormatter::new(&lex_src, &lex_l_path).unwrap();
326329
let err_indent = " ".repeat(ERROR.len());
@@ -346,24 +349,214 @@ fn main() {
346349
}
347350
}
348351

349-
let input = if &matches.free[2] == "-" {
350-
let mut s = String::new();
351-
std::io::stdin().read_to_string(&mut s).unwrap();
352-
s
353-
} else {
354-
read_file(&matches.free[2])
352+
let parser_build_ctxt = ParserBuildCtxt {
353+
header,
354+
lexerdef,
355+
grm,
356+
stable,
357+
yacc_y_path,
358+
recoverykind,
355359
};
356-
let lexer = lexerdef.lexer(&input);
357-
let pb = RTParserBuilder::new(&grm, &stable).recoverer(recoverykind);
358-
let (pt, errs) = pb.parse_generictree(&lexer);
359-
match pt {
360-
Some(pt) => println!("{}", pt.pp(&grm, &input)),
361-
None => println!("Unable to repair input sufficiently to produce parse tree.\n"),
360+
361+
if matches.free.len() == 3 {
362+
let input_path = PathBuf::from(&matches.free[2]);
363+
// If there is only one input file we want to print the generic parse tree.
364+
// We also want to handle `-` as stdin.
365+
let input = if &matches.free[2] == "-" {
366+
let mut s = String::new();
367+
std::io::stdin().read_to_string(&mut s).unwrap();
368+
s
369+
} else {
370+
read_file(&matches.free[2])
371+
};
372+
if let Err(e) = parser_build_ctxt.parse_string(input_path, input) {
373+
eprintln!("{}", e);
374+
process::exit(1);
375+
}
376+
} else if let Err(e) = parser_build_ctxt.parse_many(&matches.free[2..]) {
377+
eprintln!("{}", e);
378+
process::exit(1);
379+
}
380+
}
381+
382+
struct ParserBuildCtxt<LexerTypesT>
383+
where
384+
LexerTypesT: LexerTypes,
385+
usize: AsPrimitive<LexerTypesT::StorageT>,
386+
{
387+
header: Header<Location>,
388+
lexerdef: LRNonStreamingLexerDef<LexerTypesT>,
389+
grm: YaccGrammar<LexerTypesT::StorageT>,
390+
yacc_y_path: PathBuf,
391+
stable: StateTable<LexerTypesT::StorageT>,
392+
recoverykind: RecoveryKind,
393+
}
394+
395+
#[derive(Debug)]
396+
enum NimbleparseError {
397+
Source {
398+
// We could consider including the source text here and
399+
// using the span error formatting machinery.
400+
src_path: PathBuf,
401+
errs: Vec<String>,
402+
},
403+
Glob(glob::GlobError),
404+
Pattern(glob::PatternError),
405+
Other(Box<dyn Error>),
406+
}
407+
408+
impl From<glob::GlobError> for NimbleparseError {
409+
fn from(it: glob::GlobError) -> Self {
410+
NimbleparseError::Glob(it)
362411
}
363-
for e in &errs {
364-
println!("{}", e.pp(&lexer, &|t| grm.token_epp(t)));
412+
}
413+
414+
impl From<Box<dyn Error>> for NimbleparseError {
415+
fn from(it: Box<dyn Error>) -> Self {
416+
NimbleparseError::Other(it)
365417
}
366-
if !errs.is_empty() {
367-
process::exit(1);
418+
}
419+
420+
impl From<glob::PatternError> for NimbleparseError {
421+
fn from(it: glob::PatternError) -> Self {
422+
NimbleparseError::Pattern(it)
423+
}
424+
}
425+
426+
impl Error for NimbleparseError {}
427+
428+
impl fmt::Display for NimbleparseError {
429+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
430+
match self {
431+
Self::Source { src_path, errs } => {
432+
writeln!(f, "While parsing: {}", src_path.display())?;
433+
for e in errs {
434+
writeln!(f, "{}", e)?
435+
}
436+
Ok(())
437+
}
438+
Self::Glob(e) => {
439+
write!(f, "{}", e)
440+
}
441+
Self::Pattern(e) => {
442+
write!(f, "{}", e)
443+
}
444+
Self::Other(e) => {
445+
write!(f, "{}", e)
446+
}
447+
}
448+
}
449+
}
450+
451+
impl<LexerTypesT> ParserBuildCtxt<LexerTypesT>
452+
where
453+
LexerTypesT: LexerTypes<LexErrorT = LRLexError>,
454+
usize: AsPrimitive<LexerTypesT::StorageT>,
455+
LexerTypesT::StorageT: TryFrom<usize>,
456+
{
457+
fn parse_string(self, input_path: PathBuf, input_src: String) -> Result<(), NimbleparseError> {
458+
let lexer = self.lexerdef.lexer(&input_src);
459+
let pb = RTParserBuilder::new(&self.grm, &self.stable).recoverer(self.recoverykind);
460+
let (pt, errs) = pb.parse_generictree(&lexer);
461+
match pt {
462+
Some(pt) => println!("{}", pt.pp(&self.grm, &input_src)),
463+
None => println!("Unable to repair input sufficiently to produce parse tree.\n"),
464+
}
465+
if !errs.is_empty() {
466+
return Err(NimbleparseError::Source {
467+
src_path: input_path,
468+
errs: errs
469+
.iter()
470+
.map(|e| e.pp(&lexer, &|t| self.grm.token_epp(t)))
471+
.collect::<Vec<_>>(),
472+
});
473+
}
474+
Ok(())
475+
}
476+
477+
fn parse_many(self, input_paths: &[String]) -> Result<(), NimbleparseError> {
478+
let input_paths = if input_paths.is_empty() {
479+
// If given no input paths, try to find some with `test_files` in the header.
480+
if let Some(HeaderValue(_, val)) = self.header.get("test_files") {
481+
let s = val.expect_string_with_context("'test_files' in %grmtools")?;
482+
if let Some(yacc_y_path_dir) = self.yacc_y_path.parent() {
483+
let joined = yacc_y_path_dir.join(s);
484+
let joined = joined.as_os_str().to_str();
485+
if let Some(s) = joined {
486+
let mut paths = glob::glob(s)?.peekable();
487+
if paths.peek().is_none() {
488+
return Err(NimbleparseError::Other(
489+
format!("'test_files' glob '{}' matched no paths", s)
490+
.to_string()
491+
.into(),
492+
));
493+
}
494+
let mut input_paths = Vec::new();
495+
for path in paths {
496+
input_paths.push(path?);
497+
}
498+
input_paths
499+
} else {
500+
return Err(NimbleparseError::Other(
501+
format!(
502+
"Unable to convert joined path to str {} with glob '{}'",
503+
self.yacc_y_path.display(),
504+
s
505+
)
506+
.into(),
507+
));
508+
}
509+
} else {
510+
return Err(NimbleparseError::Other(
511+
format!(
512+
"Unable to find parent path for {}",
513+
self.yacc_y_path.display()
514+
)
515+
.into(),
516+
));
517+
}
518+
} else {
519+
return Err(NimbleparseError::Other(
520+
"Missing <input file> argument".into(),
521+
));
522+
}
523+
} else {
524+
// Just convert the given arguments to paths.
525+
input_paths
526+
.iter()
527+
.map(PathBuf::from)
528+
.collect::<Vec<PathBuf>>()
529+
};
530+
if input_paths.is_empty() {
531+
return Err(NimbleparseError::Other(
532+
"Missing <input file> argument".into(),
533+
));
534+
}
535+
let pb = RTParserBuilder::new(&self.grm, &self.stable).recoverer(self.recoverykind);
536+
// Actually parse the given arguments or the `test_files` specified in the grammar.
537+
for input_path in input_paths {
538+
let input = read_file(&input_path);
539+
let lexer = self.lexerdef.lexer(&input);
540+
let (pt, errs) = pb.parse_generictree(&lexer);
541+
if errs.is_empty() && pt.is_some() {
542+
// Since we're parsing many files, don't output all of their parse trees, just print the file name.
543+
println!("parsed: {}", input_path.display())
544+
} else {
545+
if pt.is_none() {
546+
eprintln!(
547+
"Unable to repair input at '{}' sufficiently to produce parse tree.\n",
548+
input_path.display()
549+
);
550+
}
551+
return Err(NimbleparseError::Source {
552+
src_path: input_path,
553+
errs: errs
554+
.iter()
555+
.map(|e| e.pp(&lexer, &|t| self.grm.token_epp(t)))
556+
.collect::<Vec<_>>(),
557+
});
558+
}
559+
}
560+
Ok(())
368561
}
369562
}

0 commit comments

Comments
 (0)