diff --git a/askama_parser/src/expr.rs b/askama_parser/src/expr.rs index 4cda0958..7f4d3953 100644 --- a/askama_parser/src/expr.rs +++ b/askama_parser/src/expr.rs @@ -5,31 +5,34 @@ use winnow::ascii::digit1; use winnow::combinator::{ alt, cut_err, empty, fail, not, opt, peek, preceded, repeat, separated, terminated, }; -use winnow::error::{ErrMode, ParserError as _}; -use winnow::token::{one_of, take_until}; +use winnow::stream::Stream; +use winnow::token::{any, one_of, take, take_until}; use crate::node::CondTest; use crate::{ - CharLit, ErrorContext, HashSet, Level, Num, ParseResult, PathOrIdentifier, StrLit, StrPrefix, - WithSpan, can_be_variable_name, char_lit, cut_error, filter, identifier, keyword, + CharLit, ErrorContext, HashSet, InputStream, Level, Num, ParseResult, PathOrIdentifier, StrLit, + StrPrefix, WithSpan, can_be_variable_name, char_lit, cut_error, filter, identifier, keyword, not_suffix_with_hash, num_lit, path_or_identifier, skip_ws0, skip_ws1, str_lit, ws, }; macro_rules! expr_prec_layer { ( $name:ident, $inner:ident, $op:expr ) => { - fn $name(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, WithSpan<'a, Box>> { + fn $name( + i: &mut InputStream<'a>, + level: Level<'_>, + ) -> ParseResult<'a, WithSpan<'a, Box>> { expr_prec_layer(i, level, Expr::$inner, |i: &mut _| $op.parse_next(i)) } }; } fn expr_prec_layer<'a>( - i: &mut &'a str, + i: &mut InputStream<'a>, level: Level<'_>, - inner: fn(&mut &'a str, Level<'_>) -> ParseResult<'a, WithSpan<'a, Box>>>, - op: fn(&mut &'a str) -> ParseResult<'a>, + inner: fn(&mut InputStream<'a>, Level<'_>) -> ParseResult<'a, WithSpan<'a, Box>>>, + op: fn(&mut InputStream<'a>) -> ParseResult<'a>, ) -> ParseResult<'a, WithSpan<'a, Box>>> { - let start = *i; + let start = ***i; let mut expr = inner(i, level)?; let mut level_guard = level.guard(); @@ -172,8 +175,11 @@ impl<'a> PathComponent<'a> { } } - pub(crate) fn parse(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, WithSpan<'a, Self>> { - let start = *i; + pub(crate) fn parse( + i: &mut InputStream<'a>, + level: Level<'_>, + ) -> ParseResult<'a, WithSpan<'a, Self>> { + let start = ***i; ( identifier, opt((ws("::"), |i: &mut _| TyGenerics::args(i, level))), @@ -248,12 +254,12 @@ pub struct BinOp<'a> { impl<'a> Expr<'a> { pub(super) fn arguments( - i: &mut &'a str, + i: &mut InputStream<'a>, level: Level<'_>, ) -> ParseResult<'a, Vec>>> { let _level_guard = level.nest(i)?; let mut named_arguments = HashSet::default(); - let start = *i; + let start = ***i; preceded( ws('('), @@ -286,7 +292,7 @@ impl<'a> Expr<'a> { } fn named_argument( - i: &mut &'a str, + i: &mut InputStream<'a>, level: Level<'_>, named_arguments: &mut HashSet<&'a str>, start: &'a str, @@ -310,7 +316,7 @@ impl<'a> Expr<'a> { } pub(super) fn parse( - i: &mut &'a str, + i: &mut InputStream<'a>, level: Level<'_>, allow_underscore: bool, ) -> ParseResult<'a, WithSpan<'a, Box>> { @@ -361,13 +367,16 @@ impl<'a> Expr<'a> { expr_prec_layer!(or, and, "||"); expr_prec_layer!(and, compare, "&&"); - fn compare(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, WithSpan<'a, Box>> { + fn compare( + i: &mut InputStream<'a>, + level: Level<'_>, + ) -> ParseResult<'a, WithSpan<'a, Box>> { let right = |i: &mut _| { let op = alt(("==", "!=", ">=", ">", "<=", "<")); (ws(op), |i: &mut _| Self::bor(i, level)).parse_next(i) }; - let start = *i; + let start = ***i; let expr = Self::bor(i, level)?; let Some((op, rhs)) = opt(right).parse_next(i)? else { return Ok(expr); @@ -397,14 +406,17 @@ impl<'a> Expr<'a> { expr_prec_layer!(shifts, addsub, alt((">>", "<<"))); expr_prec_layer!(addsub, concat, alt(("+", "-"))); - fn concat(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, WithSpan<'a, Box>> { + fn concat( + i: &mut InputStream<'a>, + level: Level<'_>, + ) -> ParseResult<'a, WithSpan<'a, Box>> { fn concat_expr<'a>( - i: &mut &'a str, + i: &mut InputStream<'a>, level: Level<'_>, ) -> ParseResult<'a, Option>>>> { let ws1 = |i: &mut _| opt(skip_ws1).parse_next(i); - let start = *i; + let start = ***i; let data = opt((ws1, '~', ws1, |i: &mut _| Expr::muldivmod(i, level))).parse_next(i)?; if let Some((t1, _, t2, expr)) = data { if t1.is_none() || t2.is_none() { @@ -419,7 +431,7 @@ impl<'a> Expr<'a> { } } - let start = *i; + let start = ***i; let expr = Self::muldivmod(i, level)?; let expr2 = concat_expr(i, level)?; if let Some(expr2) = expr2 { @@ -435,8 +447,11 @@ impl<'a> Expr<'a> { expr_prec_layer!(muldivmod, is_as, alt(("*", "/", "%"))); - fn is_as(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, WithSpan<'a, Box>> { - let start = *i; + fn is_as( + i: &mut InputStream<'a>, + level: Level<'_>, + ) -> ParseResult<'a, WithSpan<'a, Box>> { + let start = ***i; let lhs = Self::filtered(i, level)?; let before_keyword = *i; let rhs = opt(ws(identifier)).parse_next(i)?; @@ -495,24 +510,30 @@ impl<'a> Expr<'a> { Ok(WithSpan::new(Box::new(ctor(var_name)), start, i)) } - fn filtered(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, WithSpan<'a, Box>> { + fn filtered( + i: &mut InputStream<'a>, + level: Level<'_>, + ) -> ParseResult<'a, WithSpan<'a, Box>> { let mut res = Self::prefix(i, level)?; let mut level_guard = level.guard(); - let mut start = *i; + let mut start = ***i; while let Some((mut filter, i_before)) = opt(ws((|i: &mut _| filter(i, level)).with_taken())).parse_next(i)? { level_guard.nest(i_before)?; filter.arguments.insert(0, res); res = WithSpan::new(Box::new(Self::Filter(filter)), start.trim_start(), i); - start = *i; + start = ***i; } Ok(res) } - fn prefix(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, WithSpan<'a, Box>> { - let start = *i; + fn prefix( + i: &mut InputStream<'a>, + level: Level<'_>, + ) -> ParseResult<'a, WithSpan<'a, Box>> { + let start = ***i; // This is a rare place where we create recursion in the parsed AST // without recursing the parser call stack. However, this can lead @@ -534,7 +555,10 @@ impl<'a> Expr<'a> { Ok(expr) } - fn single(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, WithSpan<'a, Box>> { + fn single( + i: &mut InputStream<'a>, + level: Level<'_>, + ) -> ParseResult<'a, WithSpan<'a, Box>> { alt(( Self::num, Self::str, @@ -546,8 +570,11 @@ impl<'a> Expr<'a> { .parse_next(i) } - fn group(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, WithSpan<'a, Box>> { - let start = *i; + fn group( + i: &mut InputStream<'a>, + level: Level<'_>, + ) -> ParseResult<'a, WithSpan<'a, Box>> { + let start = ***i; let expr = preceded(ws('('), opt(|i: &mut _| Self::parse(i, level, true))).parse_next(i)?; let Some(expr) = expr else { let _ = ')'.parse_next(i)?; @@ -576,8 +603,11 @@ impl<'a> Expr<'a> { Ok(WithSpan::new(Box::new(Self::Tuple(exprs)), start, i)) } - fn array(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, WithSpan<'a, Box>> { - let start = *i; + fn array( + i: &mut InputStream<'a>, + level: Level<'_>, + ) -> ParseResult<'a, WithSpan<'a, Box>> { + let start = ***i; let array = preceded( ws('['), cut_err(terminated( @@ -597,10 +627,10 @@ impl<'a> Expr<'a> { } fn path_var_bool( - i: &mut &'a str, + i: &mut InputStream<'a>, level: Level<'_>, ) -> ParseResult<'a, WithSpan<'a, Box>> { - let start = *i; + let start = ***i; let ret = match path_or_identifier(i, level)? { PathOrIdentifier::Path(v) => Box::new(Self::Path(v)), PathOrIdentifier::Identifier("true") => Box::new(Self::BoolLit(true)), @@ -610,20 +640,20 @@ impl<'a> Expr<'a> { Ok(WithSpan::new(ret, start, i)) } - fn str(i: &mut &'a str) -> ParseResult<'a, WithSpan<'a, Box>> { - let start = *i; + fn str(i: &mut InputStream<'a>) -> ParseResult<'a, WithSpan<'a, Box>> { + let start = ***i; let s = str_lit.parse_next(i)?; Ok(WithSpan::new(Box::new(Self::StrLit(s)), start, i)) } - fn num(i: &mut &'a str) -> ParseResult<'a, WithSpan<'a, Box>> { - let start = *i; + fn num(i: &mut InputStream<'a>) -> ParseResult<'a, WithSpan<'a, Box>> { + let start = ***i; let (num, full) = num_lit.with_taken().parse_next(i)?; Ok(WithSpan::new(Box::new(Expr::NumLit(full, num)), start, i)) } - fn char(i: &mut &'a str) -> ParseResult<'a, WithSpan<'a, Box>> { - let start = *i; + fn char(i: &mut InputStream<'a>) -> ParseResult<'a, WithSpan<'a, Box>> { + let start = ***i; let c = char_lit.parse_next(i)?; Ok(WithSpan::new(Box::new(Self::CharLit(c)), start, i)) } @@ -661,21 +691,21 @@ impl<'a> Expr<'a> { } } -fn token_xor<'a>(i: &mut &'a str) -> ParseResult<'a> { +fn token_xor<'a>(i: &mut InputStream<'a>) -> ParseResult<'a> { let good = alt((keyword("xor").value(true), '^'.value(false))).parse_next(i)?; if good { Ok("^") } else { - cut_error!("the binary XOR operator is called `xor` in askama", *i) + cut_error!("the binary XOR operator is called `xor` in askama", ***i) } } -fn token_bitand<'a>(i: &mut &'a str) -> ParseResult<'a> { +fn token_bitand<'a>(i: &mut InputStream<'a>) -> ParseResult<'a> { let good = alt((keyword("bitand").value(true), ('&', not('&')).value(false))).parse_next(i)?; if good { Ok("&") } else { - cut_error!("the binary AND operator is called `bitand` in askama", *i) + cut_error!("the binary AND operator is called `bitand` in askama", ***i) } } @@ -686,7 +716,7 @@ pub struct Filter<'a> { } impl<'a> Filter<'a> { - pub(crate) fn parse(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, Self> { + pub(crate) fn parse(i: &mut InputStream<'a>, level: Level<'_>) -> ParseResult<'a, Self> { let (name, arguments) = ( ws(|i: &mut _| path_or_identifier(i, level)), opt(|i: &mut _| Expr::arguments(i, level)), @@ -717,8 +747,11 @@ enum Suffix<'a> { } impl<'a> Suffix<'a> { - fn parse(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, WithSpan<'a, Box>>> { - let i_start = *i; + fn parse( + i: &mut InputStream<'a>, + level: Level<'_>, + ) -> ParseResult<'a, WithSpan<'a, Box>>> { + let i_start = ***i; let mut level_guard = level.guard(); let mut expr = Expr::single(i, level)?; let mut right = alt(( @@ -728,9 +761,11 @@ impl<'a> Suffix<'a> { Self::r#try, Self::r#macro, )); - let start = *i; - while let Some((suffix, i_before)) = opt(right.by_ref().with_taken()).parse_next(i)? { - level_guard.nest(i_before)?; + + let start = ***i; + let mut i_before = i.checkpoint(); + while let Some(suffix) = opt(right.by_ref()).parse_next(i)? { + level_guard.nest(i)?; match suffix { Self::AssociatedItem(associated_item) => { expr = WithSpan::new( @@ -770,15 +805,17 @@ impl<'a> Suffix<'a> { expr = WithSpan::new(Box::new(Expr::RustMacro(vec![name], args)), start, i) } _ => { - return Err(ErrMode::from_input(&i_before).cut()); + i.reset(&i_before); + return fail(i); } }, } + i_before = i.checkpoint(); } Ok(expr) } - fn r#macro(i: &mut &'a str) -> ParseResult<'a, Self> { + fn r#macro(i: &mut InputStream<'a>) -> ParseResult<'a, Self> { #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Token { SomeOther, @@ -803,8 +840,11 @@ impl<'a> Suffix<'a> { } } - fn macro_arguments<'a>(i: &mut &'a str, open_token: Group) -> ParseResult<'a, Suffix<'a>> { - let start = *i; + fn macro_arguments<'a>( + i: &mut InputStream<'a>, + open_token: Group, + ) -> ParseResult<'a, Suffix<'a>> { + let start = ***i; let mut open_list: Vec = vec![open_token]; loop { let before = *i; @@ -837,7 +877,7 @@ impl<'a> Suffix<'a> { } } - fn token<'a>(i: &mut &'a str) -> ParseResult<'a, Token> { + fn token<'a>(i: &mut InputStream<'a>) -> ParseResult<'a, Token> { // let some_other = alt(( // literals @@ -858,8 +898,8 @@ impl<'a> Suffix<'a> { alt((open.map(Token::Open), close.map(Token::Close), some_other)).parse_next(i) } - fn line_comment<'a>(i: &mut &'a str) -> ParseResult<'a, ()> { - fn inner<'a>(i: &mut &'a str) -> ParseResult<'a, bool> { + fn line_comment<'a>(i: &mut InputStream<'a>) -> ParseResult<'a, ()> { + fn inner<'a>(i: &mut InputStream<'a>) -> ParseResult<'a, bool> { let start = "//".parse_next(i)?; let is_doc_comment = alt(( ('/', not(peek('/'))).value(true), @@ -882,8 +922,8 @@ impl<'a> Suffix<'a> { doc_comment_no_bare_cr(i, inner) } - fn block_comment<'a>(i: &mut &'a str) -> ParseResult<'a, ()> { - fn inner<'a>(i: &mut &'a str) -> ParseResult<'a, bool> { + fn block_comment<'a>(i: &mut InputStream<'a>) -> ParseResult<'a, ()> { + fn inner<'a>(i: &mut InputStream<'a>) -> ParseResult<'a, bool> { let start = "/*".parse_next(i)?; let is_doc_comment = alt(( ('*', not(peek(one_of(['*', '/'])))).value(true), @@ -916,7 +956,7 @@ impl<'a> Suffix<'a> { doc_comment_no_bare_cr(i, inner) } - fn identifier_or_prefixed_string<'a>(i: &mut &'a str) -> ParseResult<'a, ()> { + fn identifier_or_prefixed_string<'a>(i: &mut InputStream<'a>) -> ParseResult<'a, ()> { // let prefix = identifier.parse_next(i)?; @@ -948,10 +988,12 @@ impl<'a> Suffix<'a> { if opt('"').parse_next(i)?.is_some() { // got a raw string - let Some((inner, j)) = i.split_once(&format!("\"{:# Suffix<'a> { } } - fn hash<'a>(i: &mut &'a str) -> ParseResult<'a, Token> { - let start = *i; + fn hash<'a>(i: &mut InputStream<'a>) -> ParseResult<'a, Token> { + let start = ***i; '#'.parse_next(i)?; if opt('"').parse_next(i)?.is_some() { return cut_error!( @@ -1033,7 +1075,7 @@ impl<'a> Suffix<'a> { Ok(Token::SomeOther) } - fn punctuation<'a>(i: &mut &'a str) -> ParseResult<'a, ()> { + fn punctuation<'a>(i: &mut InputStream<'a>) -> ParseResult<'a, ()> { // // hash '#' omitted @@ -1045,29 +1087,39 @@ impl<'a> Suffix<'a> { ]; const THREE_CHARS: &[[u8; 3]] = &[*b"<<=", *b">>=", *b"...", *b"..="]; + let three_chars = take(3usize).verify_map(|head: &str| { + if let Ok(head) = head.as_bytes().try_into() + && THREE_CHARS.contains(head) + { + Some(()) + } else { + None + } + }); + let two_chars = take(2usize).verify_map(|head: &str| { + if let Ok(head) = head.as_bytes().try_into() + && TWO_CHARS.contains(head) + { + Some(()) + } else { + None + } + }); + let one_char = any.verify_map(|head: char| { + if let Ok(head) = head.try_into() + && ONE_CHAR.contains(&head) + { + Some(()) + } else { + None + } + }); + // need to check long to short - *i = if let Some((head, tail)) = i.split_at_checked(3) - && let Ok(head) = head.as_bytes().try_into() - && THREE_CHARS.contains(head) - { - tail - } else if let Some((head, tail)) = i.split_at_checked(2) - && let Ok(head) = head.as_bytes().try_into() - && TWO_CHARS.contains(&head) - { - tail - } else if let Some((head, tail)) = i.split_at_checked(1) - && let [head] = head.as_bytes() - && ONE_CHAR.contains(head) - { - tail - } else { - return fail(i); - }; - Ok(()) + alt((three_chars, two_chars, one_char)).parse_next(i) } - fn open<'a>(i: &mut &'a str) -> ParseResult<'a, Group> { + fn open<'a>(i: &mut InputStream<'a>) -> ParseResult<'a, Group> { alt(( '('.value(Group::Paren), '{'.value(Group::Brace), @@ -1076,7 +1128,7 @@ impl<'a> Suffix<'a> { .parse_next(i) } - fn close<'a>(i: &mut &'a str) -> ParseResult<'a, Group> { + fn close<'a>(i: &mut InputStream<'a>) -> ParseResult<'a, Group> { alt(( ')'.value(Group::Paren), '}'.value(Group::Brace), @@ -1089,7 +1141,7 @@ impl<'a> Suffix<'a> { (|i: &mut _| macro_arguments(i, open_token)).parse_next(i) } - fn associated_item(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, Self> { + fn associated_item(i: &mut InputStream<'a>, level: Level<'_>) -> ParseResult<'a, Self> { preceded( ws(('.', not('.'))), cut_err(( @@ -1113,7 +1165,7 @@ impl<'a> Suffix<'a> { .parse_next(i) } - fn index(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, Self> { + fn index(i: &mut InputStream<'a>, level: Level<'_>) -> ParseResult<'a, Self> { preceded( ws('['), cut_err(terminated( @@ -1125,7 +1177,7 @@ impl<'a> Suffix<'a> { .parse_next(i) } - fn call(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, Self> { + fn call(i: &mut InputStream<'a>, level: Level<'_>) -> ParseResult<'a, Self> { (opt(|i: &mut _| call_generics(i, level)), |i: &mut _| { Expr::arguments(i, level) }) @@ -1133,14 +1185,14 @@ impl<'a> Suffix<'a> { .parse_next(i) } - fn r#try(i: &mut &'a str) -> ParseResult<'a, Self> { + fn r#try(i: &mut InputStream<'a>) -> ParseResult<'a, Self> { preceded(skip_ws0, '?').map(|_| Self::Try).parse_next(i) } } fn doc_comment_no_bare_cr<'a>( - i: &mut &'a str, - inner: fn(i: &mut &'a str) -> ParseResult<'a, bool>, + i: &mut InputStream<'a>, + inner: fn(i: &mut InputStream<'a>) -> ParseResult<'a, bool>, ) -> ParseResult<'a, ()> { let (is_doc_comment, comment) = inner.with_taken().parse_next(i)?; if is_doc_comment && comment.split('\r').skip(1).any(|s| !s.starts_with('\n')) { @@ -1171,8 +1223,8 @@ pub struct TyGenerics<'a> { } impl<'i> TyGenerics<'i> { - fn parse(i: &mut &'i str, level: Level<'_>) -> ParseResult<'i, WithSpan<'i, Self>> { - let start = *i; + fn parse(i: &mut InputStream<'i>, level: Level<'_>) -> ParseResult<'i, WithSpan<'i, Self>> { + let start = ***i; let (refs, path, args): (_, Vec<_>, _) = ( repeat(0.., ws('&')), separated(1.., ws(identifier), "::"), @@ -1202,7 +1254,7 @@ impl<'i> TyGenerics<'i> { } fn args( - i: &mut &'i str, + i: &mut InputStream<'i>, level: Level<'_>, ) -> ParseResult<'i, Vec>>> { ws('<').parse_next(i)?; @@ -1219,7 +1271,7 @@ impl<'i> TyGenerics<'i> { } pub(crate) fn call_generics<'i>( - i: &mut &'i str, + i: &mut InputStream<'i>, level: Level<'_>, ) -> ParseResult<'i, Vec>>> { preceded(ws("::"), cut_err(|i: &mut _| TyGenerics::args(i, level))).parse_next(i) diff --git a/askama_parser/src/lib.rs b/askama_parser/src/lib.rs index 0a2b3511..b96cb77d 100644 --- a/askama_parser/src/lib.rs +++ b/askama_parser/src/lib.rs @@ -21,12 +21,12 @@ use std::{fmt, str}; use rustc_hash::FxBuildHasher; use winnow::ascii::take_escaped; use winnow::combinator::{ - alt, cut_err, delimited, empty, fail, not, opt, peek, preceded, repeat, terminated, + alt, cond, cut_err, delimited, empty, fail, not, opt, peek, preceded, repeat, terminated, }; use winnow::error::{ErrMode, FromExternalError}; use winnow::stream::AsChar; use winnow::token::{any, none_of, one_of, take_while}; -use winnow::{ModalParser, Parser}; +use winnow::{LocatingSlice, ModalParser, ModalResult, Parser, Stateful}; use crate::ascii_str::{AsciiChar, AsciiStr}; pub use crate::expr::{AssociatedItem, Expr, Filter, PathComponent, TyGenerics}; @@ -102,6 +102,8 @@ mod _parsed { pub use _parsed::Parsed; +type InputStream<'a> = Stateful, ()>; + #[derive(Debug, Default)] pub struct Ast<'a> { nodes: Vec>>, @@ -111,7 +113,7 @@ impl<'a> Ast<'a> { /// If `file_path` is `None`, it means the `source` is an inline template. Therefore, if /// a parsing error occurs, we won't display the path as it wouldn't be useful. pub fn from_str( - mut src: &'a str, + src: &'a str, file_path: Option>, syntax: &Syntax<'_>, ) -> Result { @@ -122,6 +124,10 @@ impl<'a> Ast<'a> { loop_depth: Cell::new(0), level: Level(&level), }; + let mut src = InputStream { + input: LocatingSlice::new(src), + state: (), + }; match Node::parse_template(&mut src, &state) { Ok(nodes) if src.is_empty() => Ok(Self { nodes }), Ok(_) | Err(ErrMode::Incomplete(_)) => unreachable!(), @@ -348,12 +354,13 @@ impl<'a> ErrorContext<'a> { } } -impl<'a> winnow::error::ParserError<&'a str> for ErrorContext<'a> { +impl<'a> winnow::error::ParserError> for ErrorContext<'a> { type Inner = Self; - fn from_input(input: &&'a str) -> Self { + #[inline] + fn from_input(input: &InputStream<'a>) -> Self { Self { - span: (*input).into(), + span: Span(***input), message: None, } } @@ -373,40 +380,35 @@ impl<'a, E: std::fmt::Display> FromExternalError<&'a str, E> for ErrorContext<'a } } -#[inline] -fn skip_ws0<'a>(i: &mut &'a str) -> ParseResult<'a, ()> { - *i = i.trim_ascii_start(); - Ok(()) +fn skip_ws0<'a>(i: &mut InputStream<'a>) -> ParseResult<'a, ()> { + take_while(0.., |c: char| c.is_ascii_whitespace()) + .void() + .parse_next(i) } -#[inline] -fn skip_ws1<'a>(i: &mut &'a str) -> ParseResult<'a, ()> { - let j = i.trim_ascii_start(); - if i.len() != j.len() { - *i = i.trim_ascii_start(); - Ok(()) - } else { - fail.parse_next(i) - } +fn skip_ws1<'a>(i: &mut InputStream<'a>) -> ParseResult<'a, ()> { + take_while(1.., |c: char| c.is_ascii_whitespace()) + .void() + .parse_next(i) } fn ws<'a, O>( - inner: impl ModalParser<&'a str, O, ErrorContext<'a>>, -) -> impl ModalParser<&'a str, O, ErrorContext<'a>> { + inner: impl ModalParser, O, ErrorContext<'a>>, +) -> impl ModalParser, O, ErrorContext<'a>> { delimited(skip_ws0, inner, skip_ws0) } -fn keyword(k: &str) -> impl ModalParser<&str, &str, ErrorContext<'_>> { +fn keyword<'a>(k: &str) -> impl ModalParser, &'a str, ErrorContext<'a>> { identifier.verify(move |v: &str| v == k) } -fn identifier<'i>(input: &mut &'i str) -> ParseResult<'i> { +fn identifier<'i>(input: &mut InputStream<'i>) -> ParseResult<'i> { let head = any.verify(|&c| c == '_' || unicode_ident::is_xid_start(c)); let tail = take_while(.., unicode_ident::is_xid_continue); (head, tail).take().parse_next(input) } -fn bool_lit<'i>(i: &mut &'i str) -> ParseResult<'i> { +fn bool_lit<'i>(i: &mut InputStream<'i>) -> ParseResult<'i> { alt((keyword("false"), keyword("true"))).parse_next(i) } @@ -416,12 +418,12 @@ pub enum Num<'a> { Float(&'a str, Option), } -fn num_lit<'a>(i: &mut &'a str) -> ParseResult<'a, Num<'a>> { +fn num_lit<'a>(i: &mut InputStream<'a>) -> ParseResult<'a, Num<'a>> { fn num_lit_suffix<'a, T: Copy>( kind: &'a str, list: &[(&str, T)], start: &'a str, - i: &mut &'a str, + i: &mut InputStream<'a>, ) -> ParseResult<'a, T> { let suffix = identifier.parse_next(i)?; if let Some(value) = list @@ -435,7 +437,7 @@ fn num_lit<'a>(i: &mut &'a str) -> ParseResult<'a, Num<'a>> { } } - let start = *i; + let start = ***i; // Equivalent to . let int_with_base = (opt('-'), |i: &mut _| { @@ -450,7 +452,7 @@ fn num_lit<'a>(i: &mut &'a str) -> ParseResult<'a, Num<'a>> { // Equivalent to : // no `_` directly after the decimal point `.`, or between `e` and `+/-`. - let float = |i: &mut &'a str| -> ParseResult<'a, ()> { + let float = |i: &mut InputStream<'a>| -> ParseResult<'a, ()> { let has_dot = opt(('.', separated_digits(10, true))).parse_next(i)?; let has_exp = opt(|i: &mut _| { let (kind, op) = (one_of(['e', 'E']), opt(one_of(['+', '-']))).parse_next(i)?; @@ -467,10 +469,7 @@ fn num_lit<'a>(i: &mut &'a str) -> ParseResult<'a, Num<'a>> { .parse_next(i)?; match (has_dot, has_exp) { (Some(_), _) | (_, Some(())) => Ok(()), - _ => { - *i = start; - fail.parse_next(i) - } + _ => fail(i), } }; @@ -504,12 +503,9 @@ fn num_lit<'a>(i: &mut &'a str) -> ParseResult<'a, Num<'a>> { fn separated_digits<'a>( radix: u32, start: bool, -) -> impl ModalParser<&'a str, &'a str, ErrorContext<'a>> { +) -> impl ModalParser, &'a str, ErrorContext<'a>> { ( - move |i: &mut &'a _| match start { - true => Ok(()), - false => repeat(0.., '_').parse_next(i), - }, + cond(!start, repeat(0.., '_').map(|()| ())), one_of(move |ch: char| ch.is_digit(radix)), repeat(0.., one_of(move |ch: char| ch == '_' || ch.is_digit(radix))).map(|()| ()), ) @@ -560,10 +556,10 @@ pub struct StrLit<'a> { pub contains_high_ascii: bool, } -fn str_lit<'a>(i: &mut &'a str) -> ParseResult<'a, StrLit<'a>> { +fn str_lit<'a>(i: &mut InputStream<'a>) -> ParseResult<'a, StrLit<'a>> { // - fn inner<'a>(i: &mut &'a str) -> ParseResult<'a, StrLit<'a>> { + fn inner<'a>(i: &mut InputStream<'a>) -> ParseResult<'a, StrLit<'a>> { #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum Sequence<'a> { Text(&'a str), @@ -572,7 +568,7 @@ fn str_lit<'a>(i: &mut &'a str) -> ParseResult<'a, StrLit<'a>> { Cr(bool), } - let start = *i; + let start = ***i; let mut contains_null = false; let mut contains_unicode_character = false; let mut contains_unicode_escape = false; @@ -656,7 +652,7 @@ fn str_lit<'a>(i: &mut &'a str) -> ParseResult<'a, StrLit<'a>> { }) } - let start = *i; + let start = ***i; let prefix = terminated( opt(alt(( @@ -697,7 +693,7 @@ fn str_lit<'a>(i: &mut &'a str) -> ParseResult<'a, StrLit<'a>> { Ok(lit) } -fn not_suffix_with_hash<'a>(i: &mut &'a str) -> ParseResult<'a, ()> { +fn not_suffix_with_hash<'a>(i: &mut InputStream<'a>) -> ParseResult<'a, ()> { if let Some(suffix) = opt(identifier.take()).parse_next(i)? { return cut_error!( "you are missing a space to separate two string literals", @@ -707,8 +703,8 @@ fn not_suffix_with_hash<'a>(i: &mut &'a str) -> ParseResult<'a, ()> { Ok(()) } -fn str_lit_without_prefix<'a>(i: &mut &'a str) -> ParseResult<'a> { - let start = *i; +fn str_lit_without_prefix<'a>(i: &mut InputStream<'a>) -> ParseResult<'a> { + let start = ***i; let lit = str_lit.parse_next(i)?; let kind = match lit.prefix { @@ -739,42 +735,62 @@ pub struct CharLit<'a> { // Information about allowed character escapes is available at: // . -fn char_lit<'a>(i: &mut &'a str) -> ParseResult<'a, CharLit<'a>> { - let start = *i; - - let prefix = terminated( +fn char_lit<'a>(i: &mut InputStream<'a>) -> ParseResult<'a, CharLit<'a>> { + let ((prefix, _, content, is_closed), span) = ( alt(('b'.value(Some(CharPrefix::Binary)), empty.value(None))), '\'', - ) - .parse_next(i)?; - - let content = opt(terminated( opt(take_escaped(none_of(['\\', '\'']), '\\', any)), - '\'', - )) - .parse_next(i)?; + opt('\''), + ) + .with_taken() + .parse_next(i)?; - let Some(content) = content else { + if is_closed.is_none() { if let Some(prefix) = prefix { return cut_error!( match prefix { - CharPrefix::Binary => "unterminated byte constant", + CharPrefix::Binary => "unterminated byte literal", }, - start, + span, ); } else { return fail(i); } - }; + } + let content = match content.unwrap_or_default() { - "" => return cut_error!("empty character literal", start), + "" => { + return cut_error!( + match prefix { + Some(CharPrefix::Binary) => "empty byte literal", + None => "empty character literal", + }, + span, + ); + } content => content, }; - let mut is = content; - let Ok(c) = Char::parse(&mut is) else { - return cut_error!("invalid character", start); + let mut content_i = content; + let Ok(c) = Char::parse(&mut content_i) else { + return cut_error!("invalid character", span); }; + if !content_i.is_empty() { + let (c, s) = match prefix { + Some(CharPrefix::Binary) => ("byte", "binary string"), + None => ("character", "string"), + }; + return cut_error!( + format!( + "cannot have multiple characters in a {c} literal, use `{}\"...\"` to write a {s}", + match prefix { + Some(CharPrefix::Binary) => "b", + None => "", + } + ), + span + ); + } let (nb, max_value, err1, err2) = match c { Char::Literal | Char::Escaped => { @@ -792,7 +808,7 @@ fn char_lit<'a>(i: &mut &'a str) -> ParseResult<'a, CharLit<'a>> { Some(CharPrefix::Binary) => { return cut_error!( "cannot use unicode escape in byte string in byte literal", - start, + span, ); } None => ( @@ -807,10 +823,10 @@ fn char_lit<'a>(i: &mut &'a str) -> ParseResult<'a, CharLit<'a>> { }; let Ok(nb) = u32::from_str_radix(nb, 16) else { - return cut_error!(err1, start); + return cut_error!(err1, span); }; if nb > max_value { - return cut_error!(err2, start); + return cut_error!(err2, span); } Ok(CharLit { prefix, content }) @@ -830,11 +846,9 @@ enum Char<'a> { } impl<'a> Char<'a> { - fn parse(i: &mut &'a str) -> ParseResult<'a, Self> { - if i.chars().count() == 1 { - return any.value(Self::Literal).parse_next(i); - } - ( + fn parse(i: &mut &'a str) -> ModalResult { + let unescaped = none_of(('\\', '\'')).value(Self::Literal); + let escaped = preceded( '\\', alt(( 'n'.value(Self::Escaped), @@ -854,9 +868,8 @@ impl<'a> Char<'a> { ) .map(|(_, s, _)| Self::UnicodeEscape(s)), )), - ) - .map(|(_, ch)| ch) - .parse_next(i) + ); + alt((unescaped, escaped)).parse_next(i) } } @@ -867,10 +880,10 @@ pub enum PathOrIdentifier<'a> { } fn path_or_identifier<'a>( - i: &mut &'a str, + i: &mut InputStream<'a>, level: Level<'_>, ) -> ParseResult<'a, PathOrIdentifier<'a>> { - let start_i = *i; + let start_i = ***i; let root = ws(opt("::")); let tail = opt(repeat( 1.., @@ -927,11 +940,11 @@ struct State<'a, 'l> { } impl State<'_, '_> { - fn tag_block_start<'i>(&self, i: &mut &'i str) -> ParseResult<'i, ()> { + fn tag_block_start<'i>(&self, i: &mut InputStream<'i>) -> ParseResult<'i, ()> { self.syntax.block_start.value(()).parse_next(i) } - fn tag_block_end<'i>(&self, i: &mut &'i str) -> ParseResult<'i, ()> { + fn tag_block_end<'i>(&self, i: &mut InputStream<'i>) -> ParseResult<'i, ()> { let control = alt(( self.syntax.block_end.value(None), peek(delimited('%', alt(('-', '~', '+')).map(Some), '}')), @@ -945,7 +958,7 @@ impl State<'_, '_> { control.escape_default(), self.syntax.block_end.escape_default(), ), - *i, + ***i, ); Err(err.backtrack()) } else { @@ -953,19 +966,19 @@ impl State<'_, '_> { } } - fn tag_comment_start<'i>(&self, i: &mut &'i str) -> ParseResult<'i, ()> { + fn tag_comment_start<'i>(&self, i: &mut InputStream<'i>) -> ParseResult<'i, ()> { self.syntax.comment_start.value(()).parse_next(i) } - fn tag_comment_end<'i>(&self, i: &mut &'i str) -> ParseResult<'i, ()> { + fn tag_comment_end<'i>(&self, i: &mut InputStream<'i>) -> ParseResult<'i, ()> { self.syntax.comment_end.value(()).parse_next(i) } - fn tag_expr_start<'i>(&self, i: &mut &'i str) -> ParseResult<'i, ()> { + fn tag_expr_start<'i>(&self, i: &mut InputStream<'i>) -> ParseResult<'i, ()> { self.syntax.expr_start.value(()).parse_next(i) } - fn tag_expr_end<'i>(&self, i: &mut &'i str) -> ParseResult<'i, ()> { + fn tag_expr_end<'i>(&self, i: &mut InputStream<'i>) -> ParseResult<'i, ()> { self.syntax.expr_end.value(()).parse_next(i) } @@ -1205,7 +1218,7 @@ impl LevelGuard<'_> { } } -fn filter<'a>(i: &mut &'a str, level: Level<'_>) -> ParseResult<'a, Filter<'a>> { +fn filter<'a>(i: &mut InputStream<'a>, level: Level<'_>) -> ParseResult<'a, Filter<'a>> { preceded( ('|', not('|')), cut_err(|i: &mut _| Filter::parse(i, level)), @@ -1466,13 +1479,13 @@ fn cut_context_err<'a, T>(gen_err: impl FnOnce() -> ErrorContext<'a>) -> ParseRe type HashSet = std::collections::hash_set::HashSet; -#[cfg(not(windows))] #[cfg(test)] mod test { use std::path::Path; use super::*; + #[cfg(not(windows))] #[test] fn test_strip_common() { // Full path is returned instead of empty when the entire path is in common. @@ -1499,66 +1512,78 @@ mod test { assert_eq!(strip_common(&cwd, Path::new("/a/b/c")), "/a/b/c"); } + fn parse_peek<'a, T>( + mut parser: impl ModalParser, T, ErrorContext<'a>>, + input: &'a str, + ) -> ParseResult<'a, (&'a str, T)> { + let mut i = InputStream { + input: LocatingSlice::new(input), + state: (), + }; + let value = parser.parse_next(&mut i)?; + Ok((**i, value)) + } + #[test] fn test_num_lit() { // Should fail. - assert!(num_lit.parse_peek(".").is_err()); + assert!(parse_peek(num_lit, ".").is_err()); // Should succeed. assert_eq!( - num_lit.parse_peek("1.2E-02").unwrap(), + parse_peek(num_lit, "1.2E-02").unwrap(), ("", Num::Float("1.2E-02", None)) ); assert_eq!( - num_lit.parse_peek("4e3").unwrap(), + parse_peek(num_lit, "4e3").unwrap(), ("", Num::Float("4e3", None)), ); assert_eq!( - num_lit.parse_peek("4e+_3").unwrap(), + parse_peek(num_lit, "4e+_3").unwrap(), ("", Num::Float("4e+_3", None)), ); // Not supported because Rust wants a number before the `.`. - assert!(num_lit.parse_peek(".1").is_err()); - assert!(num_lit.parse_peek(".1E-02").is_err()); + assert!(parse_peek(num_lit, ".1").is_err()); + assert!(parse_peek(num_lit, ".1E-02").is_err()); // A `_` directly after the `.` denotes a field. assert_eq!( - num_lit.parse_peek("1._0").unwrap(), + parse_peek(num_lit, "1._0").unwrap(), ("._0", Num::Int("1", None)) ); assert_eq!( - num_lit.parse_peek("1_.0").unwrap(), + parse_peek(num_lit, "1_.0").unwrap(), ("", Num::Float("1_.0", None)) ); // Not supported (voluntarily because of `1..` syntax). assert_eq!( - num_lit.parse_peek("1.").unwrap(), + parse_peek(num_lit, "1.").unwrap(), (".", Num::Int("1", None)) ); assert_eq!( - num_lit.parse_peek("1_.").unwrap(), + parse_peek(num_lit, "1_.").unwrap(), (".", Num::Int("1_", None)) ); assert_eq!( - num_lit.parse_peek("1_2.").unwrap(), + parse_peek(num_lit, "1_2.").unwrap(), (".", Num::Int("1_2", None)) ); // Numbers with suffixes assert_eq!( - num_lit.parse_peek("-1usize").unwrap(), + parse_peek(num_lit, "-1usize").unwrap(), ("", Num::Int("-1", Some(IntKind::Usize))) ); assert_eq!( - num_lit.parse_peek("123_f32").unwrap(), + parse_peek(num_lit, "123_f32").unwrap(), ("", Num::Float("123_", Some(FloatKind::F32))) ); assert_eq!( - num_lit.parse_peek("1_.2_e+_3_f64|into_isize").unwrap(), + parse_peek(num_lit, "1_.2_e+_3_f64|into_isize").unwrap(), ( "|into_isize", Num::Float("1_.2_e+_3_", Some(FloatKind::F64)) ) ); assert_eq!( - num_lit.parse_peek("4e3f128").unwrap(), + parse_peek(num_lit, "4e3f128").unwrap(), ("", Num::Float("4e3", Some(FloatKind::F128))), ); } @@ -1570,42 +1595,42 @@ mod test { content: s, }; - assert_eq!(char_lit.parse_peek("'a'").unwrap(), ("", lit("a"))); - assert_eq!(char_lit.parse_peek("'字'").unwrap(), ("", lit("字"))); + assert_eq!(parse_peek(char_lit, "'a'").unwrap(), ("", lit("a"))); + assert_eq!(parse_peek(char_lit, "'字'").unwrap(), ("", lit("字"))); // Escaped single characters. - assert_eq!(char_lit.parse_peek("'\\\"'").unwrap(), ("", lit("\\\""))); - assert_eq!(char_lit.parse_peek("'\\''").unwrap(), ("", lit("\\'"))); - assert_eq!(char_lit.parse_peek("'\\t'").unwrap(), ("", lit("\\t"))); - assert_eq!(char_lit.parse_peek("'\\n'").unwrap(), ("", lit("\\n"))); - assert_eq!(char_lit.parse_peek("'\\r'").unwrap(), ("", lit("\\r"))); - assert_eq!(char_lit.parse_peek("'\\0'").unwrap(), ("", lit("\\0"))); + assert_eq!(parse_peek(char_lit, "'\\\"'").unwrap(), ("", lit("\\\""))); + assert_eq!(parse_peek(char_lit, "'\\''").unwrap(), ("", lit("\\'"))); + assert_eq!(parse_peek(char_lit, "'\\t'").unwrap(), ("", lit("\\t"))); + assert_eq!(parse_peek(char_lit, "'\\n'").unwrap(), ("", lit("\\n"))); + assert_eq!(parse_peek(char_lit, "'\\r'").unwrap(), ("", lit("\\r"))); + assert_eq!(parse_peek(char_lit, "'\\0'").unwrap(), ("", lit("\\0"))); // Escaped ascii characters (up to `0x7F`). - assert_eq!(char_lit.parse_peek("'\\x12'").unwrap(), ("", lit("\\x12"))); - assert_eq!(char_lit.parse_peek("'\\x02'").unwrap(), ("", lit("\\x02"))); - assert_eq!(char_lit.parse_peek("'\\x6a'").unwrap(), ("", lit("\\x6a"))); - assert_eq!(char_lit.parse_peek("'\\x7F'").unwrap(), ("", lit("\\x7F"))); + assert_eq!(parse_peek(char_lit, "'\\x12'").unwrap(), ("", lit("\\x12"))); + assert_eq!(parse_peek(char_lit, "'\\x02'").unwrap(), ("", lit("\\x02"))); + assert_eq!(parse_peek(char_lit, "'\\x6a'").unwrap(), ("", lit("\\x6a"))); + assert_eq!(parse_peek(char_lit, "'\\x7F'").unwrap(), ("", lit("\\x7F"))); // Escaped unicode characters (up to `0x10FFFF`). assert_eq!( - char_lit.parse_peek("'\\u{A}'").unwrap(), + parse_peek(char_lit, "'\\u{A}'").unwrap(), ("", lit("\\u{A}")) ); assert_eq!( - char_lit.parse_peek("'\\u{10}'").unwrap(), + parse_peek(char_lit, "'\\u{10}'").unwrap(), ("", lit("\\u{10}")) ); assert_eq!( - char_lit.parse_peek("'\\u{aa}'").unwrap(), + parse_peek(char_lit, "'\\u{aa}'").unwrap(), ("", lit("\\u{aa}")) ); assert_eq!( - char_lit.parse_peek("'\\u{10FFFF}'").unwrap(), + parse_peek(char_lit, "'\\u{10FFFF}'").unwrap(), ("", lit("\\u{10FFFF}")) ); // Check with `b` prefix. assert_eq!( - char_lit.parse_peek("b'a'").unwrap(), + parse_peek(char_lit, "b'a'").unwrap(), ( "", crate::CharLit { @@ -1616,20 +1641,20 @@ mod test { ); // Should fail. - assert!(char_lit.parse_peek("''").is_err()); - assert!(char_lit.parse_peek("'\\o'").is_err()); - assert!(char_lit.parse_peek("'\\x'").is_err()); - assert!(char_lit.parse_peek("'\\x1'").is_err()); - assert!(char_lit.parse_peek("'\\x80'").is_err()); - assert!(char_lit.parse_peek("'\\u'").is_err()); - assert!(char_lit.parse_peek("'\\u{}'").is_err()); - assert!(char_lit.parse_peek("'\\u{110000}'").is_err()); + assert!(parse_peek(char_lit, "''").is_err()); + assert!(parse_peek(char_lit, "'\\o'").is_err()); + assert!(parse_peek(char_lit, "'\\x'").is_err()); + assert!(parse_peek(char_lit, "'\\x1'").is_err()); + assert!(parse_peek(char_lit, "'\\x80'").is_err()); + assert!(parse_peek(char_lit, "'\\u'").is_err()); + assert!(parse_peek(char_lit, "'\\u{}'").is_err()); + assert!(parse_peek(char_lit, "'\\u{110000}'").is_err()); } #[test] fn test_str_lit() { assert_eq!( - str_lit.parse_peek(r#"b"hello""#).unwrap(), + parse_peek(str_lit, r#"b"hello""#).unwrap(), ( "", StrLit { @@ -1643,7 +1668,7 @@ mod test { ) ); assert_eq!( - str_lit.parse_peek(r#"c"hello""#).unwrap(), + parse_peek(str_lit, r#"c"hello""#).unwrap(), ( "", StrLit { @@ -1656,7 +1681,7 @@ mod test { } ) ); - assert!(str_lit.parse_peek(r#"d"hello""#).is_err()); + assert!(parse_peek(str_lit, r#"d"hello""#).is_err()); } #[test] diff --git a/askama_parser/src/node.rs b/askama_parser/src/node.rs index 5a304be3..e5fc683b 100644 --- a/askama_parser/src/node.rs +++ b/askama_parser/src/node.rs @@ -5,13 +5,13 @@ use winnow::combinator::{ separated_pair, terminated, }; use winnow::error::ErrMode; -use winnow::stream::Stream as _; -use winnow::token::{any, rest, take_until}; +use winnow::stream::Stream; +use winnow::token::{any, rest, take, take_until}; use winnow::{ModalParser, Parser}; use crate::{ - ErrorContext, Expr, Filter, HashSet, ParseResult, Span, State, Target, WithSpan, cut_error, - filter, identifier, is_rust_keyword, keyword, skip_ws0, str_lit_without_prefix, ws, + ErrorContext, Expr, Filter, HashSet, InputStream, ParseResult, Span, State, Target, WithSpan, + cut_error, filter, identifier, is_rust_keyword, keyword, skip_ws0, str_lit_without_prefix, ws, }; #[derive(Debug, PartialEq)] @@ -37,38 +37,28 @@ pub enum Node<'a> { impl<'a> Node<'a> { pub(super) fn parse_template( - i: &mut &'a str, + i: &mut InputStream<'a>, s: &State<'_, '_>, ) -> ParseResult<'a, Vec>> { - let start = *i; - let result = match (|i: &mut _| Self::many(i, s)).parse_next(i) { - Ok(result) => result, - Err(err) => { - if let ErrMode::Backtrack(err) | ErrMode::Cut(err) = &err - && err.message.is_none() - { - *i = start; - if let Some(mut span) = err.span.as_suffix_of(i) { - opt(|i: &mut _| unexpected_tag(i, s)).parse_next(&mut span)?; - } - } - return Err(err); - } - }; + let nodes = parse_with_unexpected_fallback( + |i: &mut _| Self::many(i, s), + |i: &mut _| unexpected_tag(i, s), + ) + .parse_next(i)?; + opt(|i: &mut _| unexpected_tag(i, s)).parse_next(i)?; - let is_eof = opt(eof).parse_next(i)?; - if is_eof.is_none() { + if !i.is_empty() { return cut_error!( "cannot parse entire template\n\ - you should never encounter this error\n\ - please report this error to ", - *i, + you should never encounter this error\n\ + please report this error to ", + ***i, ); } - Ok(result) + Ok(nodes) } - fn many(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Vec>> { + fn many(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Vec>> { repeat( 0.., alt(( @@ -82,13 +72,13 @@ impl<'a> Node<'a> { .parse_next(i) } - fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Box> { - let mut start = *i; - let tag = preceded( - |i: &mut _| s.tag_block_start(i), + fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box> { + let start = i.checkpoint(); + let (span, tag) = ( + s.syntax.block_start, peek(preceded((opt(Whitespace::parse), skip_ws0), identifier)), ) - .parse_next(i)?; + .parse_next(i)?; let func = match tag { "call" => Call::parse, @@ -96,16 +86,19 @@ impl<'a> Node<'a> { "if" => If::parse, "for" => Loop::parse, "match" => Match::parse, - "extends" => |i: &mut &'a str, _s: &State<'_, '_>| Extends::parse(i), - "include" => |i: &mut &'a str, _s: &State<'_, '_>| Include::parse(i), - "import" => |i: &mut &'a str, _s: &State<'_, '_>| Import::parse(i), + "extends" => |i: &mut InputStream<'a>, _s: &State<'_, '_>| Extends::parse(i), + "include" => |i: &mut InputStream<'a>, _s: &State<'_, '_>| Include::parse(i), + "import" => |i: &mut InputStream<'a>, _s: &State<'_, '_>| Import::parse(i), "block" => BlockDef::parse, "macro" => Macro::parse, "raw" => Raw::parse, "break" => Self::r#break, "continue" => Self::r#continue, "filter" => FilterBlock::parse, - _ => return fail.parse_next(&mut start), + _ => { + i.reset(&start); + return fail.parse_next(i); + } }; let _level_guard = s.level.nest(i)?; @@ -120,18 +113,18 @@ impl<'a> Node<'a> { .parse_next(i)?; match closed { true => Ok(node), - false => Err(ErrorContext::unclosed("block", s.syntax.block_end, start).cut()), + false => Err(ErrorContext::unclosed("block", s.syntax.block_end, span).cut()), } } - fn r#break(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Box>> { + fn r#break(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box>> { let mut p = ( opt(Whitespace::parse), ws(keyword("break")), opt(Whitespace::parse), ); - let start = *i; + let start = ***i; let (pws, _, nws) = p.parse_next(i)?; if !s.is_in_loop() { return cut_error!("you can only `break` inside a `for` loop", start); @@ -139,14 +132,14 @@ impl<'a> Node<'a> { Ok(Box::new(Self::Break(WithSpan::new(Ws(pws, nws), start, i)))) } - fn r#continue(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Box>> { + fn r#continue(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box>> { let mut p = ( opt(Whitespace::parse), ws(keyword("continue")), opt(Whitespace::parse), ); - let start = *i; + let start = ***i; let (pws, _, nws) = p.parse_next(i)?; if !s.is_in_loop() { return cut_error!("you can only `continue` inside a `for` loop", start); @@ -158,8 +151,8 @@ impl<'a> Node<'a> { )))) } - fn expr(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Box> { - let start = *i; + fn expr(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box> { + let start = ***i; let level = s.level; let (pws, expr) = preceded( |i: &mut _| s.tag_expr_start(i), @@ -214,27 +207,53 @@ impl<'a> Node<'a> { } } -fn cut_node<'a, O>( - kind: Option<&'static str>, - inner: impl ModalParser<&'a str, O, ErrorContext<'a>>, -) -> impl ModalParser<&'a str, O, ErrorContext<'a>> { - let mut inner = cut_err(inner); - move |i: &mut &'a str| { - let start = *i; - let result = inner.parse_next(i); - if let Err(ErrMode::Cut(err) | ErrMode::Backtrack(err)) = &result - && err.message.is_none() +#[inline] +fn parse_with_unexpected_fallback<'a, O>( + mut parser: impl ModalParser, O, ErrorContext<'a>>, + mut unexpected_parser: impl FnMut(&mut InputStream<'a>) -> ParseResult<'a, ()>, +) -> impl ModalParser, O, ErrorContext<'a>> { + #[cold] + #[inline(never)] + fn try_assign_fallback_error<'a>( + i: &mut InputStream<'a>, + start_checkpoint: as Stream>::Checkpoint, + unexpected_parser: &mut dyn FnMut(&mut InputStream<'a>) -> ParseResult<'a, ()>, + err: &mut ErrMode>, + ) { + if let ErrMode::Backtrack(err_ctx) | ErrMode::Cut(err_ctx) = &err + && err_ctx.message.is_none() { - *i = start; - if let Some(mut span) = err.span.as_suffix_of(i) { - opt(|i: &mut _| unexpected_raw_tag(kind, i)).parse_next(&mut span)?; + let err_checkpoint = i.checkpoint(); + i.reset(&start_checkpoint); + if let Some(offset) = err_ctx.span.offset_from(***i) + && let Err(better_err) = + opt(preceded(take(offset), unexpected_parser)).parse_next(i) + { + *err = better_err; } + i.reset(&err_checkpoint); + } + } + + move |i: &mut InputStream<'a>| { + let start_checkpoint = i.checkpoint(); + let mut result = parser.parse_next(i); + if let Err(err) = &mut result { + try_assign_fallback_error(i, start_checkpoint, &mut unexpected_parser, err); } result } } -fn unexpected_tag<'a>(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, ()> { +#[inline] +fn cut_node<'a, O>( + kind: Option<&'static str>, + inner: impl ModalParser, O, ErrorContext<'a>>, +) -> impl ModalParser, O, ErrorContext<'a>> { + parse_with_unexpected_fallback(cut_err(inner), move |i: &mut _| unexpected_raw_tag(kind, i)) +} + +fn unexpected_tag<'a>(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, ()> { ( |i: &mut _| s.tag_block_start(i), opt(Whitespace::parse), @@ -244,7 +263,10 @@ fn unexpected_tag<'a>(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, ()> .parse_next(i) } -fn unexpected_raw_tag<'a>(kind: Option<&'static str>, i: &mut &'a str) -> ParseResult<'a, ()> { +fn unexpected_raw_tag<'a>( + kind: Option<&'static str>, + i: &mut InputStream<'a>, +) -> ParseResult<'a, ()> { let tag = peek(ws(identifier)).parse_next(i)?; let msg = match tag { "end" | "elif" | "else" | "when" => match kind { @@ -256,7 +278,7 @@ fn unexpected_raw_tag<'a>(kind: Option<&'static str>, i: &mut &'a str) -> ParseR tag if tag.starts_with("end") => format!("unexpected closing tag `{tag}`"), tag => format!("unknown node `{tag}`"), }; - cut_error!(msg, *i) + cut_error!(msg, tag) } #[derive(Debug, PartialEq)] @@ -267,7 +289,7 @@ pub struct When<'a> { } impl<'a> When<'a> { - fn r#else(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, WithSpan<'a, Self>> { + fn r#else(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, WithSpan<'a, Self>> { let mut p = ( |i: &mut _| s.tag_block_start(i), opt(Whitespace::parse), @@ -282,7 +304,7 @@ impl<'a> When<'a> { ), ); - let start = *i; + let start = ***i; let (_, pws, _, (nws, _, nodes)) = p.parse_next(i)?; Ok(WithSpan::new( Self { @@ -296,9 +318,9 @@ impl<'a> When<'a> { } #[allow(clippy::self_named_constructors)] - fn when(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, WithSpan<'a, Self>> { - let start = *i; - let endwhen = ws(( + fn when(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, WithSpan<'a, Self>> { + let start = ***i; + let endwhen = ws(terminated( delimited( |i: &mut _| s.tag_block_start(i), opt(Whitespace::parse), @@ -312,19 +334,19 @@ impl<'a> When<'a> { repeat(0.., ws(|i: &mut _| Comment::parse(i, s))).map(|()| ()), ), ), - )) - .map(|(pws, _)| { + ) + .with_taken()) + .map(|(pws, span)| { // A comment node is used to pass the whitespace suppressing information to the // generator. This way we don't have to fix up the next `when` node or the closing // `endmatch`. Any whitespaces after `endwhen` are to be suppressed. Actually, they // don't wind up in the AST anyway. - Box::new(Node::Comment(WithSpan::new( + Box::new(Node::Comment(WithSpan::new_with_full( Comment { ws: Ws(pws, Some(Whitespace::Suppress)), content: "", }, - start, - i, + span, ))) }); let mut p = ( @@ -366,8 +388,8 @@ pub struct Cond<'a> { } impl<'a> Cond<'a> { - fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, WithSpan<'a, Self>> { - let start = *i; + fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, WithSpan<'a, Self>> { + let start = ***i; let (_, pws, cond, nws, _, nodes) = ( |i: &mut _| s.tag_block_start(i), opt(Whitespace::parse), @@ -405,7 +427,7 @@ pub struct CondTest<'a> { } impl<'a> CondTest<'a> { - fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Self> { + fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Self> { preceded( ws(keyword("if")), cut_node(Some("if"), |i: &mut _| Self::parse_cond(i, s)), @@ -413,22 +435,32 @@ impl<'a> CondTest<'a> { .parse_next(i) } - fn parse_cond(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Self> { + fn parse_cond(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Self> { let (target, expr) = ( opt(delimited( ws(alt((keyword("let"), keyword("set")))), ws(|i: &mut _| Target::parse(i, s)), ws('='), )), - ws(|i: &mut _| { - let start = *i; + ws(|i: &mut InputStream<'a>| { + let checkpoint = i.checkpoint(); + let start = ***i; + let mut expr = Expr::parse(i, s.level, false)?; if let Expr::BinOp(v) = &mut *expr.inner && matches!(*v.rhs.inner, Expr::Var("set" | "let")) { let _level_guard = s.level.nest(i)?; - *i = v.rhs.span.as_suffix_of(start).unwrap(); - let start_span = *i; + + i.reset(&checkpoint); + i.next_slice( + v.rhs + .span + .offset_from(start) + .expect("rhs offset cannot be invalid"), + ); + + let start_span = ***i; let new_right = Self::parse_cond(i, s)?; v.rhs.inner = Box::new(Expr::LetCond(WithSpan::new(new_right, start_span, i))); } @@ -456,7 +488,7 @@ pub enum Whitespace { } impl Whitespace { - fn parse<'i>(i: &mut &'i str) -> ParseResult<'i, Self> { + fn parse<'i>(i: &mut InputStream<'i>) -> ParseResult<'i, Self> { any.verify_map(Self::parse_char).parse_next(i) } @@ -484,7 +516,7 @@ impl FromStr for Whitespace { } fn check_block_start<'a>( - i: &mut &'a str, + i: &mut InputStream<'a>, start: &'a str, s: &State<'_, '_>, node: &str, @@ -512,15 +544,18 @@ pub struct Loop<'a> { } impl<'a> Loop<'a> { - fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Box>> { - fn content<'a>(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Vec>>> { + fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box>> { + fn content<'a>( + i: &mut InputStream<'a>, + s: &State<'_, '_>, + ) -> ParseResult<'a, Vec>>> { s.enter_loop(); let result = (|i: &mut _| Node::many(i, s)).parse_next(i); s.leave_loop(); result } - let start = *i; + let start = ***i; let if_cond = preceded( ws(keyword("if")), cut_node( @@ -632,8 +667,9 @@ fn check_duplicated_name<'a>( } impl<'a> Macro<'a> { - fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Box>> { + fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box>> { let level = s.level; + #[allow(clippy::type_complexity)] let parameters = |i: &mut _| -> ParseResult< '_, @@ -659,12 +695,12 @@ impl<'a> Macro<'a> { .parse_next(i)?; match args { Some((args, Some(_))) => Ok(args), - Some((_, None)) => cut_error!("expected `)` to close macro argument list", *i), + Some((_, None)) => cut_error!("expected `)` to close macro argument list", ***i), None => Ok(None), } }; - let start_s = *i; + let start_s = ***i; let mut start = ( opt(Whitespace::parse), ws(keyword("macro")), @@ -719,8 +755,8 @@ impl<'a> Macro<'a> { cut_node( Some("macro"), preceded( - opt(|i: &mut _| { - let before = *i; + opt(|i: &mut InputStream<'a>| { + let before = ***i; let end_name = ws(identifier).parse_next(i)?; check_end_name(before, name, end_name, "macro") }), @@ -756,9 +792,9 @@ pub struct FilterBlock<'a> { } impl<'a> FilterBlock<'a> { - fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Box>> { - fn filters<'a>(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Filter<'a>> { - let mut start = *i; + fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box>> { + fn filters<'a>(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Filter<'a>> { + let mut start = ***i; let mut res = Filter::parse(i, s.level)?; res.arguments .insert(0, WithSpan::new(Box::new(Expr::FilterSource), start, i)); @@ -773,12 +809,12 @@ impl<'a> FilterBlock<'a> { WithSpan::new(Box::new(Expr::Filter(res)), start.trim_start(), i), ); res = filter; - start = *i; + start = ***i; } Ok(res) } - let start = *i; + let start = ***i; let (pws1, _, (filters, nws1, _), nodes, (_, pws2, _, nws2)) = ( opt(Whitespace::parse), ws(keyword("filter")), @@ -824,8 +860,8 @@ pub struct Import<'a> { } impl<'a> Import<'a> { - fn parse(i: &mut &'a str) -> ParseResult<'a, Box>> { - let start = *i; + fn parse(i: &mut InputStream<'a>) -> ParseResult<'a, Box>> { + let start = ***i; let mut p = ( opt(Whitespace::parse), ws(keyword("import")), @@ -863,8 +899,8 @@ pub struct Call<'a> { } impl<'a> Call<'a> { - fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Box>> { - let start_s = *i; + fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box>> { + let start_s = ***i; let parameters = |i: &mut _| -> ParseResult<'_, Option>> { let args = opt(preceded( '(', @@ -877,7 +913,7 @@ impl<'a> Call<'a> { match args { Some((args, Some(_))) => Ok(args), - Some((_, None)) => cut_error!("expected `)` to close call argument list", *i), + Some((_, None)) => cut_error!("expected `)` to close call argument list", ***i), None => Ok(None), } }; @@ -942,8 +978,8 @@ pub struct Match<'a> { } impl<'a> Match<'a> { - fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Box>> { - let start = *i; + fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box>> { + let start = ***i; let mut p = ( opt(Whitespace::parse), ws(keyword("match")), @@ -1015,8 +1051,8 @@ pub struct BlockDef<'a> { } impl<'a> BlockDef<'a> { - fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Box>> { - let start_s = *i; + fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box>> { + let start_s = ***i; let mut start = ( opt(Whitespace::parse), ws(keyword("block")), @@ -1042,8 +1078,8 @@ impl<'a> BlockDef<'a> { cut_node( Some("block"), ( - opt(|i: &mut _| { - let before = *i; + opt(|i: &mut InputStream<'a>| { + let before = ***i; let end_name = ws(identifier).parse_next(i)?; check_end_name(before, name, end_name, "block") }), @@ -1096,9 +1132,9 @@ pub struct Lit<'a> { } impl<'a> Lit<'a> { - fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Box>> { + fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box>> { not(eof).parse_next(i)?; - let start = *i; + let start = ***i; let mut content = opt(take_until( .., ( @@ -1139,9 +1175,12 @@ pub struct Raw<'a> { } impl<'a> Raw<'a> { - fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Box>> { - fn endraw<'a>(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, (Ws, &'a str)> { - let start = *i; + fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box>> { + fn endraw<'a>( + i: &mut InputStream<'a>, + s: &State<'_, '_>, + ) -> ParseResult<'a, (Ws, &'a str)> { + let start = ***i; loop { // find the string "endraw", strip any spaces before it, and look if there is a `{%` let inner = take_until(.., "endraw").parse_next(i)?; @@ -1157,7 +1196,7 @@ impl<'a> Raw<'a> { }; // We found `{% endraw`. Do we find `%}`, too? - *i = i.trim_ascii_start(); + skip_ws0(i)?; let i_before_nws = *i; let nws = opt(Whitespace::parse).parse_next(i)?; if opt(peek(s.syntax.block_end)).parse_next(i)?.is_none() { @@ -1172,7 +1211,7 @@ impl<'a> Raw<'a> { } } - let start = *i; + let start = ***i; let mut p = ( terminated(opt(Whitespace::parse), ws(keyword("raw"))), cut_node( @@ -1205,8 +1244,8 @@ pub struct Let<'a> { } impl<'a> Let<'a> { - fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Box>> { - let start = *i; + fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box>> { + let start = ***i; let mut p = ( opt(Whitespace::parse), ws(alt((keyword("let"), keyword("set")))), @@ -1276,8 +1315,8 @@ pub struct If<'a> { } impl<'a> If<'a> { - fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Box>> { - let start = *i; + fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box>> { + let start = ***i; let mut p = ( opt(Whitespace::parse), |i: &mut _| CondTest::parse(i, s), @@ -1336,8 +1375,8 @@ pub struct Include<'a> { } impl<'a> Include<'a> { - fn parse(i: &mut &'a str) -> ParseResult<'a, Box>> { - let start = *i; + fn parse(i: &mut InputStream<'a>) -> ParseResult<'a, Box>> { + let start = ***i; let mut p = ( opt(Whitespace::parse), ws(keyword("include")), @@ -1364,8 +1403,7 @@ pub struct Extends<'a> { } impl<'a> Extends<'a> { - fn parse(i: &mut &'a str) -> ParseResult<'a, Box>> { - let start = *i; + fn parse(i: &mut InputStream<'a>) -> ParseResult<'a, Box>> { preceded( (opt(Whitespace::parse), ws(keyword("extends"))), cut_node( @@ -1373,7 +1411,8 @@ impl<'a> Extends<'a> { terminated(ws(str_lit_without_prefix), opt(Whitespace::parse)), ), ) - .map(|path| Box::new(Node::Extends(WithSpan::new(Self { path }, start, i)))) + .with_taken() + .map(|(path, span)| Box::new(Node::Extends(WithSpan::new_with_full(Self { path }, span)))) .parse_next(i) } } @@ -1385,8 +1424,8 @@ pub struct Comment<'a> { } impl<'a> Comment<'a> { - fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Box>> { - fn content<'a>(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, ()> { + fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box>> { + fn content<'a>(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, ()> { let mut depth = 0usize; loop { take_until(.., (s.syntax.comment_start, s.syntax.comment_end)).parse_next(i)?; @@ -1403,7 +1442,7 @@ impl<'a> Comment<'a> { } } - let start = *i; + let start = ***i; let mut content = preceded( |i: &mut _| s.tag_comment_start(i), opt(terminated( @@ -1448,8 +1487,8 @@ pub struct Ws(pub Option, pub Option); fn end_node<'a, 'g: 'a>( node: &'g str, expected: &'g str, -) -> impl ModalParser<&'a str, &'a str, ErrorContext<'a>> + 'g { - move |i: &mut &'a str| { +) -> impl ModalParser, &'a str, ErrorContext<'a>> + 'g { + move |i: &mut InputStream<'a>| { let start = i.checkpoint(); let actual = ws(identifier).parse_next(i)?; if actual == expected { @@ -1458,7 +1497,7 @@ fn end_node<'a, 'g: 'a>( i.reset(&start); cut_error!( format!("expected `{expected}` to terminate `{node}` node, found `{actual}`"), - *i, + ***i, ) } else { i.reset(&start); diff --git a/askama_parser/src/target.rs b/askama_parser/src/target.rs index edafa54a..319eec44 100644 --- a/askama_parser/src/target.rs +++ b/askama_parser/src/target.rs @@ -4,9 +4,9 @@ use winnow::token::one_of; use winnow::{ModalParser, Parser}; use crate::{ - CharLit, ErrorContext, Num, ParseErr, ParseResult, PathComponent, PathOrIdentifier, State, - StrLit, WithSpan, bool_lit, can_be_variable_name, char_lit, cut_error, identifier, - is_rust_keyword, keyword, num_lit, path_or_identifier, str_lit, ws, + CharLit, ErrorContext, InputStream, Num, ParseErr, ParseResult, PathComponent, + PathOrIdentifier, State, StrLit, WithSpan, bool_lit, can_be_variable_name, char_lit, cut_error, + identifier, is_rust_keyword, keyword, num_lit, path_or_identifier, str_lit, ws, }; #[derive(Clone, Debug, PartialEq)] @@ -31,7 +31,7 @@ pub enum Target<'a> { impl<'a> Target<'a> { /// Parses multiple targets with `or` separating them - pub(super) fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Self> { + pub(super) fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Self> { let _level_guard = s.level.nest(i)?; let mut p = opt(preceded(ws(keyword("or")), |i: &mut _| { Self::parse_one(i, s) @@ -50,7 +50,7 @@ impl<'a> Target<'a> { } /// Parses a single target without an `or`, unless it is wrapped in parentheses. - fn parse_one(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Self> { + fn parse_one(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Self> { let mut opt_opening_paren = opt(ws('(')).map(|o| o.is_some()); let mut opt_opening_brace = opt(ws('{')).map(|o| o.is_some()); let mut opt_opening_bracket = opt(ws('[')).map(|o| o.is_some()); @@ -87,9 +87,9 @@ impl<'a> Target<'a> { } let path = |i: &mut _| path_or_identifier(i, s.level); - let path = path.try_map(|r| match r { - PathOrIdentifier::Path(v) => Ok(v), - PathOrIdentifier::Identifier(v) => Err(v), + let path = path.verify_map(|r| match r { + PathOrIdentifier::Path(v) => Some(v), + PathOrIdentifier::Identifier(_) => None, }); // match structs @@ -136,16 +136,15 @@ impl<'a> Target<'a> { } // neither literal nor struct nor path - let i_before_identifier = *i; let name = identifier.parse_next(i)?; let target = match name { - "_" => Self::Placeholder(WithSpan::new((), i_before_identifier, i)), - _ => verify_name(i_before_identifier, name)?, + "_" => Self::Placeholder(WithSpan::new_with_full((), name)), + _ => verify_name(name)?, }; Ok(target) } - fn lit(i: &mut &'a str) -> ParseResult<'a, Self> { + fn lit(i: &mut InputStream<'a>) -> ParseResult<'a, Self> { alt(( str_lit.map(Self::StrLit), char_lit.map(Self::CharLit), @@ -157,14 +156,12 @@ impl<'a> Target<'a> { .parse_next(i) } - fn unnamed(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Self> { + fn unnamed(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Self> { alt((Self::rest, |i: &mut _| Self::parse(i, s))).parse_next(i) } - fn named(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, (&'a str, Self)> { - let start = *i; - let rest = opt(Self::rest.with_taken()).parse_next(i)?; - if let Some(rest) = rest { + fn named(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, (&'a str, Self)> { + if let Some(rest) = opt(Self::rest.with_taken()).parse_next(i)? { let chr = peek(ws(opt(one_of([',', ':'])))).parse_next(i)?; if let Some(chr) = chr { return cut_error!( @@ -172,7 +169,7 @@ impl<'a> Target<'a> { "unexpected `{chr}` character after `..`\n\ note that in a named struct, `..` must come last to ignore other members" ), - *i, + ***i, ); } if let Target::Rest(ref s) = rest.0 @@ -180,30 +177,28 @@ impl<'a> Target<'a> { { return cut_error!("`@ ..` cannot be used in struct", s.span); } - return Ok((rest.1, rest.0)); + Ok((rest.1, rest.0)) + } else { + let (src, target) = ( + identifier, + opt(preceded(ws(':'), |i: &mut _| Self::parse(i, s))), + ) + .parse_next(i)?; + + if src == "_" { + return cut_error!("cannot use placeholder `_` as source in named struct", src); + } + + let target = match target { + Some(target) => target, + None => verify_name(src)?, + }; + Ok((src, target)) } - - *i = start; - let (src, target) = ( - identifier, - opt(preceded(ws(':'), |i: &mut _| Self::parse(i, s))), - ) - .parse_next(i)?; - - if src == "_" { - *i = start; - return cut_error!("cannot use placeholder `_` as source in named struct", *i); - } - - let target = match target { - Some(target) => target, - None => verify_name(start, src)?, - }; - Ok((src, target)) } - fn rest(i: &mut &'a str) -> ParseResult<'a, Self> { - let start = *i; + fn rest(i: &mut InputStream<'a>) -> ParseResult<'a, Self> { + let start = ***i; let (ident, _) = (opt((identifier, ws('@'))), "..").parse_next(i)?; Ok(Self::Rest(WithSpan::new( ident.map(|(ident, _)| ident), @@ -213,18 +208,18 @@ impl<'a> Target<'a> { } } -fn verify_name<'a>(input: &'a str, name: &'a str) -> Result, ErrMode>> { +fn verify_name<'a>(name: &'a str) -> Result, ErrMode>> { if is_rust_keyword(name) { cut_error!( format!("cannot use `{name}` as a name: it is a rust keyword"), - input, + name, ) } else if !can_be_variable_name(name) { - cut_error!(format!("`{name}` cannot be used as an identifier"), input) + cut_error!(format!("`{name}` cannot be used as an identifier"), name) } else if name.starts_with("__askama") { cut_error!( format!("cannot use `{name}` as a name: it is reserved for `askama`"), - input, + name, ) } else { Ok(Target::Name(name)) @@ -232,9 +227,9 @@ fn verify_name<'a>(input: &'a str, name: &'a str) -> Result, ErrMode< } fn collect_targets<'a, T>( - i: &mut &'a str, + i: &mut InputStream<'a>, delim: char, - one: impl ModalParser<&'a str, T, ErrorContext<'a>>, + one: impl ModalParser, T, ErrorContext<'a>>, ) -> ParseResult<'a, (bool, Vec)> { let opt_comma = ws(opt(',')).map(|o| o.is_some()); let mut opt_end = ws(opt(one_of(delim))).map(|o| o.is_some()); @@ -246,7 +241,7 @@ fn collect_targets<'a, T>( let targets = opt(separated(1.., one, ws(',')).map(|v: Vec<_>| v)).parse_next(i)?; let Some(targets) = targets else { - return cut_error!("expected comma separated list of members", *i); + return cut_error!("expected comma separated list of members", ***i); }; let (has_comma, has_end) = (opt_comma, opt_end).parse_next(i)?; @@ -256,7 +251,7 @@ fn collect_targets<'a, T>( true => format!("expected member, or `{delim}` as terminator"), false => format!("expected `,` for more members, or `{delim}` as terminator"), }, - *i + ***i ); } diff --git a/askama_parser/src/tests.rs b/askama_parser/src/tests.rs index e2a28036..b8bdc8de 100644 --- a/askama_parser/src/tests.rs +++ b/askama_parser/src/tests.rs @@ -1,9 +1,9 @@ -use winnow::Parser; +use winnow::{LocatingSlice, Parser}; use crate::node::{Lit, Raw, Whitespace, Ws}; use crate::{ - Ast, Expr, Filter, InnerSyntax, Node, Num, PathComponent, PathOrIdentifier, Span, StrLit, - Syntax, SyntaxBuilder, WithSpan, + Ast, Expr, Filter, InnerSyntax, InputStream, Node, Num, PathComponent, PathOrIdentifier, Span, + StrLit, Syntax, SyntaxBuilder, WithSpan, }; impl WithSpan<'static, T> { @@ -1349,10 +1349,13 @@ fn test_filter_with_path() { #[test] fn underscore_is_an_identifier() { - let mut input = "_"; + let mut input = InputStream { + input: LocatingSlice::new("_"), + state: (), + }; let result = crate::identifier.parse_next(&mut input); assert_eq!(result.unwrap(), "_"); - assert_eq!(input, ""); + assert_eq!(**input, ""); } #[test] diff --git a/testing/tests/ui/askama-block.stderr b/testing/tests/ui/askama-block.stderr index 3079a982..25d83b56 100644 --- a/testing/tests/ui/askama-block.stderr +++ b/testing/tests/ui/askama-block.stderr @@ -1,6 +1,6 @@ error: unknown node `fail` - --> :2:6 - " fail %}\n{% endif %}" + --> :2:7 + "fail %}\n{% endif %}" --> tests/ui/askama-block.rs:5:1 | 5 | /// Some documentation diff --git a/testing/tests/ui/char_literal.stderr b/testing/tests/ui/char_literal.stderr index 378abe5d..adc41184 100644 --- a/testing/tests/ui/char_literal.stderr +++ b/testing/tests/ui/char_literal.stderr @@ -54,7 +54,7 @@ error: unicode escape must be at most 10FFFF 28 | #[template(path = "char-literals/char-literal-7.txt")] | ^^^^^^^^ -error: invalid character +error: cannot have multiple characters in a character literal, use `"..."` to write a string --> :1:11 "'aaa' %}" --> tests/ui/char_literal.rs:32:21 @@ -62,7 +62,7 @@ error: invalid character 32 | #[template(source = "{% let s = 'aaa' %}", ext = "html")] | ^^^^^^^^^^^^^^^^^^^^^ -error: unterminated byte constant +error: unterminated byte literal --> :1:3 "b'c }}" --> tests/ui/char_literal.rs:36:21 @@ -70,7 +70,7 @@ error: unterminated byte constant 36 | #[template(source = r#"{{ b'c }}"#, ext = "html")] | ^^^^^^^^^^^^^^ -error: empty character literal +error: empty byte literal --> :1:3 "b'' }}" --> tests/ui/char_literal.rs:40:21 @@ -134,7 +134,7 @@ error: cannot use unicode escape in byte string in byte literal 68 | #[template(source = r#"{{ b'\u{10ffff}' }}"#, ext = "html")] | ^^^^^^^^^^^^^^^^^^^^^^^^ -error: unterminated byte constant +error: unterminated byte literal --> :1:6 "b'c) }}" --> tests/ui/char_literal.rs:72:21 @@ -142,7 +142,7 @@ error: unterminated byte constant 72 | #[template(source = r#"{{ a!(b'c) }}"#, ext = "html")] | ^^^^^^^^^^^^^^^^^^ -error: empty character literal +error: empty byte literal --> :1:3 "b'' }}" --> tests/ui/char_literal.rs:76:21 diff --git a/testing/tests/ui/character_literal-multiple-chars.rs b/testing/tests/ui/character_literal-multiple-chars.rs new file mode 100644 index 00000000..291c0eaf --- /dev/null +++ b/testing/tests/ui/character_literal-multiple-chars.rs @@ -0,0 +1,115 @@ +use askama::Template; + +#[derive(Template)] +#[template(source = r#"{{ 'ab' }}"#, ext = "html")] +struct Multiple1; + +#[derive(Template)] +#[template(source = r#"{{ '\0b' }}"#, ext = "html")] +struct Multiple2; + +#[derive(Template)] +#[template(source = r#"{{ 'b\0' }}"#, ext = "html")] +struct Multiple3; + +#[derive(Template)] +#[template(source = r#"{{ '\\0' }}"#, ext = "html")] +struct Multiple4; + +#[derive(Template)] +#[template(source = r#"{{ '\u{1234}b' }}"#, ext = "html")] +struct Multiple5; + +#[derive(Template)] +#[template(source = r#"{{ '\u{1234}b' }}"#, ext = "html")] +struct Multiple6; + +#[derive(Template)] +#[template(source = r#"{{ '\u{1234}\u{1234}' }}"#, ext = "html")] +struct Multiple7; + +#[derive(Template)] +#[template(source = r#"{{ b'ab' }}"#, ext = "html")] +struct ByteMultiple1; + +#[derive(Template)] +#[template(source = r#"{{ b'\0b' }}"#, ext = "html")] +struct ByteMultiple2; + +#[derive(Template)] +#[template(source = r#"{{ b'b\0' }}"#, ext = "html")] +struct ByteMultiple3; + +#[derive(Template)] +#[template(source = r#"{{ b'\\0' }}"#, ext = "html")] +struct ByteMultiple4; + +#[derive(Template)] +#[template(source = r#"{{ b'\u{1234}b' }}"#, ext = "html")] +struct ByteMultiple5; + +#[derive(Template)] +#[template(source = r#"{{ b'\u{1234}b' }}"#, ext = "html")] +struct ByteMultiple6; + +#[derive(Template)] +#[template(source = r#"{{ b'\u{1234}\u{1234}' }}"#, ext = "html")] +struct ByteMultiple7; + +#[derive(Template)] +#[template(source = r#"{{ x!('ab') }}"#, ext = "html")] +struct Multiple1InMacro; + +#[derive(Template)] +#[template(source = r#"{{ x!('\0b') }}"#, ext = "html")] +struct Multiple2InMacro; + +#[derive(Template)] +#[template(source = r#"{{ x!('b\0') }}"#, ext = "html")] +struct Multiple3InMacro; + +#[derive(Template)] +#[template(source = r#"{{ x!('\\0') }}"#, ext = "html")] +struct Multiple4InMacro; + +#[derive(Template)] +#[template(source = r#"{{ x!('\u{1234}b') }}"#, ext = "html")] +struct Multiple5InMacro; + +#[derive(Template)] +#[template(source = r#"{{ x!('\u{1234}b') }}"#, ext = "html")] +struct Multiple6InMacro; + +#[derive(Template)] +#[template(source = r#"{{ x!('\u{1234}\u{1234}') }}"#, ext = "html")] +struct Multiple7InMacro; + +#[derive(Template)] +#[template(source = r#"{{ x!(b'ab') }}"#, ext = "html")] +struct ByteMultiple1InMacro; + +#[derive(Template)] +#[template(source = r#"{{ x!(b'\0b') }}"#, ext = "html")] +struct ByteMultiple2InMacro; + +#[derive(Template)] +#[template(source = r#"{{ x!(b'b\0') }}"#, ext = "html")] +struct ByteMultiple3InMacro; + +#[derive(Template)] +#[template(source = r#"{{ x!(b'\\0') }}"#, ext = "html")] +struct ByteMultiple4InMacro; + +#[derive(Template)] +#[template(source = r#"{{ x!(b'\u{1234}b') }}"#, ext = "html")] +struct ByteMultiple5InMacro; + +#[derive(Template)] +#[template(source = r#"{{ x!(b'\u{1234}b') }}"#, ext = "html")] +struct ByteMultiple6InMacro; + +#[derive(Template)] +#[template(source = r#"{{ x!(b'\u{1234}\u{1234}') }}"#, ext = "html")] +struct ByteMultiple7InMacro; + +fn main() {} diff --git a/testing/tests/ui/character_literal-multiple-chars.stderr b/testing/tests/ui/character_literal-multiple-chars.stderr new file mode 100644 index 00000000..aa860de9 --- /dev/null +++ b/testing/tests/ui/character_literal-multiple-chars.stderr @@ -0,0 +1,223 @@ +error: cannot have multiple characters in a character literal, use `"..."` to write a string + --> :1:3 + "'ab' }}" + --> tests/ui/character_literal-multiple-chars.rs:4:21 + | +4 | #[template(source = r#"{{ 'ab' }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a character literal, use `"..."` to write a string + --> :1:3 + "'\\0b' }}" + --> tests/ui/character_literal-multiple-chars.rs:8:21 + | +8 | #[template(source = r#"{{ '\0b' }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a character literal, use `"..."` to write a string + --> :1:3 + "'b\\0' }}" + --> tests/ui/character_literal-multiple-chars.rs:12:21 + | +12 | #[template(source = r#"{{ 'b\0' }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a character literal, use `"..."` to write a string + --> :1:3 + "'\\\\0' }}" + --> tests/ui/character_literal-multiple-chars.rs:16:21 + | +16 | #[template(source = r#"{{ '\\0' }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a character literal, use `"..."` to write a string + --> :1:3 + "'\\u{1234}b' }}" + --> tests/ui/character_literal-multiple-chars.rs:20:21 + | +20 | #[template(source = r#"{{ '\u{1234}b' }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a character literal, use `"..."` to write a string + --> :1:3 + "'\\u{1234}b' }}" + --> tests/ui/character_literal-multiple-chars.rs:24:21 + | +24 | #[template(source = r#"{{ '\u{1234}b' }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a character literal, use `"..."` to write a string + --> :1:3 + "'\\u{1234}\\u{1234}' }}" + --> tests/ui/character_literal-multiple-chars.rs:28:21 + | +28 | #[template(source = r#"{{ '\u{1234}\u{1234}' }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a byte literal, use `b"..."` to write a binary string + --> :1:3 + "b'ab' }}" + --> tests/ui/character_literal-multiple-chars.rs:32:21 + | +32 | #[template(source = r#"{{ b'ab' }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a byte literal, use `b"..."` to write a binary string + --> :1:3 + "b'\\0b' }}" + --> tests/ui/character_literal-multiple-chars.rs:36:21 + | +36 | #[template(source = r#"{{ b'\0b' }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a byte literal, use `b"..."` to write a binary string + --> :1:3 + "b'b\\0' }}" + --> tests/ui/character_literal-multiple-chars.rs:40:21 + | +40 | #[template(source = r#"{{ b'b\0' }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a byte literal, use `b"..."` to write a binary string + --> :1:3 + "b'\\\\0' }}" + --> tests/ui/character_literal-multiple-chars.rs:44:21 + | +44 | #[template(source = r#"{{ b'\\0' }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a byte literal, use `b"..."` to write a binary string + --> :1:3 + "b'\\u{1234}b' }}" + --> tests/ui/character_literal-multiple-chars.rs:48:21 + | +48 | #[template(source = r#"{{ b'\u{1234}b' }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a byte literal, use `b"..."` to write a binary string + --> :1:3 + "b'\\u{1234}b' }}" + --> tests/ui/character_literal-multiple-chars.rs:52:21 + | +52 | #[template(source = r#"{{ b'\u{1234}b' }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a byte literal, use `b"..."` to write a binary string + --> :1:3 + "b'\\u{1234}\\u{1234}' }}" + --> tests/ui/character_literal-multiple-chars.rs:56:21 + | +56 | #[template(source = r#"{{ b'\u{1234}\u{1234}' }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a character literal, use `"..."` to write a string + --> :1:6 + "'ab') }}" + --> tests/ui/character_literal-multiple-chars.rs:60:21 + | +60 | #[template(source = r#"{{ x!('ab') }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a character literal, use `"..."` to write a string + --> :1:6 + "'\\0b') }}" + --> tests/ui/character_literal-multiple-chars.rs:64:21 + | +64 | #[template(source = r#"{{ x!('\0b') }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a character literal, use `"..."` to write a string + --> :1:6 + "'b\\0') }}" + --> tests/ui/character_literal-multiple-chars.rs:68:21 + | +68 | #[template(source = r#"{{ x!('b\0') }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a character literal, use `"..."` to write a string + --> :1:6 + "'\\\\0') }}" + --> tests/ui/character_literal-multiple-chars.rs:72:21 + | +72 | #[template(source = r#"{{ x!('\\0') }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a character literal, use `"..."` to write a string + --> :1:6 + "'\\u{1234}b') }}" + --> tests/ui/character_literal-multiple-chars.rs:76:21 + | +76 | #[template(source = r#"{{ x!('\u{1234}b') }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a character literal, use `"..."` to write a string + --> :1:6 + "'\\u{1234}b') }}" + --> tests/ui/character_literal-multiple-chars.rs:80:21 + | +80 | #[template(source = r#"{{ x!('\u{1234}b') }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a character literal, use `"..."` to write a string + --> :1:6 + "'\\u{1234}\\u{1234}') }}" + --> tests/ui/character_literal-multiple-chars.rs:84:21 + | +84 | #[template(source = r#"{{ x!('\u{1234}\u{1234}') }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a byte literal, use `b"..."` to write a binary string + --> :1:6 + "b'ab') }}" + --> tests/ui/character_literal-multiple-chars.rs:88:21 + | +88 | #[template(source = r#"{{ x!(b'ab') }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a byte literal, use `b"..."` to write a binary string + --> :1:6 + "b'\\0b') }}" + --> tests/ui/character_literal-multiple-chars.rs:92:21 + | +92 | #[template(source = r#"{{ x!(b'\0b') }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a byte literal, use `b"..."` to write a binary string + --> :1:6 + "b'b\\0') }}" + --> tests/ui/character_literal-multiple-chars.rs:96:21 + | +96 | #[template(source = r#"{{ x!(b'b\0') }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a byte literal, use `b"..."` to write a binary string + --> :1:6 + "b'\\\\0') }}" + --> tests/ui/character_literal-multiple-chars.rs:100:21 + | +100 | #[template(source = r#"{{ x!(b'\\0') }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a byte literal, use `b"..."` to write a binary string + --> :1:6 + "b'\\u{1234}b') }}" + --> tests/ui/character_literal-multiple-chars.rs:104:21 + | +104 | #[template(source = r#"{{ x!(b'\u{1234}b') }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a byte literal, use `b"..."` to write a binary string + --> :1:6 + "b'\\u{1234}b') }}" + --> tests/ui/character_literal-multiple-chars.rs:108:21 + | +108 | #[template(source = r#"{{ x!(b'\u{1234}b') }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: cannot have multiple characters in a byte literal, use `b"..."` to write a binary string + --> :1:6 + "b'\\u{1234}\\u{1234}') }}" + --> tests/ui/character_literal-multiple-chars.rs:112:21 + | +112 | #[template(source = r#"{{ x!(b'\u{1234}\u{1234}') }}"#, ext = "html")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/testing/tests/ui/unexpected-tag.stderr b/testing/tests/ui/unexpected-tag.stderr index a9ea23db..1dbc1eff 100644 --- a/testing/tests/ui/unexpected-tag.stderr +++ b/testing/tests/ui/unexpected-tag.stderr @@ -1,46 +1,46 @@ error: node `end` was not expected in the current context - --> :1:2 - " end %}" + --> :1:3 + "end %}" --> tests/ui/unexpected-tag.rs:5:1 | 5 | /// ```askama | ^^^^^^^^^^^^^ error: node `elif` was not expected in the current context: `for` block - --> :3:2 - " elif %}\n what?\n{% endfor %}" + --> :3:3 + "elif %}\n what?\n{% endfor %}" --> tests/ui/unexpected-tag.rs:12:1 | 12 | /// ```askama | ^^^^^^^^^^^^^ error: node `else` was not expected in the current context: `block` block - --> :3:2 - " else %}\n else\n{% endblock meta %}" + --> :3:3 + "else %}\n else\n{% endblock meta %}" --> tests/ui/unexpected-tag.rs:23:1 | 23 | /// ```askama | ^^^^^^^^^^^^^ error: node `when` was not expected in the current context - --> :1:2 - " when condition %}\n true\n{% endwhen %}" + --> :1:3 + "when condition %}\n true\n{% endwhen %}" --> tests/ui/unexpected-tag.rs:34:1 | 34 | /// ```askama | ^^^^^^^^^^^^^ error: unexpected closing tag `endlet` - --> :1:20 - " endlet %}" + --> :1:21 + "endlet %}" --> tests/ui/unexpected-tag.rs:43:1 | 43 | /// ```askama | ^^^^^^^^^^^^^ error: unknown node `syntax` - --> :1:2 - " syntax error %}" + --> :1:3 + "syntax error %}" --> tests/ui/unexpected-tag.rs:50:1 | 50 | /// ```askama