expr-lang 2.0.0

Implementation of expr language in Rust
Documentation
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" }