diff --git a/src/build_tables/build_parse_table.rs b/src/build_tables/build_parse_table.rs index 5fc015af..e642c3cd 100644 --- a/src/build_tables/build_parse_table.rs +++ b/src/build_tables/build_parse_table.rs @@ -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::>(); + 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 } diff --git a/src/error.rs b/src/error.rs index b03efa93..9a5801f8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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 = std::result::Result; 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 for Error { fn from(error: serde_json::Error) -> Self { - Error::GrammarError(error.to_string()) + Error(error.to_string()) } } diff --git a/src/main.rs b/src/main.rs index 10820ed1..c3dbf33d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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 ") diff --git a/src/prepare_grammar/expand_tokens.rs b/src/prepare_grammar/expand_tokens.rs index 91a0e364..2678df19 100644 --- a/src/prepare_grammar/expand_tokens.rs +++ b/src/prepare_grammar/expand_tokens.rs @@ -64,12 +64,7 @@ pub(crate) fn expand_tokens(mut grammar: ExtractedLexicalGrammar) -> Result { - 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) => { diff --git a/src/prepare_grammar/extract_tokens.rs b/src/prepare_grammar/extract_tokens.rs index 115933ee..5a54d34e 100644 --- a/src/prepare_grammar/extract_tokens.rs +++ b/src/prepare_grammar/extract_tokens.rs @@ -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"); } _ => { diff --git a/src/prepare_grammar/intern_symbols.rs b/src/prepare_grammar/intern_symbols.rs index 5165875c..2e6f5b1c 100644 --- a/src/prepare_grammar/intern_symbols.rs +++ b/src/prepare_grammar/intern_symbols.rs @@ -7,7 +7,7 @@ pub(super) fn intern_symbols(grammar: &InputGrammar) -> Result 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 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 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"), } }