Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 99 additions & 25 deletions lrlex/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,38 @@
use getopts::Options;
use std::{
env,
error::Error,
fmt,
fs::File,
io::{stderr, stdin, Read, Write},
path::Path,
process,
str::FromStr,
};

use cfgrammar::{newlinecache::NewlineCache, Spanned};
use lrlex::{DefaultLexerTypes, LRNonStreamingLexerDef, LexerDef};
use lrpar::{Lexeme, Lexer};
use cfgrammar::header::{GrmtoolsSectionParser, HeaderValue};
use lrlex::{DefaultLexerTypes, LRNonStreamingLexerDef, LexFlags, LexerDef, LexerKind};
use lrpar::{
diagnostics::{DiagnosticFormatter, SpannedDiagnosticFormatter},
Lexeme, Lexer,
};

const ERROR: &str = "[Error]";

/// A string which uses `Display` for it's `Debug` impl.
struct ErrorString(String);
impl fmt::Display for ErrorString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let ErrorString(s) = self;
write!(f, "{}", s)
}
}
impl fmt::Debug for ErrorString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let ErrorString(s) = self;
write!(f, "{}", s)
}
}
impl Error for ErrorString {}

fn usage(prog: &str, msg: &str) {
let path = Path::new(prog);
Expand Down Expand Up @@ -42,43 +64,80 @@ fn read_file(path: &str) -> String {
s
}

fn main() {
fn main() -> Result<(), Box<dyn Error>> {
let args: Vec<String> = env::args().collect();
let prog = args[0].clone();
let matches = match Options::new().optflag("h", "help", "").parse(&args[1..]) {
Ok(m) => m,
Err(f) => {
usage(&prog, f.to_string().as_str());
return;
return Ok(());
}
};
if matches.opt_present("h") || matches.free.len() != 2 {
usage(&prog, "");
return;
return Ok(());
}

let lex_l_path = &matches.free[0];
let lex_src = read_file(lex_l_path);
let lexerdef = LRNonStreamingLexerDef::<DefaultLexerTypes<u32>>::from_str(&lex_src)
.unwrap_or_else(|errs| {
let nlcache = NewlineCache::from_str(&lex_src).unwrap();
for e in errs {
if let Some((line, column)) = nlcache
.byte_to_line_num_and_col_num(&lex_src, e.spans().first().unwrap().start())
{
writeln!(
stderr(),
"{}: {} at line {line} column {column}",
&lex_l_path,
&e
)
.ok();
} else {
writeln!(stderr(), "{}: {}", &lex_l_path, &e).ok();
}
let lex_diag = SpannedDiagnosticFormatter::new(&lex_src, Path::new(lex_l_path));
let (mut header, _) = match GrmtoolsSectionParser::new(&lex_src, false).parse() {
Ok(x) => x,
Err(es) => {
eprintln!(
"\n{ERROR}{}",
lex_diag.file_location_msg(" parsing the `%grmtools` section", None)
);
for e in es {
eprintln!(
"{}",
&indent(" ", &lex_diag.format_error(e).to_string())
);
}
process::exit(1);
});
}
};
header.mark_used(&"lexerkind".to_string());
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marking as used here, but never doing the check for unused values.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be fixed in 51b4b97

let lexerkind = if let Some(HeaderValue(_, lk_val)) = header.get("lexerkind") {
LexerKind::try_from(lk_val)?
} else {
LexerKind::LRNonStreamingLexer
};

let lexerdef = match lexerkind {
LexerKind::LRNonStreamingLexer => {
let lex_flags = LexFlags::try_from(&mut header)?;
let lexerdef = match LRNonStreamingLexerDef::<DefaultLexerTypes<u32>>::new_with_options(
&lex_src, lex_flags,
) {
Ok(x) => x,
Err(errs) => {
eprintln!("\n{ERROR}{}", lex_diag.file_location_msg("", None));
for e in errs {
eprintln!(
"{}",
&indent(" ", &lex_diag.format_error(e).to_string())
);
}
process::exit(1);
}
};
lexerdef
}
_ => {
return Err(ErrorString("Unrecognized lexer kind".to_string()))?;
}
};
{
let unused_header_values = header.unused();
if !unused_header_values.is_empty() {
return Err(ErrorString(format!(
"Unused header values: {}",
unused_header_values.join(", ")
)))?;
}
}
let input = &read_file(&matches.free[1]);
for r in lexerdef.lexer(input).iter() {
match r {
Expand All @@ -93,4 +152,19 @@ fn main() {
}
}
}
Ok(())
}

/// Indents a multi-line string and trims any trailing newline.
/// This currently assumes that indentation on blank lines does not matter.
///
/// The algorithm used by this function is:
/// 1. Prefix `s` with the indentation, indenting the first line.
/// 2. Trim any trailing newlines.
/// 3. Replace all newlines with `\n{indent}`` to indent all lines after the first.
///
/// It is plausible that we should a step 4, but currently do not:
/// 4. Replace all `\n{indent}\n` with `\n\n`
fn indent(indent: &str, s: &str) -> String {
format!("{indent}{}\n", s.trim_end_matches('\n')).replace('\n', &format!("\n{}", indent))
}