mpl-lang 0.4.2

Axioms Metrics Processing Language
Documentation
COMMENT    = _{ "//" ~ (!"\n" ~ ANY)* }
WHITESPACE = _{ " " | "\t" | "\r" | "\n" }

plain_ident         = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHA | ASCII_DIGIT | "_")* }
escaped_ident       = ${ "`" ~ escaped_ident_inner ~ "`" }
escaped_ident_inner = @{ escaped_ident_char* }
escaped_ident_char  =  {
    !("`" | "\\") ~ ANY
  | "\\" ~ ("`" | "\\" | "/" | "b" | "f" | "n" | "r" | "t")
  | "\\" ~ ("u" ~ ASCII_HEX_DIGIT{4})
}

pipe_keyword = { "|" }

ident = _{ plain_ident | escaped_ident }

as = { "as" ~ metric_name }

string       = ${ "\"" ~ string_inner ~ "\"" }
string_inner = @{ string_char* }
string_char  =  {
    !("\"" | "\\") ~ ANY
  | "\\" ~ ("\"" | "\\" | "/" | "b" | "f" | "n" | "r" | "t")
  | "\\" ~ ("u" ~ ASCII_HEX_DIGIT{4})
}
inf          =  { "inf" | "+inf" | "-inf" }
number       =  { inf | float | int }
float        = @{ int ~ ("." ~ ASCII_DIGIT*) ~ (^"e" ~ int)? }
int          =  { ("+" | "-")? ~ ASCII_DIGIT+ }
bool         = @{ "true" | "false" }

regex_replace_src =  { regex_inner }
regex_replace_dst =  { regex_inner }
regex_replace     = ${ "#s/" ~ regex_replace_src ~ "/" ~ regex_replace_dst ~ "/" }

regex       = ${ "#/" ~ regex_inner ~ "/" }
regex_inner = @{ regex_char* }
regex_char  =  {
    !("/" | "\\") ~ ANY
  | "\\" ~ ANY
}

time_unit_ms     = @{ "ms" }
time_unit_second = @{ "s" }
time_unit_minute = @{ "m" }
time_unit_hour   = @{ "h" }
time_unit_day    = @{ "d" }
time_unit_week   = @{ "w" }
time_unit_month  = @{ "M" }
time_unit_year   = @{ "y" }
time_unit        = _{ time_unit_ms | time_unit_second | time_unit_minute | time_unit_hour | time_unit_day | time_unit_week | time_unit_month | time_unit_year }

time_unit_digits            = { ASCII_DIGIT+ }
time_relative               = { time_unit_digits ~ time_unit }
time_relative_parameterized = { time_unit_digits ~ time_unit | param_ident }

time_timestamp = { ASCII_DIGIT+ }

// 2025-03-01T13:00:00Z
time_rfc_3339 = @{ ASCII_DIGIT{4} ~ "-" ~ ASCII_DIGIT{2} ~ "-" ~ ASCII_DIGIT{2} ~ "T" ~ ASCII_DIGIT{2} ~ ":" ~ ASCII_DIGIT{2} ~ ":" ~ ASCII_DIGIT{2} ~ "Z"? }

time_modifier_direction = { "+" | "-" }
time_modifier           = { time_modifier_direction ~ time_relative }

time = _{ time_relative | time_rfc_3339 | time_timestamp | time_modifier }

time_range = { "[" ~ time ~ ".." ~ time? ~ "]" }

dataset     = { ident | param_ident }
metric_name = { ident }

metric_id = { dataset ~ ":" ~ metric_name }

tag = _{ ident }

tags = { tag ~ ("," ~ tag)* }

source = { metric_id ~ time_range? ~ as? }

cmp    = @{ "==" | "!=" | "<=" | "<" | ">=" | ">" }
cmp_re = @{ "==" | "!=" }
value  =  { string | number | bool | param_ident }

kw_is = { "is" }

is_filter    = { kw_is ~ tag_type }
value_filter = { cmp ~ value }
regex_filter = { cmp_re ~ regex }

kw_not = { "not" }

