Simplify error handling, finish up LR conflict message generation

This commit is contained in:
Max Brunsfeld 2019-01-04 12:42:45 -08:00
parent d0c3e26e84
commit ba96e4961b
6 changed files with 117 additions and 39 deletions

View file

@ -455,9 +455,9 @@ impl<'a> ParseTableBuilder<'a> {
self.symbol_name(&conflicting_lookahead)
)
.unwrap();
write!(&mut msg, "Possible interpretations:\n").unwrap();
write!(&mut msg, "Possible interpretations:\n\n").unwrap();
for (i, item) in conflicting_items.iter().enumerate() {
write!(&mut msg, "\n {}:", i).unwrap();
write!(&mut msg, " {}:", i + 1).unwrap();
for preceding_symbol in preceding_symbols
.iter()
@ -501,11 +501,89 @@ impl<'a> ParseTableBuilder<'a> {
)
.unwrap();
}
write!(&mut msg, "\n").unwrap();
}
// TODO - generate suggested resolutions
let mut resolution_count = 0;
write!(&mut msg, "\nPossible resolutions:\n\n").unwrap();
let shift_items = conflicting_items
.iter()
.filter(|i| !i.is_done())
.cloned()
.collect::<Vec<_>>();
if shift_items.len() > 0 {
resolution_count += 1;
write!(
&mut msg,
" {}: Specify a higher precedence in",
resolution_count
)
.unwrap();
for (i, item) in shift_items.iter().enumerate() {
if i > 0 {
write!(&mut msg, " and").unwrap();
}
write!(
&mut msg,
" `{}`",
self.symbol_name(&Symbol::non_terminal(item.variable_index as usize))
)
.unwrap();
}
write!(&mut msg, " than in the other rules.\n").unwrap();
}
Err(Error::ConflictError(msg))
if considered_associativity {
resolution_count += 1;
write!(
&mut msg,
" {}: Specify a left or right associativity in ",
resolution_count
)
.unwrap();
for (i, item) in conflicting_items.iter().filter(|i| i.is_done()).enumerate() {
if i > 0 {
write!(&mut msg, " and ").unwrap();
}
write!(
&mut msg,
"{}",
self.symbol_name(&Symbol::non_terminal(item.variable_index as usize))
)
.unwrap();
}
}
for item in &conflicting_items {
if item.is_done() {
resolution_count += 1;
write!(
&mut msg,
" {}: Specify a higher precedence in `{}` than in the other rules.\n",
resolution_count,
self.symbol_name(&Symbol::non_terminal(item.variable_index as usize))
)
.unwrap();
}
}
resolution_count += 1;
write!(
&mut msg,
" {}: Add a conflict for these rules: ",
resolution_count
)
.unwrap();
for (i, symbol) in actual_conflict.iter().enumerate() {
if i > 0 {
write!(&mut msg, ", ").unwrap();
}
write!(&mut msg, "{}", self.symbol_name(symbol)).unwrap();
}
write!(&mut msg, "\n").unwrap();
Err(Error(msg))
}
fn get_auxiliary_node_info(
@ -517,8 +595,11 @@ impl<'a> ParseTableBuilder<'a> {
.entries
.keys()
.filter_map(|item| {
if item.symbol() == Some(symbol) {
None
let variable_index = item.variable_index as usize;
if item.symbol() == Some(symbol)
&& !self.syntax_grammar.variables[variable_index].is_auxiliary()
{
Some(Symbol::non_terminal(variable_index))
} else {
None
}

View file

@ -1,25 +1,24 @@
#[derive(Debug)]
pub enum Error {
GrammarError(String),
SymbolError(String),
RegexError(String),
ConflictError(String),
}
pub struct Error(pub String);
pub type Result<T> = std::result::Result<T, Error>;
impl Error {
pub fn grammar(message: &str) -> Self {
Error::GrammarError(message.to_string())
Error(format!("Grammar error: {}", message))
}
pub fn regex(message: &str) -> Self {
Error::RegexError(message.to_string())
Error(format!("Regex error: {}", message))
}
pub fn undefined_symbol(name: &str) -> Self {
Error(format!("Undefined symbol `{}`", name))
}
}
impl From<serde_json::Error> for Error {
fn from(error: serde_json::Error) -> Self {
Error::GrammarError(error.to_string())
Error(error.to_string())
}
}

View file

@ -11,7 +11,7 @@ use clap::{App, Arg, SubCommand};
use std::env;
use std::io::Write;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::process::{exit, Command, Stdio};
mod build_tables;
mod error;
@ -25,7 +25,14 @@ mod render;
mod rules;
mod tables;
fn main() -> error::Result<()> {
fn main() {
if let Err(e) = run() {
eprintln!("{}", e.0);
exit(1);
}
}
fn run() -> error::Result<()> {
let matches = App::new("tree-sitter")
.version("0.1")
.author("Max Brunsfeld <maxbrunsfeld@gmail.com>")

View file

@ -64,12 +64,7 @@ pub(crate) fn expand_tokens(mut grammar: ExtractedLexicalGrammar) -> Result<Lexi
let last_state_id = builder.nfa.last_state_id();
builder
.expand_rule(&variable.rule, last_state_id)
.map_err(|e| match e {
Error::RegexError(msg) => {
Error::RegexError(format!("Rule {} {}", variable.name, msg))
}
_ => e,
})?;
.map_err(|Error(msg)| Error(format!("Rule {} {}", variable.name, msg)))?;
if !is_immediate_token {
builder.is_sep = true;
@ -97,7 +92,7 @@ impl NfaBuilder {
Rule::Pattern(s) => {
let ast = parse::Parser::new()
.parse(&s)
.map_err(|e| Error::GrammarError(e.to_string()))?;
.map_err(|e| Error(e.to_string()))?;
self.expand_regex(&ast, next_state_id)
}
Rule::String(s) => {

View file

@ -89,7 +89,7 @@ pub(super) fn extract_tokens(
if let Rule::Symbol(symbol) = rule {
let new_symbol = symbol_replacer.replace_symbol(symbol);
if new_symbol.is_non_terminal() {
return Err(Error::GrammarError(format!(
return Err(Error(format!(
"Non-token symbol '{}' cannot be used as an extra token",
&variables[new_symbol.index].name
)));
@ -110,7 +110,7 @@ pub(super) fn extract_tokens(
let rule = symbol_replacer.replace_symbols_in_rule(&external_token.rule);
if let Rule::Symbol(symbol) = rule {
if symbol.is_non_terminal() {
return Err(Error::GrammarError(format!(
return Err(Error(format!(
"Rule '{}' cannot be used as both an external token and a non-terminal rule",
&variables[symbol.index].name,
)));
@ -130,7 +130,7 @@ pub(super) fn extract_tokens(
})
}
} else {
return Err(Error::GrammarError(format!(
return Err(Error(format!(
"Non-symbol rules cannot be used as external tokens"
)));
}
@ -140,7 +140,7 @@ pub(super) fn extract_tokens(
if let Some(token) = grammar.word_token {
let token = symbol_replacer.replace_symbol(token);
if token.is_non_terminal() {
return Err(Error::GrammarError(format!(
return Err(Error(format!(
"Non-terminal symbol '{}' cannot be used as the word token",
&variables[token.index].name
)));
@ -475,7 +475,7 @@ mod test {
grammar.extra_tokens = vec![Rule::non_terminal(1)];
match extract_tokens(grammar) {
Err(Error::GrammarError(s)) => {
Err(Error(s)) => {
assert_eq!(
s,
"Non-token symbol 'rule_1' cannot be used as an extra token"
@ -503,7 +503,7 @@ mod test {
grammar.external_tokens = vec![Variable::named("rule_1", Rule::non_terminal(1))];
match extract_tokens(grammar) {
Err(Error::GrammarError(s)) => {
Err(Error(s)) => {
assert_eq!(s, "Rule 'rule_1' cannot be used as both an external token and a non-terminal rule");
}
_ => {

View file

@ -7,7 +7,7 @@ pub(super) fn intern_symbols(grammar: &InputGrammar) -> Result<InternedGrammar>
let interner = Interner { grammar };
if variable_type_for_name(&grammar.variables[0].name) == VariableType::Hidden {
return Err(Error::GrammarError(
return Err(Error(
"Grammar's start rule must be visible".to_string(),
));
}
@ -44,7 +44,7 @@ pub(super) fn intern_symbols(grammar: &InputGrammar) -> Result<InternedGrammar>
interned_conflict.push(
interner
.intern_name(&name)
.ok_or_else(|| symbol_error(name))?,
.ok_or_else(|| Error::undefined_symbol(name))?,
);
}
expected_conflicts.push(interned_conflict);
@ -62,7 +62,7 @@ pub(super) fn intern_symbols(grammar: &InputGrammar) -> Result<InternedGrammar>
word_token = Some(
interner
.intern_name(&name)
.ok_or_else(|| symbol_error(&name))?,
.ok_or_else(|| Error::undefined_symbol(&name))?,
);
}
@ -107,7 +107,7 @@ impl<'a> Interner<'a> {
if let Some(symbol) = self.intern_name(&name) {
Ok(Rule::Symbol(symbol))
} else {
Err(symbol_error(name))
Err(Error::undefined_symbol(name))
}
}
@ -134,10 +134,6 @@ impl<'a> Interner<'a> {
}
}
fn symbol_error(name: &str) -> Error {
Error::SymbolError(format!("Undefined symbol '{}'", name))
}
fn variable_type_for_name(name: &str) -> VariableType {
if name.starts_with("_") {
VariableType::Hidden
@ -223,7 +219,7 @@ mod tests {
let result = intern_symbols(&build_grammar(vec![Variable::named("x", Rule::named("y"))]));
match result {
Err(Error::SymbolError(message)) => assert_eq!(message, "Undefined symbol 'y'"),
Err(Error(message)) => assert_eq!(message, "Undefined symbol 'y'"),
_ => panic!("Expected an error but got none"),
}
}