From 01e7ee59b779f4cf58153013fcd0f932c18f73d3 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Fri, 23 Mar 2018 18:34:26 -0500 Subject: [PATCH] Unify Errors so better error messages can be had. --- src/ast.rs | 23 ------ src/error.rs | 34 +++++++- src/parse.rs | 199 +++++++++++++++++++++++++---------------------- src/tokenizer.rs | 29 +++---- 4 files changed, 152 insertions(+), 133 deletions(-) diff --git a/src/ast.rs b/src/ast.rs index fd706f0..199b305 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -24,29 +24,6 @@ use std::cmp::PartialEq; use std::hash::Hasher; use std::hash::Hash; -/// Encodes a parsing error with position information and a helpful description. -#[derive(Debug, PartialEq)] -pub struct ParseError { - pub pos: Position, - pub description: String, -} - -impl std::fmt::Display for ParseError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - write!( - f, - "Parsing Error {} at line: {} column: {}", - self.description, self.pos.line, self.pos.column - ) - } -} - -impl std::error::Error for ParseError { - fn description(&self) -> &str { - &self.description - } -} - macro_rules! enum_type_equality { ( $slf:ident, $r:expr, $( $l:pat ),* ) => { match $slf { diff --git a/src/error.rs b/src/error.rs index 3a6a25e..462f312 100644 --- a/src/error.rs +++ b/src/error.rs @@ -18,6 +18,8 @@ use std::fmt; use ast::*; +use nom; + /// ErrorType defines the various types of errors that can result from compiling UCG into an /// output format. pub enum ErrorType { @@ -32,6 +34,7 @@ pub enum ErrorType { // Parsing Errors UnexpectedToken, EmptyExpression, + ParseError, } impl fmt::Display for ErrorType { @@ -46,6 +49,7 @@ impl fmt::Display for ErrorType { &ErrorType::FormatError => "FormatError", &ErrorType::UnexpectedToken => "UnexpectedToken", &ErrorType::EmptyExpression => "EmptyExpression", + &ErrorType::ParseError => "ParseError", }; w.write_str(name) } @@ -56,6 +60,7 @@ pub struct Error { pub err_type: ErrorType, pub pos: Position, pub msg: String, + pub cause: Option>, _pkgonly: (), } @@ -65,16 +70,41 @@ impl Error { err_type: t, pos: pos, msg: msg.into(), + cause: None, _pkgonly: (), } } + + pub fn new_with_cause>( + msg: S, + t: ErrorType, + pos: Position, + cause: Error, + ) -> Self { + let mut e = Self::new(msg, t, pos); + e.cause = Some(Box::new(cause)); + return e; + } + + pub fn new_with_errorkind>( + msg: S, + t: ErrorType, + pos: Position, + cause: nom::ErrorKind, + ) -> Self { + match cause { + nom::ErrorKind::Custom(e) => Self::new_with_cause(msg, t, pos, e), + // TODO(jwall): We could get more creative here with our messaging. + _ => Self::new(msg, t, pos), + } + } } impl fmt::Debug for Error { fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { write!( w, - "{}: \"{}\" {}:{}", + "{}: \"{}\" at line: {} column: {}", self.err_type, self.msg, self.pos.line, self.pos.column ) } @@ -84,7 +114,7 @@ impl fmt::Display for Error { fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { write!( w, - "{}: \"{}\" {}:{}", + "{}: \"{}\" at line: {} column: {}", self.err_type, self.msg, self.pos.line, self.pos.column ) } diff --git a/src/parse.rs b/src/parse.rs index ed9a7ed..685b7fb 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -23,10 +23,11 @@ use nom::IResult; use ast::*; use tokenizer::*; +use error; -type NomResult<'a, O> = nom::IResult, O, ParseError>; +type NomResult<'a, O> = nom::IResult, O, error::Error>; -type ParseResult = Result; +type ParseResult = Result; fn symbol_to_value(s: &Token) -> ParseResult { Ok(Value::Symbol(value_node!( @@ -36,7 +37,7 @@ fn symbol_to_value(s: &Token) -> ParseResult { } // symbol is a bare unquoted field. -named!(symbol, +named!(symbol, match_type!(BAREWORD => symbol_to_value) ); @@ -48,7 +49,7 @@ fn str_to_value(s: &Token) -> ParseResult { } // quoted_value is a quoted string. -named!(quoted_value, +named!(quoted_value, match_type!(STR => str_to_value) ); @@ -65,10 +66,11 @@ fn triple_to_number(v: (Option, Option, Option)) -> ParseRe let i = match FromStr::from_str(pref) { Ok(i) => i, Err(_) => { - return Err(ParseError { - description: format!("Not an integer! {}", pref), - pos: pref_pos, - }) + return Err(error::Error::new( + format!("Not an integer! {}", pref), + error::ErrorType::UnexpectedToken, + pref_pos, + )) } }; return Ok(Value::Int(value_node!(i, pref_pos))); @@ -88,10 +90,11 @@ fn triple_to_number(v: (Option, Option, Option)) -> ParseRe let f = match FromStr::from_str(&to_parse) { Ok(f) => f, Err(_) => { - return Err(ParseError { - description: format!("Not a float! {}", to_parse), - pos: maybepos.unwrap(), - }) + return Err(error::Error::new( + format!("Not a float! {}", to_parse), + error::ErrorType::UnexpectedToken, + maybepos.unwrap(), + )) } }; return Ok(Value::Float(value_node!(f, pref_pos))); @@ -109,7 +112,7 @@ fn triple_to_number(v: (Option, Option, Option)) -> ParseRe // *IMPORTANT* // It also means this combinator is risky when used with partial // inputs. So handle with care. -named!(number, +named!(number, map_res!(alt!( complete!(do_parse!( // 1.0 prefix: match_type!(DIGIT) >> @@ -138,7 +141,7 @@ named!(number, ); // trace_macros!(false); -named!(boolean_value, +named!(boolean_value, do_parse!( b: match_type!(BOOLEAN) >> (Value::Boolean(Positioned{ @@ -149,7 +152,7 @@ named!(boolean_value, ); named!( - field_value, + field_value, do_parse!( field: match_type!(BAREWORD) >> punct!("=") >> @@ -167,13 +170,13 @@ fn vec_to_tuple(t: (Position, Option)) -> ParseResult { ))) } -named!(field_list, +named!(field_list, separated_list!(punct!(","), field_value) ); named!( #[doc="Capture a tuple of named fields with values. {=,...}"], - tuple, + tuple, map_res!( do_parse!( pos: pos >> @@ -193,7 +196,7 @@ fn tuple_to_list>(t: (Sp, Vec)) -> ParseResult, +named!(list_value, map_res!( do_parse!( start: punct!("[") >> @@ -205,7 +208,7 @@ named!(list_value, ) ); -named!(empty_value, +named!(empty_value, do_parse!( pos: pos >> match_type!(EMPTY) >> @@ -213,7 +216,7 @@ named!(empty_value, ) ); -named!(value, +named!(value, alt!( boolean_value | empty_value | @@ -228,7 +231,7 @@ fn value_to_expression(v: Value) -> ParseResult { Ok(Expression::Simple(v)) } -named!(simple_expression, +named!(simple_expression, map_res!( value, value_to_expression @@ -266,20 +269,20 @@ macro_rules! do_binary_expr { } // trace_macros!(true); -named!(add_expression, +named!(add_expression, do_binary_expr!(punct!("+"), BinaryExprType::Add) ); // trace_macros!(false); -named!(sub_expression, +named!(sub_expression, do_binary_expr!(punct!("-"), BinaryExprType::Sub) ); -named!(mul_expression, +named!(mul_expression, do_binary_expr!(punct!("*"), BinaryExprType::Mul) ); -named!(div_expression, +named!(div_expression, do_binary_expr!(punct!("/"), BinaryExprType::Div) ); @@ -287,7 +290,7 @@ fn expression_to_grouped_expression(e: Expression) -> ParseResult { Ok(Expression::Grouped(Box::new(e))) } -named!(grouped_expression, +named!(grouped_expression, map_res!( preceded!(punct!("("), terminated!(expression, punct!(")"))), expression_to_grouped_expression @@ -346,10 +349,11 @@ fn selector_list(input: TokenIter) -> NomResult { }; if list.is_empty() { - return IResult::Error(nom::ErrorKind::Custom(ParseError { - description: "(.) with no selector fields after".to_string(), - pos: is_dot.unwrap().pos, - })); + return IResult::Error(nom::ErrorKind::Custom(error::Error::new( + "(.) with no selector fields after".to_string(), + error::ErrorType::IncompleteParsing, + is_dot.unwrap().pos, + ))); } else { (rest, Some(list)) } @@ -374,7 +378,7 @@ fn tuple_to_copy(t: (SelectorDef, FieldList)) -> ParseResult { })) } -named!(copy_expression, +named!(copy_expression, map_res!( do_parse!( pos: pos >> @@ -402,16 +406,17 @@ fn tuple_to_macro(mut t: (Position, Vec, Value)) -> ParseResult Err(ParseError { - description: format!("Expected Tuple Got {:?}", val), - pos: t.0, - }), + val => Err(error::Error::new( + format!("Expected Tuple Got {:?}", val), + error::ErrorType::UnexpectedToken, + t.0, + )), } } -named!(arglist, ParseError>, separated_list!(punct!(","), symbol)); +named!(arglist, error::Error>, separated_list!(punct!(","), symbol)); -named!(macro_expression, +named!(macro_expression, map_res!( do_parse!( pos: pos >> @@ -435,14 +440,15 @@ fn tuple_to_select(t: (Position, Expression, Expression, Value)) -> ParseResult< tuple: v.val, pos: t.0, })), - val => Err(ParseError { - description: format!("Expected Tuple Got {:?}", val), - pos: t.0, - }), + val => Err(error::Error::new( + format!("Expected Tuple Got {:?}", val), + error::ErrorType::UnexpectedToken, + t.0, + )), } } -named!(select_expression, +named!(select_expression, map_res!( do_parse!( start: word!("select") >> @@ -463,7 +469,7 @@ fn tuple_to_format(t: (Token, Vec)) -> ParseResult { })) } -named!(format_expression, +named!(format_expression, map_res!( do_parse!( tmpl: match_type!(STR) >> @@ -485,10 +491,11 @@ fn tuple_to_call(t: (Position, Value, Vec)) -> ParseResult ParseResult { ))) } -named!(selector_value, +named!(selector_value, map_res!( do_parse!( sl: selector_list >> @@ -510,7 +517,7 @@ named!(selector_value, ) ); -named!(call_expression, +named!(call_expression, map_res!( do_parse!( macroname: selector_value >> @@ -557,13 +564,14 @@ fn tuple_to_list_op(tpl: (Position, Token, Value, Value)) -> ParseResult ParseResult { - return Err(ParseError { - description: format!("Missing a result field for the macro"), - pos: pos, - }); + return Err(error::Error::new( + format!("Missing a result field for the macro"), + error::ErrorType::IncompleteParsing, + pos, + )); } &mut Some(ref mut tl) => { if tl.len() < 1 { - return Err(ParseError { - description: format!("Missing a result field for the macro"), - pos: def.pos.clone(), - }); + return Err(error::Error::new( + format!("Missing a result field for the macro"), + error::ErrorType::IncompleteParsing, + def.pos.clone(), + )); } let fname = tl.pop(); fname.unwrap().fragment @@ -598,18 +608,20 @@ fn tuple_to_list_op(tpl: (Position, Token, Value, Value)) -> ParseResult, +named!(list_op_expression, map_res!( do_parse!( pos: pos >> @@ -632,7 +644,7 @@ named!(list_op_expression, // *IMPORTANT* // It also means this combinator is risky when used with partial // inputs. So handle with care. -named!(expression, +named!(expression, do_parse!( expr: alt!( complete!(list_op_expression) | @@ -656,7 +668,7 @@ fn expression_to_statement(v: Expression) -> ParseResult { Ok(Statement::Expression(v)) } -named!(expression_statement, +named!(expression_statement, map_res!( terminated!(expression, punct!(";")), expression_to_statement @@ -670,7 +682,7 @@ fn tuple_to_let(t: (Token, Expression)) -> ParseResult { })) } -named!(let_statement, +named!(let_statement, map_res!( do_parse!( word!("let") >> @@ -691,7 +703,7 @@ fn tuple_to_import(t: (Token, Token)) -> ParseResult { })) } -named!(import_statement, +named!(import_statement, map_res!( do_parse!( word!("import") >> @@ -705,7 +717,7 @@ named!(import_statement, ) ); -named!(statement, +named!(statement, do_parse!( stmt: alt_complete!( import_statement | @@ -716,8 +728,8 @@ named!(statement, ) ); -/// Parses a LocatedSpan into a list of Statements or a ParseError. -pub fn parse(input: LocatedSpan<&str>) -> Result, ParseError> { +/// Parses a LocatedSpan into a list of Statements or an error::Error. +pub fn parse(input: LocatedSpan<&str>) -> Result, error::Error> { match tokenize(input) { Ok(tokenized) => { let mut out = Vec::new(); @@ -734,25 +746,25 @@ pub fn parse(input: LocatedSpan<&str>) -> Result, ParseError> { return Err(e); } IResult::Error(e) => { - return Err(ParseError { - description: format!( - "Statement Parse error: {:?} current token: {:?}", - e, i_[0] - ), - pos: Position { + return Err(error::Error::new_with_errorkind( + format!("Statement Parse error: {:?} current token: {:?}", e, i_[0]), + error::ErrorType::ParseError, + Position { line: i_[0].pos.line, column: i_[0].pos.column, }, - }); + e, + )); } IResult::Incomplete(ei) => { - return Err(ParseError { - description: format!("Unexpected end of parsing input: {:?}", ei), - pos: Position { + return Err(error::Error::new( + format!("Unexpected end of parsing input: {:?}", ei), + error::ErrorType::IncompleteParsing, + Position { line: i_[0].pos.line, column: i_[0].pos.column, }, - }); + )); } IResult::Done(rest, stmt) => { out.push(stmt); @@ -766,12 +778,11 @@ pub fn parse(input: LocatedSpan<&str>) -> Result, ParseError> { return Ok(out); } Err(e) => { - // FIXME(jwall): We should really capture the location - // of the tokenization error here. - return Err(ParseError { - description: format!("Tokenize Error: {:?}", e.1), - pos: e.0, - }); + return Err(error::Error::new( + format!("Tokenization Error {:?}", e.1), + error::ErrorType::ParseError, + e.0, + )); } } } diff --git a/src/tokenizer.rs b/src/tokenizer.rs index e93f359..aca0cc4 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -18,6 +18,7 @@ use nom; use nom::{alpha, digit, is_alphanumeric, multispace}; use nom::{InputIter, InputLength, Slice}; use ast::*; +use error; use std; use std::result::Result; @@ -327,8 +328,6 @@ named!(token( Span ) -> Token, end_of_input) ); -// TODO(jwall): This should return a ParseError instead. - /// Consumes an input Span and returns either a Vec or a nom::ErrorKind. pub fn tokenize(input: Span) -> Result, (Position, nom::ErrorKind)> { let mut out = Vec::new(); @@ -378,7 +377,7 @@ pub fn tokenize(input: Span) -> Result, (Position, nom::ErrorKind)> { Ok(out) } -pub fn token_clone(t: &Token) -> Result { +pub fn token_clone(t: &Token) -> Result { Ok(t.clone()) } @@ -448,10 +447,10 @@ macro_rules! match_type { use std::convert::Into; if i_.input_len() == 0 { nom::IResult::Error( - nom::ErrorKind::Custom(ParseError{ - description: format!("End of Input! {}", $msg), - pos: Position{line: 0, column: 0} - })) + nom::ErrorKind::Custom(error::Error::new( + format!("End of Input! {}", $msg), + error::ErrorType::IncompleteParsing, + Position{line: 0, column: 0}))) } else { let tok = &(i_[0]); if tok.typ == $t { @@ -461,9 +460,10 @@ macro_rules! match_type { nom::ErrorKind::Custom(e.into())), } } else { - nom::IResult::Error(nom::ErrorKind::Custom(ParseError{ - description: $msg.to_string(), - pos: tok.pos.clone()})) + nom::IResult::Error(nom::ErrorKind::Custom(error::Error::new( + $msg.to_string(), + error::ErrorType::UnexpectedToken, + tok.pos.clone()))) } } } @@ -502,9 +502,10 @@ macro_rules! match_token { nom::ErrorKind::Custom(e.into())), } } else { - nom::IResult::Error(nom::ErrorKind::Custom(ParseError{ - description: format!("{} Instead is ({})", $msg, tok.fragment), - pos: tok.pos.clone()})) + nom::IResult::Error(nom::ErrorKind::Custom(error::Error::new( + format!("{} Instead is ({})", $msg, tok.fragment), + error::ErrorType::UnexpectedToken, + tok.pos.clone()))) } } }; @@ -525,7 +526,7 @@ macro_rules! word { } /// pos gets the current position from a TokenIter input without consuming it. -pub fn pos(i: TokenIter) -> nom::IResult { +pub fn pos(i: TokenIter) -> nom::IResult { let tok = &i[0]; let line = tok.pos.line; let column = tok.pos.column;