ludtwig-parser 0.10.0

Lossless parser for HTML / Twig templating syntax.
Documentation
mod expression;
pub(crate) mod literal;
mod shopware;
mod tags;

pub(crate) use tags::at_twig_termination_tag;

use crate::T;
use crate::grammar::twig::expression::{TWIG_EXPRESSION_RECOVERY_SET, parse_twig_expression};
use crate::grammar::{ParseFunction, parse_ludtwig_directive, parse_many};
use crate::parser::event::{CompletedMarker, Marker};
use crate::parser::{ParseErrorBuilder, Parser, TWIG_COMMENT_CLOSE_SET, TWIG_VAR_CLOSE_SET};
use crate::syntax::untyped::SyntaxKind;

pub use literal::TWIG_NAME_REGEX;

pub(super) fn parse_any_twig(
    parser: &mut Parser,
    child_parser: ParseFunction,
) -> Option<CompletedMarker> {
    if parser.at_twig_block_open() {
        tags::parse_twig_block_statement(parser, child_parser)
    } else if parser.at_twig_var_open() {
        Some(parse_twig_var_statement(parser))
    } else if parser.at_twig_comment_open() {
        Some(parse_twig_comment_statement(parser))
    } else {
        None
    }
}

fn parse_twig_comment_statement(parser: &mut Parser) -> CompletedMarker {
    debug_assert!(parser.at_twig_comment_open());
    let m = parser.start();
    parser.bump();

    if parser.at_set(&[T!["ludtwig-ignore-file"], T!["ludtwig-ignore"]]) {
        parse_ludtwig_directive(parser, m, TWIG_COMMENT_CLOSE_SET)
    } else {
        parse_twig_plain_comment(parser, m)
    }
}

fn parse_twig_plain_comment(parser: &mut Parser, outer: Marker) -> CompletedMarker {
    parse_many(
        parser,
        |p| p.at_set(TWIG_COMMENT_CLOSE_SET),
        |p| {
            p.bump();
        },
    );

    parser.expect_any(TWIG_COMMENT_CLOSE_SET, &[]);
    parser.complete(outer, SyntaxKind::TWIG_COMMENT)
}

pub(crate) fn parse_twig_var_statement(parser: &mut Parser) -> CompletedMarker {
    debug_assert!(parser.at_twig_var_open());
    let m = parser.start();
    parser.bump();

    if parse_twig_expression(parser).is_none() {
        parser.add_error(ParseErrorBuilder::new("twig expression"));
        parser.recover(TWIG_EXPRESSION_RECOVERY_SET);
    }

    parser.expect_any(TWIG_VAR_CLOSE_SET, &[]);
    parser.complete(m, SyntaxKind::TWIG_VAR)
}

#[cfg(test)]
mod tests {
    use expect_test::expect;

    use crate::parser::check_parse;

