diff options
author | René Kijewski <rene.kijewski@fu-berlin.de> | 2023-01-30 12:52:50 +0100 |
---|---|---|
committer | Dirkjan Ochtman <dirkjan@ochtman.nl> | 2023-01-30 14:19:46 +0100 |
commit | 7b6f1df433a7f11612608644342b898cd6be8ff5 (patch) | |
tree | 478aafb88f9814fe235ee983213da5aa74a13e0a /askama_derive/src/parser/expr.rs | |
parent | 63b98ec7d379768d771966c6aa44de20862e0994 (diff) | |
download | askama-7b6f1df433a7f11612608644342b898cd6be8ff5.tar.gz askama-7b6f1df433a7f11612608644342b898cd6be8ff5.tar.bz2 askama-7b6f1df433a7f11612608644342b898cd6be8ff5.zip |
derive: refactor parser
`parser.rs` was a single file containing almost 2000 lines.
This PR split the file into multiple, smaller files. `Expr`, `Node`, and
`Target` each get an own file. Each struct gets a `parse()` method that
return `Result<Self>`, and every other detail is private to the file.
This PR should make this essential part of Askama more easy to
understand, and make future modifications easier.
Diffstat (limited to 'askama_derive/src/parser/expr.rs')
-rw-r--r-- | askama_derive/src/parser/expr.rs | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/askama_derive/src/parser/expr.rs b/askama_derive/src/parser/expr.rs new file mode 100644 index 0000000..4546e04 --- /dev/null +++ b/askama_derive/src/parser/expr.rs @@ -0,0 +1,346 @@ +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<Expr<'a>>), + Attr(Box<Expr<'a>>, &'a str), + Index(Box<Expr<'a>>, Box<Expr<'a>>), + Filter(&'a str, Vec<Expr<'a>>), + Unary(&'a str, Box<Expr<'a>>), + BinOp(&'a str, Box<Expr<'a>>, Box<Expr<'a>>), + Range(&'a str, Option<Box<Expr<'a>>>, Option<Box<Expr<'a>>>), + Group(Box<Expr<'a>>), + Tuple(Vec<Expr<'a>>), + Call(Box<Expr<'a>>, Vec<Expr<'a>>), + RustMacro(&'a str, &'a str), + Try(Box<Expr<'a>>), +} + +impl Expr<'_> { + pub(super) fn parse(i: &str) -> IResult<&str, Expr<'_>> { + expr_any(i) + } + + pub(super) fn parse_arguments(i: &str) -> IResult<&str, Vec<Expr<'_>>> { + 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_cachable(&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_cachable()), + Expr::Attr(lhs, _) => lhs.is_cachable(), + Expr::Index(lhs, rhs) => lhs.is_cachable() && rhs.is_cachable(), + Expr::Filter(_, args) => args.iter().all(|arg| arg.is_cachable()), + Expr::Unary(_, arg) => arg.is_cachable(), + Expr::BinOp(_, lhs, rhs) => lhs.is_cachable() && rhs.is_cachable(), + Expr::Range(_, lhs, rhs) => { + lhs.as_ref().map_or(true, |v| v.is_cachable()) + && rhs.as_ref().map_or(true, |v| v.is_cachable()) + } + Expr::Group(arg) => arg.is_cachable(), + Expr::Tuple(args) => args.iter().all(|arg| arg.is_cachable()), + // 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<Expr<'a>>), + 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<Vec<Expr<'_>>>)> { + 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<Expr<'_>>> { + delimited( + ws(char('(')), + separated_list0(char(','), ws(expr_any)), + ws(char(')')), + )(i) +} |