rshtml_core 0.1.0

RsHtml: A Template Engine for Seamless HTML and Rust Integration.
Documentation
// region --- Utility & Lexical Rules ---

WHITESPACE = _{ " " | "\t" | NEWLINE }
rust_identifier = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }

// endregion

// region --- Core Template Rules ---

BOM = _{ "\u{FEFF}" }
template = { SOI ~ BOM? ~ extends_directive? ~ WHITESPACE* ~ template_content ~ EOI }
template_content = ${ (comment_block | block | text)* }
inner_template = ${ (comment_block | block | inner_text)* }
tag_template = ${ (comment_block | block | text)* }

// endregion

// region --- Text and Comment Rules ---

text = @{ ("@@}" | "@@{" | "@@" |  (!("@" | ("<" ~ component_tag_identifier) | ("</" ~ component_tag_identifier)) ~ ANY))+ }
inner_text = @{ ("@@}" | "@@{" | "@@" | (!("@" | "}" | ("<" ~ component_tag_identifier) | ("</" ~ component_tag_identifier)) ~ ANY))+ }

comment_block = { "@*" ~ comment_content ~ "*@" }
comment_content = @{ (("*" ~ !("@")) | (!("*") ~ ANY))* }

// endregion

// region --- Block Types ---

block = !{
    component_tag
    | (
    "@" ~ (
        (raw_block | render_directive | include_directive | section_directive | section_block
        | render_body_directive | child_content_directive | component | use_directive)
        | (rust_block | rust_expr | rust_expr_paren | match_expr | continue_directive | break_directive |rust_expr_simple)
        )
    )
}

// endregion

// region --- Rust Expression Blocks (@if, @for, @while etc.) ---

rust_expr = {
    (rust_expr_head ~  WHITESPACE* ~ "{" ~ inner_template ~ "}")+
}

rust_expr_head = @{
    (("if" | ("else" ~ WHITESPACE+ ~ "if") | "for" | "while") ~ WHITESPACE+ ~ (!("{" | "@" | "}") ~ ANY)+)
  | ("else")
}

// endregion

// region --- Simple Rust Expressions (@identifier...) ---

rust_expr_simple = @{
    !(WHITESPACE*
    ~ ("{"|"if"|"for"|"while"|"else"|"match"|"include"|"extends"|"render"|"section"|"render_body"|"raw"|"use")
    ~ WHITESPACE+
    )
    ~ "#"? ~ "&"* ~ rust_identifier ~ chain_segment*
}

chain_segment = {
    "&" ~ rust_identifier
    | "." ~ rust_identifier
    | "::" ~ rust_identifier
    | ("(" ~ nested_content* ~ ")")
    | ("[" ~ nested_content* ~ "]")
}

nested_content = _{
    ("(" ~ nested_content* ~ ")")
    | ("[" ~ nested_content* ~ "]")
    | _normal_string | (!((")" | "]") | expression_boundary) ~ ANY)
}

expression_boundary = _{
    ("<" ~ ("/" | ASCII_ALPHA))
  | "@"
  | "{"
  | NEWLINE
}

//endregion

// region --- Parenthesized Rust Expressions (@(expressions)) ---

rust_expr_paren = @{
	"#"? ~ "(" ~ (nested_expression | (!(")") ~ ANY))* ~ ")"
}

nested_expression = _{
    ( "(" ~ (nested_expression | (!(")") ~ ANY))* ~ ")" )
  | ( "[" ~ (nested_expression | (!("]") ~ ANY))* ~ "]" )
  | ( "{" ~ (nested_expression | (!("}") ~ ANY))*   ~ "}" )
}

// endregion

// region --- Rust Code Blocks (@{ ... }) ---

rust_block = {
    WHITESPACE* ~ "{" ~ rust_block_content* ~ "}"
}

rust_block_content = _{
    text_line_directive |
    text_block_tag |
    nested_block  |
    rust_code
}

nested_block = {
    "{" ~ rust_block_content* ~ "}"
}


