use std::str; use nom::branch::alt; use nom::bytes::complete::{tag, take_till}; use nom::character::complete::char; use nom::combinator::{cut, map, not, opt, peek, recognize}; use nom::multi::{fold_many0, many0, separated_list0, separated_list1}; use nom::sequence::{delimited, pair, preceded, terminated, tuple}; use nom::IResult; use super::{ bool_lit, char_lit, identifier, nested_parenthesis, not_ws, num_lit, path, str_lit, ws, }; #[derive(Debug, PartialEq)] pub(crate) enum Expr<'a> { BoolLit(&'a str), NumLit(&'a str), StrLit(&'a str), CharLit(&'a str), Var(&'a str), Path(Vec<&'a str>), Array(Vec>), Attr(Box>, &'a str), Index(Box>, Box>), Filter(&'a str, Vec>), Unary(&'a str, Box>), BinOp(&'a str, Box>, Box>), Range(&'a str, Option>>, Option>>), Group(Box>), Tuple(Vec>), Call(Box>, Vec>), RustMacro(&'a str, &'a str), Try(Box>), } impl Expr<'_> { pub(super) fn parse(i: &str) -> IResult<&str, Expr<'_>> { expr_any(i) } pub(super) fn parse_arguments(i: &str) -> IResult<&str, Vec>> { arguments(i) } /// Returns `true` if enough assumptions can be made, /// to determine that `self` is copyable. pub(crate) fn is_copyable(&self) -> bool { self.is_copyable_within_op(false) } fn is_copyable_within_op(&self, within_op: bool) -> bool { use Expr::*; match self { BoolLit(_) | NumLit(_) | StrLit(_) | CharLit(_) => true, Unary(.., expr) => expr.is_copyable_within_op(true), BinOp(_, lhs, rhs) => { lhs.is_copyable_within_op(true) && rhs.is_copyable_within_op(true) } Range(..) => true, // The result of a call likely doesn't need to be borrowed, // as in that case the call is more likely to return a // reference in the first place then. Call(..) | Path(..) => true, // If the `expr` is within a `Unary` or `BinOp` then // an assumption can be made that the operand is copy. // If not, then the value is moved and adding `.clone()` // will solve that issue. However, if the operand is // implicitly borrowed, then it's likely not even possible // to get the template to compile. _ => within_op && self.is_attr_self(), } } /// Returns `true` if this is an `Attr` where the `obj` is `"self"`. pub(crate) fn is_attr_self(&self) -> bool { match self { Expr::Attr(obj, _) if matches!(obj.as_ref(), Expr::Var("self")) => true, Expr::Attr(obj, _) if matches!(obj.as_ref(), Expr::Attr(..)) => obj.is_attr_self(), _ => false, } } /// Returns `true` if the outcome of this expression may be used multiple times in the same /// `write!()` call, without evaluating the expression again, i.e. the expression should be /// side-effect free. pub(crate) fn is_cacheable(&self) -> bool { match self { // Literals are the definition of pure: Expr::BoolLit(_) => true, Expr::NumLit(_) => true, Expr::StrLit(_) => true, Expr::CharLit(_) => true, // fmt::Display should have no effects: Expr::Var(_) => true, Expr::Path(_) => true, // Check recursively: Expr::Array(args) => args.iter().all(|arg| arg.is_cacheable()), Expr::Attr(lhs, _) => lhs.is_cacheable(), Expr::Index(lhs, rhs) => lhs.is_cacheable() && rhs.is_cacheable(), Expr::Filter(_, args) => args.iter().all(|arg| arg.is_cacheable()), Expr::Unary(_, arg) => arg.is_cacheable(), Expr::BinOp(_, lhs, rhs) => lhs.is_cacheable() && rhs.is_cacheable(), Expr::Range(_, lhs, rhs) => { lhs.as_ref().map_or(true, |v| v.is_cacheable()) && rhs.as_ref().map_or(true, |v| v.is_cacheable()) } Expr::Group(arg) => arg.is_cacheable(), Expr::Tuple(args) => args.iter().all(|arg| arg.is_cacheable()), // We have too little information to tell if the expression is pure: Expr::Call(_, _) => false, Expr::RustMacro(_, _) => false, Expr::Try(_) => false, } } } fn expr_bool_lit(i: &str) -> IResult<&str, Expr<'_>> { map(bool_lit, Expr::BoolLit)(i) } fn expr_num_lit(i: &str) -> IResult<&str, Expr<'_>> { map(num_lit, Expr::NumLit)(i) } fn expr_array_lit(i: &str) -> IResult<&str, Expr<'_>> { delimited( ws(char('[')), map(separated_list1(ws(char(',')), expr_any), Expr::Array), ws(char(']')), )(i) } fn expr_str_lit(i: &str) -> IResult<&str, Expr<'_>> { map(str_lit, Expr::StrLit)(i) } fn expr_char_lit(i: &str) -> IResult<&str, Expr<'_>> { map(char_lit, Expr::CharLit)(i) } fn expr_var(i: &str) -> IResult<&str, Expr<'_>> { map(identifier, Expr::Var)(i) } fn expr_path(i: &str) -> IResult<&str, Expr<'_>> { let (i, path) = path(i)?; Ok((i, Expr::Path(path))) } fn expr_group(i: &str) -> IResult<&str, Expr<'_>> { let (i, expr) = preceded(ws(char('(')), opt(expr_any))(i)?; let expr = match expr { Some(expr) => expr, None => { let (i, _) = char(')')(i)?; return Ok((i, Expr::Tuple(vec![]))); } }; let (i, comma) = ws(opt(peek(char(','))))(i)?; if comma.is_none() { let (i, _) = char(')')(i)?; return Ok((i, Expr::Group(Box::new(expr)))); } let mut exprs = vec![expr]; let (i, _) = fold_many0( preceded(char(','), ws(expr_any)), || (), |_, expr| { exprs.push(expr); }, )(i)?; let (i, _) = pair(ws(opt(char(','))), char(')'))(i)?; Ok((i, Expr::Tuple(exprs))) } fn expr_single(i: &str) -> IResult<&str, Expr<'_>> { alt(( expr_bool_lit, expr_num_lit, expr_str_lit, expr_char_lit, expr_path, expr_rust_macro, expr_array_lit, expr_var, expr_group, ))(i) } enum Suffix<'a> { Attr(&'a str), Index(Expr<'a>), Call(Vec>), Try, } fn expr_attr(i: &str) -> IResult<&str, Suffix<'_>> { map( preceded( ws(pair(char('.'), not(char('.')))), cut(alt((num_lit, identifier))), ), Suffix::Attr, )(i) } fn expr_index(i: &str) -> IResult<&str, Suffix<'_>> { map( preceded(ws(char('[')), cut(terminated(expr_any, ws(char(']'))))), Suffix::Index, )(i) } fn expr_call(i: &str) -> IResult<&str, Suffix<'_>> { map(arguments, Suffix::Call)(i) } fn expr_try(i: &str) -> IResult<&str, Suffix<'_>> { map(preceded(take_till(not_ws), char('?')), |_| Suffix::Try)(i) } fn filter(i: &str) -> IResult<&str, (&str, Option>>)> { let (i, (_, fname, args)) = tuple((char('|'), ws(identifier), opt(arguments)))(i)?; Ok((i, (fname, args))) } fn expr_filtered(i: &str) -> IResult<&str, Expr<'_>> { let (i, (obj, filters)) = tuple((expr_prefix, many0(filter)))(i)?; let mut res = obj; for (fname, args) in filters { res = Expr::Filter(fname, { let mut args = match args { Some(inner) => inner, None => Vec::new(), }; args.insert(0, res); args }); } Ok((i, res)) } fn expr_prefix(i: &str) -> IResult<&str, Expr<'_>> { let (i, (ops, mut expr)) = pair(many0(ws(alt((tag("!"), tag("-"))))), expr_suffix)(i)?; for op in ops.iter().rev() { expr = Expr::Unary(op, Box::new(expr)); } Ok((i, expr)) } fn expr_suffix(i: &str) -> IResult<&str, Expr<'_>> { let (mut i, mut expr) = expr_single(i)?; loop { let (j, suffix) = opt(alt((expr_attr, expr_index, expr_call, expr_try)))(i)?; i = j; match suffix { Some(Suffix::Attr(attr)) => expr = Expr::Attr(expr.into(), attr), Some(Suffix::Index(index)) => expr = Expr::Index(expr.into(), index.into()), Some(Suffix::Call(args)) => expr = Expr::Call(expr.into(), args), Some(Suffix::Try) => expr = Expr::Try(expr.into()), None => break, } } Ok((i, expr)) } fn macro_arguments(i: &str) -> IResult<&str, &str> { delimited(char('('), recognize(nested_parenthesis), char(')'))(i) } fn expr_rust_macro(i: &str) -> IResult<&str, Expr<'_>> { let (i, (mname, _, args)) = tuple((identifier, char('!'), macro_arguments))(i)?; Ok((i, Expr::RustMacro(mname, args))) } macro_rules! expr_prec_layer { ( $name:ident, $inner:ident, $op:expr ) => { fn $name(i: &str) -> IResult<&str, Expr<'_>> { let (i, left) = $inner(i)?; let (i, right) = many0(pair( ws(tag($op)), $inner, ))(i)?; Ok(( i, right.into_iter().fold(left, |left, (op, right)| { Expr::BinOp(op, Box::new(left), Box::new(right)) }), )) } }; ( $name:ident, $inner:ident, $( $op:expr ),+ ) => { fn $name(i: &str) -> IResult<&str, Expr<'_>> { let (i, left) = $inner(i)?; let (i, right) = many0(pair( ws(alt(($( tag($op) ),+,))), $inner, ))(i)?; Ok(( i, right.into_iter().fold(left, |left, (op, right)| { Expr::BinOp(op, Box::new(left), Box::new(right)) }), )) } } } expr_prec_layer!(expr_muldivmod, expr_filtered, "*", "/", "%"); expr_prec_layer!(expr_addsub, expr_muldivmod, "+", "-"); expr_prec_layer!(expr_shifts, expr_addsub, ">>", "<<"); expr_prec_layer!(expr_band, expr_shifts, "&"); expr_prec_layer!(expr_bxor, expr_band, "^"); expr_prec_layer!(expr_bor, expr_bxor, "|"); expr_prec_layer!(expr_compare, expr_bor, "==", "!=", ">=", ">", "<=", "<"); expr_prec_layer!(expr_and, expr_compare, "&&"); expr_prec_layer!(expr_or, expr_and, "||"); fn expr_any(i: &str) -> IResult<&str, Expr<'_>> { let range_right = |i| pair(ws(alt((tag("..="), tag("..")))), opt(expr_or))(i); alt(( map(range_right, |(op, right)| { Expr::Range(op, None, right.map(Box::new)) }), map( pair(expr_or, opt(range_right)), |(left, right)| match right { Some((op, right)) => Expr::Range(op, Some(Box::new(left)), right.map(Box::new)), None => left, }, ), ))(i) } fn arguments(i: &str) -> IResult<&str, Vec>> { delimited( ws(char('(')), separated_list0(char(','), ws(expr_any)), ws(char(')')), )(i) }