Skip to content

Commit 73e26cf

Browse files
committed
feat(errors): Report context during render
Fixes #105, mostly. You get enough context. This is also more universal (hard to track line numbers in every context). Any further improvements will be a part of #138. This locks mostly locks down the error API, minus `FilterError` which won't happen until filters get refactored. See #114.
1 parent 932efd4 commit 73e26cf

File tree

10 files changed

+192
-69
lines changed

10 files changed

+192
-69
lines changed

src/compiler/parser.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub fn unexpected_token_error<S: ToString>(expected: &str, actual: Option<S>) ->
5151

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

5757
// creates an expression, which wraps everything that gets rendered
@@ -230,7 +230,7 @@ pub fn expect<'a, T>(tokens: &mut T, expected: &Token) -> Result<&'a Token>
230230
{
231231
match tokens.next() {
232232
Some(x) if x == expected => Ok(x),
233-
x => Err(unexpected_token_error(&expected.to_string(), x)),
233+
x => Err(unexpected_token_error(&format!("`{}`", expected), x)),
234234
}
235235
}
236236

@@ -336,7 +336,7 @@ mod test_parse_output {
336336

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

342342
#[test]
@@ -345,7 +345,7 @@ mod test_parse_output {
345345

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

351351
#[test]
@@ -354,7 +354,7 @@ mod test_parse_output {
354354

355355
let result = parse_output(&tokens);
356356
assert_eq!(result.unwrap_err().to_string(),
357-
"Parsing error: Expected :, found 1");
357+
"liquid: Expected `:`, found `1`\n");
358358
}
359359
}
360360

src/interpreter/context.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub fn unexpected_value_error<S: ToString>(expected: &str, actual: Option<S>) ->
1414

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

2020
#[derive(Clone, Debug)]

src/interpreter/output.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
use error::{Error, Result, ResultLiquidChainExt};
1+
use std::fmt;
2+
3+
use itertools;
4+
5+
use error::{Error, Result, ResultLiquidChainExt, ResultLiquidExt};
26
use value::Value;
37

48
use super::Context;
@@ -20,12 +24,30 @@ impl FilterPrototype {
2024
}
2125
}
2226

27+
impl fmt::Display for FilterPrototype {
28+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
29+
write!(f,
30+
"{}: {}",
31+
self.name,
32+
itertools::join(&self.arguments, ", "))
33+
}
34+
}
35+
2336
#[derive(Clone, Debug, PartialEq)]
2437
pub struct Output {
2538
entry: Argument,
2639
filters: Vec<FilterPrototype>,
2740
}
2841

