// The whole file must either succeed or fail
root = {
    SOI
  ~ model_entry*
  // Expect the data keyword before the data section starts
  // (Inserted automatically in Rust if not present)
  ~ ("data;"+ ~ data_entry*)?
  ~ EOI
}

// //////////////////////
// MODEL
// GMPL can be split into .mod model files and .dat data files
// The bulk of this grammar deals with the former
// The root level entries are capitalised to make scanning a bit easier,
// it doesn't have any other significance.
// //////////////////////
model_entry = _{
  // Main model entries
  | VAR
  | PARAM
  | SET
  | OBJECTIVE
  | CONSTRAINT
  | CHECK
  // Below here are ignored
  | FOR
  | TABLE
  | PRINT
  | DISPLAY
  | SOLVE
  | END
}

// Variable
// Example: var Demand >= 0;
VAR        = { "var" ~ name ~ alias? ~ domain? ~ (","? ~ var_attrib)* ~ ";" }
var_attrib = { var_bounds | var_type }
var_bounds = { rel_op ~ number }
var_type      =  { "integer" | "binary" }

// Parameter
// The comma separator seems to be optional
// Example: param Discount{r in REGION} := (1 + DiscountRate[r])
PARAM           =  { "param" ~ name ~ alias? ~ domain? ~ (","? ~ param_attrib)* ~ param_assign? ~ ";" }
param_attrib    = _{ param_type | param_condition | param_in | param_default }
param_type      =  { "integer" | "binary" | "symbolic" }
param_condition =  { rel_op ~ expr }
param_in        =  { "in" ~ expr }
param_assign    =  { ":=" ~ expr }
param_default   =  { "default" ~ (expr | string_literal) }

// Set
// Example: set DAILYTIMEBRACKET;
SET             =  { "set" ~ name ~ alias? ~ domain? ~ (","? ~ set_attrib)* ~ ";" }
set_attrib      =  { set_dimen | set_within | set_assign | set_default }
set_dimen       =  { "dimen" ~ int }
set_assign      =  { ":=" ~ (set_expr | set_vals | set_tuples) }

set_default     =  { "default" ~ ( set_expr | set_vals | set_tuples) }
set_expr        =  { set_atom ~ (set_infix_op ~ set_atom)* }
set_infix_op    =  { "inter" | "union" }
set_atom        =  { domain | set_setof | set_arith | set_ref | ("(" ~ set_expr ~ ")") }
set_ref         =  { id ~ subscript? }
set_setof       =  { "setof" ~ domain ~ domain_var }
set_arith       =  { set_arith_end ~ ".." ~ set_arith_end }
set_arith_end   =  { int | (id ~ subscript?) | set_arith_fancy }
set_arith_fancy =  { "(" ~ id ~ subscript? ~ (add | sub) ~ int ~ ")" }

set_tuples       = { set_tuple+ }
set_tuple        = { "(" ~ set_val ~ ("," ~ set_val)+ ~ ")" }
set_vals         = { (","? ~ set_val)+ }
set_val          = { id | int }

set_within      =  { "within" ~ (id | domain) ~ ("cross" ~ cross_set)* }
within_set      = @{ id }
cross_set       = @{ id }

// Constraint
// Example: s.t. Capacity{r in REGION}: NewCap[r] = 2
// Apparently multiple expressions are supported?
// Osemosys doesn't use this
CONSTRAINT      =  { constraint_kw? ~ name ~ alias? ~ domain? ~ ":" ~ constraint_expr ~ ";" }
constraint_expr =  { expr ~ rel_op ~ expr }
constraint_kw   = _{ "subject to" | "subj to" | "s.t." }

// Objective
// Example: minimize cost: sum{y in YEAR} Cost[y];
OBJECTIVE = { obj_sense ~ name ~ alias? ~ ":" ~ expr ~ ";" }
obj_sense = { "maximize" | "minimize" }

// Check
CHECK =  { "check" ~ domain? ~ ":"? ~ logic_expr  ~ ";" }

// For (ignored)
FOR       = { "for" ~ domain ~ "{" ~ for_inner* ~ "}" }
for_inner = { FOR | PRINT }

// Print (ignored)
PRINT   = @{ "printf" ~ (!";" ~ ANY)* ~ ";" }
DISPLAY = @{ "display" ~ (!";" ~ ANY)* ~ ";" }

// Solve (ignored)
SOLVE = { "solve" ~ ";" }

// End (ignored)
END = { "end" ~ ";" }

