Skip to content

Commit b5b22a2

Browse files
committed
Add array values to GrmtoolsSectionParser and switch test_files
1. Split up the current `parse_value` function into `parse_key_value`, and `parse_setting` functions. 2. Add support for arrays to `parse_setting`. 3. Switch the top-level `test_files` support to expect an array.
1 parent da8e421 commit b5b22a2

File tree

14 files changed

+161
-90
lines changed

14 files changed

+161
-90
lines changed

cfgrammar/src/lib/header.rs

Lines changed: 83 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,85 @@ fn add_duplicate_occurrence<T: Eq + PartialEq + Clone>(
295295
}
296296

297297
impl<'input> GrmtoolsSectionParser<'input> {
298-
pub fn parse_value(
298+
fn parse_setting(&'_ self, mut i: usize) -> Result<(Setting<Span>, usize), HeaderError<Span>> {
299+
i = self.parse_ws(i);
300+
match RE_DIGITS.find(&self.src[i..]) {
301+
Some(m) => {
302+
let num_span = Span::new(i + m.start(), i + m.end());
303+
let num_str = &self.src[num_span.start()..num_span.end()];
304+
// If the above regex matches we expect this to succeed.
305+
let num = str::parse::<u64>(num_str).unwrap();
306+
let val = Setting::Num(num, num_span);
307+
i = self.parse_ws(num_span.end());
308+
Ok((val, i))
309+
}
310+
None => match RE_STRING.find(&self.src[i..]) {
311+
Some(m) => {
312+
let end = i + m.end();
313+
// Trim the leading and trailing quotes.
314+
let str_span = Span::new(i + m.start() + 1, end - 1);
315+
let str = &self.src[str_span.start()..str_span.end()];
316+
let setting = Setting::String(str.to_string(), str_span);
317+
// After the trailing quotes.
318+
i = self.parse_ws(end);
319+
Ok((setting, i))
320+
}
321+
None => {
322+
if let Some(mut j) = self.lookahead_is("[", i) {
323+
let mut vals = Vec::new();
324+
let open_pos = j;
325+
326+
loop {
327+
j = self.parse_ws(j);
328+
if let Some(end_pos) = self.lookahead_is("]", j) {
329+
return Ok((
330+
Setting::Array(
331+
vals,
332+
Span::new(i, open_pos),
333+
Span::new(j, end_pos),
334+
),
335+
end_pos,
336+
));
337+
}
338+
if let Ok((val, k)) = self.parse_setting(j) {
339+
vals.push(val);
340+
j = self.parse_ws(k);
341+
}
342+
if let Some(k) = self.lookahead_is(",", j) {
343+
j = k
344+
}
345+
}
346+
} else {
347+
let (path_val, j) = self.parse_namespaced(i)?;
348+
i = self.parse_ws(j);
349+
if let Some(j) = self.lookahead_is("(", i) {
350+
let (arg, j) = self.parse_namespaced(j)?;
351+
i = self.parse_ws(j);
352+
if let Some(j) = self.lookahead_is(")", i) {
353+
i = self.parse_ws(j);
354+
Ok((
355+
Setting::Constructor {
356+
ctor: path_val,
357+
arg,
358+
},
359+
i,
360+
))
361+
} else {
362+
Err(HeaderError {
363+
kind: HeaderErrorKind::ExpectedToken(')'),
364+
locations: vec![Span::new(i, i)],
365+
})
366+
}
367+
} else {
368+
Ok((Setting::Unitary(path_val), i))
369+
}
370+
}
371+
}
372+
},
373+
}
374+
}
375+
376+
pub fn parse_key_value(
299377
&'_ self,
300378
mut i: usize,
301379
) -> Result<(String, Span, Value<Span>, usize), HeaderError<Span>> {
@@ -312,62 +390,8 @@ impl<'input> GrmtoolsSectionParser<'input> {
312390
let key_span = Span::new(i, j);
313391
i = self.parse_ws(j);
314392
if let Some(j) = self.lookahead_is(":", i) {
315-
i = self.parse_ws(j);
316-
match RE_DIGITS.find(&self.src[i..]) {
317-
Some(m) => {
318-
let num_span = Span::new(i + m.start(), i + m.end());
319-
let num_str = &self.src[num_span.start()..num_span.end()];
320-
// If the above regex matches we expect this to succeed.
321-
let num = str::parse::<u64>(num_str).unwrap();
322-
let val = Setting::Num(num, num_span);
323-
i = self.parse_ws(num_span.end());
324-
Ok((key_name, key_span, Value::Setting(val), i))
325-
}
326-
None => match RE_STRING.find(&self.src[i..]) {
327-
Some(m) => {
328-
let end = i + m.end();
329-
// Trim the leading and trailing quotes.
330-
let str_span = Span::new(i + m.start() + 1, end - 1);
331-
let str = &self.src[str_span.start()..str_span.end()];
332-
let setting = Setting::String(str.to_string(), str_span);
333-
// After the trailing quotes.
334-
i = self.parse_ws(end);
335-
Ok((key_name, key_span, Value::Setting(setting), i))
336-
}
337-
None => {
338-
let (path_val, j) = self.parse_namespaced(i)?;
339-
i = self.parse_ws(j);
340-
if let Some(j) = self.lookahead_is("(", i) {
341-
let (arg, j) = self.parse_namespaced(j)?;
342-
i = self.parse_ws(j);
343-
if let Some(j) = self.lookahead_is(")", i) {
344-
i = self.parse_ws(j);
345-
Ok((
346-
key_name,
347-
key_span,
348-
Value::Setting(Setting::Constructor {
349-
ctor: path_val,
350-
arg,
351-
}),
352-
i,
353-
))
354-
} else {
355-
Err(HeaderError {
356-
kind: HeaderErrorKind::ExpectedToken(')'),
357-
locations: vec![Span::new(i, i)],
358-
})
359-
}
360-
} else {
361-
Ok((
362-
key_name,
363-
key_span,
364-
Value::Setting(Setting::Unitary(path_val)),
365-
i,
366-
))
367-
}
368-
}
369-
},
370-
}
393+
let (val, j) = self.parse_setting(j)?;
394+
Ok((key_name, key_span, Value::Setting(val), j))
371395
} else {
372396
Ok((key_name, key_span, Value::Flag(true, key_span), i))
373397
}
@@ -428,7 +452,7 @@ impl<'input> GrmtoolsSectionParser<'input> {
428452
if let Some(j) = self.lookahead_is("{", i) {
429453
i = self.parse_ws(j);
430454
while self.lookahead_is("}", i).is_none() && i < self.src.len() {
431-
let (key, key_loc, val, j) = match self.parse_value(i) {
455+
let (key, key_loc, val, j) = match self.parse_key_value(i) {
432456
Ok((key, key_loc, val, pos)) => (key, key_loc, val, pos),
433457
Err(e) => {
434458
errs.push(e);
@@ -453,7 +477,7 @@ impl<'input> GrmtoolsSectionParser<'input> {
453477
i = self.parse_ws(j);
454478
continue;
455479
} else {
456-
i = j;
480+
i = self.parse_ws(j);
457481
break;
458482
}
459483
}

doc/src/yaccextensions.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ But a default can be set or forced by using a `YaccKindResolver`.
77
|------------------|-------------------------------------------------|--------------|
88
| `yacckind` | [YaccKind](yacccompatibility.md#yacckinds) | &checkmark; |
99
| `recoverykind` | [RecoveryKind](errorrecovery.md#recoverykinds) | &cross; |
10-
| `test_files`[^] | String | &cross; |
10+
| `test_files`[^] | Array of string values | &cross; |
1111

12-
[^]: String containing a glob relative to the yacc `.y` source file, experimental.
12+
[^]: Strings containing globs are resolved relative to the yacc `.y` source file.
13+
`test_files` is currently experimental.
1314

1415
## Example
1516

lrlex/src/lib/ctbuilder.rs

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -545,29 +545,65 @@ where
545545
.collect::<HashMap<_, _>>();
546546
closure_lexerdef.set_rule_ids(&owned_map);
547547
yacc_header.mark_used(&"test_files".to_string());
548+
let grammar = rtpb.grammar();
548549
let test_glob = yacc_header.get("test_files");
550+
let mut err_str = None;
551+
let add_error_line = |err_str: &mut Option<String>, line| {
552+
if let Some(err_str) = err_str {
553+
err_str.push_str(&format!("{}\n", line));
554+
} else {
555+
let _ = err_str.insert(format!("{}\n", line));
556+
}
557+
};
549558
match test_glob {
550-
Some(HeaderValue(_, Value::Setting(Setting::String(test_files, _)))) => {
551-
let path_joined = grm_path.parent().unwrap().join(test_files);
552-
for path in
553-
glob(&path_joined.to_string_lossy()).map_err(|e| e.to_string())?
554-
{
555-
let path = path?;
556-
if let Some(ext) = path.extension() {
557-
if let Some(ext) = ext.to_str() {
558-
if ext.starts_with("grm") {
559-
Err(ErrorString("test_files extensions beginning with `grm` are reserved.".into()))?
559+
Some(HeaderValue(_, Value::Setting(Setting::Array(test_globs, _, _)))) => {
560+
for setting in test_globs {
561+
match setting {
562+
Setting::String(test_files, _) => {
563+
let path_joined = grm_path.parent().unwrap().join(test_files);
564+
let path_str = &path_joined.to_string_lossy();
565+
let mut glob_paths = glob(path_str).map_err(|e| e.to_string())?.peekable();
566+
if glob_paths.peek().is_none() {
567+
return Err(format!("'test_files' glob '{}' matched no paths", path_str)
568+
.to_string()
569+
.into(),
570+
);
571+
}
572+
573+
for path in glob_paths {
574+
let path = path?;
575+
if let Some(ext) = path.extension() {
576+
if let Some(ext) = ext.to_str() {
577+
if ext.starts_with("grm") {
578+
add_error_line(&mut err_str, "test_files extensions beginning with `grm` are reserved.".into());
579+
}
580+
}
581+
}
582+
let input = fs::read_to_string(&path)?;
583+
let l: LRNonStreamingLexer<LexerTypesT> =
584+
closure_lexerdef.lexer(&input);
585+
let errs = rtpb.parse_map(&l, &|_| (), &|_, _| ()).1;
586+
if !errs.is_empty() {
587+
add_error_line(&mut err_str, format!("While parsing {}:", path.display()));
588+
for e in errs {
589+
let e_pp = e.pp(&l, &|t| grammar.token_epp(t));
590+
let e_lines = e_pp.split("\n");
591+
for e in e_lines {
592+
add_error_line(&mut err_str, format!("\t{}", e));
593+
}
594+
}
595+
}
560596
}
561597
}
598+
_ => return Err("Invalid value for setting 'test_files'".into()),
562599
}
563-
let input = fs::read_to_string(&path)?;
564-
let l: LRNonStreamingLexer<LexerTypesT> =
565-
closure_lexerdef.lexer(&input);
566-
for e in rtpb.parse_map(&l, &|_| (), &|_, _| ()).1 {
567-
Err(format!("parsing {}: {}", path.display(), e))?
568-
}
569600
}
570-
Ok(())
601+
if let Some(err_str) = err_str {
602+
Err(ErrorString(err_str))?
603+
} else {
604+
Ok(())
605+
}
606+
571607
}
572608
Some(_) => Err("Invalid value for setting 'test_files'".into()),
573609
None => Ok(()),

lrpar/cttests/src/calc_input.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ grammar: |
33
%grmtools {
44
yacckind: Original(YaccOriginalActionKind::UserAction),
55
recoverer: RecoveryKind::None,
6-
test_files: "*.calc_input"
6+
test_files: ["*.calc_input"],
77
}
88
%start Expr
99
%actiontype Result<u64, ()>

lrpar/cttests/src/ctfails/calc_bad_input.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ grammar: |
33
%grmtools {
44
yacckind: Original(YaccOriginalActionKind::UserAction),
55
recoverer: RecoveryKind::None,
6-
test_files: "*.bad_input"
6+
test_files: ["*.bad_input"]
77
}
88
%start Expr
99
%actiontype Result<u64, ()>

lrpar/cttests/src/grmtools_section.test

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
grammar: |
2-
%grmtools{yacckind: Grmtools}
2+
%grmtools{
3+
yacckind: Grmtools,
4+
recoverer: RecoveryKind::CPCTPlus,
5+
test_files: ["*.input_grmtools_section"]
6+
}
37
%token MAGIC IDENT NUM STRING
48
%epp MAGIC "%grmtools"
59
%%
@@ -164,3 +168,6 @@ lexer: |
164168
: ':'
165169
\"(\\.|[^"\\])*\" 'STRING'
166170
\p{Pattern_White_Space} ;
171+
extra_files:
172+
test.input_grmtools_section: |
173+
%grmtools{yacckind: Grmtools, !b, !a}

lrpar/cttests/src/lib.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -355,16 +355,15 @@ fn test_expect() {
355355
#[test]
356356
fn test_grmtools_section_files() {
357357
use glob::glob;
358-
use std::env;
359358
use std::fs::File;
360359
use std::io::BufReader;
361360
use std::io::{BufRead as _, Read as _};
362361

363-
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
362+
let manifest_dir = env!("CARGO_MANIFEST_DIR");
364363
let examples_glob = format!("{manifest_dir}/../examples/**");
365364
let examples_l_glob = format!("{examples_glob}/*.l");
366365
let examples_y_glob = format!("{examples_glob}/*.y");
367-
let out_dir = env::var("OUT_DIR").unwrap();
366+
let out_dir = env!("OUT_DIR");
368367
let cttests_l_glob = format!("{out_dir}/*.l");
369368
let cttests_y_glob = format!("{out_dir}/*.y");
370369
let files = glob(&examples_l_glob)

lrpar/examples/calc_actions/src/calc.y

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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_arena/src/calc.y

Lines changed: 1 addition & 1 deletion
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"

0 commit comments

Comments
 (0)