Skip to content

Commit

Permalink
Adds boolean operators, and whitespace near dot and brackets
Browse files Browse the repository at this point in the history
  • Loading branch information
tailhook committed Aug 5, 2017
1 parent 29bf052 commit c2bff15
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 4 deletions.
54 changes: 52 additions & 2 deletions src/grammar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ fn atom<'a>(input: TokenStream<'a>)
.unwrap_or_else(|_| Float(t.value.parse().unwrap()))
}));
(position(), expr, position())
.skip(ws())
.map(|(s, c, e)| Expr {
position: (s, e),
code: c,
Expand All @@ -180,10 +181,12 @@ fn attr<'a>(input: TokenStream<'a>)

parser(atom)
.and(many(
operator(".").with(kind(Ident)).map(Suffix::Attr).or(
operator(".").skip(ws()).with(kind(Ident)).map(Suffix::Attr).or(
paren("[")
.skip(ws())
.with(parser(top_level_expression))
.skip(paren("]"))
.skip(ws())
.map(Suffix::Item)
).and(position())))
.map(|(atom, vec): (_, Vec<_>)| {
Expand Down Expand Up @@ -230,10 +233,57 @@ fn unary<'a>(input: TokenStream<'a>)
.parse_stream(input)
}

fn bool_and<'a>(input: TokenStream<'a>)
-> ParseResult<Expr, TokenStream<'a>>
{
use helpers::*;

parser(unary)
.and(many(
operator("and")
.skip(ws())
.with(parser(unary))
.and(position())))
.map(|(expr, vec): (_, Vec<_>)| {
vec.into_iter().fold(expr,
|a: Expr, (b, e): (Expr, _)| {
Expr {
position: (a.position.0, e),
code: ExprCode::And(Box::new(a), Box::new(b)),
}
})
})
.parse_stream(input)
}

fn bool_or<'a>(input: TokenStream<'a>)
-> ParseResult<Expr, TokenStream<'a>>
{
use helpers::*;

parser(bool_and)
.and(many(
operator("or")
.skip(ws())
.with(parser(bool_and))
.and(position())))
.map(|(expr, vec): (_, Vec<_>)| {
vec.into_iter().fold(expr,
|a: Expr, (b, e): (Expr, _)| {
Expr {
position: (a.position.0, e),
code: ExprCode::Or(Box::new(a), Box::new(b)),
}
})
})
.parse_stream(input)
}

fn top_level_expression<'a>(input: TokenStream<'a>)
-> ParseResult<Expr, TokenStream<'a>>
{
unary(input)
parser(bool_or)
.parse_stream(input)
}

fn expression<'a>(input: TokenStream<'a>)
Expand Down
38 changes: 36 additions & 2 deletions src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,39 @@ fn eval_expr<'x, 'render: 'x>(r: &mut Renderer, root: &SubContext<'x, 'render>,
Err(v) => v,
}
}
ExprCode::And(ref a, ref b) => {
let left = eval_expr(r, root, a);
match left.as_bool() {
Ok(true) | Err(BoolUnsupported(_)) => {
eval_expr(r, root, b)
}
Ok(false) => {
left
}
Err(e) => {
r.errors.push((expr.position.0, e));
// this is kinda undefined, so false
OwningRef::new(nothing(&r.nothing, root))
.map(|_| UNDEFINED as &Variable)
}
}
}
ExprCode::Or(ref a, ref b) => {
let left = eval_expr(r, root, a);
match left.as_bool() {
Ok(true) | Err(BoolUnsupported(_)) => {
left
}
Ok(false) => {
eval_expr(r, root, b)
}
Err(e) => {
r.errors.push((expr.position.0, e));
// this is kinda undefined, so false
eval_expr(r, root, b)
}
}
}
ExprCode::Item(ref e, ref a) => {
let value = eval_expr(r, root, e);
let index = eval_expr(r, root, a);
Expand Down Expand Up @@ -156,6 +189,7 @@ fn write_block<'x, 'render>(r: &mut Renderer,
-> Result<(), fmt::Error>
{
use grammar::StatementCode::*;
use render_error::DataError::*;

'outer: for (idx, item) in items.iter().enumerate() {
match item.code {
Expand Down Expand Up @@ -262,7 +296,7 @@ fn write_block<'x, 'render>(r: &mut Renderer,
let condval = eval_expr(r, root, &cond);

match condval.as_bool() {
Ok(x) if x => {
Ok(true) | Err(BoolUnsupported(..)) => {
let bstatements = items.clone()
.map(|x| match x[idx].code {
Cond { ref conditional, .. }
Expand All @@ -273,7 +307,7 @@ fn write_block<'x, 'render>(r: &mut Renderer,
write_block(r, &mut sub, &bstatements)?;
continue 'outer;
}
Ok(_) => {}
Ok(false) => {}
Err(e) => {
r.errors.push((cond.position.0, e));
// treating as false
Expand Down
51 changes: 51 additions & 0 deletions src/tests/boolean.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use {Parser, Context};


fn render(template: &str) -> String {
let tpl = Parser::new().parse(template).unwrap();
let yes = true;
let no = false;
let a = "a";
let b = "b";
let mut vars: Context = Context::new();
vars.set("yes", &yes);
vars.set("no", &no);
vars.set("a", &a);
vars.set("b", &b);
tpl.render(&vars).unwrap()
}

#[test]
fn test_and_true() {
assert_eq!(render("{{ yes and a }}"), "a");
}

#[test]
fn test_and_false() {
assert_eq!(render("{{ no and a }}"), "false");
}

#[test]
fn test_or_true() {
assert_eq!(render("{{ yes or a }}"), "true");
}

#[test]
fn test_or_false() {
assert_eq!(render("{{ no or a }}"), "a");
}

#[test]
fn test_and_or_a() {
assert_eq!(render("{{ yes and a or b }}"), "a");
}

#[test]
fn test_and_or_b() {
assert_eq!(render("{{ no and a or b }}"), "b");
}

#[test]
fn test_and_or_false() {
assert_eq!(render("{{ yes and no or b }}"), "b");
}
1 change: 1 addition & 0 deletions src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ mod indent;
mod parse;
mod oneline;
mod vars;
mod boolean;

pub use self::diff::assert_eq;
20 changes: 20 additions & 0 deletions src/tests/vars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,26 @@ fn undefined_attrs() {
assert_eq!(tpl.render(&vars).unwrap(), "k: v, k2: , k3.b: ");
}

#[test]
fn space_attrs() {
let p = Parser::new();
let tpl = p.parse(r#"## syntax: oneline
a: {{ x .k1 }},
b: {{ x. k1 }},
c: {{ x . k1 }},
A: {{ x ["k1"] }},
B: {{ x[ "k1" ] }},
C: {{ x [ "k1"] }},
"#).unwrap();
let x: HashMap<String, String> = vec![
("k1".into(), "v".into()),
].into_iter().collect();
let mut vars: Context = Context::new();
vars.set("x", &x);
assert_eq!(tpl.render(&vars).unwrap(),
"a: v, b: v, c: v, A: v, B: v, C: v,");
}

#[test]
#[cfg(feature="json")]
fn undefined_attrs_serde() {
Expand Down

0 comments on commit c2bff15

Please sign in to comment.