diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 822f825..63af6d9 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -13,6 +13,7 @@ use winnow::ascii::multispace1; use winnow::combinator::alt; use winnow::combinator::cut_err; use winnow::combinator::eof; +use winnow::combinator::not; use winnow::combinator::opt; use winnow::combinator::peek; use winnow::combinator::repeat_till; @@ -204,6 +205,14 @@ pub enum TokenKind { Ident, Whitespace, Invalid, + ConditionalIf, + End, + Literal(TokenLiteral), +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum TokenLiteral { + Bool(bool), } impl PartialEq for TemplateToken { @@ -242,52 +251,35 @@ pub struct TemplateToken { source: TempleInput, } +macro_rules! impl_token_kind_builders { + ($($name:ident => $kind:expr),+ $(,)?) => { + $( + fn $name(source: TempleInput) -> Self { + TemplateToken { + kind: $kind, + source, + } + } + )+ + }; +} + impl TemplateToken { - fn content(source: TempleInput) -> Self { - TemplateToken { - kind: TokenKind::Content, - source, - } + impl_token_kind_builders! { + content => TokenKind::Content, + left_delim => TokenKind::LeftDelim, + right_delim => TokenKind::RightDelim, + wants_output => TokenKind::WantsOutput, + ident => TokenKind::Ident, + whitespace => TokenKind::Whitespace, + invalid => TokenKind::Invalid, + conditional_if => TokenKind::ConditionalIf, + end => TokenKind::End, } - fn left_delim(source: TempleInput) -> Self { + pub fn literal(literal: TokenLiteral, source: TempleInput) -> Self { TemplateToken { - kind: TokenKind::LeftDelim, - source, - } - } - - fn right_delim(source: TempleInput) -> Self { - TemplateToken { - kind: TokenKind::RightDelim, - source, - } - } - - fn wants_output(source: TempleInput) -> Self { - TemplateToken { - kind: TokenKind::WantsOutput, - source, - } - } - - fn ident(source: TempleInput) -> Self { - TemplateToken { - kind: TokenKind::Ident, - source, - } - } - - fn whitespace(source: TempleInput) -> Self { - TemplateToken { - kind: TokenKind::Whitespace, - source, - } - } - - fn invalid(source: TempleInput) -> Self { - TemplateToken { - kind: TokenKind::Invalid, + kind: TokenKind::Literal(literal), source, } } @@ -364,11 +356,43 @@ fn parse_interpolate<'input>(input: &mut Input<'input>) -> PResult<'input, Vec(input: &mut Input<'input>) -> PResult<'input, TemplateToken> { trace( "parse_interpolate_token", - alt((parse_ident, parse_whitespace)), + alt(( + parse_ident, + parse_literal, + parse_condition, + parse_end, + parse_whitespace, + )), ) .parse_next(input) } +fn parse_literal<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> { + trace( + "parse_condition", + alt((parse_boolean,)) + .with_taken() + .map(|(lit, span)| TemplateToken::literal(lit, span)), + ) + .parse_next(input) +} + +fn parse_boolean<'input>(input: &mut Input<'input>) -> PResult<'input, TokenLiteral> { + alt(( + "true".value(TokenLiteral::Bool(true)), + "false".value(TokenLiteral::Bool(false)), + )) + .parse_next(input) +} + +fn parse_condition<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> { + trace("parse_condition", "if".map(TemplateToken::conditional_if)).parse_next(input) +} + +fn parse_end<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> { + trace("parse_condition", "end".map(TemplateToken::end)).parse_next(input) +} + fn parse_whitespace<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> { trace( "parse_whitespace", @@ -396,6 +420,10 @@ fn parse_ident<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateTok } fn ident<'input>(input: &mut Input<'input>) -> PResult<'input, TempleInput> { + peek(not(parse_literal)) + .context(ParseError::ctx().msg("Expected an ident, but found a literal instead")) + .parse_next(input)?; + take_while(1.., |c: char| c.is_alphanumeric() || "_".contains(c)).parse_next(input) } @@ -517,4 +545,83 @@ mod tests { insta::assert_snapshot!(error.to_report()); } + + #[test] + fn parse_simple_condition() { + let input = "{{ if true }} Hello! {{ end }}"; + let output = parse(input.into()); + + insta::assert_debug_snapshot!(output, @r#" + Ok( + ParsedTemplate { + tokens: [ + TemplateToken { + kind: LeftDelim, + source: "{{", + }, + TemplateToken { + kind: Whitespace, + source: " ", + }, + TemplateToken { + kind: Ident, + source: "if", + }, + TemplateToken { + kind: Whitespace, + source: " ", + }, + TemplateToken { + kind: Literal( + Bool( + true, + ), + ), + source: "true", + }, + TemplateToken { + kind: Whitespace, + source: " ", + }, + TemplateToken { + kind: RightDelim, + source: "}}", + }, + TemplateToken { + kind: Whitespace, + source: " ", + }, + TemplateToken { + kind: Content, + source: "Hello!", + }, + TemplateToken { + kind: Whitespace, + source: " ", + }, + TemplateToken { + kind: LeftDelim, + source: "{{", + }, + TemplateToken { + kind: Whitespace, + source: " ", + }, + TemplateToken { + kind: Ident, + source: "end", + }, + TemplateToken { + kind: Whitespace, + source: " ", + }, + TemplateToken { + kind: RightDelim, + source: "}}", + }, + ], + }, + ) + "#); + } }