parser: introduce askama_parser::expr::BinOp

This commit is contained in:
René Kijewski 2025-06-23 18:05:35 +02:00 committed by René Kijewski
parent edfa31f9cf
commit 9f882e2ca7
6 changed files with 184 additions and 168 deletions

View File

@ -581,9 +581,9 @@ fn is_copyable_within_op(expr: &Expr<'_>, within_op: bool) -> bool {
| Expr::NumLit(_, _) | Expr::NumLit(_, _)
| Expr::StrLit(_) | Expr::StrLit(_)
| Expr::CharLit(_) | Expr::CharLit(_)
| Expr::BinOp(_, _, _) => true, | Expr::BinOp(_)
| Expr::Range(..) => true,
Expr::Unary(.., expr) => is_copyable_within_op(expr, true), Expr::Unary(.., expr) => is_copyable_within_op(expr, true),
Expr::Range(..) => true,
// The result of a call likely doesn't need to be borrowed, // The result of a call likely doesn't need to be borrowed,
// as in that case the call is more likely to return a // as in that case the call is more likely to return a
// reference in the first place then. // reference in the first place then.

View File

@ -75,7 +75,7 @@ impl<'a> Generator<'a, '_> {
ref generics, ref generics,
}) => self.visit_filter(ctx, buf, name, arguments, generics, expr.span())?, }) => self.visit_filter(ctx, buf, name, arguments, generics, expr.span())?,
Expr::Unary(op, ref inner) => self.visit_unary(ctx, buf, op, inner)?, Expr::Unary(op, ref inner) => self.visit_unary(ctx, buf, op, inner)?,
Expr::BinOp(op, ref left, ref right) => self.visit_binop(ctx, buf, op, left, right)?, Expr::BinOp(ref v) => self.visit_binop(ctx, buf, v.op, &v.lhs, &v.rhs)?,
Expr::Range(op, ref left, ref right) => { Expr::Range(op, ref left, ref right) => {
self.visit_range(ctx, buf, op, left.as_deref(), right.as_deref())? self.visit_range(ctx, buf, op, left.as_deref(), right.as_deref())?
} }
@ -113,9 +113,9 @@ impl<'a> Generator<'a, '_> {
expr: &WithSpan<'a, Expr<'a>>, expr: &WithSpan<'a, Expr<'a>>,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
match **expr { match **expr {
Expr::BinOp(op @ ("||" | "&&"), ref left, _) => { Expr::BinOp(ref v) if matches!(v.op, "&&" | "||") => {
let ret = self.visit_expr(ctx, buf, left)?; let ret = self.visit_expr(ctx, buf, &v.lhs)?;
buf.write(format_args!(" {op} ")); buf.write(format_args!(" {} ", &v.op));
return Ok(ret); return Ok(ret);
} }
Expr::Unary(op, ref inner) => { Expr::Unary(op, ref inner) => {
@ -135,8 +135,8 @@ impl<'a> Generator<'a, '_> {
prev_display_wrap: DisplayWrap, prev_display_wrap: DisplayWrap,
) -> Result<DisplayWrap, CompileError> { ) -> Result<DisplayWrap, CompileError> {
match **expr { match **expr {
Expr::BinOp("||" | "&&", _, ref right) => { Expr::BinOp(ref v) if matches!(v.op, "&&" | "||") => {
self.visit_condition(ctx, buf, right)?; self.visit_condition(ctx, buf, &v.rhs)?;
Ok(DisplayWrap::Unwrapped) Ok(DisplayWrap::Unwrapped)
} }
Expr::Unary(_, ref inner) => { Expr::Unary(_, ref inner) => {
@ -160,10 +160,10 @@ impl<'a> Generator<'a, '_> {
buf.write('!'); buf.write('!');
self.visit_condition(ctx, buf, expr)?; self.visit_condition(ctx, buf, expr)?;
} }
Expr::BinOp(op @ ("&&" | "||"), left, right) => { Expr::BinOp(v) if matches!(v.op, "&&" | "||") => {
self.visit_condition(ctx, buf, left)?; self.visit_condition(ctx, buf, &v.lhs)?;
buf.write(format_args!(" {op} ")); buf.write(format_args!(" {} ", v.op));
self.visit_condition(ctx, buf, right)?; self.visit_condition(ctx, buf, &v.rhs)?;
} }
Expr::Group(expr) => { Expr::Group(expr) => {
buf.write('('); buf.write('(');

View File

@ -3,6 +3,7 @@ use std::collections::hash_map::{Entry, HashMap};
use std::fmt::{self, Debug, Write}; use std::fmt::{self, Debug, Write};
use std::mem; use std::mem;
use parser::expr::BinOp;
use parser::node::{ use parser::node::{
Call, Comment, Cond, CondTest, FilterBlock, If, Include, Let, Lit, Loop, Macro, Match, Call, Comment, Cond, CondTest, FilterBlock, If, Include, Let, Lit, Loop, Macro, Match,
Whitespace, Ws, Whitespace, Ws,
@ -232,15 +233,15 @@ impl<'a> Generator<'a, '_> {
} }
} }
Expr::Unary(_, _) => (EvaluatedResult::Unknown, WithSpan::new(expr, span)), Expr::Unary(_, _) => (EvaluatedResult::Unknown, WithSpan::new(expr, span)),
Expr::BinOp("&&", left, right) => { Expr::BinOp(v) if v.op == "&&" => {
let (result_left, expr_left) = let (result_left, expr_left) =
self.evaluate_condition(*left, only_contains_is_defined); self.evaluate_condition(v.lhs, only_contains_is_defined);
if result_left == EvaluatedResult::AlwaysFalse { if result_left == EvaluatedResult::AlwaysFalse {
// The right side of the `&&` won't be evaluated, no need to go any further. // The right side of the `&&` won't be evaluated, no need to go any further.
return (result_left, WithSpan::new(Expr::BoolLit(false), "")); return (result_left, WithSpan::new(Expr::BoolLit(false), ""));
} }
let (result_right, expr_right) = let (result_right, expr_right) =
self.evaluate_condition(*right, only_contains_is_defined); self.evaluate_condition(v.rhs, only_contains_is_defined);
match (result_left, result_right) { match (result_left, result_right) {
(EvaluatedResult::AlwaysTrue, EvaluatedResult::AlwaysTrue) => ( (EvaluatedResult::AlwaysTrue, EvaluatedResult::AlwaysTrue) => (
EvaluatedResult::AlwaysTrue, EvaluatedResult::AlwaysTrue,
@ -248,31 +249,25 @@ impl<'a> Generator<'a, '_> {
), ),
(_, EvaluatedResult::AlwaysFalse) => ( (_, EvaluatedResult::AlwaysFalse) => (
EvaluatedResult::AlwaysFalse, EvaluatedResult::AlwaysFalse,
WithSpan::new( bin_op(span, "&&", expr_left, expr_right),
Expr::BinOp("&&", Box::new(expr_left), Box::new(expr_right)),
span,
),
), ),
(EvaluatedResult::AlwaysTrue, _) => (result_right, expr_right), (EvaluatedResult::AlwaysTrue, _) => (result_right, expr_right),
(_, EvaluatedResult::AlwaysTrue) => (result_left, expr_left), (_, EvaluatedResult::AlwaysTrue) => (result_left, expr_left),
_ => ( _ => (
EvaluatedResult::Unknown, EvaluatedResult::Unknown,
WithSpan::new( bin_op(span, "&&", expr_left, expr_right),
Expr::BinOp("&&", Box::new(expr_left), Box::new(expr_right)),
span,
),
), ),
} }
} }
Expr::BinOp("||", left, right) => { Expr::BinOp(v) if v.op == "||" => {
let (result_left, expr_left) = let (result_left, expr_left) =
self.evaluate_condition(*left, only_contains_is_defined); self.evaluate_condition(v.lhs, only_contains_is_defined);
if result_left == EvaluatedResult::AlwaysTrue { if result_left == EvaluatedResult::AlwaysTrue {
// The right side of the `||` won't be evaluated, no need to go any further. // The right side of the `||` won't be evaluated, no need to go any further.
return (result_left, WithSpan::new(Expr::BoolLit(true), "")); return (result_left, WithSpan::new(Expr::BoolLit(true), ""));
} }
let (result_right, expr_right) = let (result_right, expr_right) =
self.evaluate_condition(*right, only_contains_is_defined); self.evaluate_condition(v.rhs, only_contains_is_defined);
match (result_left, result_right) { match (result_left, result_right) {
(EvaluatedResult::AlwaysFalse, EvaluatedResult::AlwaysFalse) => ( (EvaluatedResult::AlwaysFalse, EvaluatedResult::AlwaysFalse) => (
EvaluatedResult::AlwaysFalse, EvaluatedResult::AlwaysFalse,
@ -280,23 +275,17 @@ impl<'a> Generator<'a, '_> {
), ),
(_, EvaluatedResult::AlwaysTrue) => ( (_, EvaluatedResult::AlwaysTrue) => (
EvaluatedResult::AlwaysTrue, EvaluatedResult::AlwaysTrue,
WithSpan::new( bin_op(span, "||", expr_left, expr_right),
Expr::BinOp("||", Box::new(expr_left), Box::new(expr_right)),
span,
),
), ),
(EvaluatedResult::AlwaysFalse, _) => (result_right, expr_right), (EvaluatedResult::AlwaysFalse, _) => (result_right, expr_right),
(_, EvaluatedResult::AlwaysFalse) => (result_left, expr_left), (_, EvaluatedResult::AlwaysFalse) => (result_left, expr_left),
_ => ( _ => (
EvaluatedResult::Unknown, EvaluatedResult::Unknown,
WithSpan::new( bin_op(span, "||", expr_left, expr_right),
Expr::BinOp("||", Box::new(expr_left), Box::new(expr_right)),
span,
),
), ),
} }
} }
Expr::BinOp(_, _, _) => { Expr::BinOp(_) => {
*only_contains_is_defined = false; *only_contains_is_defined = false;
(EvaluatedResult::Unknown, WithSpan::new(expr, span)) (EvaluatedResult::Unknown, WithSpan::new(expr, span))
} }
@ -386,14 +375,18 @@ impl<'a> Generator<'a, '_> {
// left expression has been handled but before the right expression is handled // left expression has been handled but before the right expression is handled
// but this one should have access to the let-bound variable. // but this one should have access to the let-bound variable.
match &**expr { match &**expr {
Expr::BinOp(op @ ("||" | "&&"), ref left, ref right) => { Expr::BinOp(v) if matches!(v.op, "||" | "&&") => {
let display_wrap = let display_wrap =
this.visit_expr_first(ctx, &mut expr_buf, left)?; this.visit_expr_first(ctx, &mut expr_buf, &v.lhs)?;
this.visit_target(buf, true, true, target); this.visit_target(buf, true, true, target);
this.visit_expr_not_first(ctx, &mut expr_buf, left, display_wrap)?; this.visit_expr_not_first(
buf.write(format_args!("= &{expr_buf}")); ctx,
buf.write(format_args!(" {op} ")); &mut expr_buf,
this.visit_condition(ctx, buf, right)?; &v.lhs,
display_wrap,
)?;
buf.write(format_args!("= &{expr_buf} {} ", v.op));
this.visit_condition(ctx, buf, &v.rhs)?;
} }
_ => { _ => {
let display_wrap = let display_wrap =
@ -1392,6 +1385,15 @@ impl<'a> Generator<'a, '_> {
} }
} }
fn bin_op<'a>(
span: impl Into<Span<'a>>,
op: &'a str,
lhs: WithSpan<'a, Expr<'a>>,
rhs: WithSpan<'a, Expr<'a>>,
) -> WithSpan<'a, Expr<'a>> {
WithSpan::new(Expr::BinOp(Box::new(BinOp { op, lhs, rhs })), span)
}
struct CondInfo<'a> { struct CondInfo<'a> {
cond: &'a WithSpan<'a, Cond<'a>>, cond: &'a WithSpan<'a, Cond<'a>>,
cond_expr: Option<WithSpan<'a, Expr<'a>>>, cond_expr: Option<WithSpan<'a, Expr<'a>>>,
@ -1645,7 +1647,7 @@ fn is_cacheable(expr: &WithSpan<'_, Expr<'_>>) -> bool {
Expr::Index(lhs, rhs) => is_cacheable(lhs) && is_cacheable(rhs), Expr::Index(lhs, rhs) => is_cacheable(lhs) && is_cacheable(rhs),
Expr::Filter(Filter { arguments, .. }) => arguments.iter().all(is_cacheable), Expr::Filter(Filter { arguments, .. }) => arguments.iter().all(is_cacheable),
Expr::Unary(_, arg) => is_cacheable(arg), Expr::Unary(_, arg) => is_cacheable(arg),
Expr::BinOp(_, lhs, rhs) => is_cacheable(lhs) && is_cacheable(rhs), Expr::BinOp(v) => is_cacheable(&v.lhs) && is_cacheable(&v.rhs),
Expr::IsDefined(_) | Expr::IsNotDefined(_) => true, Expr::IsDefined(_) | Expr::IsNotDefined(_) => true,
Expr::Range(_, lhs, rhs) => { Expr::Range(_, lhs, rhs) => {
lhs.as_ref().is_none_or(|v| is_cacheable(v)) lhs.as_ref().is_none_or(|v| is_cacheable(v))

View File

@ -19,21 +19,31 @@ use crate::{
macro_rules! expr_prec_layer { macro_rules! expr_prec_layer {
( $name:ident, $inner:ident, $op:expr ) => { ( $name:ident, $inner:ident, $op:expr ) => {
fn $name(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { fn $name(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, WithSpan<'a, Self>> {
let mut level_guard = level.guard(); expr_prec_layer(i, level, Expr::$inner, |i: &mut _| $op.parse_next(i))
let start = *i;
let mut expr = Self::$inner(i, level)?;
let mut i_before = *i;
let mut right = opt((ws($op), |i: &mut _| Self::$inner(i, level)));
while let Some((op, right)) = right.parse_next(i)? {
level_guard.nest(i_before)?;
i_before = *i;
expr = WithSpan::new(Self::BinOp(op, Box::new(expr), Box::new(right)), start);
}
Ok(expr)
} }
}; };
} }
fn expr_prec_layer<'a>(
i: &mut &'a str,
level: Level<'_>,
inner: fn(&mut &'a str, Level<'_>) -> ParseResult<'a, WithSpan<'a, Expr<'a>>>,
op: fn(&mut &'a str) -> ParseResult<'a>,
) -> ParseResult<'a, WithSpan<'a, Expr<'a>>> {
let start = *i;
let mut expr = inner(i, level)?;
let mut i_before = *i;
let mut level_guard = level.guard();
while let Some((op, rhs)) = opt((ws(op), |i: &mut _| inner(i, level))).parse_next(i)? {
level_guard.nest(i_before)?;
i_before = *i;
expr = WithSpan::new(Expr::BinOp(Box::new(BinOp { op, lhs: expr, rhs })), start);
}
Ok(expr)
}
#[derive(Clone, Copy, Default)] #[derive(Clone, Copy, Default)]
struct Allowed { struct Allowed {
underscore: bool, underscore: bool,
@ -83,10 +93,14 @@ fn check_expr<'a>(expr: &WithSpan<'a, Expr<'a>>, allowed: Allowed) -> Result<(),
check_expr(elem, Allowed::default()) check_expr(elem, Allowed::default())
} }
} }
Expr::Index(elem1, elem2) | Expr::BinOp(_, elem1, elem2) => { Expr::Index(elem1, elem2) => {
check_expr(elem1, Allowed::default())?; check_expr(elem1, Allowed::default())?;
check_expr(elem2, Allowed::default()) check_expr(elem2, Allowed::default())
} }
Expr::BinOp(v) => {
check_expr(&v.lhs, Allowed::default())?;
check_expr(&v.rhs, Allowed::default())
}
Expr::Range(_, elem1, elem2) => { Expr::Range(_, elem1, elem2) => {
if let Some(elem1) = elem1 { if let Some(elem1) = elem1 {
check_expr(elem1, Allowed::default())?; check_expr(elem1, Allowed::default())?;
@ -155,11 +169,7 @@ pub enum Expr<'a> {
As(Box<WithSpan<'a, Expr<'a>>>, &'a str), As(Box<WithSpan<'a, Expr<'a>>>, &'a str),
NamedArgument(&'a str, Box<WithSpan<'a, Expr<'a>>>), NamedArgument(&'a str, Box<WithSpan<'a, Expr<'a>>>),
Unary(&'a str, Box<WithSpan<'a, Expr<'a>>>), Unary(&'a str, Box<WithSpan<'a, Expr<'a>>>),
BinOp( BinOp(Box<BinOp<'a>>),
&'a str,
Box<WithSpan<'a, Expr<'a>>>,
Box<WithSpan<'a, Expr<'a>>>,
),
Range( Range(
&'a str, &'a str,
Option<Box<WithSpan<'a, Expr<'a>>>>, Option<Box<WithSpan<'a, Expr<'a>>>>,
@ -186,6 +196,13 @@ pub enum Expr<'a> {
ArgumentPlaceholder, ArgumentPlaceholder,
} }
#[derive(Clone, Debug, PartialEq)]
pub struct BinOp<'a> {
pub op: &'a str,
pub lhs: WithSpan<'a, Expr<'a>>,
pub rhs: WithSpan<'a, Expr<'a>>,
}
impl<'a> Expr<'a> { impl<'a> Expr<'a> {
pub(super) fn arguments( pub(super) fn arguments(
i: &mut &'a str, i: &mut &'a str,
@ -320,7 +337,7 @@ impl<'a> Expr<'a> {
let Some((op, rhs)) = opt(right).parse_next(i)? else { let Some((op, rhs)) = opt(right).parse_next(i)? else {
return Ok(expr); return Ok(expr);
}; };
let expr = WithSpan::new(Self::BinOp(op, Box::new(expr), Box::new(rhs)), start); let expr = WithSpan::new(Expr::BinOp(Box::new(BinOp { op, lhs: expr, rhs })), start);
if let Some((op2, _)) = opt(right).parse_next(i)? { if let Some((op2, _)) = opt(right).parse_next(i)? {
return Err(ErrMode::Cut(ErrorContext::new( return Err(ErrMode::Cut(ErrorContext::new(
@ -583,8 +600,8 @@ impl<'a> Expr<'a> {
match self { match self {
Self::BoolLit(_) | Self::IsDefined(_) | Self::IsNotDefined(_) => true, Self::BoolLit(_) | Self::IsDefined(_) | Self::IsNotDefined(_) => true,
Self::Unary(_, expr) | Self::Group(expr) => expr.contains_bool_lit_or_is_defined(), Self::Unary(_, expr) | Self::Group(expr) => expr.contains_bool_lit_or_is_defined(),
Self::BinOp("&&" | "||", left, right) => { Self::BinOp(v) if matches!(v.op, "&&" | "||") => {
left.contains_bool_lit_or_is_defined() || right.contains_bool_lit_or_is_defined() v.lhs.contains_bool_lit_or_is_defined() || v.rhs.contains_bool_lit_or_is_defined()
} }
Self::NumLit(_, _) Self::NumLit(_, _)
| Self::StrLit(_) | Self::StrLit(_)
@ -602,7 +619,7 @@ impl<'a> Expr<'a> {
| Self::Index(_, _) | Self::Index(_, _)
| Self::Tuple(_) | Self::Tuple(_)
| Self::Array(_) | Self::Array(_)
| Self::BinOp(_, _, _) | Self::BinOp(_)
| Self::Path(_) | Self::Path(_)
| Self::Concat(_) | Self::Concat(_)
| Self::LetCond(_) | Self::LetCond(_)

View File

@ -423,13 +423,13 @@ impl<'a> CondTest<'a> {
ws(|i: &mut _| { ws(|i: &mut _| {
let start = *i; let start = *i;
let mut expr = Expr::parse(i, s.level, false)?; let mut expr = Expr::parse(i, s.level, false)?;
if let Expr::BinOp(_, _, ref mut right) = expr.inner { if let Expr::BinOp(v) = &mut expr.inner {
if matches!(right.inner, Expr::Var("set" | "let")) { if matches!(v.rhs.inner, Expr::Var("set" | "let")) {
let _level_guard = s.level.nest(i)?; let _level_guard = s.level.nest(i)?;
*i = right.span.as_suffix_of(start).unwrap(); *i = v.rhs.span.as_suffix_of(start).unwrap();
let start_span = Span::from(*i); let start_span = Span::from(*i);
let new_right = Self::parse_cond(i, s)?; let new_right = Self::parse_cond(i, s)?;
right.inner = Expr::LetCond(Box::new(WithSpan::new(new_right, start_span))); v.rhs.inner = Expr::LetCond(Box::new(WithSpan::new(new_right, start_span)));
} }
} }
Ok(expr) Ok(expr)

View File

@ -41,6 +41,14 @@ fn int_lit(i: &str) -> Expr<'_> {
Expr::NumLit(i, Num::Int(i, None)) Expr::NumLit(i, Num::Int(i, None))
} }
fn bin_op<'a>(
op: &'a str,
lhs: WithSpan<'a, Expr<'a>>,
rhs: WithSpan<'a, Expr<'a>>,
) -> WithSpan<'a, Expr<'a>> {
WithSpan::new_without_span(Expr::BinOp(Box::new(crate::expr::BinOp { op, lhs, rhs })))
}
#[test] #[test]
fn test_parse_filter() { fn test_parse_filter() {
let syntax = Syntax::default(); let syntax = Syntax::default();
@ -90,14 +98,11 @@ fn test_parse_filter() {
Ws(None, None), Ws(None, None),
WithSpan::no_span(Expr::Filter(Filter { WithSpan::no_span(Expr::Filter(Filter {
name: PathOrIdentifier::Identifier("abs"), name: PathOrIdentifier::Identifier("abs"),
arguments: vec![WithSpan::no_span(Expr::Group( arguments: vec![WithSpan::no_span(Expr::Group(Box::new(bin_op(
WithSpan::no_span(Expr::BinOp( "-",
"-", WithSpan::no_span(int_lit("1")),
WithSpan::no_span(int_lit("1")).into(), WithSpan::no_span(int_lit("2")),
WithSpan::no_span(int_lit("2")).into() ))))],
))
.into()
))],
generics: vec![], generics: vec![],
})), })),
)], )],
@ -445,16 +450,15 @@ fn test_precedence() {
.nodes, .nodes,
vec![Node::Expr( vec![Node::Expr(
Ws(None, None), Ws(None, None),
WithSpan::no_span(Expr::BinOp( bin_op(
"==", "==",
WithSpan::no_span(Expr::BinOp( bin_op(
"+", "+",
WithSpan::no_span(Expr::Var("a")).into(), WithSpan::no_span(Expr::Var("a")),
WithSpan::no_span(Expr::Var("b")).into() WithSpan::no_span(Expr::Var("b"))
)) ),
.into(), WithSpan::no_span(Expr::Var("c"))
WithSpan::no_span(Expr::Var("c")).into() )
))
)], )],
); );
assert_eq!( assert_eq!(
@ -463,26 +467,23 @@ fn test_precedence() {
.nodes, .nodes,
vec![Node::Expr( vec![Node::Expr(
Ws(None, None), Ws(None, None),
WithSpan::no_span(Expr::BinOp( bin_op(
"-", "-",
WithSpan::no_span(Expr::BinOp( bin_op(
"+", "+",
WithSpan::no_span(Expr::Var("a")).into(), WithSpan::no_span(Expr::Var("a")),
WithSpan::no_span(Expr::BinOp( bin_op(
"*", "*",
WithSpan::no_span(Expr::Var("b")).into(), WithSpan::no_span(Expr::Var("b")),
WithSpan::no_span(Expr::Var("c")).into() WithSpan::no_span(Expr::Var("c"))
)) )
.into() ),
)) bin_op(
.into(),
WithSpan::no_span(Expr::BinOp(
"/", "/",
WithSpan::no_span(Expr::Var("d")).into(), WithSpan::no_span(Expr::Var("d")),
WithSpan::no_span(Expr::Var("e")).into() WithSpan::no_span(Expr::Var("e"))
)) ),
.into(), )
))
)], )],
); );
assert_eq!( assert_eq!(
@ -491,24 +492,22 @@ fn test_precedence() {
.nodes, .nodes,
vec![Node::Expr( vec![Node::Expr(
Ws(None, None), Ws(None, None),
WithSpan::no_span(Expr::BinOp( bin_op(
"/", "/",
Box::new(WithSpan::no_span(Expr::BinOp( bin_op(
"*", "*",
Box::new(WithSpan::no_span(Expr::Var("a"))), WithSpan::no_span(Expr::Var("a")),
Box::new(WithSpan::no_span(Expr::Group(Box::new(WithSpan::no_span( WithSpan::no_span(Expr::Group(Box::new(bin_op(
Expr::BinOp( "+",
"+", WithSpan::no_span(Expr::Var("b")),
Box::new(WithSpan::no_span(Expr::Var("b"))), WithSpan::no_span(Expr::Var("c"))
Box::new(WithSpan::no_span(Expr::Var("c"))) ))))
) ),
))))) WithSpan::no_span(Expr::Unary(
))),
Box::new(WithSpan::no_span(Expr::Unary(
"-", "-",
Box::new(WithSpan::no_span(Expr::Var("d"))) Box::new(WithSpan::no_span(Expr::Var("d")))
))) ))
)) )
)], )],
); );
assert_eq!( assert_eq!(
@ -517,23 +516,23 @@ fn test_precedence() {
.nodes, .nodes,
vec![Node::Expr( vec![Node::Expr(
Ws(None, None), Ws(None, None),
WithSpan::no_span(Expr::BinOp( bin_op(
"||", "||",
Box::new(WithSpan::no_span(Expr::BinOp( bin_op(
"||", "||",
Box::new(WithSpan::no_span(Expr::Var("a"))), WithSpan::no_span(Expr::Var("a")),
Box::new(WithSpan::no_span(Expr::BinOp( bin_op(
"&&", "&&",
Box::new(WithSpan::no_span(Expr::Var("b"))), WithSpan::no_span(Expr::Var("b")),
Box::new(WithSpan::no_span(Expr::Var("c"))) WithSpan::no_span(Expr::Var("c"))
))), ),
))), ),
Box::new(WithSpan::no_span(Expr::BinOp( bin_op(
"&&", "&&",
Box::new(WithSpan::no_span(Expr::Var("d"))), WithSpan::no_span(Expr::Var("d")),
Box::new(WithSpan::no_span(Expr::Var("e"))) WithSpan::no_span(Expr::Var("e"))
))), ),
)) )
)], )],
); );
} }
@ -547,15 +546,15 @@ fn test_associativity() {
.nodes, .nodes,
vec![Node::Expr( vec![Node::Expr(
Ws(None, None), Ws(None, None),
WithSpan::no_span(Expr::BinOp( bin_op(
"+", "+",
Box::new(WithSpan::no_span(Expr::BinOp( bin_op(
"+", "+",
Box::new(WithSpan::no_span(Expr::Var("a"))), WithSpan::no_span(Expr::Var("a")),
Box::new(WithSpan::no_span(Expr::Var("b"))) WithSpan::no_span(Expr::Var("b"))
))), ),
Box::new(WithSpan::no_span(Expr::Var("c"))) WithSpan::no_span(Expr::Var("c"))
)) )
)], )],
); );
assert_eq!( assert_eq!(
@ -564,15 +563,15 @@ fn test_associativity() {
.nodes, .nodes,
vec![Node::Expr( vec![Node::Expr(
Ws(None, None), Ws(None, None),
WithSpan::no_span(Expr::BinOp( bin_op(
"*", "*",
Box::new(WithSpan::no_span(Expr::BinOp( bin_op(
"*", "*",
Box::new(WithSpan::no_span(Expr::Var("a"))), WithSpan::no_span(Expr::Var("a")),
Box::new(WithSpan::no_span(Expr::Var("b"))) WithSpan::no_span(Expr::Var("b"))
))), ),
Box::new(WithSpan::no_span(Expr::Var("c"))) WithSpan::no_span(Expr::Var("c"))
)) )
)], )],
); );
assert_eq!( assert_eq!(
@ -581,15 +580,15 @@ fn test_associativity() {
.nodes, .nodes,
vec![Node::Expr( vec![Node::Expr(
Ws(None, None), Ws(None, None),
WithSpan::no_span(Expr::BinOp( bin_op(
"&&", "&&",
Box::new(WithSpan::no_span(Expr::BinOp( bin_op(
"&&", "&&",
Box::new(WithSpan::no_span(Expr::Var("a"))), WithSpan::no_span(Expr::Var("a")),
Box::new(WithSpan::no_span(Expr::Var("b"))) WithSpan::no_span(Expr::Var("b"))
))), ),
Box::new(WithSpan::no_span(Expr::Var("c"))) WithSpan::no_span(Expr::Var("c"))
)) )
)], )],
); );
assert_eq!( assert_eq!(
@ -598,19 +597,19 @@ fn test_associativity() {
.nodes, .nodes,
vec![Node::Expr( vec![Node::Expr(
Ws(None, None), Ws(None, None),
WithSpan::no_span(Expr::BinOp( bin_op(
"+", "+",
Box::new(WithSpan::no_span(Expr::BinOp( bin_op(
"-", "-",
Box::new(WithSpan::no_span(Expr::BinOp( bin_op(
"+", "+",
Box::new(WithSpan::no_span(Expr::Var("a"))), WithSpan::no_span(Expr::Var("a")),
Box::new(WithSpan::no_span(Expr::Var("b"))) WithSpan::no_span(Expr::Var("b"))
))), ),
Box::new(WithSpan::no_span(Expr::Var("c"))) WithSpan::no_span(Expr::Var("c"))
))), ),
Box::new(WithSpan::no_span(Expr::Var("d"))) WithSpan::no_span(Expr::Var("d"))
)) )
)], )],
); );
} }
@ -639,12 +638,10 @@ fn test_odd_calls() {
vec![Node::Expr( vec![Node::Expr(
Ws(None, None), Ws(None, None),
WithSpan::no_span(Expr::Call { WithSpan::no_span(Expr::Call {
path: Box::new(WithSpan::no_span(Expr::Group(Box::new(WithSpan::no_span( path: Box::new(WithSpan::no_span(Expr::Group(Box::new(bin_op(
Expr::BinOp( "+",
"+", WithSpan::no_span(Expr::Var("a")),
Box::new(WithSpan::no_span(Expr::Var("a"))), WithSpan::no_span(Expr::Var("b"))
Box::new(WithSpan::no_span(Expr::Var("b")))
)
))))), ))))),
args: vec![WithSpan::no_span(Expr::Var("c"))], args: vec![WithSpan::no_span(Expr::Var("c"))],
generics: vec![], generics: vec![],
@ -657,15 +654,15 @@ fn test_odd_calls() {
.nodes, .nodes,
vec![Node::Expr( vec![Node::Expr(
Ws(None, None), Ws(None, None),
WithSpan::no_span(Expr::BinOp( bin_op(
"+", "+",
Box::new(WithSpan::no_span(Expr::Var("a"))), WithSpan::no_span(Expr::Var("a")),
Box::new(WithSpan::no_span(Expr::Call { WithSpan::no_span(Expr::Call {
path: Box::new(WithSpan::no_span(Expr::Var("b"))), path: Box::new(WithSpan::no_span(Expr::Var("b"))),
args: vec![WithSpan::no_span(Expr::Var("c"))], args: vec![WithSpan::no_span(Expr::Var("c"))],
generics: vec![], generics: vec![],
})), }),
)), ),
)], )],
); );
assert_eq!( assert_eq!(