aboutsummaryrefslogtreecommitdiffstats
path: root/askama_parser/src/expr.rs
diff options
context:
space:
mode:
Diffstat (limited to 'askama_parser/src/expr.rs')
-rw-r--r--askama_parser/src/expr.rs79
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> {