Skip to content

Commit

Permalink
Merge pull request #164 from epage/error
Browse files Browse the repository at this point in the history
Provide context to errors
  • Loading branch information
epage authored Jan 21, 2018
2 parents 727d992 + 521ef0e commit e46c3e0
Show file tree
Hide file tree
Showing 22 changed files with 709 additions and 265 deletions.
3 changes: 1 addition & 2 deletions src/compiler/block.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use error::Result;

use interpreter::Renderable;
use super::Result;
use super::Element;
use super::LiquidOptions;
use super::Token;
Expand Down
33 changes: 23 additions & 10 deletions src/compiler/include.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::fs::File;
use std::io::prelude::Read;
use std::path;

use error::{Error, Result};
use super::{Error, Result, ResultLiquidChainExt, ResultLiquidExt};

pub trait Include: Send + Sync + IncludeClone {
fn include(&self, path: &str) -> Result<String>;
Expand Down Expand Up @@ -38,7 +38,7 @@ impl NullInclude {

impl Include for NullInclude {
fn include(&self, relative_path: &str) -> Result<String> {
Err(Error::from(&*format!("{:?} does not exist", relative_path)))
Err(Error::with_msg("File does not exist").context("path", &relative_path.to_owned()))
}
}

Expand All @@ -57,20 +57,33 @@ impl FilesystemInclude {

impl Include for FilesystemInclude {
fn include(&self, relative_path: &str) -> Result<String> {
let root = self.root.canonicalize()?;
let root = self.root
.canonicalize()
.chain("Snippet does not exist")
.context_with(|| {
let key = "non-existent source".into();
let value = self.root.to_string_lossy().into();
(key, value)
})?;
let mut path = root.clone();
path.extend(relative_path.split('/'));
if !path.exists() {
return Err(Error::from(&*format!("{:?} does not exist", path)));
}
let path = path.canonicalize()?;
let path =
path.canonicalize()
.chain("Snippet does not exist")
.context_with(|| ("non-existent path".into(), path.to_string_lossy().into()))?;
if !path.starts_with(&root) {
return Err(Error::from(&*format!("{:?} is outside the include path", path)));
return Err(Error::with_msg("Snippet is outside of source")
.context("source", &root.to_string_lossy())
.context("full path", &path.to_string_lossy()));
}

let mut file = File::open(path)?;
let mut file = File::open(&path)
.chain("Failed to open snippet")
.context_with(|| ("full path".into(), path.to_string_lossy().into()))?;
let mut content = String::new();
file.read_to_string(&mut content)?;
file.read_to_string(&mut content)
.chain("Failed to read snippet")
.context_with(|| ("full path".into(), path.to_string_lossy().into()))?;
Ok(content)
}
}
29 changes: 23 additions & 6 deletions src/compiler/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
//! This module contains elements than can be used for writing plugins
//! but can be ignored for simple usage.
use std::fmt;

use regex::Regex;

use error::{Error, Result};
use super::{Error, Result};

use super::Token;
use super::ComparisonOperator;
Expand All @@ -17,6 +19,16 @@ pub enum Element {
Raw(String),
}

impl fmt::Display for Element {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let out = match *self {
Element::Expression(_, ref x) |
Element::Tag(_, ref x) |
Element::Raw(ref x) => x,
};
write!(f, "{}", out)
}
}
lazy_static! {
static ref MARKUP: Regex = {
let t = "(?:[[:space:]]*\\{\\{-|\\{\\{).*?(?:-\\}\\}[[:space:]]*|\\}\\})";
Expand Down Expand Up @@ -145,15 +157,15 @@ pub fn granularize(block: &str) -> Result<Vec<Token>> {
x if NUMBER_LITERAL.is_match(x) => {
x.parse::<i32>().map(Token::IntegerLiteral).unwrap_or_else(
|_e| {
Token::FloatLiteral(x.parse::<f32>()
.expect(&format!("Could not parse {:?} as float",
x)))
let x = x.parse::<f32>()
.expect("matches to NUMBER_LITERAL are parseable as floats");
Token::FloatLiteral(x)
},
)
}
x if BOOLEAN_LITERAL.is_match(x) => {
Token::BooleanLiteral(x.parse::<bool>().expect(
&format!("Could not parse {:?} as bool", x),
"matches to BOOLEAN_LITERAL are parseable as bools",
))
}
x if INDEX.is_match(x) => {
Expand All @@ -163,7 +175,12 @@ pub fn granularize(block: &str) -> Result<Vec<Token>> {
Token::Dot
}
x if IDENTIFIER.is_match(x) => Token::Identifier(x.to_owned()),
x => return Err(Error::Lexer(format!("{} is not a valid identifier", x))),
x => {
return Err(Error::with_msg("Invalid identifier").context(
"identifier",
&x,
))
}
});
if let Some(v) = push_more {
result.extend(v);
Expand Down
6 changes: 4 additions & 2 deletions src/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ mod parser;
mod tag;
mod token;

pub use super::error::{Result, Error, ResultLiquidChainExt, ResultLiquidExt};

pub use self::block::{ParseBlock, ParseBlockClone, BoxedBlockParser, FnParseBlock};
pub use self::include::{Include, IncludeClone, NullInclude, FilesystemInclude};
pub use self::lexer::{Element, tokenize};
pub use self::options::LiquidOptions;
pub use self::parser::{parse_output, expect, parse, consume_value_token, split_block, value_token,
parse_indexes};
pub use self::parser::{parse_output, expect, parse, consume_value_token, split_block, BlockSplit,
value_token, parse_indexes, unexpected_token_error};
pub use self::tag::{ParseTag, ParseTagClone, BoxedTagParser, FnParseTag};
pub use self::token::{Token, ComparisonOperator};
41 changes: 27 additions & 14 deletions src/compiler/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::slice::Iter;
use std::collections::HashSet;
use std::iter::FromIterator;

use error::{Error, Result};
use super::{Error, Result};

use interpreter::Renderable;
use interpreter::Text;
Expand Down Expand Up @@ -42,6 +42,18 @@ pub fn parse(elements: &[Element], options: &LiquidOptions) -> Result<Vec<Box<Re
Ok(ret)
}

const NOTHING: Option<&str> = None;

pub fn unexpected_token_error<S: ToString>(expected: &str, actual: Option<S>) -> Error {
let actual = actual.map(|x| x.to_string());
unexpected_token_error_string(expected, actual)
}

pub fn unexpected_token_error_string(expected: &str, actual: Option<String>) -> Error {
let actual = actual.unwrap_or_else(|| "nothing".to_owned());
Error::with_msg(format!("Expected {}, found `{}`", expected, actual))
}

// creates an expression, which wraps everything that gets rendered
fn parse_expression(tokens: &[Token], options: &LiquidOptions) -> Result<Box<Renderable>> {
match tokens.get(0) {
Expand All @@ -56,7 +68,7 @@ fn parse_expression(tokens: &[Token], options: &LiquidOptions) -> Result<Box<Ren
Some(&Token::Identifier(ref x)) if options.tags.contains_key(x.as_str()) => {
options.tags[x.as_str()].parse(x, &tokens[1..], options)
}
None => Error::parser("expression", None),
None => Err(unexpected_token_error("expression", NOTHING)),
_ => {
let output = parse_output(tokens)?;
Ok(Box::new(output))
Expand All @@ -75,7 +87,7 @@ pub fn parse_indexes(mut tokens: &[Token]) -> Result<Vec<Index>> {
match tokens[1] {
Token::Identifier(ref x) => indexes.push(Index::with_key(x.as_ref())),
_ => {
return Error::parser("identifier", Some(&tokens[0]));
return Err(unexpected_token_error("identifier", Some(&tokens[0])));
}
};
2
Expand All @@ -85,13 +97,14 @@ pub fn parse_indexes(mut tokens: &[Token]) -> Result<Vec<Index>> {
Token::StringLiteral(ref x) => Index::with_key(x.as_ref()),
Token::IntegerLiteral(ref x) => Index::with_index(*x as isize),
_ => {
return Error::parser("integer | string", Some(&tokens[0]));
return Err(unexpected_token_error("string | whole number",
Some(&tokens[0])));
}
};
indexes.push(index);

if tokens[2] != Token::CloseSquare {
return Error::parser("]", Some(&tokens[1]));
return Err(unexpected_token_error("`]`", Some(&tokens[0])));
}
3
}
Expand All @@ -118,7 +131,7 @@ pub fn parse_output(tokens: &[Token]) -> Result<Output> {
let name = match iter.next() {
Some(&Token::Identifier(ref name)) => name,
x => {
return Error::parser("an identifier", x);
return Err(unexpected_token_error("identifier", x));
}
};
let mut args = vec![];
Expand Down Expand Up @@ -147,7 +160,7 @@ pub fn parse_output(tokens: &[Token]) -> Result<Output> {
Some(&&Token::Pipe) |
None => break,
_ => {
return Error::parser("a comma or a pipe", Some(iter.next().unwrap()));
return Err(unexpected_token_error("`,` | `|`", Some(iter.next().unwrap())));
}
}
}
Expand Down Expand Up @@ -206,7 +219,7 @@ fn parse_tag(iter: &mut Iter<Element>,
options.blocks[x.as_str()].parse(x, &tokens[1..], &children, options)
}

ref x => Err(Error::Parser(format!("parse_tag: {:?} not implemented", x))),
ref x => Err(Error::with_msg("Tag is not supported").context("tag", x)),
}
}

Expand All @@ -217,7 +230,7 @@ pub fn expect<'a, T>(tokens: &mut T, expected: &Token) -> Result<&'a Token>
{
match tokens.next() {
Some(x) if x == expected => Ok(x),
x => Error::parser(&expected.to_string(), x),
x => Err(unexpected_token_error(&format!("`{}`", expected), x)),
}
}

Expand All @@ -227,7 +240,7 @@ pub fn expect<'a, T>(tokens: &mut T, expected: &Token) -> Result<&'a Token>
pub fn consume_value_token(tokens: &mut Iter<Token>) -> Result<Token> {
match tokens.next() {
Some(t) => value_token(t.clone()),
None => Error::parser("string | number | boolean | identifier", None),
None => Err(unexpected_token_error("string | number | boolean | identifier", NOTHING)),
}
}

Expand All @@ -240,7 +253,7 @@ pub fn value_token(t: Token) -> Result<Token> {
v @ Token::FloatLiteral(_) |
v @ Token::BooleanLiteral(_) |
v @ Token::Identifier(_) => Ok(v),
x => Error::parser("string | number | boolean | identifier", Some(&x)),
x => Err(unexpected_token_error("string | number | boolean | identifier", Some(&x))),
}
}

Expand Down Expand Up @@ -323,7 +336,7 @@ mod test_parse_output {

let result = parse_output(&tokens);
assert_eq!(result.unwrap_err().to_string(),
"Parsing error: Expected an identifier, found 1");
"liquid: Expected identifier, found `1`\n");
}

#[test]
Expand All @@ -332,7 +345,7 @@ mod test_parse_output {

let result = parse_output(&tokens);
assert_eq!(result.unwrap_err().to_string(),
"Parsing error: Expected a comma or a pipe, found blabla");
"liquid: Expected `,` | `|`, found `blabla`\n");
}

#[test]
Expand All @@ -341,7 +354,7 @@ mod test_parse_output {

let result = parse_output(&tokens);
assert_eq!(result.unwrap_err().to_string(),
"Parsing error: Expected :, found 1");
"liquid: Expected `:`, found `1`\n");
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/compiler/tag.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use error::Result;
use super::Result;

use interpreter::Renderable;
use super::LiquidOptions;
Expand Down
31 changes: 20 additions & 11 deletions src/compiler/token.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use std::fmt;

use error::{Error, Result};
use super::Result;
use super::parser::unexpected_token_error;
use value::{Index, Value};
use interpreter::Argument;
use interpreter::Variable;

#[derive(Clone, Debug, PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ComparisonOperator {
Equals,
NotEquals,
Expand All @@ -16,6 +17,20 @@ pub enum ComparisonOperator {
Contains,
}

impl fmt::Display for ComparisonOperator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let out = match *self {
ComparisonOperator::Equals => "==",
ComparisonOperator::NotEquals => "!=",
ComparisonOperator::LessThanEquals => "<=",
ComparisonOperator::GreaterThanEquals => ">=",
ComparisonOperator::LessThan => "<",
ComparisonOperator::GreaterThan => ">",
ComparisonOperator::Contains => "contains",
};
write!(f, "{}", out)
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Token {
Pipe,
Expand Down Expand Up @@ -49,7 +64,7 @@ impl Token {
&Token::IntegerLiteral(x) => Ok(Value::scalar(x)),
&Token::FloatLiteral(x) => Ok(Value::scalar(x)),
&Token::BooleanLiteral(x) => Ok(Value::scalar(x)),
x => Error::parser("Value", Some(x)),
x => Err(unexpected_token_error("string | number | boolean", Some(x))),
}
}

Expand All @@ -66,7 +81,7 @@ impl Token {
var.extend(id.split('.').map(Index::with_key));
Ok(Argument::Var(var))
}
ref x => Error::parser("Argument", Some(x)),
ref x => Err(unexpected_token_error("string | number | boolean | identifier", Some(x))),
}
}
}
Expand All @@ -88,13 +103,7 @@ impl fmt::Display for Token {
Token::Assignment => "=".to_owned(),
Token::Or => "or".to_owned(),

Token::Comparison(ComparisonOperator::Equals) => "==".to_owned(),
Token::Comparison(ComparisonOperator::NotEquals) => "!=".to_owned(),
Token::Comparison(ComparisonOperator::LessThanEquals) => "<=".to_owned(),
Token::Comparison(ComparisonOperator::GreaterThanEquals) => ">=".to_owned(),
Token::Comparison(ComparisonOperator::LessThan) => "<".to_owned(),
Token::Comparison(ComparisonOperator::GreaterThan) => ">".to_owned(),
Token::Comparison(ComparisonOperator::Contains) => "contains".to_owned(),
Token::Comparison(ref x) => x.to_string(),
Token::Identifier(ref x) |
Token::StringLiteral(ref x) => x.clone(),
Token::IntegerLiteral(ref x) => x.to_string(),
Expand Down
Loading

0 comments on commit e46c3e0

Please sign in to comment.