42+
impl fmt::Display for Output {
43+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
44+
write!(f,
45+
"{} | {}",
46+
self.entry,
47+
itertools::join(&self.filters, " | "))
48+
}
49+
}
50+
2951
impl Renderable for Output {
3052
fn render(&self, context: &mut Context) -> Result<Option<String>> {
3153
let entry = self.apply_filters(context)?;
@@ -60,7 +82,10 @@ impl Output {
6082
.map(|a| a.evaluate(context))
6183
.collect();
6284
let arguments = arguments?;
63-
entry = f.filter(&entry, &*arguments).chain("Filter error")?;
85+
entry = f.filter(&entry, &*arguments)
86+
.chain("Filter error")
87+
.context("input", &entry)
88+
.context_with(|| ("args".into(), itertools::join(&arguments, ", ")))?;
6489
}
6590

6691
Ok(entry)

src/tags/assign_tag.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,25 @@ use interpreter::Renderable;
66
use compiler::LiquidOptions;
77
use compiler::Token;
88
use compiler::{parse_output, expect, unexpected_token_error};
9+
use compiler::ResultLiquidExt;
910

1011
#[derive(Clone, Debug)]
1112
struct Assign {
1213
dst: String,
1314
src: Output,
1415
}
1516

17+
impl Assign {
18+
fn trace(&self) -> String {
19+
format!("{{% assign {} = {}%}}", self.dst, self.src)
20+
}
21+
}
22+
1623
impl Renderable for Assign {
1724
fn render(&self, context: &mut Context) -> Result<Option<String>> {
18-
let value = self.src.apply_filters(context)?;
25+
let value = self.src
26+
.apply_filters(context)
27+
.trace_with(|| self.trace().into())?;
1928
context.set_global_val(&self.dst, value);
2029
Ok(None)
2130
}

src/tags/capture_block.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,18 @@ struct Capture {
1515
template: Template,
1616
}
1717

18+
impl Capture {
19+
fn trace(&self) -> String {
20+
format!("{{% capture {} %}}", self.id)
21+
}
22+
}
23+
1824
impl Renderable for Capture {
1925
fn render(&self, context: &mut Context) -> Result<Option<String>> {
20-
let output = match self.template.render(context) {
21-
Ok(Some(s)) => s.clone(),
22-
Ok(None) => "".to_owned(),
23-
Err(x) => return Err(x),
24-
};
26+
let output = self.template
27+
.render(context)
28+
.trace_with(|| self.trace().into())?
29+
.unwrap_or_else(|| "".to_owned());
2530

2631
context.set_global_val(&self.id, Value::scalar(output));
2732
Ok(None)

src/tags/case_block.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ impl CaseOption {
3232
}
3333
Ok(false)
3434
}
35+
36+
fn trace(&self) -> String {
37+
format!("{{% when {} %}}", itertools::join(self.args.iter(), " or "))
38+
}
3539
}
3640

3741
#[derive(Debug)]
@@ -41,17 +45,30 @@ struct Case {
4145
else_block: Option<Template>,
4246
}
4347

48+
impl Case {
49+
fn trace(&self) -> String {
50+
format!("{{% case {} %}}", self.target)
51+
}
52+
}
53+
4454
impl Renderable for Case {
4555
fn render(&self, context: &mut Context) -> Result<Option<String>> {
4656
let value = self.target.evaluate(context)?;
4757
for case in &self.cases {
4858
if case.evaluate(&value, context)? {
49-
return case.template.render(context);
59+
return case.template
60+
.render(context)
61+
.trace_with(|| case.trace().into())
62+
.trace_with(|| self.trace().into())
63+
.context_with(|| (self.target.to_string().into(), value.to_string()));
5064
}
5165
}
5266

5367
if let Some(ref t) = self.else_block {
54-
return t.render(context);
68+
return t.render(context)
69+
.trace_with(|| "{{% else %}}".to_owned().into())
70+
.trace_with(|| self.trace().into())
71+
.context_with(|| (self.target.to_string().into(), value.to_string()));
5572
}
5673

5774
Ok(None)

src/tags/cycle_tag.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use itertools;
22

3-
use error::Result;
3+
use error::{Result, ResultLiquidExt};
44

55
use interpreter::Argument;
66
use interpreter::Context;
@@ -15,9 +15,18 @@ struct Cycle {
1515
values: Vec<Argument>,
1616
}
1717

18+
impl Cycle {
19+
fn trace(&self) -> String {
20+
format!("{{% cycle {} %}}",
21+
itertools::join(self.values.iter(), ", "))
22+
}
23+
}
24+
1825
impl Renderable for Cycle {
1926
fn render(&self, context: &mut Context) -> Result<Option<String>> {
20-
let value = context.cycle_element(&self.name, &self.values)?;
27+
let value = context
28+
.cycle_element(&self.name, &self.values)
29+
.trace_with(|| self.trace().into())?;
2130
Ok(value.map(|v| v.to_string()))
2231
}
2332
}

src/tags/for_block.rs

Lines changed: 72 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::fmt;
12
use std::slice::Iter;
23

34
use itertools;
@@ -20,6 +21,31 @@ enum Range {
2021
Counted(Argument, Argument),
2122
}
2223

24+
impl Range {
25+
pub fn evaluate(&self, context: &Context) -> Result<Vec<Value>> {
26+
let range = match *self {
27+
Range::Array(ref array_id) => get_array(context, array_id)?,
28+
29+
Range::Counted(ref start_arg, ref stop_arg) => {
30+
let start = int_argument(start_arg, context, "start")?;
31+
let stop = int_argument(stop_arg, context, "end")?;
32+
(start..stop).map(|x| Value::scalar(x as i32)).collect()
33+
}
34+
};
35+
36+
Ok(range)
37+
}
38+
}
39+
40+
impl fmt::Display for Range {
41+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
42+
match *self {
43+
Range::Array(ref arr) => write!(f, "{}", arr),
44+
Range::Counted(ref start, ref end) => write!(f, "({}..{})", start, end),
45+
}
46+
}
47+
}
48+
2349
#[derive(Debug)]
2450
struct For {
2551
var_name: String,
@@ -31,6 +57,16 @@ struct For {
3157
reversed: bool,
3258
}
3359

60+
impl For {
61+
fn trace(&self) -> String {
62+
trace_for_tag(&self.var_name,
63+
&self.range,
64+
self.limit,
65+
self.offset,
66+
self.reversed)
67+
}
68+
}
69+
3470
fn get_array(context: &Context, array_id: &Argument) -> Result<Vec<Value>> {
3571
let array = array_id.evaluate(context)?;
3672
match array {
@@ -39,49 +75,50 @@ fn get_array(context: &Context, array_id: &Argument) -> Result<Vec<Value>> {
3975
}
4076
}
4177

42-
fn int_argument(arg: &Argument, context: &Context) -> Result<isize> {
78+
fn int_argument(arg: &Argument, context: &Context, arg_name: &str) -> Result<isize> {
4379
let value = arg.evaluate(context)?;
4480

45-
let value =
46-
value
47-
.as_scalar()
48-
.and_then(Scalar::to_integer)
49-
.ok_or_else(|| unexpected_value_error("whole number", Some(value.type_name())))?;
81+
let value = value
82+
.as_scalar()
83+
.and_then(Scalar::to_integer)
84+
.ok_or_else(|| unexpected_value_error("whole number", Some(value.type_name())))
85+
.context_with(|| (arg_name.to_string().into(), value.to_string()))?;
5086

5187
Ok(value as isize)
5288
}
5389

54-
impl Renderable for For {
55-
fn render(&self, context: &mut Context) -> Result<Option<String>> {
56-
let mut range = match self.range {
57-
Range::Array(ref array_id) => get_array(context, array_id)?,
90+
fn for_slice(range: &mut [Value], limit: Option<usize>, offset: usize, reversed: bool) -> &[Value] {
91+
let end = match limit {
92+
Some(n) => offset + n,
93+
None => range.len(),
94+
};
5895

59-
Range::Counted(ref start_arg, ref stop_arg) => {
60-
let start = int_argument(start_arg, context)?;
61-
let stop = int_argument(stop_arg, context)?;
62-
(start..stop).map(|x| Value::scalar(x as i32)).collect()
63-
}
64-
};
96+
let slice = if end > range.len() {
97+
&mut range[offset..]
98+
} else {
99+
&mut range[offset..end]
100+
};
65101

66-
let end = match self.limit {
67-
Some(n) => self.offset + n,
68-
None => range.len(),
69-
};
102+
if reversed {
103+
slice.reverse();
104+
};
70105

71-
let slice = if end > range.len() {
72-
&mut range[self.offset..]
73-
} else {
74-
&mut range[self.offset..end]
75-
};
106+
slice
107+
}
76108

77-
if self.reversed {
78-
slice.reverse();
79-
};
109+
impl Renderable for For {
110+
fn render(&self, context: &mut Context) -> Result<Option<String>> {
111+
let mut range = self.range
112+
.evaluate(context)
113+
.trace_with(|| self.trace().into())?;
114+
let range = for_slice(&mut range, self.limit, self.offset, self.reversed);
80115

81-
match slice.len() {
116+
match range.len() {
82117
0 => {
83118
if let Some(ref t) = self.else_template {
84119
t.render(context)
120+
.trace_with(|| "{{% else %}}".to_owned().into())
121+
.trace_with(|| self.trace().into())
85122
} else {
86123
Ok(None)
87124
}
@@ -93,7 +130,7 @@ impl Renderable for For {
93130
let mut helper_vars = Object::new();
94131
helper_vars.insert("length".to_owned(), Value::scalar(range_len as i32));
95132

96-
for (i, v) in slice.iter().enumerate() {
133+
for (i, v) in range.iter().enumerate() {
97134
helper_vars.insert("index0".to_owned(), Value::scalar(i as i32));
98135
helper_vars.insert("index".to_owned(), Value::scalar((i + 1) as i32));
99136
helper_vars.insert("rindex0".to_owned(),
@@ -105,7 +142,11 @@ impl Renderable for For {
105142

106143
scope.set_val("forloop", Value::Object(helper_vars.clone()));
107144
scope.set_val(&self.var_name, v.clone());
108-
let inner = try!(self.item_template.render(&mut scope))
145+
let inner = self.item_template
146+
.render(&mut scope)
147+
.trace_with(|| self.trace().into())
148+
.context_with(|| (self.var_name.clone().into(), v.to_string()))
149+
.context("index", &(i + 1))?
109150
.unwrap_or_else(String::new);
110151
ret = ret + &inner;
111152

@@ -159,10 +200,6 @@ fn trace_for_tag(var_name: &str,
159200
if reversed {
160201
parameters.push("reversed".to_owned());
161202
}
162-
let range = match *range {
163-
Range::Array(ref arr) => arr.to_string(),
164-
Range::Counted(ref start, ref end) => format!("({}..{})", start, end),
165-
};
166203
format!("{{% for {} in {} {} %}}",
167204
var_name,
168205
range,

0 commit comments

Comments
 (0)