diff options
Diffstat (limited to 'askama_parser/src/expr.rs')
-rw-r--r-- | askama_parser/src/expr.rs | 79 |
1 files changed, 74 insertions, 5 deletions
diff --git a/askama_parser/src/expr.rs b/askama_parser/src/expr.rs index 76691be..2928b57 100644 --- a/askama_parser/src/expr.rs +++ b/askama_parser/src/expr.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; +use std::collections::HashSet; use std::str; use nom::branch::alt; @@ -12,7 +14,7 @@ use nom::sequence::{pair, preceded, terminated, tuple}; use super::{ char_lit, identifier, not_ws, num_lit, path_or_identifier, str_lit, ws, Level, PathOrIdentifier, }; -use crate::ParseResult; +use crate::{ErrorContext, ParseResult}; macro_rules! expr_prec_layer { ( $name:ident, $inner:ident, $op:expr ) => { @@ -61,6 +63,7 @@ pub enum Expr<'a> { Attr(Box<Expr<'a>>, &'a str), Index(Box<Expr<'a>>, Box<Expr<'a>>), Filter(&'a str, Vec<Expr<'a>>), + NamedArgument(&'a str, Box<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>>>), @@ -72,17 +75,83 @@ pub enum Expr<'a> { } impl<'a> Expr<'a> { - pub(super) fn arguments(i: &'a str, level: Level) -> ParseResult<'a, Vec<Self>> { + pub(super) fn arguments( + i: &'a str, + level: Level, + is_template_macro: bool, + ) -> ParseResult<'a, Vec<Self>> { let (_, level) = level.nest(i)?; + let mut named_arguments = HashSet::new(); + let start = i; + preceded( ws(char('(')), cut(terminated( - separated_list0(char(','), ws(move |i| Self::parse(i, level))), + separated_list0( + char(','), + ws(move |i| { + // Needed to prevent borrowing it twice between this closure and the one + // calling `Self::named_arguments`. + let named_arguments = &mut named_arguments; + let has_named_arguments = !named_arguments.is_empty(); + + let (i, expr) = alt(( + move |i| { + Self::named_argument( + i, + level, + named_arguments, + start, + is_template_macro, + ) + }, + move |i| Self::parse(i, level), + ))(i)?; + if has_named_arguments && !matches!(expr, Self::NamedArgument(_, _)) { + Err(nom::Err::Failure(ErrorContext { + input: start, + message: Some(Cow::Borrowed( + "named arguments must always be passed last", + )), + })) + } else { + Ok((i, expr)) + } + }), + ), char(')'), )), )(i) } + fn named_argument( + i: &'a str, + level: Level, + named_arguments: &mut HashSet<&'a str>, + start: &'a str, + is_template_macro: bool, + ) -> ParseResult<'a, Self> { + if !is_template_macro { + // If this is not a template macro, we don't want to parse named arguments so + // we instead return an error which will allow to continue the parsing. + return Err(nom::Err::Error(error_position!(i, ErrorKind::Alt))); + } + + let (_, level) = level.nest(i)?; + let (i, (argument, _, value)) = + tuple((identifier, ws(char('=')), move |i| Self::parse(i, level)))(i)?; + if named_arguments.insert(argument) { + Ok((i, Self::NamedArgument(argument, Box::new(value)))) + } else { + Err(nom::Err::Failure(ErrorContext { + input: start, + message: Some(Cow::Owned(format!( + "named argument `{argument}` was passed more than once" + ))), + })) + } + } + pub(super) fn parse(i: &'a str, level: Level) -> ParseResult<'a, Self> { let (_, level) = level.nest(i)?; let range_right = move |i| { @@ -122,7 +191,7 @@ impl<'a> Expr<'a> { let (i, (_, fname, args)) = tuple(( char('|'), ws(identifier), - opt(|i| Expr::arguments(i, level)), + opt(|i| Expr::arguments(i, level, false)), ))(i)?; Ok((i, (fname, args))) } @@ -354,7 +423,7 @@ impl<'a> Suffix<'a> { fn call(i: &'a str, level: Level) -> ParseResult<'a, Self> { let (_, level) = level.nest(i)?; - map(move |i| Expr::arguments(i, level), Self::Call)(i) + map(move |i| Expr::arguments(i, level, false), Self::Call)(i) } fn r#try(i: &'a str) -> ParseResult<'a, Self> { |