Skip to content

Commit 7de0aae

Browse files
committed
Update the lrlex tool to read %grmtools section, emit diagnostics.
1 parent da567b8 commit 7de0aae

File tree

1 file changed

+99
-25
lines changed

1 file changed

+99
-25
lines changed

lrlex/src/main.rs

Lines changed: 99 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,38 @@
11
use getopts::Options;
22
use std::{
33
env,
4+
error::Error,
5+
fmt,
46
fs::File,
57
io::{stderr, stdin, Read, Write},
68
path::Path,
79
process,
8-
str::FromStr,
910
};
1011

11-
use cfgrammar::{newlinecache::NewlineCache, Spanned};
12-
use lrlex::{DefaultLexerTypes, LRNonStreamingLexerDef, LexerDef};
13-
use lrpar::{Lexeme, Lexer};
12+
use cfgrammar::header::{GrmtoolsSectionParser, HeaderValue};
13+
use lrlex::{DefaultLexerTypes, LRNonStreamingLexerDef, LexFlags, LexerDef, LexerKind};
14+
use lrpar::{
15+
diagnostics::{DiagnosticFormatter, SpannedDiagnosticFormatter},
16+
Lexeme, Lexer,
17+
};
18+
19+
const ERROR: &str = "[Error]";
20+
21+
/// A string which uses `Display` for it's `Debug` impl.
22+
struct ErrorString(String);
23+
impl fmt::Display for ErrorString {
24+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
25+
let ErrorString(s) = self;
26+
write!(f, "{}", s)
27+
}
28+
}
29+
impl fmt::Debug for ErrorString {
30+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
31+
let ErrorString(s) = self;
32+
write!(f, "{}", s)
33+
}
34+
}
35+
impl Error for ErrorString {}
1436

1537
fn usage(prog: &str, msg: &str) {
1638
let path = Path::new(prog);
@@ -42,43 +64,80 @@ fn read_file(path: &str) -> String {
4264
s
4365
}
4466

45-
fn main() {
67+
fn main() -> Result<(), Box<dyn Error>> {
4668
let args: Vec<String> = env::args().collect();
4769
let prog = args[0].clone();
4870
let matches = match Options::new().optflag("h", "help", "").parse(&args[1..]) {
4971
Ok(m) => m,
5072
Err(f) => {
5173
usage(&prog, f.to_string().as_str());
52-
return;
74+
return Ok(());
5375
}
5476
};
5577
if matches.opt_present("h") || matches.free.len() != 2 {
5678
usage(&prog, "");
57-
return;
79+
return Ok(());
5880
}
5981

6082
let lex_l_path = &matches.free[0];
6183
let lex_src = read_file(lex_l_path);
62-
let lexerdef = LRNonStreamingLexerDef::<DefaultLexerTypes<u32>>::from_str(&lex_src)
63-
.unwrap_or_else(|errs| {
64-
let nlcache = NewlineCache::from_str(&lex_src).unwrap();
65-
for e in errs {
66-
if let Some((line, column)) = nlcache
67-
.byte_to_line_num_and_col_num(&lex_src, e.spans().first().unwrap().start())
68-
{
69-
writeln!(
70-
stderr(),
71-
"{}: {} at line {line} column {column}",
72-
&lex_l_path,
73-
&e
74-
)
75-
.ok();
76-
} else {
77-
writeln!(stderr(), "{}: {}", &lex_l_path, &e).ok();
78-
}
84+
let lex_diag = SpannedDiagnosticFormatter::new(&lex_src, Path::new(lex_l_path));
85+
let (mut header, _) = match GrmtoolsSectionParser::new(&lex_src, false).parse() {
86+
Ok(x) => x,
87+
Err(es) => {
88+
eprintln!(
89+
"\n{ERROR}{}",
90+
lex_diag.file_location_msg(" parsing the `%grmtools` section", None)
91+
);
92+
for e in es {
93+
eprintln!(
94+
"{}",
95+
&indent(" ", &lex_diag.format_error(e).to_string())
96+
);
7997
}
8098
process::exit(1);
81-
});
99+
}
100+
};
101+
header.mark_used(&"lexerkind".to_string());
102+
let lexerkind = if let Some(HeaderValue(_, lk_val)) = header.get("lexerkind") {
103+
LexerKind::try_from(lk_val)?
104+
} else {
105+
LexerKind::LRNonStreamingLexer
106+
};
107+
108+
let lexerdef = match lexerkind {
109+
LexerKind::LRNonStreamingLexer => {
110+
let lex_flags = LexFlags::try_from(&mut header)?;
111+
let lexerdef = match LRNonStreamingLexerDef::<DefaultLexerTypes<u32>>::new_with_options(
112+
&lex_src, lex_flags,
113+
) {
114+
Ok(x) => x,
115+
Err(errs) => {
116+
eprintln!("\n{ERROR}{}", lex_diag.file_location_msg("", None));
117+
for e in errs {
118+
eprintln!(
119+
"{}",
120+
&indent(" ", &lex_diag.format_error(e).to_string())
121+
);
122+
}
123+
process::exit(1);
124+
}
125+
};
126+
lexerdef
127+
}
128+
_ => {
129+
return Err(ErrorString("Unrecognized lexer kind".to_string()))?;
130+
}
131+
};
132+
{
133+
let unused_header_values = header.unused();
134+
if !unused_header_values.is_empty() {
135+
return Err(ErrorString(format!(
136+
"Unused header values: {}",
137+
unused_header_values.join(", ")
138+
)))?;
139+
}
140+
}
82141
let input = &read_file(&matches.free[1]);
83142
for r in lexerdef.lexer(input).iter() {
84143
match r {
@@ -93,4 +152,19 @@ fn main() {
93152
}
94153
}
95154
}
155+
Ok(())
156+
}
157+
158+
/// Indents a multi-line string and trims any trailing newline.
159+
/// This currently assumes that indentation on blank lines does not matter.
160+
///
161+
/// The algorithm used by this function is:
162+
/// 1. Prefix `s` with the indentation, indenting the first line.
163+
/// 2. Trim any trailing newlines.
164+
/// 3. Replace all newlines with `\n{indent}`` to indent all lines after the first.
165+
///
166+
/// It is plausible that we should a step 4, but currently do not:
167+
/// 4. Replace all `\n{indent}\n` with `\n\n`
168+
fn indent(indent: &str, s: &str) -> String {
169+
format!("{indent}{}\n", s.trim_end_matches('\n')).replace('\n', &format!("\n{}", indent))
96170
}

0 commit comments

Comments
 (0)