use std::collections::HashSet; use std::str::{self, FromStr}; use winnow::combinator::{ alt, cut_err, delimited, empty, eof, fail, not, opt, peek, preceded, repeat, separated, terminated, }; use winnow::stream::Stream as _; use winnow::token::{any, literal, rest}; use winnow::{ModalParser, Parser}; use crate::memchr_splitter::{Splitter1, Splitter2, Splitter3}; use crate::{ ErrorContext, Expr, Filter, ParseResult, Span, State, Target, WithSpan, filter, identifier, is_rust_keyword, keyword, skip_till, skip_ws0, str_lit_without_prefix, ws, }; #[derive(Debug, PartialEq)] pub enum Node<'a> { Lit(WithSpan<'a, Lit<'a>>), Comment(WithSpan<'a, Comment<'a>>), Expr(Ws, WithSpan<'a, Expr<'a>>), Call(WithSpan<'a, Call<'a>>), Let(WithSpan<'a, Let<'a>>), If(WithSpan<'a, If<'a>>), Match(WithSpan<'a, Match<'a>>), Loop(Box>>), Extends(WithSpan<'a, Extends<'a>>), BlockDef(WithSpan<'a, BlockDef<'a>>), Include(WithSpan<'a, Include<'a>>), Import(WithSpan<'a, Import<'a>>), Macro(WithSpan<'a, Macro<'a>>), Raw(WithSpan<'a, Raw<'a>>), Break(WithSpan<'a, Ws>), Continue(WithSpan<'a, Ws>), FilterBlock(WithSpan<'a, FilterBlock<'a>>), } impl<'a> Node<'a> { pub(super) fn parse_template(i: &mut &'a str, 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 winnow::error::ErrMode::Backtrack(err) | winnow::error::ErrMode::Cut(err) = &err { if 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); } }; opt(|i: &mut _| unexpected_tag(i, s)).parse_next(i)?; let is_eof = opt(eof).parse_next(i)?; if is_eof.is_none() { return Err(winnow::error::ErrMode::Cut(ErrorContext::new( "cannot parse entire template\n\ you should never encounter this error\n\ please report this error to ", *i, ))); } Ok(result) } fn many(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Vec> { repeat( 0.., alt(( |i: &mut _| Lit::parse(i, s).map(Self::Lit), |i: &mut _| Comment::parse(i, s).map(Self::Comment), |i: &mut _| Self::expr(i, s), |i: &mut _| Self::parse(i, s), )), ) .map(|v: Vec<_>| v) .parse_next(i) } fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Self> { let mut start = *i; let tag = preceded( |i: &mut _| s.tag_block_start(i), peek(preceded((opt(Whitespace::parse), skip_ws0), identifier)), ) .parse_next(i)?; let func = match tag { "call" => |i: &mut _, s| Call::parse(i, s).map(Self::Call), "let" | "set" => |i: &mut _, s| Let::parse(i, s).map(Self::Let), "if" => |i: &mut _, s| If::parse(i, s).map(Self::If), "for" => |i: &mut _, s| Loop::parse(i, s).map(|n| Self::Loop(Box::new(n))), "match" => |i: &mut _, s| Match::parse(i, s).map(Self::Match), "extends" => |i: &mut _, _s| Extends::parse(i).map(Self::Extends), "include" => |i: &mut _, _s| Include::parse(i).map(Self::Include), "import" => |i: &mut _, _s| Import::parse(i).map(Self::Import), "block" => |i: &mut _, s| BlockDef::parse(i, s).map(Self::BlockDef), "macro" => |i: &mut _, s| Macro::parse(i, s).map(Self::Macro), "raw" => |i: &mut _, s| Raw::parse(i, s).map(Self::Raw), "break" => |i: &mut _, s| Self::r#break(i, s), "continue" => |i: &mut _, s| Self::r#continue(i, s), "filter" => |i: &mut _, s| FilterBlock::parse(i, s).map(Self::FilterBlock), _ => return fail.parse_next(&mut start), }; let _level_guard = s.level.nest(i)?; let node = func(i, s)?; let closed = cut_node( None, alt(( ws(eof).value(false), (|i: &mut _| s.tag_block_end(i)).value(true), )), ) .parse_next(i)?; match closed { true => Ok(node), false => Err(ErrorContext::unclosed("block", s.syntax.block_end, start).cut()), } } fn r#break(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Self> { let mut p = ( opt(Whitespace::parse), ws(keyword("break")), opt(Whitespace::parse), ); let start = *i; let (pws, _, nws) = p.parse_next(i)?; if !s.is_in_loop() { return Err(winnow::error::ErrMode::Cut(ErrorContext::new( "you can only `break` inside a `for` loop", start, ))); } Ok(Self::Break(WithSpan::new(Ws(pws, nws), start))) } fn r#continue(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Self> { let mut p = ( opt(Whitespace::parse), ws(keyword("continue")), opt(Whitespace::parse), ); let start = *i; let (pws, _, nws) = p.parse_next(i)?; if !s.is_in_loop() { return Err(winnow::error::ErrMode::Cut(ErrorContext::new( "you can only `continue` inside a `for` loop", start, ))); } Ok(Self::Continue(WithSpan::new(Ws(pws, nws), start))) } fn expr(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Self> { let start = *i; let level = s.level; let (pws, expr) = preceded( |i: &mut _| s.tag_expr_start(i), cut_node( None, ( opt(Whitespace::parse), ws(|i: &mut _| Expr::parse(i, level, false)), ), ), ) .parse_next(i)?; let (nws, closed) = cut_node( None, ( opt(Whitespace::parse), alt(( (|i: &mut _| s.tag_expr_end(i)).value(true), ws(eof).value(false), )), ), ) .parse_next(i)?; match closed { true => Ok(Self::Expr(Ws(pws, nws), expr)), false => Err(ErrorContext::unclosed("expression", s.syntax.expr_end, start).cut()), } } #[must_use] pub fn span(&self) -> Span<'a> { match self { Self::Lit(span) => span.span, Self::Comment(span) => span.span, Self::Expr(_, span) => span.span, Self::Call(span) => span.span, Self::Let(span) => span.span, Self::If(span) => span.span, Self::Match(span) => span.span, Self::Loop(span) => span.span, Self::Extends(span) => span.span, Self::BlockDef(span) => span.span, Self::Include(span) => span.span, Self::Import(span) => span.span, Self::Macro(span) => span.span, Self::Raw(span) => span.span, Self::Break(span) => span.span, Self::Continue(span) => span.span, Self::FilterBlock(span) => span.span, } } } 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(winnow::error::ErrMode::Cut(err) | winnow::error::ErrMode::Backtrack(err)) = &result { if err.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)?; } } } result } } fn unexpected_tag<'a>(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, ()> { ( |i: &mut _| s.tag_block_start(i), opt(Whitespace::parse), |i: &mut _| unexpected_raw_tag(None, i), ) .void() .parse_next(i) } fn unexpected_raw_tag<'a>(kind: Option<&'static str>, i: &mut &'a str) -> ParseResult<'a, ()> { let tag = peek(ws(identifier)).parse_next(i)?; let msg = match tag { "end" | "elif" | "else" | "when" => match kind { Some(kind) => { format!("node `{tag}` was not expected in the current context: `{kind}` block") } None => format!("node `{tag}` was not expected in the current context"), }, tag if tag.starts_with("end") => format!("unexpected closing tag `{tag}`"), tag => format!("unknown node `{tag}`"), }; Err(winnow::error::ErrMode::Cut(ErrorContext::new(msg, *i))) } #[derive(Debug, PartialEq)] pub struct When<'a> { pub ws: Ws, pub target: Vec>, pub nodes: Vec>, } impl<'a> When<'a> { fn r#else(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, WithSpan<'a, Self>> { let mut p = ( |i: &mut _| s.tag_block_start(i), opt(Whitespace::parse), ws(keyword("else")), cut_node( Some("match-else"), ( opt(Whitespace::parse), |i: &mut _| s.tag_block_end(i), cut_node(Some("match-else"), |i: &mut _| Node::many(i, s)), ), ), ); let start = *i; let (_, pws, _, (nws, _, nodes)) = p.parse_next(i)?; Ok(WithSpan::new( Self { ws: Ws(pws, nws), target: vec![Target::Placeholder(WithSpan::new((), start))], nodes, }, start, )) } #[allow(clippy::self_named_constructors)] fn when(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, WithSpan<'a, Self>> { let start = *i; let endwhen = ws(( delimited( |i: &mut _| s.tag_block_start(i), opt(Whitespace::parse), ws(keyword("endwhen")), ), cut_node( Some("match-endwhen"), ( opt(Whitespace::parse), |i: &mut _| s.tag_block_end(i), repeat(0.., ws(|i: &mut _| Comment::parse(i, s))).map(|()| ()), ), ), )) .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. Node::Comment(WithSpan::new( Comment { ws: Ws(pws, Some(Whitespace::Suppress)), content: "", }, span, )) }); let mut p = ( |i: &mut _| s.tag_block_start(i), opt(Whitespace::parse), ws(keyword("when")), cut_node( Some("match-when"), ( separated(1.., ws(|i: &mut _| Target::parse(i, s)), '|'), opt(Whitespace::parse), |i: &mut _| s.tag_block_end(i), cut_node(Some("match-when"), |i: &mut _| Node::many(i, s)), opt(endwhen), ), ), ); let (_, pws, _, (target, nws, _, mut nodes, endwhen)) = p.parse_next(i)?; if let Some(endwhen) = endwhen { nodes.push(endwhen); } Ok(WithSpan::new( Self { ws: Ws(pws, nws), target, nodes, }, start, )) } } #[derive(Debug, PartialEq)] pub struct Cond<'a> { pub ws: Ws, pub cond: Option>, pub nodes: Vec>, } impl<'a> Cond<'a> { fn parse(i: &mut &'a str, 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), alt(( preceded(ws(keyword("else")), opt(|i: &mut _| CondTest::parse(i, s))), preceded( ws(keyword("elif")), cut_node(Some("if-elif"), |i: &mut _| { CondTest::parse_cond(i, s).map(Some) }), ), )), opt(Whitespace::parse), cut_node(Some("if"), |i: &mut _| s.tag_block_end(i)), cut_node(Some("if"), |i: &mut _| Node::many(i, s)), ) .parse_next(i)?; Ok(WithSpan::new( Self { ws: Ws(pws, nws), cond, nodes, }, start, )) } } #[derive(Debug, PartialEq, Clone)] pub struct CondTest<'a> { pub target: Option>, pub expr: WithSpan<'a, Expr<'a>>, pub contains_bool_lit_or_is_defined: bool, } impl<'a> CondTest<'a> { fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Self> { preceded( ws(keyword("if")), cut_node(Some("if"), |i: &mut _| Self::parse_cond(i, s)), ) .parse_next(i) } fn parse_cond(i: &mut &'a str, 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; let mut expr = Expr::parse(i, s.level, false)?; if let Expr::BinOp(_, _, ref mut right) = expr.inner { if matches!(right.inner, Expr::Var("set" | "let")) { let _level_guard = s.level.nest(i)?; *i = right.span.as_suffix_of(start).unwrap(); let start_span = Span::from(*i); let new_right = Self::parse_cond(i, s)?; right.inner = Expr::LetCond(Box::new(WithSpan::new(new_right, start_span))); } } Ok(expr) }), ) .parse_next(i)?; let contains_bool_lit_or_is_defined = expr.contains_bool_lit_or_is_defined(); Ok(Self { target, expr, contains_bool_lit_or_is_defined, }) } } #[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash)] #[cfg_attr(feature = "config", derive(serde_derive::Deserialize))] #[cfg_attr(feature = "config", serde(field_identifier, rename_all = "lowercase"))] pub enum Whitespace { #[default] Preserve, Suppress, Minimize, } impl Whitespace { fn parse<'i>(i: &mut &'i str) -> ParseResult<'i, Self> { any.verify_map(Self::parse_char).parse_next(i) } fn parse_char(c: char) -> Option { match c { '+' => Some(Self::Preserve), '-' => Some(Self::Suppress), '~' => Some(Self::Minimize), _ => None, } } } impl FromStr for Whitespace { type Err = String; fn from_str(s: &str) -> Result { match s { "+" | "preserve" => Ok(Whitespace::Preserve), "-" | "suppress" => Ok(Whitespace::Suppress), "~" | "minimize" => Ok(Whitespace::Minimize), s => Err(format!("invalid value for `whitespace`: {s:?}")), } } } fn check_block_start<'a>( i: &mut &'a str, start: &'a str, s: &State<'_, '_>, node: &str, expected: &str, ) -> ParseResult<'a, ()> { if i.is_empty() { return Err(winnow::error::ErrMode::Cut(ErrorContext::new( format!("expected `{expected}` to terminate `{node}` node, found nothing"), start, ))); } (|i: &mut _| s.tag_block_start(i)).parse_next(i) } #[derive(Debug, PartialEq)] pub struct Loop<'a> { pub ws1: Ws, pub var: Target<'a>, pub iter: WithSpan<'a, Expr<'a>>, pub cond: Option>>, pub body: Vec>, pub ws2: Ws, pub else_nodes: Vec>, pub ws3: Ws, } impl<'a> Loop<'a> { fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, WithSpan<'a, Self>> { fn content<'a>(i: &mut &'a str, 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 if_cond = preceded( ws(keyword("if")), cut_node( Some("for-if"), ws(|i: &mut _| Expr::parse(i, s.level, true)), ), ); let else_block = |i: &mut _| { let mut p = preceded( ws(keyword("else")), cut_node( Some("for-else"), ( opt(Whitespace::parse), delimited( |i: &mut _| s.tag_block_end(i), |i: &mut _| Node::many(i, s), |i: &mut _| s.tag_block_start(i), ), opt(Whitespace::parse), ), ), ); let (pws, nodes, nws) = p.parse_next(i)?; Ok((pws, nodes, nws)) }; let body_and_end = |i: &mut _| { let (body, (_, pws, else_block, _, nws)) = cut_node( Some("for"), ( |i: &mut _| content(i, s), cut_node( Some("for"), ( |i: &mut _| check_block_start(i, start, s, "for", "endfor"), opt(Whitespace::parse), opt(else_block), end_node("for", "endfor"), opt(Whitespace::parse), ), ), ), ) .parse_next(i)?; Ok((body, pws, else_block, nws)) }; let mut p = ( opt(Whitespace::parse), ws(keyword("for")), cut_node( Some("for"), ( ws(|i: &mut _| Target::parse(i, s)), ws(keyword("in")), cut_node( Some("for"), ( ws(|i: &mut _| Expr::parse(i, s.level, true)), opt(if_cond), opt(Whitespace::parse), |i: &mut _| s.tag_block_end(i), body_and_end, ), ), ), ), ); let (pws1, _, (var, _, (iter, cond, nws1, _, (body, pws2, else_block, nws2)))) = p.parse_next(i)?; let (nws3, else_nodes, pws3) = else_block.unwrap_or_default(); Ok(WithSpan::new( Self { ws1: Ws(pws1, nws1), var, iter, cond, body, ws2: Ws(pws2, nws3), else_nodes, ws3: Ws(pws3, nws2), }, start, )) } } #[derive(Debug, PartialEq)] pub struct Macro<'a> { pub ws1: Ws, pub name: &'a str, pub args: Vec<(&'a str, Option>>)>, pub nodes: Vec>, pub ws2: Ws, } fn check_duplicated_name<'a>( names: &mut HashSet<&'a str>, arg_name: &'a str, i: &'a str, ) -> Result<(), crate::ParseErr<'a>> { if !names.insert(arg_name) { return Err(winnow::error::ErrMode::Cut(ErrorContext::new( format!("duplicated argument `{arg_name}`"), i, ))); } Ok(()) } impl<'a> Macro<'a> { fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, WithSpan<'a, Self>> { let level = s.level; #[allow(clippy::type_complexity)] let parameters = |i: &mut _| -> ParseResult< '_, Option>>)>>, > { let args = opt(preceded( '(', ( opt(terminated( separated( 1.., ( ws(identifier), opt(preceded('=', ws(|i: &mut _| Expr::parse(i, level, false)))), ), ',', ), opt(','), )), ws(opt(')')), ), )) .parse_next(i)?; match args { Some((args, Some(_))) => Ok(args), Some((_, None)) => Err(winnow::error::ErrMode::Cut(ErrorContext::new( "expected `)` to close macro argument list", *i, ))), None => Ok(None), } }; let start_s = *i; let mut start = ( opt(Whitespace::parse), ws(keyword("macro")), cut_node( Some("macro"), ( ws(identifier), parameters, opt(Whitespace::parse), |i: &mut _| s.tag_block_end(i), ), ), ); let (pws1, _, (name, params, nws1, _)) = start.parse_next(i)?; if is_rust_keyword(name) { return Err(winnow::error::ErrMode::Cut(ErrorContext::new( format!("'{name}' is not a valid name for a macro"), start_s, ))); } if let Some(ref params) = params { let mut names = HashSet::new(); let mut iter = params.iter(); while let Some((arg_name, default_value)) = iter.next() { check_duplicated_name(&mut names, arg_name, start_s)?; if default_value.is_some() { for (new_arg_name, default_value) in iter.by_ref() { check_duplicated_name(&mut names, new_arg_name, start_s)?; if default_value.is_none() { return Err(winnow::error::ErrMode::Cut(ErrorContext::new( format!( "all arguments following `{arg_name}` should have a default \ value, `{new_arg_name}` doesn't have a default value" ), start_s, ))); } } } } } let mut end = cut_node( Some("macro"), ( |i: &mut _| Node::many(i, s), cut_node( Some("macro"), ( |i: &mut _| check_block_start(i, start_s, s, "macro", "endmacro"), opt(Whitespace::parse), end_node("macro", "endmacro"), cut_node( Some("macro"), preceded( opt(|i: &mut _| { let before = *i; let end_name = ws(identifier).parse_next(i)?; check_end_name(before, name, end_name, "macro") }), opt(Whitespace::parse), ), ), ), ), ), ); let (contents, (_, pws2, _, nws2)) = end.parse_next(i)?; Ok(WithSpan::new( Self { ws1: Ws(pws1, nws1), name, args: params.unwrap_or_default(), nodes: contents, ws2: Ws(pws2, nws2), }, start_s, )) } } #[derive(Debug, PartialEq)] pub struct FilterBlock<'a> { pub ws1: Ws, pub filters: Filter<'a>, pub nodes: Vec>, pub ws2: Ws, } impl<'a> FilterBlock<'a> { fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, WithSpan<'a, Self>> { let start_s = *i; let mut level_guard = s.level.guard(); let mut start = ( opt(Whitespace::parse), ws(keyword("filter")), cut_node( Some("filter"), ( ws(identifier), opt(|i: &mut _| crate::expr::call_generics(i, s.level)), opt(|i: &mut _| Expr::arguments(i, s.level, false)), repeat(0.., |i: &mut _| { #[allow(clippy::explicit_auto_deref)] // false positive level_guard.nest(*i)?; let start = *i; filter(i, s.level) .map(|(name, generics, params)| (name, generics, params, start)) }) .map(|v: Vec<_>| v), ws(empty), opt(Whitespace::parse), |i: &mut _| s.tag_block_end(i), ), ), ); let (pws1, _, (filter_name, generics, params, extra_filters, (), nws1, _)) = start.parse_next(i)?; let mut arguments = params.unwrap_or_default(); arguments.insert(0, WithSpan::new(Expr::FilterSource, start_s)); let mut filters = Filter { name: filter_name, arguments, generics: generics.unwrap_or_default(), }; for (filter_name, generics, args, span) in extra_filters { filters = Filter { name: filter_name, arguments: { let mut args = args.unwrap_or_default(); args.insert(0, WithSpan::new(Expr::Filter(filters), span)); args }, generics, }; } let mut end = cut_node( Some("filter"), ( |i: &mut _| Node::many(i, s), cut_node( Some("filter"), ( |i: &mut _| check_block_start(i, start_s, s, "filter", "endfilter"), opt(Whitespace::parse), end_node("filter", "endfilter"), opt(Whitespace::parse), ), ), ), ); let (nodes, (_, pws2, _, nws2)) = end.parse_next(i)?; Ok(WithSpan::new( Self { ws1: Ws(pws1, nws1), filters, nodes, ws2: Ws(pws2, nws2), }, start_s, )) } } #[derive(Debug, PartialEq)] pub struct Import<'a> { pub ws: Ws, pub path: &'a str, pub scope: &'a str, } impl<'a> Import<'a> { fn parse(i: &mut &'a str) -> ParseResult<'a, WithSpan<'a, Self>> { let start = *i; let mut p = ( opt(Whitespace::parse), ws(keyword("import")), cut_node( Some("import"), ( ws(str_lit_without_prefix), ws(keyword("as")), cut_node(Some("import"), (ws(identifier), opt(Whitespace::parse))), ), ), ); let (pws, _, (path, _, (scope, nws))) = p.parse_next(i)?; Ok(WithSpan::new( Self { ws: Ws(pws, nws), path, scope, }, start, )) } } #[derive(Debug, PartialEq)] pub struct Call<'a> { pub ws: Ws, pub scope: Option<&'a str>, pub name: &'a str, pub args: Vec>>, } impl<'a> Call<'a> { fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, WithSpan<'a, Self>> { let start = *i; let mut p = ( opt(Whitespace::parse), ws(keyword("call")), cut_node( Some("call"), ( opt((ws(identifier), ws("::"))), ws(identifier), opt(ws(|nested: &mut _| Expr::arguments(nested, s.level, true))), opt(Whitespace::parse), ), ), ); let (pws, _, (scope, name, args, nws)) = p.parse_next(i)?; let scope = scope.map(|(scope, _)| scope); let args = args.unwrap_or_default(); Ok(WithSpan::new( Self { ws: Ws(pws, nws), scope, name, args, }, start, )) } } #[derive(Debug, PartialEq)] pub struct Match<'a> { pub ws1: Ws, pub expr: WithSpan<'a, Expr<'a>>, pub arms: Vec>>, pub ws2: Ws, } impl<'a> Match<'a> { fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, WithSpan<'a, Self>> { let start = *i; let mut p = ( opt(Whitespace::parse), ws(keyword("match")), cut_node( Some("match"), ( ws(|i: &mut _| Expr::parse(i, s.level, false)), opt(Whitespace::parse), |i: &mut _| s.tag_block_end(i), cut_node( Some("match"), ( ws(repeat(0.., ws(|i: &mut _| Comment::parse(i, s)))).map(|()| ()), repeat(0.., |i: &mut _| When::when(i, s)).map(|v: Vec<_>| v), cut_node( Some("match"), ( opt(|i: &mut _| When::r#else(i, s)), cut_node( Some("match"), ( ws(|i: &mut _| { check_block_start(i, start, s, "match", "endmatch") }), opt(Whitespace::parse), end_node("match", "endmatch"), opt(Whitespace::parse), ), ), ), ), ), ), ), ), ); let (pws1, _, (expr, nws1, _, (_, mut arms, (else_arm, (_, pws2, _, nws2))))) = p.parse_next(i)?; if let Some(arm) = else_arm { arms.push(arm); } if arms.is_empty() { return Err(winnow::error::ErrMode::Cut(ErrorContext::new( "`match` nodes must contain at least one `when` node and/or an `else` case", start, ))); } Ok(WithSpan::new( Self { ws1: Ws(pws1, nws1), expr, arms, ws2: Ws(pws2, nws2), }, start, )) } } #[derive(Debug, PartialEq)] pub struct BlockDef<'a> { pub ws1: Ws, pub name: &'a str, pub nodes: Vec>, pub ws2: Ws, } impl<'a> BlockDef<'a> { fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, WithSpan<'a, Self>> { let start_s = *i; let mut start = ( opt(Whitespace::parse), ws(keyword("block")), cut_node( Some("block"), (ws(identifier), opt(Whitespace::parse), |i: &mut _| { s.tag_block_end(i) }), ), ); let (pws1, _, (name, nws1, _)) = start.parse_next(i)?; let mut end = cut_node( Some("block"), ( |i: &mut _| Node::many(i, s), cut_node( Some("block"), ( |i: &mut _| check_block_start(i, start_s, s, "block", "endblock"), opt(Whitespace::parse), end_node("block", "endblock"), cut_node( Some("block"), ( opt(|i: &mut _| { let before = *i; let end_name = ws(identifier).parse_next(i)?; check_end_name(before, name, end_name, "block") }), opt(Whitespace::parse), ), ), ), ), ), ); let (nodes, (_, pws2, _, (_, nws2))) = end.parse_next(i)?; Ok(WithSpan::new( BlockDef { ws1: Ws(pws1, nws1), name, nodes, ws2: Ws(pws2, nws2), }, start_s, )) } } fn check_end_name<'a>( before: &'a str, name: &'a str, end_name: &'a str, kind: &str, ) -> ParseResult<'a> { if name == end_name { return Ok(end_name); } Err(winnow::error::ErrMode::Cut(ErrorContext::new( match name.is_empty() && !end_name.is_empty() { true => format!("unexpected name `{end_name}` in `end{kind}` tag for unnamed `{kind}`"), false => format!("expected name `{name}` in `end{kind}` tag, found `{end_name}`"), }, before, ))) } #[derive(Debug, PartialEq)] pub struct Lit<'a> { pub lws: &'a str, pub val: &'a str, pub rws: &'a str, } impl<'a> Lit<'a> { fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, WithSpan<'a, Self>> { let start = *i; not(eof).parse_next(i)?; let candidate_finder = Splitter3::new( s.syntax.block_start, s.syntax.comment_start, s.syntax.expr_start, ); let p_start = alt(( literal(s.syntax.block_start), literal(s.syntax.comment_start), literal(s.syntax.expr_start), )); let content = opt(skip_till(candidate_finder, p_start).take()).parse_next(i)?; let content = match content { Some("") => { // {block,comment,expr}_start follows immediately. return fail.parse_next(i); } Some(content) => content, None => rest.parse_next(i)?, /* there is no {block,comment,expr}_start: take everything */ }; Ok(WithSpan::new(Self::split_ws_parts(content), start)) } pub(crate) fn split_ws_parts(s: &'a str) -> Self { let trimmed_start = s.trim_ascii_start(); let len_start = s.len() - trimmed_start.len(); let trimmed = trimmed_start.trim_ascii_end(); Self { lws: &s[..len_start], val: trimmed, rws: &trimmed_start[trimmed.len()..], } } } #[derive(Debug, PartialEq)] pub struct Raw<'a> { pub ws1: Ws, pub lit: Lit<'a>, pub ws2: Ws, } impl<'a> Raw<'a> { fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, WithSpan<'a, Self>> { let start = *i; let endraw = ( |i: &mut _| s.tag_block_start(i), opt(Whitespace::parse), ws(keyword("endraw")), // sic: ignore `{% end %}` in raw blocks opt(Whitespace::parse), peek(|i: &mut _| s.tag_block_end(i)), ); let mut p = ( opt(Whitespace::parse), ws(keyword("raw")), cut_node( Some("raw"), ( opt(Whitespace::parse), |i: &mut _| s.tag_block_end(i), skip_till(Splitter1::new(s.syntax.block_start), endraw).with_taken(), ), ), ); let (pws1, _, (nws1, _, ((new_i, (_, pws2, _, nws2, _)), contents))) = p.parse_next(i)?; *i = new_i; let lit = Lit::split_ws_parts(contents); let ws1 = Ws(pws1, nws1); let ws2 = Ws(pws2, nws2); Ok(WithSpan::new(Self { ws1, lit, ws2 }, start)) } } #[derive(Debug, PartialEq)] pub struct Let<'a> { pub ws: Ws, pub var: Target<'a>, pub val: Option>>, } impl<'a> Let<'a> { fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, WithSpan<'a, Self>> { let start = *i; let mut p = ( opt(Whitespace::parse), ws(alt((keyword("let"), keyword("set")))), cut_node( Some("let"), ( ws(|i: &mut _| Target::parse(i, s)), opt(preceded( ws('='), ws(|i: &mut _| Expr::parse(i, s.level, false)), )), opt(Whitespace::parse), ), ), ); let (pws, _, (var, val, nws)) = p.parse_next(i)?; if val.is_none() { let kind = match &var { Target::Name(_) => None, Target::Tuple(..) => Some("a tuple"), Target::Array(..) => Some("an array"), Target::Struct(..) => Some("a struct"), Target::NumLit(..) | Target::StrLit(..) | Target::CharLit(..) | Target::BoolLit(..) => Some("a literal"), Target::Path(..) => Some("a path or enum variant"), Target::OrChain(..) | Target::Placeholder(..) | Target::Rest(..) => { Some("a pattern") } }; if let Some(kind) = kind { return Err(winnow::error::ErrMode::Cut(ErrorContext::new( format!( "when you forward-define a variable, you cannot use {kind} in place of \ a variable name" ), start, ))); } } Ok(WithSpan::new( Let { ws: Ws(pws, nws), var, val, }, start, )) } } #[derive(Debug, PartialEq)] pub struct If<'a> { pub ws: Ws, pub branches: Vec>>, } impl<'a> If<'a> { fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, WithSpan<'a, Self>> { let start = *i; let mut p = ( opt(Whitespace::parse), |i: &mut _| CondTest::parse(i, s), cut_node( Some("if"), ( opt(Whitespace::parse), |i: &mut _| s.tag_block_end(i), cut_node( Some("if"), ( |i: &mut _| Node::many(i, s), repeat(0.., |i: &mut _| Cond::parse(i, s)).map(|v: Vec<_>| v), cut_node( Some("if"), ( |i: &mut _| check_block_start(i, start, s, "if", "endif"), opt(Whitespace::parse), end_node("if", "endif"), opt(Whitespace::parse), ), ), ), ), ), ), ); let (pws1, cond, (nws1, _, (nodes, elifs, (_, pws2, _, nws2)))) = p.parse_next(i)?; let mut branches = vec![WithSpan::new( Cond { ws: Ws(pws1, nws1), cond: Some(cond), nodes, }, start, )]; branches.extend(elifs); Ok(WithSpan::new( Self { ws: Ws(pws2, nws2), branches, }, start, )) } } #[derive(Debug, PartialEq)] pub struct Include<'a> { pub ws: Ws, pub path: &'a str, } impl<'a> Include<'a> { fn parse(i: &mut &'a str) -> ParseResult<'a, WithSpan<'a, Self>> { let start = *i; let mut p = ( opt(Whitespace::parse), ws(keyword("include")), cut_node( Some("include"), (ws(str_lit_without_prefix), opt(Whitespace::parse)), ), ); let (pws, _, (path, nws)) = p.parse_next(i)?; Ok(WithSpan::new( Self { ws: Ws(pws, nws), path, }, start, )) } } #[derive(Debug, PartialEq)] pub struct Extends<'a> { pub path: &'a str, } impl<'a> Extends<'a> { fn parse(i: &mut &'a str) -> ParseResult<'a, WithSpan<'a, Self>> { let start = *i; preceded( (opt(Whitespace::parse), ws(keyword("extends"))), cut_node( Some("extends"), terminated(ws(str_lit_without_prefix), opt(Whitespace::parse)), ), ) .map(|path| WithSpan::new(Self { path }, start)) .parse_next(i) } } #[derive(Debug, PartialEq)] pub struct Comment<'a> { pub ws: Ws, pub content: &'a str, } impl<'a> Comment<'a> { fn parse(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, WithSpan<'a, Self>> { #[derive(Debug, Clone, Copy)] enum Tag { Open, Close, } fn tag<'a>(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a, Tag> { alt(( (|i: &mut _| s.tag_comment_start(i)).value(Tag::Open), (|i: &mut _| s.tag_comment_end(i)).value(Tag::Close), )) .parse_next(i) } fn content<'a>(i: &mut &'a str, s: &State<'_, '_>) -> ParseResult<'a> { let mut depth = 0usize; let start = *i; loop { let splitter = Splitter2::new(s.syntax.comment_start, s.syntax.comment_end); let tag = opt(skip_till(splitter, |i: &mut _| tag(i, s))).parse_next(i)?; let Some((inclusive, tag)) = tag else { return Err( ErrorContext::unclosed("comment", s.syntax.comment_end, start).cut(), ); }; match tag { Tag::Open => match depth.checked_add(1) { Some(new_depth) => depth = new_depth, None => { return Err(winnow::error::ErrMode::Cut(ErrorContext::new( "too deeply nested comments", start, ))); } }, Tag::Close => match depth.checked_sub(1) { Some(new_depth) => depth = new_depth, None => { let exclusive = *i; *i = inclusive; return Ok(&start[..start.len() - exclusive.len()]); } }, } *i = inclusive; } } let start = *i; let content = preceded( |i: &mut _| s.tag_comment_start(i), cut_node(Some("comment"), |i: &mut _| content(i, s)), ) .parse_next(i)?; let mut ws = Ws(None, None); if content.len() == 1 && matches!(content, "-" | "+" | "~") { return Err(winnow::error::ErrMode::Cut(ErrorContext::new( format!( "ambiguous whitespace stripping\n\ use `{}{content} {content}{}` to apply the same whitespace stripping on both \ sides", s.syntax.comment_start, s.syntax.comment_end, ), start, ))); } else if content.len() >= 2 { ws.0 = Whitespace::parse_char(content.chars().next().unwrap_or_default()); ws.1 = Whitespace::parse_char(content.chars().next_back().unwrap_or_default()); } Ok(WithSpan::new(Self { ws, content }, start)) } } /// First field is "minus/plus sign was used on the left part of the item". /// /// Second field is "minus/plus sign was used on the right part of the item". #[derive(Clone, Copy, Debug, PartialEq)] 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| { let start = i.checkpoint(); let actual = ws(identifier).parse_next(i)?; if actual == expected { Ok(actual) } else if actual.starts_with("end") { i.reset(&start); Err(winnow::error::ErrMode::Cut(ErrorContext::new( format!("expected `{expected}` to terminate `{node}` node, found `{actual}`"), *i, ))) } else { i.reset(&start); fail.parse_next(i) } } }