// 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
  | gen_eq
  | gen_ne
  | gen_le
  | gen_lt
  | gen_ge
  | gen_gt
  | str_eq
  | str_ne
  | str_le
  | str_lt
  | str_ge
  | str_gt
  | concat
  | in_op
  | not_in
  | and
  | or
  | pipe
}

gen_eq = { "==" }
gen_ne = { "!=" }
gen_lt = { "<" }
gen_le = { "<=" }
gen_gt = { ">" }
gen_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            =  { "-" }

open_indexing = { "[" }

// Functions
func_arg = { ((ident ~ "=")? ~ expr) | expr }
func     = { ident ~ "(" ~ func_arg? ~ ("," ~ func_arg)* ~ ")" }
lambda   = { (ident | ("(" ~ (ident ~ ","?)* ~ ")")) ~ "=>" ~ expr }

// Expressions
full_slice  =  { unary_operator* ~ term ~ ":" ~ unary_operator* ~ term }
start_slice =  { unary_operator* ~ term ~ ":" }
end_slice   =  { ":" ~ unary_operator* ~ term }
slice       = _{ full_slice | start_slice | end_slice }
expr        =  { unary_operator* ~ term ~ (binary_operator ~ unary_operator* ~ term | open_indexing ~ (slice | unary_operator* ~ term) ~ "]")* }
term        = _{
    func
  | lambda
  | 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 }