full = _{ SOI ~ program ~ EOI }
program = { stmt* ~ expr }
stmt = { "let" ~ ident ~ "=" ~ expr ~ ";" }
expr = { prefix? ~ term ~ postfix* ~ (infix ~ prefix? ~ term ~ postfix*)* }
prefix = _{ negation_op | sign_op }
infix = _{ exponent_op | multiplication_op | addition_op | range_op | comparison_op | equal_op | and_op | or_op | not_in_op | string_op }
postfix = _{ ternary | range_start_op | range_end_op | method_call | opt_membership_op | opt_index_op | membership_op | index_op | default_op | pipe }
func = { ident ~ "(" ~ (expr ~ ("," ~ expr)*)? ~ (","? ~ predicate ~ ("," ~ expr)*)? ~ ")" }
pipe = { "|" ~ func }
method_call = { "." ~ ident ~ "(" ~ (expr ~ ("," ~ expr)*)? ~ ")" }
predicate = { "." ~ ident | "{" ~ program ~ "}" }
term = _{ conditional_if | func | value | ident | array | map | "(" ~ expr ~ ")" }
conditional_if = { if_keyword ~ expr ~ block ~ else_keyword ~ (conditional_if | block) }
if_keyword = _{ !if_identifier_prefix ~ "if" }
else_keyword = _{ !else_identifier_prefix ~ "else" }
if_identifier_prefix = @{ "if" ~ (ASCII_ALPHANUMERIC | "_") }
else_identifier_prefix = @{ "else" ~ (ASCII_ALPHANUMERIC | "_") }
block = { "{" ~ program ~ "}" }
ternary = { "?" ~ expr ~ ":" ~ expr }
membership_op = { "." ~ ident }
opt_membership_op = { "?." ~ ident }
opt_index_op = { "?." ~ "[" ~ expr ~ "]" }
default_op = { "??" ~ expr }
index_op = { "[" ~ expr ~ "]" }
range_start_op = { "[" ~ expr ~ ":" ~ expr? ~ "]" }
range_end_op = { "[" ~ ":" ~ expr? ~ "]" }
negation_op = { "!" | "not" }
sign_op = { "+" | "-" }
exponent_op = { "**" | "^" }
multiplication_op = { "*" | "/" | "%" }
addition_op = { "+" | "-" }
range_op = { ".." }
comparison_op = { "<=" | ">=" | "<" | ">" }
equal_op = { "==" | "!=" }
and_op = { "&&" | "and" }
or_op = { "||" | "or" }
string_op = { "in" | "contains" | "startsWith" | "endsWith" | "matches" }
value = { bytes | string | string_multiline | decimal | int | bool | nil }
array = { "[" ~ (expr ~ ("," ~ expr)* ~ ","?)? ~ "]" }
map = { "{" ~ (map_key ~ ":" ~ expr ~ ("," ~ map_key ~ ":" ~ expr)* ~ ","?)? ~ "}" }
map_key = _{ ident | string }
string = ${ ("\"" ~ inner_double ~ "\"" | "'" ~ inner_single ~ "'") }
bytes = ${ ("b" | "B") ~ ("\"" ~ inner_bytes_double ~ "\"" | "'" ~ inner_bytes_single ~ "'") }
string_multiline = ${ "`" ~ inner_multiline ~ "`" }
inner_single = @{ char_single* }
inner_double = @{ char_double* }
inner_bytes_single = @{ char_bytes_single* }
inner_bytes_double = @{ char_bytes_double* }
inner_multiline = @{ (!"`" ~ ANY)* }
int = @{
"0x" ~ "_"? ~ ASCII_HEX_DIGIT ~ ("_"? ~ ASCII_HEX_DIGIT)*
| "0X" ~ "_"? ~ ASCII_HEX_DIGIT ~ ("_"? ~ ASCII_HEX_DIGIT)*
| "0o" ~ "_"? ~ ASCII_OCT_DIGIT ~ ("_"? ~ ASCII_OCT_DIGIT)*
| "0O" ~ "_"? ~ ASCII_OCT_DIGIT ~ ("_"? ~ ASCII_OCT_DIGIT)*
| "0b" ~ "_"? ~ ("0" | "1") ~ ("_"? ~ ("0" | "1"))*
| "0B" ~ "_"? ~ ("0" | "1") ~ ("_"? ~ ("0" | "1"))*
| ASCII_DIGIT ~ ("_"? ~ ASCII_DIGIT)*
}
bool = { "true" | "false" }
nil = { "nil" }
decimal = @{
decimal_digits ~ "." ~ !"." ~ decimal_digits? ~ exponent?
| "." ~ decimal_digits ~ exponent?
| decimal_digits ~ exponent
}
decimal_digits = _{ ASCII_DIGIT ~ ("_"? ~ ASCII_DIGIT)* }
exponent = _{ ("e" | "E") ~ ("+" | "-")? ~ decimal_digits }
not_in_op = @{ "not" ~ WHITESPACE+ ~ "in" }
ident = @{ "#" ~ (ASCII_ALPHANUMERIC | "_")* | (("$" | ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")*) }
char_single = {
!("'" | "\\" | "\r" | "\n") ~ ANY
| "\\" ~ ("'" | "\\" | "/" | "b" | "f" | "n" | "r" | "t")
| "\\" ~ ("u" ~ ASCII_HEX_DIGIT{4})
}
char_double = {
!("\"" | "\\" | "\r" | "\n") ~ ANY
| "\\" ~ ("\"" | "\\" | "/" | "b" | "f" | "n" | "r" | "t")
| "\\" ~ ("u" ~ ASCII_HEX_DIGIT{4})
}
char_bytes_single = {
!("'" | "\\" | "\r" | "\n") ~ ANY
| "\\" ~ ("'" | "\\" | "a" | "b" | "f" | "n" | "r" | "t" | "v")
| "\\x" ~ ASCII_HEX_DIGIT{2}
| "\\" ~ ("0" | "1" | "2" | "3") ~ ASCII_OCT_DIGIT{2}
}
char_bytes_double = {
!("\"" | "\\" | "\r" | "\n") ~ ANY
| "\\" ~ ("\"" | "\\" | "a" | "b" | "f" | "n" | "r" | "t" | "v")
| "\\x" ~ ASCII_HEX_DIGIT{2}
| "\\" ~ ("0" | "1" | "2" | "3") ~ ASCII_OCT_DIGIT{2}
}
LINE_COMMENT = _{ "//" ~ (!"\n" ~ ANY)* }
BLOCK_COMMENT = _{ "/*" ~ (!"*/" ~ ANY)* ~ "*/" }
COMMENT = _{ LINE_COMMENT | BLOCK_COMMENT }
WHITESPACE = _{ " " | "\t" | "\r" | "\n" }