| use std::borrow::Cow; |
| use std::str; |
| |
| use nom::branch::alt; |
| use nom::bytes::complete::{tag, take_until}; |
| use nom::character::complete::char; |
| use nom::combinator::{ |
| complete, consumed, cut, eof, map, map_res, not, opt, peek, recognize, value, |
| }; |
| use nom::error::{Error, ErrorKind}; |
| use nom::error_position; |
| use nom::multi::{fold_many0, many0, many1, separated_list0, separated_list1}; |
| use nom::sequence::{delimited, pair, preceded, terminated, tuple}; |
| |
| use crate::{ErrorContext, ParseResult}; |
| |
| use super::{ |
| bool_lit, char_lit, identifier, is_ws, keyword, num_lit, path_or_identifier, skip_till, |
| str_lit, ws, Expr, PathOrIdentifier, State, |
| }; |
| |
| #[derive(Debug, PartialEq)] |
| pub enum Node<'a> { |
| Lit(Lit<'a>), |
| Comment(Comment<'a>), |
| Expr(Ws, Expr<'a>), |
| Call(Call<'a>), |
| Let(Let<'a>), |
| If(If<'a>), |
| Match(Match<'a>), |
| Loop(Box<Loop<'a>>), |
| Extends(Extends<'a>), |
| BlockDef(BlockDef<'a>), |
| Include(Include<'a>), |
| Import(Import<'a>), |
| Macro(Macro<'a>), |
| Raw(Raw<'a>), |
| Break(Ws), |
| Continue(Ws), |
| } |
| |
| impl<'a> Node<'a> { |
| pub(super) fn many(i: &'a str, s: &State<'_>) -> ParseResult<'a, Vec<Self>> { |
| complete(many0(alt(( |
| map(|i| Lit::parse(i, s), Self::Lit), |
| map(|i| Comment::parse(i, s), Self::Comment), |
| |i| Self::expr(i, s), |
| |i| Self::parse(i, s), |
| ))))(i) |
| } |
| |
| fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
| let mut p = delimited( |
| |i| s.tag_block_start(i), |
| alt(( |
| map(|i| Call::parse(i, s), Self::Call), |
| map(|i| Let::parse(i, s), Self::Let), |
| map(|i| If::parse(i, s), Self::If), |
| map(|i| Loop::parse(i, s), |l| Self::Loop(Box::new(l))), |
| map(|i| Match::parse(i, s), Self::Match), |
| map(Extends::parse, Self::Extends), |
| map(Include::parse, Self::Include), |
| map(Import::parse, Self::Import), |
| map(|i| BlockDef::parse(i, s), Self::BlockDef), |
| map(|i| Macro::parse(i, s), Self::Macro), |
| map(|i| Raw::parse(i, s), Self::Raw), |
| |i| Self::r#break(i, s), |
| |i| Self::r#continue(i, s), |
| )), |
| cut(|i| s.tag_block_end(i)), |
| ); |
| |
| s.nest(i)?; |
| let result = p(i); |
| s.leave(); |
| |
| result |
| } |
| |
| fn r#break(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
| let mut p = tuple(( |
| opt(Whitespace::parse), |
| ws(keyword("break")), |
| opt(Whitespace::parse), |
| )); |
| let (j, (pws, _, nws)) = p(i)?; |
| if !s.is_in_loop() { |
| return Err(nom::Err::Failure(error_position!(i, ErrorKind::Tag))); |
| } |
| Ok((j, Self::Break(Ws(pws, nws)))) |
| } |
| |
| fn r#continue(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
| let mut p = tuple(( |
| opt(Whitespace::parse), |
| ws(keyword("continue")), |
| opt(Whitespace::parse), |
| )); |
| let (j, (pws, _, nws)) = p(i)?; |
| if !s.is_in_loop() { |
| return Err(nom::Err::Failure(error_position!(i, ErrorKind::Tag))); |
| } |
| Ok((j, Self::Continue(Ws(pws, nws)))) |
| } |
| |
| fn expr(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
| let mut p = tuple(( |
| |i| s.tag_expr_start(i), |
| cut(tuple(( |
| opt(Whitespace::parse), |
| ws(|i| Expr::parse(i, s.level.get())), |
| opt(Whitespace::parse), |
| |i| s.tag_expr_end(i), |
| ))), |
| )); |
| let (i, (_, (pws, expr, nws, _))) = p(i)?; |
| Ok((i, Self::Expr(Ws(pws, nws), expr))) |
| } |
| } |
| |
| #[derive(Clone, Debug, PartialEq)] |
| pub enum Target<'a> { |
| Name(&'a str), |
| Tuple(Vec<&'a str>, Vec<Target<'a>>), |
| Struct(Vec<&'a str>, Vec<(&'a str, Target<'a>)>), |
| NumLit(&'a str), |
| StrLit(&'a str), |
| CharLit(&'a str), |
| BoolLit(&'a str), |
| Path(Vec<&'a str>), |
| OrChain(Vec<Target<'a>>), |
| } |
| |
| impl<'a> Target<'a> { |
| /// Parses multiple targets with `or` separating them |
| pub(super) fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
| map( |
| separated_list1(ws(tag("or")), |i| { |
| s.nest(i)?; |
| let ret = Self::parse_one(i, s)?; |
| s.leave(); |
| Ok(ret) |
| }), |
| |mut opts| match opts.len() { |
| 1 => opts.pop().unwrap(), |
| _ => Self::OrChain(opts), |
| }, |
| )(i) |
| } |
| |
| /// Parses a single target without an `or`, unless it is wrapped in parentheses. |
| fn parse_one(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
| let mut opt_opening_paren = map(opt(ws(char('('))), |o| o.is_some()); |
| let mut opt_closing_paren = map(opt(ws(char(')'))), |o| o.is_some()); |
| let mut opt_opening_brace = map(opt(ws(char('{'))), |o| o.is_some()); |
| |
| let (i, lit) = opt(Self::lit)(i)?; |
| if let Some(lit) = lit { |
| return Ok((i, lit)); |
| } |
| |
| // match tuples and unused parentheses |
| let (i, target_is_tuple) = opt_opening_paren(i)?; |
| if target_is_tuple { |
| let (i, is_empty_tuple) = opt_closing_paren(i)?; |
| if is_empty_tuple { |
| return Ok((i, Self::Tuple(Vec::new(), Vec::new()))); |
| } |
| |
| let (i, first_target) = Self::parse(i, s)?; |
| let (i, is_unused_paren) = opt_closing_paren(i)?; |
| if is_unused_paren { |
| return Ok((i, first_target)); |
| } |
| |
| let mut targets = vec![first_target]; |
| let (i, _) = cut(tuple(( |
| fold_many0( |
| preceded(ws(char(',')), |i| Self::parse(i, s)), |
| || (), |
| |_, target| { |
| targets.push(target); |
| }, |
| ), |
| opt(ws(char(','))), |
| ws(cut(char(')'))), |
| )))(i)?; |
| return Ok((i, Self::Tuple(Vec::new(), targets))); |
| } |
| |
| let path = |i| { |
| map_res(path_or_identifier, |v| match v { |
| PathOrIdentifier::Path(v) => Ok(v), |
| PathOrIdentifier::Identifier(v) => Err(v), |
| })(i) |
| }; |
| |
| // match structs |
| let (i, path) = opt(path)(i)?; |
| if let Some(path) = path { |
| let i_before_matching_with = i; |
| let (i, _) = opt(ws(keyword("with")))(i)?; |
| |
| let (i, is_unnamed_struct) = opt_opening_paren(i)?; |
| if is_unnamed_struct { |
| let (i, targets) = alt(( |
| map(char(')'), |_| Vec::new()), |
| terminated( |
| cut(separated_list1(ws(char(',')), |i| Self::parse(i, s))), |
| pair(opt(ws(char(','))), ws(cut(char(')')))), |
| ), |
| ))(i)?; |
| return Ok((i, Self::Tuple(path, targets))); |
| } |
| |
| let (i, is_named_struct) = opt_opening_brace(i)?; |
| if is_named_struct { |
| let (i, targets) = alt(( |
| map(char('}'), |_| Vec::new()), |
| terminated( |
| cut(separated_list1(ws(char(',')), |i| Self::named(i, s))), |
| pair(opt(ws(char(','))), ws(cut(char('}')))), |
| ), |
| ))(i)?; |
| return Ok((i, Self::Struct(path, targets))); |
| } |
| |
| return Ok((i_before_matching_with, Self::Path(path))); |
| } |
| |
| // neither literal nor struct nor path |
| let (new_i, name) = identifier(i)?; |
| Ok((new_i, Self::verify_name(i, name)?)) |
| } |
| |
| fn lit(i: &'a str) -> ParseResult<'a, Self> { |
| alt(( |
| map(str_lit, Self::StrLit), |
| map(char_lit, Self::CharLit), |
| map(num_lit, Self::NumLit), |
| map(bool_lit, Self::BoolLit), |
| ))(i) |
| } |
| |
| fn named(init_i: &'a str, s: &State<'_>) -> ParseResult<'a, (&'a str, Self)> { |
| let (i, (src, target)) = pair( |
| identifier, |
| opt(preceded(ws(char(':')), |i| Self::parse(i, s))), |
| )(init_i)?; |
| |
| let target = match target { |
| Some(target) => target, |
| None => Self::verify_name(init_i, src)?, |
| }; |
| |
| Ok((i, (src, target))) |
| } |
| |
| fn verify_name(input: &'a str, name: &'a str) -> Result<Self, nom::Err<ErrorContext<'a>>> { |
| match name { |
| "self" | "writer" => Err(nom::Err::Failure(ErrorContext { |
| input, |
| message: Some(Cow::Owned(format!("Cannot use `{name}` as a name"))), |
| })), |
| _ => Ok(Self::Name(name)), |
| } |
| } |
| } |
| |
| #[derive(Debug, PartialEq)] |
| pub struct When<'a> { |
| pub ws: Ws, |
| pub target: Target<'a>, |
| pub nodes: Vec<Node<'a>>, |
| } |
| |
| impl<'a> When<'a> { |
| fn r#match(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
| let mut p = tuple(( |
| |i| s.tag_block_start(i), |
| opt(Whitespace::parse), |
| ws(keyword("else")), |
| cut(tuple(( |
| opt(Whitespace::parse), |
| |i| s.tag_block_end(i), |
| cut(|i| Node::many(i, s)), |
| ))), |
| )); |
| let (i, (_, pws, _, (nws, _, nodes))) = p(i)?; |
| Ok(( |
| i, |
| Self { |
| ws: Ws(pws, nws), |
| target: Target::Name("_"), |
| nodes, |
| }, |
| )) |
| } |
| |
| #[allow(clippy::self_named_constructors)] |
| fn when(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
| let mut p = tuple(( |
| |i| s.tag_block_start(i), |
| opt(Whitespace::parse), |
| ws(keyword("when")), |
| cut(tuple(( |
| ws(|i| Target::parse(i, s)), |
| opt(Whitespace::parse), |
| |i| s.tag_block_end(i), |
| cut(|i| Node::many(i, s)), |
| ))), |
| )); |
| let (i, (_, pws, _, (target, nws, _, nodes))) = p(i)?; |
| Ok(( |
| i, |
| Self { |
| ws: Ws(pws, nws), |
| target, |
| nodes, |
| }, |
| )) |
| } |
| } |
| |
| #[derive(Debug, PartialEq)] |
| pub struct Cond<'a> { |
| pub ws: Ws, |
| pub cond: Option<CondTest<'a>>, |
| pub nodes: Vec<Node<'a>>, |
| } |
| |
| impl<'a> Cond<'a> { |
| fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
| let mut p = tuple(( |
| |i| s.tag_block_start(i), |
| opt(Whitespace::parse), |
| ws(alt((keyword("else"), |i| { |
| let _ = keyword("elif")(i)?; |
| Err(nom::Err::Failure(ErrorContext { |
| input: i, |
| message: Some(Cow::Borrowed( |
| "unknown `elif` keyword; did you mean `else if`?", |
| )), |
| })) |
| }))), |
| cut(tuple(( |
| opt(|i| CondTest::parse(i, s)), |
| opt(Whitespace::parse), |
| |i| s.tag_block_end(i), |
| cut(|i| Node::many(i, s)), |
| ))), |
| )); |
| let (i, (_, pws, _, (cond, nws, _, nodes))) = p(i)?; |
| Ok(( |
| i, |
| Self { |
| ws: Ws(pws, nws), |
| cond, |
| nodes, |
| }, |
| )) |
| } |
| } |
| |
| #[derive(Debug, PartialEq)] |
| pub struct CondTest<'a> { |
| pub target: Option<Target<'a>>, |
| pub expr: Expr<'a>, |
| } |
| |
| impl<'a> CondTest<'a> { |
| fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
| let mut p = preceded( |
| ws(keyword("if")), |
| cut(tuple(( |
| opt(delimited( |
| ws(alt((keyword("let"), keyword("set")))), |
| ws(|i| Target::parse(i, s)), |
| ws(char('=')), |
| )), |
| ws(|i| Expr::parse(i, s.level.get())), |
| ))), |
| ); |
| let (i, (target, expr)) = p(i)?; |
| Ok((i, Self { target, expr })) |
| } |
| } |
| |
| #[derive(Clone, Copy, Debug, PartialEq)] |
| pub enum Whitespace { |
| Preserve, |
| Suppress, |
| Minimize, |
| } |
| |
| impl Whitespace { |
| fn parse(i: &str) -> ParseResult<'_, Self> { |
| alt(( |
| value(Self::Preserve, char('+')), |
| value(Self::Suppress, char('-')), |
| value(Self::Minimize, char('~')), |
| ))(i) |
| } |
| } |
| |
| #[derive(Debug, PartialEq)] |
| pub struct Loop<'a> { |
| pub ws1: Ws, |
| pub var: Target<'a>, |
| pub iter: Expr<'a>, |
| pub cond: Option<Expr<'a>>, |
| pub body: Vec<Node<'a>>, |
| pub ws2: Ws, |
| pub else_nodes: Vec<Node<'a>>, |
| pub ws3: Ws, |
| } |
| |
| impl<'a> Loop<'a> { |
| fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
| fn content<'a>(i: &'a str, s: &State<'_>) -> ParseResult<'a, Vec<Node<'a>>> { |
| s.enter_loop(); |
| let result = Node::many(i, s); |
| s.leave_loop(); |
| result |
| } |
| |
| let if_cond = preceded( |
| ws(keyword("if")), |
| cut(ws(|i| Expr::parse(i, s.level.get()))), |
| ); |
| |
| let else_block = |i| { |
| let mut p = preceded( |
| ws(keyword("else")), |
| cut(tuple(( |
| opt(Whitespace::parse), |
| delimited( |
| |i| s.tag_block_end(i), |
| |i| Node::many(i, s), |
| |i| s.tag_block_start(i), |
| ), |
| opt(Whitespace::parse), |
| ))), |
| ); |
| let (i, (pws, nodes, nws)) = p(i)?; |
| Ok((i, (pws, nodes, nws))) |
| }; |
| |
| let mut p = tuple(( |
| opt(Whitespace::parse), |
| ws(keyword("for")), |
| cut(tuple(( |
| ws(|i| Target::parse(i, s)), |
| ws(keyword("in")), |
| cut(tuple(( |
| ws(|i| Expr::parse(i, s.level.get())), |
| opt(if_cond), |
| opt(Whitespace::parse), |
| |i| s.tag_block_end(i), |
| cut(tuple(( |
| |i| content(i, s), |
| cut(tuple(( |
| |i| s.tag_block_start(i), |
| opt(Whitespace::parse), |
| opt(else_block), |
| ws(keyword("endfor")), |
| opt(Whitespace::parse), |
| ))), |
| ))), |
| ))), |
| ))), |
| )); |
| let (i, (pws1, _, (var, _, (iter, cond, nws1, _, (body, (_, pws2, else_block, _, nws2)))))) = |
| p(i)?; |
| let (nws3, else_block, pws3) = else_block.unwrap_or_default(); |
| Ok(( |
| i, |
| Self { |
| ws1: Ws(pws1, nws1), |
| var, |
| iter, |
| cond, |
| body, |
| ws2: Ws(pws2, nws3), |
| else_nodes: else_block, |
| ws3: Ws(pws3, nws2), |
| }, |
| )) |
| } |
| } |
| |
| #[derive(Debug, PartialEq)] |
| pub struct Macro<'a> { |
| pub ws1: Ws, |
| pub name: &'a str, |
| pub args: Vec<&'a str>, |
| pub nodes: Vec<Node<'a>>, |
| pub ws2: Ws, |
| } |
| |
| impl<'a> Macro<'a> { |
| fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
| fn parameters(i: &str) -> ParseResult<'_, Vec<&str>> { |
| delimited( |
| ws(char('(')), |
| separated_list0(char(','), ws(identifier)), |
| tuple((opt(ws(char(','))), char(')'))), |
| )(i) |
| } |
| |
| let mut start = tuple(( |
| opt(Whitespace::parse), |
| ws(keyword("macro")), |
| cut(tuple(( |
| ws(identifier), |
| opt(ws(parameters)), |
| opt(Whitespace::parse), |
| |i| s.tag_block_end(i), |
| ))), |
| )); |
| let (i, (pws1, _, (name, params, nws1, _))) = start(i)?; |
| |
| let mut end = cut(tuple(( |
| |i| Node::many(i, s), |
| cut(tuple(( |
| |i| s.tag_block_start(i), |
| opt(Whitespace::parse), |
| ws(keyword("endmacro")), |
| cut(preceded( |
| opt(|before| { |
| let (after, end_name) = ws(identifier)(before)?; |
| check_end_name(before, after, name, end_name, "macro") |
| }), |
| opt(Whitespace::parse), |
| )), |
| ))), |
| ))); |
| let (i, (contents, (_, pws2, _, nws2))) = end(i)?; |
| |
| if name == "super" { |
| // TODO: yield a a better error message here |
| return Err(ErrorContext::from_err(nom::Err::Failure(Error::new( |
| i, |
| ErrorKind::Fail, |
| )))); |
| } |
| |
| Ok(( |
| i, |
| Self { |
| ws1: Ws(pws1, nws1), |
| name, |
| args: params.unwrap_or_default(), |
| nodes: contents, |
| ws2: Ws(pws2, nws2), |
| }, |
| )) |
| } |
| } |
| |
| #[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: &'a str) -> ParseResult<'a, Self> { |
| let mut p = tuple(( |
| opt(Whitespace::parse), |
| ws(keyword("import")), |
| cut(tuple(( |
| ws(str_lit), |
| ws(keyword("as")), |
| cut(pair(ws(identifier), opt(Whitespace::parse))), |
| ))), |
| )); |
| let (i, (pws, _, (path, _, (scope, nws)))) = p(i)?; |
| Ok(( |
| i, |
| Self { |
| ws: Ws(pws, nws), |
| path, |
| scope, |
| }, |
| )) |
| } |
| } |
| |
| #[derive(Debug, PartialEq)] |
| pub struct Call<'a> { |
| pub ws: Ws, |
| pub scope: Option<&'a str>, |
| pub name: &'a str, |
| pub args: Vec<Expr<'a>>, |
| } |
| |
| impl<'a> Call<'a> { |
| fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
| let mut p = tuple(( |
| opt(Whitespace::parse), |
| ws(keyword("call")), |
| cut(tuple(( |
| opt(tuple((ws(identifier), ws(tag("::"))))), |
| ws(identifier), |
| opt(ws(|nested| Expr::arguments(nested, s.level.get(), true))), |
| opt(Whitespace::parse), |
| ))), |
| )); |
| let (i, (pws, _, (scope, name, args, nws))) = p(i)?; |
| let scope = scope.map(|(scope, _)| scope); |
| let args = args.unwrap_or_default(); |
| Ok(( |
| i, |
| Self { |
| ws: Ws(pws, nws), |
| scope, |
| name, |
| args, |
| }, |
| )) |
| } |
| } |
| |
| #[derive(Debug, PartialEq)] |
| pub struct Match<'a> { |
| pub ws1: Ws, |
| pub expr: Expr<'a>, |
| pub arms: Vec<When<'a>>, |
| pub ws2: Ws, |
| } |
| |
| impl<'a> Match<'a> { |
| fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
| let mut p = tuple(( |
| opt(Whitespace::parse), |
| ws(keyword("match")), |
| cut(tuple(( |
| ws(|i| Expr::parse(i, s.level.get())), |
| opt(Whitespace::parse), |
| |i| s.tag_block_end(i), |
| cut(tuple(( |
| ws(many0(ws(value((), |i| Comment::parse(i, s))))), |
| many1(|i| When::when(i, s)), |
| cut(tuple(( |
| opt(|i| When::r#match(i, s)), |
| cut(tuple(( |
| ws(|i| s.tag_block_start(i)), |
| opt(Whitespace::parse), |
| ws(keyword("endmatch")), |
| opt(Whitespace::parse), |
| ))), |
| ))), |
| ))), |
| ))), |
| )); |
| let (i, (pws1, _, (expr, nws1, _, (_, arms, (else_arm, (_, pws2, _, nws2)))))) = p(i)?; |
| |
| let mut arms = arms; |
| if let Some(arm) = else_arm { |
| arms.push(arm); |
| } |
| |
| Ok(( |
| i, |
| Self { |
| ws1: Ws(pws1, nws1), |
| expr, |
| arms, |
| ws2: Ws(pws2, nws2), |
| }, |
| )) |
| } |
| } |
| |
| #[derive(Debug, PartialEq)] |
| pub struct BlockDef<'a> { |
| pub ws1: Ws, |
| pub name: &'a str, |
| pub nodes: Vec<Node<'a>>, |
| pub ws2: Ws, |
| } |
| |
| impl<'a> BlockDef<'a> { |
| fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
| let mut start = tuple(( |
| opt(Whitespace::parse), |
| ws(keyword("block")), |
| cut(tuple((ws(identifier), opt(Whitespace::parse), |i| { |
| s.tag_block_end(i) |
| }))), |
| )); |
| let (i, (pws1, _, (name, nws1, _))) = start(i)?; |
| |
| let mut end = cut(tuple(( |
| |i| Node::many(i, s), |
| cut(tuple(( |
| |i| s.tag_block_start(i), |
| opt(Whitespace::parse), |
| ws(keyword("endblock")), |
| cut(tuple(( |
| opt(|before| { |
| let (after, end_name) = ws(identifier)(before)?; |
| check_end_name(before, after, name, end_name, "block") |
| }), |
| opt(Whitespace::parse), |
| ))), |
| ))), |
| ))); |
| let (i, (nodes, (_, pws2, _, (_, nws2)))) = end(i)?; |
| |
| Ok(( |
| i, |
| BlockDef { |
| ws1: Ws(pws1, nws1), |
| name, |
| nodes, |
| ws2: Ws(pws2, nws2), |
| }, |
| )) |
| } |
| } |
| |
| fn check_end_name<'a>( |
| before: &'a str, |
| after: &'a str, |
| name: &'a str, |
| end_name: &'a str, |
| kind: &str, |
| ) -> ParseResult<'a> { |
| if name == end_name { |
| return Ok((after, end_name)); |
| } |
| let message = if name.is_empty() && !end_name.is_empty() { |
| format!("unexpected name `{end_name}` in `end{kind}` tag for unnamed `{kind}`") |
| } else { |
| format!("expected name `{name}` in `end{kind}` tag, found `{end_name}`") |
| }; |
| Err(nom::Err::Failure(ErrorContext { |
| input: before, |
| message: Some(Cow::Owned(message)), |
| })) |
| } |
| |
| #[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: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
| let p_start = alt(( |
| tag(s.syntax.block_start), |
| tag(s.syntax.comment_start), |
| tag(s.syntax.expr_start), |
| )); |
| |
| let (i, _) = not(eof)(i)?; |
| let (i, content) = opt(recognize(skip_till(p_start)))(i)?; |
| let (i, content) = match content { |
| Some("") => { |
| // {block,comment,expr}_start follows immediately. |
| return Err(nom::Err::Error(error_position!(i, ErrorKind::TakeUntil))); |
| } |
| Some(content) => (i, content), |
| None => ("", i), // there is no {block,comment,expr}_start: take everything |
| }; |
| Ok((i, Self::split_ws_parts(content))) |
| } |
| |
| pub(crate) fn split_ws_parts(s: &'a str) -> Self { |
| let trimmed_start = s.trim_start_matches(is_ws); |
| let len_start = s.len() - trimmed_start.len(); |
| let trimmed = trimmed_start.trim_end_matches(is_ws); |
| 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: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
| let endraw = tuple(( |
| |i| s.tag_block_start(i), |
| opt(Whitespace::parse), |
| ws(keyword("endraw")), |
| opt(Whitespace::parse), |
| peek(|i| s.tag_block_end(i)), |
| )); |
| |
| let mut p = tuple(( |
| opt(Whitespace::parse), |
| ws(keyword("raw")), |
| cut(tuple(( |
| opt(Whitespace::parse), |
| |i| s.tag_block_end(i), |
| consumed(skip_till(endraw)), |
| ))), |
| )); |
| |
| let (_, (pws1, _, (nws1, _, (contents, (i, (_, pws2, _, nws2, _)))))) = p(i)?; |
| let lit = Lit::split_ws_parts(contents); |
| let ws1 = Ws(pws1, nws1); |
| let ws2 = Ws(pws2, nws2); |
| Ok((i, Self { ws1, lit, ws2 })) |
| } |
| } |
| |
| #[derive(Debug, PartialEq)] |
| pub struct Let<'a> { |
| pub ws: Ws, |
| pub var: Target<'a>, |
| pub val: Option<Expr<'a>>, |
| } |
| |
| impl<'a> Let<'a> { |
| fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
| let mut p = tuple(( |
| opt(Whitespace::parse), |
| ws(alt((keyword("let"), keyword("set")))), |
| cut(tuple(( |
| ws(|i| Target::parse(i, s)), |
| opt(preceded( |
| ws(char('=')), |
| ws(|i| Expr::parse(i, s.level.get())), |
| )), |
| opt(Whitespace::parse), |
| ))), |
| )); |
| let (i, (pws, _, (var, val, nws))) = p(i)?; |
| |
| Ok(( |
| i, |
| Let { |
| ws: Ws(pws, nws), |
| var, |
| val, |
| }, |
| )) |
| } |
| } |
| |
| #[derive(Debug, PartialEq)] |
| pub struct If<'a> { |
| pub ws: Ws, |
| pub branches: Vec<Cond<'a>>, |
| } |
| |
| impl<'a> If<'a> { |
| fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
| let mut p = tuple(( |
| opt(Whitespace::parse), |
| |i| CondTest::parse(i, s), |
| cut(tuple(( |
| opt(Whitespace::parse), |
| |i| s.tag_block_end(i), |
| cut(tuple(( |
| |i| Node::many(i, s), |
| many0(|i| Cond::parse(i, s)), |
| cut(tuple(( |
| |i| s.tag_block_start(i), |
| opt(Whitespace::parse), |
| ws(keyword("endif")), |
| opt(Whitespace::parse), |
| ))), |
| ))), |
| ))), |
| )); |
| |
| let (i, (pws1, cond, (nws1, _, (nodes, elifs, (_, pws2, _, nws2))))) = p(i)?; |
| let mut branches = vec![Cond { |
| ws: Ws(pws1, nws1), |
| cond: Some(cond), |
| nodes, |
| }]; |
| branches.extend(elifs); |
| |
| Ok(( |
| i, |
| Self { |
| ws: Ws(pws2, nws2), |
| branches, |
| }, |
| )) |
| } |
| } |
| |
| #[derive(Debug, PartialEq)] |
| pub struct Include<'a> { |
| pub ws: Ws, |
| pub path: &'a str, |
| } |
| |
| impl<'a> Include<'a> { |
| fn parse(i: &'a str) -> ParseResult<'a, Self> { |
| let mut p = tuple(( |
| opt(Whitespace::parse), |
| ws(keyword("include")), |
| cut(pair(ws(str_lit), opt(Whitespace::parse))), |
| )); |
| let (i, (pws, _, (path, nws))) = p(i)?; |
| Ok(( |
| i, |
| Self { |
| ws: Ws(pws, nws), |
| path, |
| }, |
| )) |
| } |
| } |
| |
| #[derive(Debug, PartialEq)] |
| pub struct Extends<'a> { |
| pub path: &'a str, |
| } |
| |
| impl<'a> Extends<'a> { |
| fn parse(i: &'a str) -> ParseResult<'a, Self> { |
| let (i, path) = preceded(ws(keyword("extends")), cut(ws(str_lit)))(i)?; |
| Ok((i, Self { path })) |
| } |
| } |
| |
| #[derive(Debug, PartialEq)] |
| pub struct Comment<'a> { |
| pub ws: Ws, |
| pub content: &'a str, |
| } |
| |
| impl<'a> Comment<'a> { |
| fn parse(i: &'a str, s: &State<'_>) -> ParseResult<'a, Self> { |
| fn body<'a>(mut i: &'a str, s: &State<'_>) -> ParseResult<'a> { |
| let mut level = 0; |
| loop { |
| let (end, tail) = take_until(s.syntax.comment_end)(i)?; |
| match take_until::<_, _, ErrorContext<'_>>(s.syntax.comment_start)(i) { |
| Ok((start, _)) if start.as_ptr() < end.as_ptr() => { |
| level += 1; |
| i = &start[2..]; |
| } |
| _ if level > 0 => { |
| level -= 1; |
| i = &end[2..]; |
| } |
| _ => return Ok((end, tail)), |
| } |
| } |
| } |
| |
| let mut p = tuple(( |
| |i| s.tag_comment_start(i), |
| cut(tuple(( |
| opt(Whitespace::parse), |
| |i| body(i, s), |
| |i| s.tag_comment_end(i), |
| ))), |
| )); |
| let (i, (content, (pws, tail, _))) = p(i)?; |
| let nws = if tail.ends_with('-') { |
| Some(Whitespace::Suppress) |
| } else if tail.ends_with('+') { |
| Some(Whitespace::Preserve) |
| } else if tail.ends_with('~') { |
| Some(Whitespace::Minimize) |
| } else { |
| None |
| }; |
| Ok(( |
| i, |
| Self { |
| ws: Ws(pws, nws), |
| content, |
| }, |
| )) |
| } |
| } |
| |
| /// 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<Whitespace>, pub Option<Whitespace>); |