// Table (ignored)
TABLE      = { "table" ~ id ~ domain? ~ table_out ~ table_path ~ table_vars ~ ";" }
table_out  = { "OUT" ~ string_literal }
table_path = { "ResultsPath" ~ "&" ~ string_literal }
table_vars = { ":" ~ table_var ~ ("," ~ table_var)* }
table_var  = { expr ~ "~" ~ var_subscripted }

// //////////////////////
// DATA FILES
// Generally a separate .dat file.
// Mostly param values.
// Less of the grammar but will be far majority of text parsed.
// //////////////////////
data_entry = _{
  | SET_DATA
  | PARAM_DATA
  | END
}

// Data: Set
SET_DATA         = { "set" ~ id ~ index_data? ~ set_assign_data? ~ ";" }
set_assign_data  = { ":=" ~ (set_vals_data | set_tuples_data)? }
set_tuples_data  = { set_tuple_data+ }
set_tuple_data   = { "(" ~ set_val_data ~ ("," ~ set_val_data)+ ~ ")" }
set_vals_data    = { (","? ~ set_val_data)+ }
set_val_data     = { id_data | int }

// Index
// Whereas a subscript is symbolic, an "index" is made up of concrete values
// from a given set.
index       =  { "[" ~ set_val ~ ("," ~ set_val)* ~ "]" }
index_data  =  { "[" ~ set_val_data ~ ("," ~ set_val_data)* ~ "]" }

// Data: Param
// Two surface forms from the GMPL spec:
//   - Record form:  param NAME [domain] [default V] [:= body] ;
//   - Tabbing form: param [default V] : [s :] p1 [, p2 ...] := rows ;
// In tabbing form each row is n tuple values followed by k param values
// (n = set dimension, k = number of params in the header). Commas are
// optional; row splitting is done by the caller using n and k.
PARAM_DATA          =  { "param" ~ (param_data_tabbing | param_data_record) ~ ";" }
param_data_record   = _{
    id ~ domain? ~ ("default" ~ param_data_default)? ~ ":="? ~ param_data_body?
}
param_data_tabbing  =  {
    ("default" ~ param_data_default)? ~ ":" ~ tabbing_header ~ ":=" ~ tabbing_body?
}
tabbing_header      = _{ tabbing_with_set | tabbing_params }
tabbing_with_set    =  { tabbing_set ~ ":" ~ tabbing_param ~ (","? ~ tabbing_param)* }
tabbing_params      =  { tabbing_param ~ (","? ~ tabbing_param)* }
tabbing_set         = @{ id }
tabbing_param       = @{ id }
tabbing_body        =  { (","? ~ param_data_val)+ }
param_data_default  = @{ number }
param_data_body     = { param_data_symbolic | param_data_matrix+ | param_data_entry+ }
param_data_entry    = { param_data_target? ~ ":="? ~ (param_data_pair+ | param_data_val) }
param_data_symbolic = @{ string_literal }
param_data_scalar   =  { param_data_target? ~ ":="? ~ param_data_val }
param_data_list     =  { param_data_target? ~ ":="? ~ param_data_pair+ }
param_data_pair     =  { set_val_data ~ param_data_val ~ ","? }
param_data_matrix   =  { param_data_target? ~ ":" ~ param_data_cols ~ ":="? ~ param_data_row+ }
param_data_cols     =  { set_val_data+ }
param_data_row      = ${ set_val_data ~ param_data_row_vals }
param_data_row_vals = ${ ((" " | "\t")+ ~ param_data_val)+ }
// Param symbolic allows id to be used in param_val
param_data_val      =  { id | number }
param_data_target   =  { "[" ~ (set_val_data | param_data_any) ~ ("," ~ (set_val_data | param_data_any))* ~ "]" }
param_data_any      =  { "*" }

// //////////////////////
// LANGUAGE COMPONENTS
// //////////////////////

// Domain
// example:
// {r in REG, t in TECH:Capacity[r,t,y]<>0}
domain             =  { "{" ~ (domain_part ~ ","?)+ ~ (":" ~ logic_expr)? ~ "}" }
domain_part        =  { (domain_var ~ "in")? ~ set_expr }
domain_var         =  { domain_var_single | domain_var_tuple }
domain_var_single  = @{ id }
domain_var_tuple   = { "(" ~ id ~ ("," ~ id)+ ~ ")" }

