rshtml_core 0.4.0

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

BOM                      = _{ "\u{FEFF}" }
WHITESPACE               = _{ " " | "\t" | NEWLINE }
COMMENT                  = _{ "@*" ~ (("*" ~ !("@")) | (!("*") ~ ANY))* ~ "*@" }
rust_identifier          = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }
component_tag_identifier = @{ ASCII_ALPHA_UPPER ~ ASCII_ALPHANUMERIC* }

// endregion

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

template         =  { SOI ~ BOM? ~ (&("@" ~ "(") ~ template_params | !( "@" ~ "(" )) ~ template_content ~ EOI }
template_content = ${ (COMMENT | block | text)* }
inner_template   = ${ (COMMENT | block | inner_text)* }
tag_template     = ${ (COMMENT | block | text)* }

// endregion

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

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

// endregion

// region @template_params

template_params = { "@" ~ params ~ ";"? }

// endregion

// region parameters

params            = _{ ("(" ~ (param ~ ("," ~ param)* ~ ","?)? ~ ")") }
param             =  { (param_name ~ (":" ~ param_type)?) }
param_name        = @{ rust_identifier }
param_type        = @{ (param_type_nested | !("(" | "[" | "{" | "<" | "," | ")") ~ ANY)+ }
param_type_nested = _{
    ("(" ~ (param_type_nested | (!")" ~ ANY))* ~ ")")
  | ("[" ~ (param_type_nested | (!"]" ~ ANY))* ~ "]")
  | ("{" ~ (param_type_nested | (!"}" ~ ANY))* ~ "}")
  | ("<" ~ (param_type_nested | "->" | "=>" | (!">" ~ ANY))* ~ ">")
}

// endregion

// region --- Block Types ---

block = !{
    component
  | ("@" ~ ((raw_block | child_content_directive | 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 ~ "{" ~ 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" | "props" | "fn" | "raw" | "use" | "child_content") ~ (WHITESPACE+ | !rust_identifier)) ~ "#"? ~ "&"* ~ 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 = {
    "{" ~ rust_block_content ~ "}"
}

rust_block_content = {
    (nested_block | rust_code)*
}

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

rust_code = @{
    (_line_comment | _block_comment | _string_literal | !("{" | "}" | "\"" | "r\"" | "r#\"") ~ 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)* ~ "*/" }

// endregion

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

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

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

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

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 --- raw block @raw { ... } ---

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

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

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

// endregion

/// region Code blocks and code transfers

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

// region @child_content directive @child_content

child_content_directive = @{
    &"child_content" ~ "child_content" ~ ("(" ~ ")" | &(!rust_identifier))
}

// endregion

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

component = {
    &("<" ~ component_tag_identifier) ~ "<" ~ component_tag_identifier ~ (attribute)* ~ ("/>" | (">" ~ tag_template ~ "</" ~ component_tag_identifier ~ ">"))
}

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

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

// endregion

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

use_directive = { &"use" ~ "use" ~ string_line ~ ("as" ~ component_tag_identifier)? ~ ";"? }

// endregion

/// endregion Code blocks and code transfers

// TODO 999: Maybe add @client {...} support for client side codes and templates