// Config
WHITESPACE = _{ " " }

// Literals
true_lit   =  { "true" }
false_lit  =  { "false" }
bool       = _{ true_lit | false_lit }
null       =  { "null" }
underscore =  { "_" }

// Numbers
digit        = _{ '0'..'9' }
positive_int = _{ digit ~ (digit | "_")* }
int          = @{ positive_int }
plus         =  { "+" }
minus        =  { "-" }
exp          = _{ ^"e" ~ (plus | minus)? ~ int }
float        = @{ int ~ "." ~ positive_int? ~ exp? | int ~ exp }

// Strings
raw_single_quoted_string = { (!("\\" | "'") ~ ANY)+ }
raw_double_quoted_string = { (!("\\" | "\"") ~ ANY)+ }
// hex                      = { '0'..'9' | 'a'..'f' | 'A'..'F' }
// unicode_hex              = { hex{1, 6} }
predefined = { "n" | "r" | "t" | "\\" | "\"" | "'" }
// byte                     = { "x" ~ hex{2} }
// unicode                  = { "u" ~ "{" ~ unicode_hex ~ "}" }
escape               =  { "\\" ~ predefined }
single_quoted_string = _{ "'" ~ (raw_single_quoted_string | escape)* ~ "'" }
double_quoted_string = _{ "\"" ~ (raw_double_quoted_string | escape | " ")* ~ "\"" }
string               = ${ single_quoted_string | double_quoted_string }

// Regexes
raw_regex_string = { (!("\\" | "/") ~ ANY)+ }
escape_regex     = { "\\" ~ ANY }
regex_flag       = { "i" }
regex            = { "/" ~ (raw_regex_string | escape_regex)* ~ "/" ~ (regex_flag)* }

// Identifiers
ident_char = _{ 'a'..'z' | 'A'..'Z' | '0'..'9' | "_" }
ident      = @{ ASCII_ALPHA ~ ident_char* | "_" ~ ident_char+ }

// Operators
// NOTE: order IS important
binary_operator = _{
    add
  | sub
  | pow
  | mul
  | idiv
  | div
  | rem
  | num_eq
  | num_ne
  | num_le
  | num_lt
  | num_ge
  | num_gt
  | str_eq
  | str_ne
  | str_le
  | str_lt
  | str_ge
  | str_gt
  | concat
  | in_op
  | not_in
  | and
  | or
  | pipe
}

num_eq = { "==" }
num_ne = { "!=" }
num_lt = { "<" }
num_le = { "<=" }
num_gt = { ">" }
num_ge = { ">=" }

str_eq = { "eq" }
str_ne = { "ne" }
str_lt = { "lt" }
str_le = { "le" }
str_gt = { "gt" }
str_ge = { "ge" }

add  = { "+" }
sub  = { "-" }
mul  = { "*" }
div  = { "/" }
idiv = { "//" }
rem  = { "%" }
pow  = { "**" }

concat = { "." }

pipe = { "|" }

and = { "&&" | "and" }
or  = { "||" | "or" }

in_op  = { "in" }
not_in = { "not in" }

unary_operator = _{ not | neg }
not            =  { "!" }
neg            =  { "-" }

// Functions
func = { ident ~ "(" ~ (expr ~ ","?)* ~ ")" }

// Expressions
expr =  { unary_operator* ~ term ~ (binary_operator ~ term)* }
term = _{
    func
  | bool
  | null
  | regex
  | string
  | float
  | int
  | ident
  | underscore
  | list
  | map
  | "(" ~ expr ~ ")"
}

// Lists & Maps
list_body     = _{ expr ~ ("," ~ expr)* }
list          =  { "[" ~ list_body* ~ "]" }
map_entry     =  { (string | ident) ~ ":" ~ expr }
map_body      = _{ map_entry ~ ("," ~ map_entry)* }
map           =  { "{" ~ map_body* ~ "}" }

// End-chain parsers
full_expr = _{ SOI ~ expr ~ EOI }

expr_name      =  { ident | string }
named_expr     =  { expr ~ "as" ~ expr_name }
opt_named_expr = _{ named_expr | expr }
named_exprs    = _{ SOI ~ opt_named_expr ~ ("," ~ opt_named_expr)* ~ EOI }

named_func     =  { func ~ "as" ~ expr_name }
opt_named_func = _{ named_func | func }
named_aggs     = _{ SOI ~ opt_named_func ~ ("," ~ opt_named_func)* ~ EOI }

pipeline = _{ SOI ~ expr ~ ("|" ~ expr)* ~ EOI }