Better error handling

This commit is contained in:
elkowar 2021-07-16 18:57:22 +02:00
parent d8ffd3153d
commit 3b6180ad7d
No known key found for this signature in database
GPG key ID: E321AD71B1D1F27F
7 changed files with 66 additions and 135 deletions

View file

@ -1,11 +1,19 @@
use std::collections::HashMap;
use simplexpr::dynval::DynVal;
fn main() {
let mut files = codespan_reporting::files::SimpleFiles::new();
let input = "12 + \"hi\" * foo ) ? bar == baz : false";
let input = "12 + foo * 2 < 2 ? bar == true : false";
let _ = files.add("foo.eww", input);
let ast = simplexpr::parser::parse_string(input);
match ast {
let mut vars = HashMap::new();
vars.insert("foo".to_string(), "2".into());
match ast.and_then(|x| x.eval(&vars).map_err(|e| e.into())) {
Ok(ast) => {
println!("{:?}", ast);
}

View file

@ -5,6 +5,20 @@ use serde::{Deserialize, Serialize};
#[derive(Eq, PartialEq, Clone, Copy, Serialize, Deserialize)]
pub struct Span(pub usize, pub usize);
pub trait MaybeSpanned {
fn try_span(&self) -> Option<Span>;
}
impl MaybeSpanned for Span {
fn try_span(&self) -> Option<Span> {
Some(*self)
}
}
pub fn span_between(a: impl MaybeSpanned, b: impl MaybeSpanned) -> Option<Span> {
Some(Span(a.try_span()?.0, b.try_span()?.1))
}
impl std::fmt::Display for Span {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}..{}", self.0, self.1)
@ -51,6 +65,11 @@ pub enum SimplExpr {
JsonAccess(Span, Box<SimplExpr>, Box<SimplExpr>),
FunctionCall(Span, String, Vec<SimplExpr>),
}
impl MaybeSpanned for SimplExpr {
fn try_span(&self) -> Option<Span> {
Some(self.span())
}
}
impl std::fmt::Display for SimplExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -68,6 +87,10 @@ impl std::fmt::Display for SimplExpr {
}
}
impl SimplExpr {
pub fn literal(span: Span, s: String) -> Self {
Self::Literal(span, DynVal(s, Some(span)))
}
pub fn span(&self) -> Span {
match self {
SimplExpr::Literal(span, _) => *span,

View file

@ -1,4 +1,4 @@
use crate::ast::Span;
use crate::ast::{MaybeSpanned, Span};
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use std::{convert::TryFrom, fmt, iter::FromIterator};
@ -6,7 +6,7 @@ use std::{convert::TryFrom, fmt, iter::FromIterator};
pub type Result<T> = std::result::Result<T, ConversionError>;
#[derive(Debug, thiserror::Error)]
#[error("Type error: Failed to turn {value} into a {target_type}")]
#[error("Failed to turn {value} into a {target_type}")]
pub struct ConversionError {
value: DynVal,
target_type: &'static str,
@ -17,11 +17,21 @@ impl ConversionError {
fn new(value: DynVal, target_type: &'static str, source: Box<dyn std::error::Error>) -> Self {
ConversionError { value, target_type, source: Some(source) }
}
pub fn span(&self) -> Option<Span> {
self.value.1
}
}
#[derive(Clone, Deserialize, Serialize, Default)]
pub struct DynVal(pub String, pub Option<Span>);
impl MaybeSpanned for DynVal {
fn try_span(&self) -> Option<Span> {
self.1
}
}
impl From<String> for DynVal {
fn from(s: String) -> Self {
DynVal(s, None)
@ -30,7 +40,7 @@ impl From<String> for DynVal {
impl fmt::Display for DynVal {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
write!(f, "\"{}\"", self.0)
}
}
impl fmt::Debug for DynVal {
@ -106,6 +116,10 @@ impl From<&serde_json::Value> for DynVal {
}
impl DynVal {
pub fn at(self, span: Span) -> Self {
DynVal(self.0, Some(span))
}
pub fn from_string(s: String) -> Self {
DynVal(s, None)
}

View file

@ -6,16 +6,13 @@ pub type Result<T> = std::result::Result<T, Error>;
pub enum Error {
#[error("Parse error: {source}")]
ParseError { source: lalrpop_util::ParseError<usize, lexer::Token, lexer::LexicalError> },
#[error("Conversion error: {source}")]
ConversionError {
#[from]
source: dynval::ConversionError,
},
#[error("Conversion error: {0}")]
ConversionError(#[from] dynval::ConversionError),
#[error("At: {0}: {1}")]
Spanned(Span, Box<dyn std::error::Error>),
#[error(transparent)]
Eval(crate::eval::EvalError),
Eval(#[from] crate::eval::EvalError),
#[error(transparent)]
Other(#[from] Box<dyn std::error::Error>),
@ -31,6 +28,7 @@ impl Error {
Self::ParseError { source } => get_parse_error_span(source),
Self::Spanned(span, _) => Some(*span),
Self::Eval(err) => err.span(),
Self::ConversionError(err) => err.span(),
_ => None,
}
}

View file

@ -11,10 +11,10 @@ pub enum EvalError {
#[error("Invalid regex: {0}")]
InvalidRegex(#[from] regex::Error),
#[error("got unresolved variable {0}")]
#[error("got unresolved variable `{0}`")]
UnresolvedVariable(VarName),
#[error("Conversion error: {0}")]
#[error("Type error: {0}")]
ConversionError(#[from] ConversionError),
#[error("Incorrect number of arguments given to function: {0}")]
@ -34,6 +34,7 @@ impl EvalError {
pub fn span(&self) -> Option<Span> {
match self {
EvalError::Spanned(span, _) => Some(*span),
EvalError::ConversionError(err) => err.span(),
_ => None,
}
}
@ -103,10 +104,11 @@ impl SimplExpr {
}
pub fn eval(self, values: &HashMap<VarName, DynVal>) -> Result<DynVal, EvalError> {
match self {
let span = self.span();
let value = match self {
SimplExpr::Literal(_, x) => Ok(x),
SimplExpr::VarRef(span, ref name) => {
values.get(name).cloned().ok_or_else(|| EvalError::UnresolvedVariable(name.to_string()).at(span))
Ok(values.get(name).cloned().ok_or_else(|| EvalError::UnresolvedVariable(name.to_string()).at(span))?.at(span))
}
SimplExpr::BinOp(_, a, op, b) => {
let a = a.eval(values)?;
@ -167,7 +169,8 @@ impl SimplExpr {
let args = args.into_iter().map(|a| a.eval(values)).collect::<Result<_, EvalError>>()?;
call_expr_function(&function_name, args).map_err(|e| e.at(span))
}
}
};
Ok(value?.at(span))
}
}

View file

@ -1,114 +0,0 @@
use crate::ast::{SimplExpr::{self, *}, Span, BinOp::*, UnaryOp::*};
use crate::parser::lexer::{Token, LexicalError};
use crate::parser::lalrpop_helpers::*;
grammar;
extern {
type Location = usize;
type Error = LexicalError;
enum Token {
"+" => Token::Plus,
"-" => Token::Minus,
"*" => Token::Times,
"/" => Token::Div,
"%" => Token::Mod,
"==" => Token::Equals,
"!=" => Token::NotEquals,
"&&" => Token::And,
"||" => Token::Or,
">" => Token::GT,
"<" => Token::LT,
"?:" => Token::Elvis,
"=~" => Token::RegexMatch,
"!" => Token::Not,
"," => Token::Comma,
"?" => Token::Question,
":" => Token::Colon,
"(" => Token::LPren,
")" => Token::RPren,
"[" => Token::LBrack,
"]" => Token::RBrack,
"." => Token::Dot,
"true" => Token::True,
"false" => Token::False,
"identifier" => Token::Ident(<String>),
"number" => Token::NumLit(<String>),
"string" => Token::StrLit(<String>),
}
}
Comma<T>: Vec<T> = {
<mut v:(<T> ",")*> <e:T?> => match e {
None => v,
Some(e) => {
v.push(e);
v
}
}
};
pub Expr: SimplExpr = {
#[precedence(level="0")]
<Literal>,
<l:@L> <ident:"identifier"> <r:@R> => VarRef(Span(l, r), ident.to_string()),
"(" <ExprReset> ")",
#[precedence(level="1")] #[assoc(side="right")]
<l:@L> <ident:"identifier"> "(" <args: Comma<ExprReset>> ")" <r:@R> => FunctionCall(Span(l, r), ident, args),
<l:@L> <value:Expr> "[" <index: ExprReset> "]" <r:@R> => JsonAccess(Span(l, r), b(value), b(index)),
<l:@L> <value:Expr> "." <lit_l:@L> <index:"identifier"> <r:@R> => {
JsonAccess(Span(l, r), b(value), b(Literal(Span(lit_l, r), index.into())))
},
#[precedence(level="2")] #[assoc(side="right")]
<l:@L> "!" <e:Expr> <r:@R> => UnaryOp(Span(l, r), Not, b(e)),
#[precedence(level="3")] #[assoc(side="left")]
<l:@L> <le:Expr> "*" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), Times, b(re)),
<l:@L> <le:Expr> "/" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), Div, b(re)),
<l:@L> <le:Expr> "%" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), Mod, b(re)),
#[precedence(level="4")] #[assoc(side="left")]
<l:@L> <le:Expr> "+" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), Plus, b(re)),
<l:@L> <le:Expr> "-" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), Minus, b(re)),
#[precedence(level="5")] #[assoc(side="left")]
<l:@L> <le:Expr> "==" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), Equals, b(re)),
<l:@L> <le:Expr> "!=" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), NotEquals, b(re)),
<l:@L> <le:Expr> "<" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), GT, b(re)),
<l:@L> <le:Expr> ">" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), LT, b(re)),
<l:@L> <le:Expr> "=~" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), RegexMatch, b(re)),
#[precedence(level="6")] #[assoc(side="left")]
<l:@L> <le:Expr> "&&" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), And, b(re)),
<l:@L> <le:Expr> "||" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), Or, b(re)),
<l:@L> <le:Expr> "?:" <re:Expr> <r:@R> => BinOp(Span(l, r), b(le), Elvis, b(re)),
#[precedence(level="7")] #[assoc(side="right")]
<l:@L> <cond:Expr> "?" <then:ExprReset> ":" <els:Expr> <r:@R> => {
IfElse(Span(l, r), b(cond), b(then), b(els))
},
};
ExprReset = <Expr>;
Literal: SimplExpr = {
<l:@L> <x:StrLit> <r:@R> => Literal(Span(l, r), x.into()),
<l:@L> <x:"number"> <r:@R> => Literal(Span(l, r), x.into()),
<l:@L> "true" <r:@R> => Literal(Span(l, r), "true".into()),
<l:@L> "false" <r:@R> => Literal(Span(l, r), "false".into()),
}
StrLit: String = {
<x:"string"> => x[1..x.len() - 1].to_owned(),
};

View file

@ -1,4 +1,3 @@
use crate::ast::{SimplExpr::{self, *}, Span, BinOp::*, UnaryOp::*};
use crate::parser::lexer::{Token, LexicalError};
use crate::parser::lalrpop_helpers::*;
@ -103,10 +102,10 @@ pub Expr: SimplExpr = {
ExprReset = <Expr>;
Literal: SimplExpr = {
<l:@L> <x:StrLit> <r:@R> => Literal(Span(l, r), x.into()),
<l:@L> <x:"number"> <r:@R> => Literal(Span(l, r), x.into()),
<l:@L> "true" <r:@R> => Literal(Span(l, r), "true".into()),
<l:@L> "false" <r:@R> => Literal(Span(l, r), "false".into()),
<l:@L> <x:StrLit> <r:@R> => SimplExpr::literal(Span(l, r), x),
<l:@L> <x:"number"> <r:@R> => SimplExpr::literal(Span(l, r), x),
<l:@L> "true" <r:@R> => SimplExpr::literal(Span(l, r), "true".into()),
<l:@L> "false" <r:@R> => SimplExpr::literal(Span(l, r), "false".into()),
}
StrLit: String = {