parser: use LocatingSlice<&str> instead of &str

This will enable better span getting in subsequent PRs.
This commit is contained in:
René Kijewski 2025-08-07 05:39:13 +02:00
parent e9021aa1a5
commit 329771152d
10 changed files with 873 additions and 421 deletions

View File

@ -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<Self>>> {
fn $name(
i: &mut InputStream<'a>,
level: Level<'_>,
) -> ParseResult<'a, WithSpan<'a, Box<Self>>> {
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<Expr<'a>>>>,
op: fn(&mut &'a str) -> ParseResult<'a>,
inner: fn(&mut InputStream<'a>, Level<'_>) -> ParseResult<'a, WithSpan<'a, Box<Expr<'a>>>>,
op: fn(&mut InputStream<'a>) -> ParseResult<'a>,
) -> ParseResult<'a, WithSpan<'a, Box<Expr<'a>>>> {
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<WithSpan<'a, Box<Self>>>> {
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<Self>>> {
@ -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<Self>>> {
fn compare(
i: &mut InputStream<'a>,
level: Level<'_>,
) -> ParseResult<'a, WithSpan<'a, Box<Self>>> {
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<Self>>> {
fn concat(
i: &mut InputStream<'a>,
level: Level<'_>,
) -> ParseResult<'a, WithSpan<'a, Box<Self>>> {
fn concat_expr<'a>(
i: &mut &'a str,
i: &mut InputStream<'a>,
level: Level<'_>,
) -> ParseResult<'a, Option<WithSpan<'a, Box<Expr<'a>>>>> {
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<Self>>> {
let start = *i;
fn is_as(
i: &mut InputStream<'a>,
level: Level<'_>,
) -> ParseResult<'a, WithSpan<'a, Box<Self>>> {
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<Self>>> {
fn filtered(
i: &mut InputStream<'a>,
level: Level<'_>,
) -> ParseResult<'a, WithSpan<'a, Box<Self>>> {
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<Self>>> {
let start = *i;
fn prefix(
i: &mut InputStream<'a>,
level: Level<'_>,
) -> ParseResult<'a, WithSpan<'a, Box<Self>>> {
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<Self>>> {
fn single(
i: &mut InputStream<'a>,
level: Level<'_>,
) -> ParseResult<'a, WithSpan<'a, Box<Self>>> {
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<Self>>> {
let start = *i;
fn group(
i: &mut InputStream<'a>,
level: Level<'_>,
) -> ParseResult<'a, WithSpan<'a, Box<Self>>> {
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<Self>>> {
let start = *i;
fn array(
i: &mut InputStream<'a>,
level: Level<'_>,
) -> ParseResult<'a, WithSpan<'a, Box<Self>>> {
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<Self>>> {
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<Self>>> {
let start = *i;
fn str(i: &mut InputStream<'a>) -> ParseResult<'a, WithSpan<'a, Box<Self>>> {
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<Self>>> {
let start = *i;
fn num(i: &mut InputStream<'a>) -> ParseResult<'a, WithSpan<'a, Box<Self>>> {
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<Self>>> {
let start = *i;
fn char(i: &mut InputStream<'a>) -> ParseResult<'a, WithSpan<'a, Box<Self>>> {
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<Expr<'a>>>> {
let i_start = *i;
fn parse(
i: &mut InputStream<'a>,
level: Level<'_>,
) -> ParseResult<'a, WithSpan<'a, Box<Expr<'a>>>> {
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<Group> = 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> {
// <https://doc.rust-lang.org/reference/tokens.html>
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, ()> {
// <https://doc.rust-lang.org/reference/tokens.html#r-lex.token.literal.str-raw.syntax>
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!("\"{:#<hashes$}", "")) else {
let delim = format!("\"{:#<hashes$}", "");
let Some(inner) = opt(terminated(take_until(.., delim.as_str()), delim.as_str()))
.parse_next(i)?
else {
return cut_error!("unterminated raw string", prefix);
};
*i = j;
if inner.split('\r').skip(1).any(|s| !s.starts_with('\n')) {
return cut_error!(
@ -1021,8 +1063,8 @@ impl<'a> 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, ()> {
// <https://doc.rust-lang.org/reference/tokens.html#punctuation>
// 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<WithSpan<'i, TyGenerics<'i>>>> {
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<WithSpan<'i, TyGenerics<'i>>>> {
preceded(ws("::"), cut_err(|i: &mut _| TyGenerics::args(i, level))).parse_next(i)

View File

@ -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<LocatingSlice<&'a str>, ()>;
#[derive(Debug, Default)]
pub struct Ast<'a> {
nodes: Vec<Box<Node<'a>>>,
@ -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<Arc<Path>>,
syntax: &Syntax<'_>,
) -> Result<Self, ParseError> {
@ -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<InputStream<'a>> 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<InputStream<'a>, O, ErrorContext<'a>>,
) -> impl ModalParser<InputStream<'a>, 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<InputStream<'a>, &'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<FloatKind>),
}
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 <https://github.com/rust-lang/rust/blob/e3f909b2bbd0b10db6f164d466db237c582d3045/compiler/rustc_lexer/src/lib.rs#L587-L620>.
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 <https://github.com/rust-lang/rust/blob/e3f909b2bbd0b10db6f164d466db237c582d3045/compiler/rustc_lexer/src/lib.rs#L626-L653>:
// 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<InputStream<'a>, &'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>> {
// <https://doc.rust-lang.org/reference/tokens.html#r-lex.token.literal.str.syntax>
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:
// <https://doc.rust-lang.org/reference/tokens.html#character-literals>.
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<Self, ()> {
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<T> = std::collections::hash_set::HashSet<T, FxBuildHasher>;
#[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<InputStream<'a>, 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]

View File

@ -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<Box<Self>>> {
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 <https://github.com/askama-rs/askama/issues>",
*i,
you should never encounter this error\n\
please report this error to <https://github.com/askama-rs/askama/issues>",
***i,
);
}
Ok(result)
Ok(nodes)
}
fn many(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Vec<Box<Self>>> {
fn many(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Vec<Box<Self>>> {
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<Self>> {
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<Self>> {
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<Node<'a>>> {
fn r#break(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box<Node<'a>>> {
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<Node<'a>>> {
fn r#continue(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box<Node<'a>>> {
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<Self>> {
let start = *i;
fn expr(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box<Self>> {
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<InputStream<'a>, O, ErrorContext<'a>>,
mut unexpected_parser: impl FnMut(&mut InputStream<'a>) -> ParseResult<'a, ()>,
) -> impl ModalParser<InputStream<'a>, O, ErrorContext<'a>> {
#[cold]
#[inline(never)]
fn try_assign_fallback_error<'a>(
i: &mut InputStream<'a>,
start_checkpoint: <InputStream<'a> as Stream>::Checkpoint,
unexpected_parser: &mut dyn FnMut(&mut InputStream<'a>) -> ParseResult<'a, ()>,
err: &mut ErrMode<ErrorContext<'a>>,
) {
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<InputStream<'a>, O, ErrorContext<'a>>,
) -> impl ModalParser<InputStream<'a>, 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<Node<'a>>> {
fn content<'a>(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Vec<Box<Node<'a>>>> {
fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box<Node<'a>>> {
fn content<'a>(
i: &mut InputStream<'a>,
s: &State<'_, '_>,
) -> ParseResult<'a, Vec<Box<Node<'a>>>> {
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<Node<'a>>> {
fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box<Node<'a>>> {
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<Node<'a>>> {
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<Node<'a>>> {
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<Node<'a>>> {
let start = *i;
fn parse(i: &mut InputStream<'a>) -> ParseResult<'a, Box<Node<'a>>> {
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<Node<'a>>> {
let start_s = *i;
fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box<Node<'a>>> {
let start_s = ***i;
let parameters = |i: &mut _| -> ParseResult<'_, Option<Vec<&str>>> {
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<Node<'a>>> {
let start = *i;
fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box<Node<'a>>> {
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<Node<'a>>> {
let start_s = *i;
fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box<Node<'a>>> {
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<Node<'a>>> {
fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box<Node<'a>>> {
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<Node<'a>>> {
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<Node<'a>>> {
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<Node<'a>>> {
let start = *i;
fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box<Node<'a>>> {
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<Node<'a>>> {
let start = *i;
fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box<Node<'a>>> {
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<Node<'a>>> {
let start = *i;
fn parse(i: &mut InputStream<'a>) -> ParseResult<'a, Box<Node<'a>>> {
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<Node<'a>>> {
let start = *i;
fn parse(i: &mut InputStream<'a>) -> ParseResult<'a, Box<Node<'a>>> {
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<Node<'a>>> {
fn content<'a>(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, ()> {
fn parse(i: &mut InputStream<'a>, s: &State<'_, '_>) -> ParseResult<'a, Box<Node<'a>>> {
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<Whitespace>, pub Option<Whitespace>);
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<InputStream<'a>, &'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);

View File

@ -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<Target<'a>, ErrMode<ErrorContext<'a>>> {
fn verify_name<'a>(name: &'a str) -> Result<Target<'a>, ErrMode<ErrorContext<'a>>> {
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<Target<'a>, 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<InputStream<'a>, T, ErrorContext<'a>>,
) -> ParseResult<'a, (bool, Vec<T>)> {
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
);
}

View File

@ -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<T> 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]

View File

@ -1,6 +1,6 @@
error: unknown node `fail`
--> <source attribute>:2:6
" fail %}\n{% endif %}"
--> <source attribute>:2:7
"fail %}\n{% endif %}"
--> tests/ui/askama-block.rs:5:1
|
5 | /// Some documentation

View File

@ -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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>:1:3
"b'' }}"
--> tests/ui/char_literal.rs:76:21

View File

@ -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() {}

View File

@ -0,0 +1,223 @@
error: cannot have multiple characters in a character literal, use `"..."` to write a string
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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
--> <source attribute>: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")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -1,46 +1,46 @@
error: node `end` was not expected in the current context
--> <source attribute>:1:2
" end %}"
--> <source attribute>:1:3
"end %}"
--> tests/ui/unexpected-tag.rs:5:1
|
5 | /// ```askama
| ^^^^^^^^^^^^^
error: node `elif` was not expected in the current context: `for` block
--> <source attribute>:3:2
" elif %}\n what?\n{% endfor %}"
--> <source attribute>: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
--> <source attribute>:3:2
" else %}\n else\n{% endblock meta %}"
--> <source attribute>: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
--> <source attribute>:1:2
" when condition %}\n true\n{% endwhen %}"
--> <source attribute>:1:3
"when condition %}\n true\n{% endwhen %}"
--> tests/ui/unexpected-tag.rs:34:1
|
34 | /// ```askama
| ^^^^^^^^^^^^^
error: unexpected closing tag `endlet`
--> <source attribute>:1:20
" endlet %}"
--> <source attribute>:1:21
"endlet %}"
--> tests/ui/unexpected-tag.rs:43:1
|
43 | /// ```askama
| ^^^^^^^^^^^^^
error: unknown node `syntax`
--> <source attribute>:1:2
" syntax error %}"
--> <source attribute>:1:3
"syntax error %}"
--> tests/ui/unexpected-tag.rs:50:1
|
50 | /// ```askama