kw_filter      =  { "filter" }
kw_sample      =  { "sample" }
kw_where       =  { "where" }
kw_ifdef       =  { "ifdef" }
filter_keyword = _{ kw_filter | kw_where }
filter_atom    =  { (tag ~ (value_filter | regex_filter | is_filter)) }
filter_clause  =  { filter_atom | ("(" ~ filter_or ~ ")") }
filter_not     =  { (kw_not ~ filter_clause) | filter_clause }
filter_and     =  { filter_not ~ ("and" ~ filter_not)* }
filter_or      =  { filter_and ~ ("or" ~ filter_and)* }
filter_expr    = _{ filter_keyword ~ filter_or }
filter_rule    =  { pipe_keyword ~ filter_expr }
ifdef_rule     =  { pipe_keyword ~ kw_ifdef ~ "(" ~ param_ident ~ ")" ~ "{" ~ filter_expr ~ "}" }
sample_expr    =  { kw_sample ~ number }
sample_rule    =  { pipe_keyword ~ sample_expr }

map_fn = { func ~ ("(" ~ number ~ ")")? }

map_calc_op = { "+" | "-" | "*" | "/" }
map_eval    = { map_calc_op ~ number }

map = { "map" ~ (map_eval | map_fn) }

module = { ident }

func = { (module ~ "::")* ~ ident }

param_native_type =  { "Dataset" | "Duration" | "duration" | "Regex" }
tag_type          =  { "string" | "int" | "float" | "bool" }
optional_type     =  { "Option" ~ "<" ~ ( tag_type | param_native_type ) ~ ">" }
param_type        =  { param_native_type | tag_type | optional_type }
param_ident       = ${ "$" ~ ident }
param             =  { "param" ~ param_ident ~ ":" ~ param_type ~ ";" }
param_value       =  { time_relative | string | float | int | bool | regex | ident }
// used for parsing provided params

align = { "align" ~ "to" ~ time_relative_parameterized ~ ("over" ~ time_relative_parameterized)? ~ "using" ~ func }

group_by = { "group" ~ ("by" ~ tags)? ~ "using" ~ func }

replace_tag        = { tag ~ "~" ~ regex_replace }
replace_rename     = { tag ~ "=" ~ tag }
replace_rename_tag = { tag ~ "=" ~ tag ~ "~" ~ regex_replace }
replace            = { "replace" ~ (replace_rename_tag | replace_tag | replace_rename) }

bucket_conversion              = { "rate" | "increase" }
bucket_by_fn                   = { "histogram" | "interpolate_delta_histogram" }
bucket_by_with_conversion_fn   = { "interpolate_cumulative_histogram" }
bucket_spec                    = { "count" | "avg" | "sum" | "min" | "max" | number }
bucket_specs                   = { bucket_spec ~ ("," ~ bucket_spec)* }
bucket_fn_call_with_conversion = { bucket_by_with_conversion_fn ~ "(" ~ bucket_conversion ~ "," ~ bucket_specs ~ ")" }
bucket_fn_call_simple          = { bucket_by_fn ~ "(" ~ bucket_specs ~ ")" }
bucket_fn_call                 = { bucket_fn_call_with_conversion | bucket_fn_call_simple }
bucket_by                      = { "bucket" ~ ("by" ~ tags)? ~ "to" ~ time_relative_parameterized ~ "using" ~ bucket_fn_call }
join                           = { "join" ~ tags ~ "from" ~ metric_id ~ "by" ~ tags }
pipe_rule                      = { pipe_keyword ~ (align | map | group_by | replace | bucket_by | join | as) }

simple_query = { source ~ (sample_rule)? ~ (filter_rule | ifdef_rule)* ~ pipe_rule* }

compute_op   = { "/" | "*" | "+" | "-" }
compute_fn   = { func | compute_op }
compute_rule = { pipe_keyword ~ "compute" ~ metric_name ~ "using" ~ compute_fn }

compute_query = { "(" ~ query ~ "," ~ query ~ ","? ~ ")" ~ compute_rule ~ pipe_rule* }

query = _{ simple_query | compute_query }

directive = { "set" ~ ident ~ ("=" ~ (value | ident))? ~ ";" }

file = _{ SOI ~ directive* ~ param* ~ query ~ EOI }