    #[test]
    fn parse_twig_var() {
        check_parse(
            "{{ something }} plain {{ else }}",
            expect![[r#"
                ROOT@0..32
                  TWIG_VAR@0..15
                    TK_OPEN_CURLY_CURLY@0..2 "{{"
                    TWIG_EXPRESSION@2..12
                      TWIG_LITERAL_NAME@2..12
                        TK_WHITESPACE@2..3 " "
                        TK_WORD@3..12 "something"
                    TK_WHITESPACE@12..13 " "
                    TK_CLOSE_CURLY_CURLY@13..15 "}}"
                  HTML_TEXT@15..21
                    TK_WHITESPACE@15..16 " "
                    TK_WORD@16..21 "plain"
                  TWIG_VAR@21..32
                    TK_WHITESPACE@21..22 " "
                    TK_OPEN_CURLY_CURLY@22..24 "{{"
                    TWIG_EXPRESSION@24..29
                      TWIG_LITERAL_NAME@24..29
                        TK_WHITESPACE@24..25 " "
                        TK_WORD@25..29 "else"
                    TK_WHITESPACE@29..30 " "
                    TK_CLOSE_CURLY_CURLY@30..32 "}}""#]],
        );
    }

    #[test]
    fn parse_twig_comment() {
        check_parse(
            "{# something #} plain {# {{ comment }} {% block asdf %} #}",
            expect![[r##"
                ROOT@0..58
                  TWIG_COMMENT@0..15
                    TK_OPEN_CURLY_HASHTAG@0..2 "{#"
                    TK_WHITESPACE@2..3 " "
                    TK_WORD@3..12 "something"
                    TK_WHITESPACE@12..13 " "
                    TK_HASHTAG_CLOSE_CURLY@13..15 "#}"
                  HTML_TEXT@15..21
                    TK_WHITESPACE@15..16 " "
                    TK_WORD@16..21 "plain"
                  TWIG_COMMENT@21..58
                    TK_WHITESPACE@21..22 " "
                    TK_OPEN_CURLY_HASHTAG@22..24 "{#"
                    TK_WHITESPACE@24..25 " "
                    TK_OPEN_CURLY_CURLY@25..27 "{{"
                    TK_WHITESPACE@27..28 " "
                    TK_WORD@28..35 "comment"
                    TK_WHITESPACE@35..36 " "
                    TK_CLOSE_CURLY_CURLY@36..38 "}}"
                    TK_WHITESPACE@38..39 " "
                    TK_CURLY_PERCENT@39..41 "{%"
                    TK_WHITESPACE@41..42 " "
                    TK_BLOCK@42..47 "block"
                    TK_WHITESPACE@47..48 " "
                    TK_WORD@48..52 "asdf"
                    TK_WHITESPACE@52..53 " "
                    TK_PERCENT_CURLY@53..55 "%}"
                    TK_WHITESPACE@55..56 " "
                    TK_HASHTAG_CLOSE_CURLY@56..58 "#}""##]],
        );
    }

    #[test]
    fn parse_twig_function_call() {
        check_parse(
            "{{ component('Alert', { message: 'Hello Twig Components!' }) }}",
            expect![[r#"
                ROOT@0..63
                  TWIG_VAR@0..63
                    TK_OPEN_CURLY_CURLY@0..2 "{{"
                    TWIG_EXPRESSION@2..60
                      TWIG_FUNCTION_CALL@2..60
                        TWIG_OPERAND@2..12
                          TWIG_LITERAL_NAME@2..12
                            TK_WHITESPACE@2..3 " "
                            TK_WORD@3..12 "component"
                        TWIG_ARGUMENTS@12..60
                          TK_OPEN_PARENTHESIS@12..13 "("
                          TWIG_EXPRESSION@13..20
                            TWIG_LITERAL_STRING@13..20
                              TK_SINGLE_QUOTES@13..14 "'"
                              TWIG_LITERAL_STRING_INNER@14..19
                                TK_WORD@14..19 "Alert"
                              TK_SINGLE_QUOTES@19..20 "'"
                          TK_COMMA@20..21 ","
                          TWIG_EXPRESSION@21..59
                            TWIG_LITERAL_HASH@21..59
                              TK_WHITESPACE@21..22 " "
                              TK_OPEN_CURLY@22..23 "{"
                              TWIG_LITERAL_HASH_ITEMS@23..57
                                TWIG_LITERAL_HASH_PAIR@23..57
                                  TWIG_LITERAL_HASH_KEY@23..31
                                    TK_WHITESPACE@23..24 " "
                                    TK_WORD@24..31 "message"
                                  TK_COLON@31..32 ":"
                                  TWIG_EXPRESSION@32..57
                                    TWIG_LITERAL_STRING@32..57
                                      TK_WHITESPACE@32..33 " "
                                      TK_SINGLE_QUOTES@33..34 "'"
                                      TWIG_LITERAL_STRING_INNER@34..56
                                        TK_WORD@34..39 "Hello"
                                        TK_WHITESPACE@39..40 " "
                                        TK_WORD@40..44 "Twig"
                                        TK_WHITESPACE@44..45 " "
                                        TK_WORD@45..55 "Components"
                                        TK_EXCLAMATION_MARK@55..56 "!"
                                      TK_SINGLE_QUOTES@56..57 "'"
                              TK_WHITESPACE@57..58 " "
                              TK_CLOSE_CURLY@58..59 "}"
                          TK_CLOSE_PARENTHESIS@59..60 ")"
                    TK_WHITESPACE@60..61 " "
                    TK_CLOSE_CURLY_CURLY@61..63 "}}""#]],
        );
    }

    #[test]
    fn parse_twig_var_whitespace_control_dash() {
        check_parse(
            "{{- something -}}",
            expect![[r#"
                ROOT@0..17
                  TWIG_VAR@0..17
                    TK_OPEN_CURLY_CURLY_MINUS@0..3 "{{-"
                    TWIG_EXPRESSION@3..13
                      TWIG_LITERAL_NAME@3..13
                        TK_WHITESPACE@3..4 " "
                        TK_WORD@4..13 "something"
                    TK_WHITESPACE@13..14 " "
                    TK_MINUS_CLOSE_CURLY_CURLY@14..17 "-}}""#]],
        );
    }

    #[test]
    fn parse_twig_var_whitespace_control_tilde() {
        check_parse(
            "{{~ something ~}}",
            expect![[r#"
                ROOT@0..17
                  TWIG_VAR@0..17
                    TK_OPEN_CURLY_CURLY_TILDE@0..3 "{{~"
                    TWIG_EXPRESSION@3..13
                      TWIG_LITERAL_NAME@3..13
                        TK_WHITESPACE@3..4 " "
                        TK_WORD@4..13 "something"
                    TK_WHITESPACE@13..14 " "
                    TK_TILDE_CLOSE_CURLY_CURLY@14..17 "~}}""#]],
        );
    }

    #[test]
    fn parse_twig_comment_whitespace_control() {
        check_parse(
            "{#- comment -#}",
            expect![[r#"
                ROOT@0..15
                  TWIG_COMMENT@0..15
                    TK_OPEN_CURLY_HASHTAG_MINUS@0..3 "{#-"
                    TK_WHITESPACE@3..4 " "
                    TK_WORD@4..11 "comment"
                    TK_WHITESPACE@11..12 " "
                    TK_MINUS_HASHTAG_CLOSE_CURLY@12..15 "-#}""#]],
        );
    }
}