// 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