// Logical expression
// Expressions that evaluate to true or false
// Uses Pratt parser for proper operator precedence (and > or)
// Example: Demand[r] <> 0
logic_expr     =  { logic_primary ~ (logic_infix ~ logic_primary)* }
logic_primary  = _{ logic_compare | logic_member | logic_subset | logic_compound | expr }
logic_infix    = _{ bool_and | bool_or }
logic_compare  =  { expr ~ rel_op ~ expr }
logic_member   =  { set_tuple ~ member_op ~ set_expr }
logic_subset   =  { set_expr ~ subset_op ~ set_expr }
logic_compound =  { "(" ~ logic_expr ~ ")" }

// Subscripted var or param
var_subscripted =  { id ~ subscript? }
subscript       =  { "[" ~ subscript_part ~ ("," ~ subscript_part)* ~ "]" }
// If the subscript part itself has a subscript, eg foo[bar[i]]
// then bar must be a param. In theory with foo[bar], bar could also
// be a param, but the code doesn't currently check if bar exists..
subscript_part  =  { (var_subscripted | int | string_literal) ~ subscript_shift? }
subscript_shift =  { (add | sub) ~ int }

// Expression
// Bundled up with string_literal as often that is also allowed
// Uses Pratt parser for proper operator precedence
// Precedence (lowest to highest): conditional, add/sub, sum/prod, mul/div, neg, pow
// Example: Rate[r] * Ratio[r]
expr            =  { prefix* ~ primary ~ (infix ~ prefix* ~ primary)* }
prefix          = _{ neg | sum_prefix }
neg             =  { "-" }
sum_prefix      =  { "sum" ~ domain }
infix           = _{ add | sub | mul | div | pow }
add             =  { "+" }
sub             =  { "-" }
mul             =  { "*" }
div             =  { "/" }
pow             =  { "^" }
primary         = _{ conditional | number | func | var_subscripted | "(" ~ expr ~ ")" }
conditional     =  { "if" ~ logic_expr ~ "then" ~ expr ~ ("else" ~ expr)? }

// Function
// min/max have special syntax: min{domain} min(var)
// They should technically have the same priority as sum (prefix) but they're never used
// like that so just leaving them as-is
func             = _{ func_min | func_max | func_card }
func_min         =  { "min" ~ domain ~ "min" ~ "(" ~ func_var ~ ")" }
func_max         =  { "max" ~ domain ~ "max" ~ "(" ~ func_var ~ ")" }
func_card        =  { "card" ~ "(" ~ set_expr ~ ")" }
func_var         = @{ id }
bool_and         =  { "and" | "&&" }
bool_or          =  { "or" | "||" }
rel_op           =  { "<=" | "<>" | "<" | "==" | "=" | ">=" | ">" | "!=" }
member_op        =  { "in" | "not in" | "!in" }
subset_op        =  { "within" | "not within" | "!within" }

// //////////////////////
// GRAMMAR BASICS
// //////////////////////

// Primitives
name       = @{ id }
id         = @{ !reserved ~ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* }
// rules are much looser for values in the data section
id_data    = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_" | "-")* }
int        = @{ ASCII_DIGIT+ }

// Reserved
reserved = @{ ( "and" | "by" | "cross" | "diff" | "div" | "else" | "if"
              | "in" | "inter" | "less" | "mod" | "not" | "or" | "symdiff"
              | "then" | "union" | "within"
              | "check" | "set" | "var" | "param" | "maximize" | "minimize"
              | "table" | "solve" | "display" | "printf" | "for" | "end"
              ) ~ !( ASCII_ALPHANUMERIC | "_" ) }

// float1 and float2 is to handle all of: 1.2, 1., .2
// (Just doing DIGIT* "."? DIGIT* captures anything so doesn't work
number     = @{ "-"? ~ (float1 | float2) ~ exponent? }
float1     = @{ ASCII_DIGIT+ ~ ("." ~ ASCII_DIGIT*)? }
float2     = @{ "." ~ ASCII_DIGIT+ }
exponent   = @{ ("e" | "E") ~ ("+" | "-")? ~ ASCII_DIGIT+ }

// String literal
alias          = @{ string_literal }
string_literal =  { single_quoted | double_quoted }
single_quoted  = _{ "'" ~ single_char* ~ "'" }
single_char    = _{ "''" | (!"'" ~ ANY) }
double_quoted  = _{ "\"" ~ double_char* ~ "\"" }
double_char    = _{ "\"\"" | (!"\"" ~ ANY) }

// Comments and whitespace are automatically inserted between all rules
WHITESPACE       = _{ " " | "\t" | NEWLINE }
COMMENT          = @{ comment_multi | comment_single }
comment_single   = @{ "#" ~ (!NEWLINE ~ ANY)* }
comment_multi    =  { "/*" ~ (!"*/" ~ ANY)* ~ "*/" }