rust_code = @{
    (
        _line_comment |
        _block_comment |
        _string_literal |
        !( "@:" | "{" | "}" | "\"" | "r\"" | "r#\"" | text_block_tag ) ~ ANY
    )+
}

escaped_char = _{ "\\" ~ ANY }
_normal_string = _{
      ("\"" ~ (escaped_char | !("\"" | "\\") ~ ANY)* ~ "\"")
    | ("'" ~ (escaped_char | !("'" | "\\") ~ ANY) ~ "'")
 }

_raw_string = _{
    ("r\"" ~ ( !"\"" ~ ANY )* ~ "\"") |
    ("r#\"" ~ ( !"\"#" ~ ANY )* ~ "\"#")
}
_string_literal = _{ _normal_string | _raw_string }

_line_comment = _{ "//" ~ (!NEWLINE ~ ANY)* }
_block_comment = _{ "/*" ~ (_block_comment | !"*/" ~ ANY)* ~ "*/" }

text_line_directive = ${ ("@:" ~ (("@" ~ rust_expr_simple) | text_line)*) }
text_line = @{ ("@@" | !(NEWLINE | ("@" ~ rust_expr_simple)) ~ ANY)+ }


text_block_tag = ${ "<text>" ~ (("@" ~ (rust_expr_simple)) |  text_block)* ~ "</text>" }
text_block = @{ ("@@" | !("</text>" | ("@" ~ (rust_expr_simple))) ~ ANY)+ }

// endregion

// region --- Rust match expression @match ... { ... => ... } ---

match_expr = {
    match_expr_head ~ WHITESPACE*
    ~ "{" ~ WHITESPACE*
    ~ match_expr_arm*
    ~ WHITESPACE* ~ "}"
}

match_expr_head = @{
    "match" ~ WHITESPACE+ ~ (rust_expr_simple | rust_expr_paren | !("{" | "@" | "}") ~ ANY)+
}

match_expr_arm = {
    match_expr_arm_head ~ WHITESPACE*
    ~ "=>" ~ WHITESPACE*
    ~ (
    ("{" ~ inner_template ~ "}")
    | continue_directive
    | break_directive
    | rust_expr_paren
    | rust_expr_simple
    | match_inner_text
    )
    ~ WHITESPACE* ~ ","?
}

match_expr_arm_head = @{ (!("@" | "=>") ~ ANY)+ }

match_inner_text = @{ ( "@@}" | "@@{" | "@@" | (!(NEWLINE | "@" | ("," ~ (" " | "\t" )* ~ NEWLINE) | ("," ~ WHITESPACE* ~ "}") | "}") ~ ANY) )+ }

// endregion

// region --- Rust @continue and @break directives for the loops

continue_directive = @{
    "continue"
    ~ WHITESPACE*
}

break_directive = @{
    "break"
    ~ WHITESPACE*
}


// endregion

/// region Code blocks and code transfers

string_line = @{
    "\"" ~ ( escaped_char | !("\"" | "\\") ~ ANY )* ~ "\"" |
    "'" ~ ( escaped_char | !("'" | "\\") ~ ANY )* ~ "'"
}

// region @include directive @include('other_view.html')

include_directive = {
    &"include"
    ~ "include" ~ WHITESPACE* ~ "(" ~ WHITESPACE*
        ~ string_line
    ~ WHITESPACE* ~ ")"
}

// endregion

// region @extends directive @extends('layout.rs.html')

extends_directive = {
    "@" ~ "extends" ~ WHITESPACE* ~ ("(" ~ WHITESPACE*
        ~ string_line?
    ~ WHITESPACE* ~ ")")?
}

// endregion

// region @section directive and block @section('section_name'), @section content { ... }

section_block = { &(section_head ~ WHITESPACE* ~ "{") ~ section_head ~ WHITESPACE* ~ "{" ~ inner_template ~ "}" }
section_head = ${ "section" ~ WHITESPACE+ ~ rust_identifier }

