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