// one line section
section_directive = {
        &("section" ~ WHITESPACE* ~ "(") ~
        "section" ~ WHITESPACE* ~ "(" ~ WHITESPACE*
        ~ string_line
        ~ ","
        ~ (string_line | (rust_expr_simple))
        ~ WHITESPACE* ~ ")"
}

// endregion

// region @render directive @render('section_name')

render_directive = {
    &"render"
    ~ "render" ~ WHITESPACE* ~ "(" ~ WHITESPACE*
    ~ string_line
    ~ WHITESPACE* ~ ")"
}

// endregion

// region @render_body directive @render_body

render_body_directive = @{
    &"render_body"
    ~ "render_body"
    ~ (&(WHITESPACE+ | EOI) | ("(" ~ ")"))
}

// endregion

// region @child_content directive @child_content

child_content_directive = @{
    &"child_content"
    ~ "child_content"
    ~ (&(WHITESPACE+ | EOI) | ("(" ~ ")"))
}

// endregion

// region Component block @component_name(param1:value1, param2:value2){inside}

    component = {
        &(rust_identifier ~ "(")
        ~ rust_identifier ~ WHITESPACE* ~ "(" ~ WHITESPACE*
        ~ (component_parameter ~(WHITESPACE* ~ "," ~ WHITESPACE* ~ component_parameter)*)*
        ~ WHITESPACE* ~ ")" ~ WHITESPACE*
        ~ "{" ~ inner_template ~ "}"
    }

    component_parameter = {
        rust_identifier ~ WHITESPACE*
        ~ ":" ~ WHITESPACE* ~ (
        bool
        | number
        | string
        | ("@" ~ rust_expr_paren)
        | ("@" ~ rust_expr_simple)
        | ("{" ~ inner_template ~ "}")
        )
    }

    bool = @{ "true" | "false" }
    number = @{ ("-")? ~ ASCII_DIGIT+ ~ ("." ~ ASCII_DIGIT+)? }
    string = @{ _normal_string }

// endregion

// region Component tag <ComponentName param1="value", param2=@value2> ... <ComponentName/>

    component_tag = ${
        &("<" ~ component_tag_identifier) ~
        "<" ~ component_tag_name ~ attributes ~ WHITESPACE*
        ~ ("/>" | (">" ~ tag_template ~ "</" ~ component_tag_identifier ~ ">"))
    }

    attributes = _{ (WHITESPACE+ ~ attribute)* }
    attribute = { attribute_name ~ (WHITESPACE* ~ "=" ~ WHITESPACE* ~ attribute_value)? }
    attribute_name = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_" | "-")* }
    attribute_value = _{
        bool
        | number
        | string
        | ("@" ~ rust_expr_paren)
        | ("@" ~ rust_expr_simple)
        | ("{" ~ inner_template ~ "}")
    }

    component_tag_identifier = _{ (ASCII_ALPHA_UPPER ~ ASCII_ALPHANUMERIC*) ~ ("." ~ (ASCII_ALPHA_UPPER ~ ASCII_ALPHANUMERIC*))* }
    component_tag_name = @{ component_tag_identifier }


// endregion

// region @raw block @raw { ... }

    raw_block = { "raw" ~ WHITESPACE* ~ "{" ~ raw_content ~ "}" }

    raw_content = @{ ("@{" | "@}"| raw_nested_block | !("{" | "}") ~ ANY)* }

    raw_nested_block = { "{" ~ raw_content ~ "}" }

// endregion

// region @use directive @use "components/Comp.rshtml" as Component

       use_directive = ${
         &"use"
         ~ "use" ~ WHITESPACE+
         ~ string_line ~ WHITESPACE+
         ~ ("as" ~ WHITESPACE+ ~ rust_identifier ~ (";" | &(WHITESPACE+ | EOI)))?
        }

// endregion

/// endregion Code blocks and code transfers

// TODO 1: can be use @use "Component" without rs.html postfix

// TODO 998: try to add source mapping or something for better error messages, sourcemap crate
// TODO 999: Maybe add @client {...} support for client side codes and templates