// Status: WIP (Draft)
// Take a look at https://pest-parser.github.io/book/grammars/syntax.html
// TODO we can probably mark some rules as atomic or hidden
IncompleteSpec = { SOI ~ ImportStmts ~ Declaration* }
Spec = { SOI~ (BOM)? ~ ImportStmts ~ Declaration* ~ EOI }
ImportStmts = _{ ImportStmt* }
ImportStmt = { "import " ~ Ident }
Declaration = _{IncludeStatement | TypeDecl | ConstantStream | InputStream | OutputStream | Trigger}
BOM = _{"\u{FEFF}"}
//////////////////////////////////////////////////
/////////////////// Statements ///////////////////
//////////////////////////////////////////////////
ParamList = { "(" ~(ParameterDecl ~("," ~ParameterDecl)*)? ~")"}
ParameterDecl = { Ident ~ (":" ~ Type)? }
SpawnDecl = { "spawn"~ ActivationCondition? ~SpawnWith? ~(SpawnIf | SpawnUnless)? }
SpawnIf = { "if" ~Expr }
SpawnUnless = { "unless" ~Expr }
SpawnWith = {"with " ~Expr }
FilterDecl = { "filter" ~ Expr }
ActivationCondition = { "@" ~ Expr }
CloseDecl = { "close" ~Expr }
IncludeStatement = { "include"~ StringLiteral}
TypeDecl = { "type " ~ Ident ~"{" ~Ident~ ":"~Type~ ("," ~Ident~ ":"~Type)* ~ "}"}
ConstantStream = { "constant " ~ Ident ~ ":" ~ Type ~":=" ~Literal}
InputStream = { "input " ~ Ident ~ ParamList? ~ ":" ~ Type ~(","~ Ident~ ParamList? ~ ":" ~ Type)*}
OutputStream = { "output " ~ Ident ~ ParamList?~ (":" ~ Type)? ~ ActivationCondition? ~ SpawnDecl? ~ FilterDecl? ~CloseDecl? ~":="~ Expr}
Trigger = { "trigger " ~ ActivationCondition? ~ Expr ~ (StringLiteral ~ IdentList?)?}
IdentList = {"(" ~(Ident ~("," ~Ident)*) ~")"}
//////////////////////////////////////////////////
////////////////// Expressions ///////////////////
//////////////////////////////////////////////////
// TODO take a look at https://pest-parser.github.io/book/ and the usage of the PrecClimber
// Precedences:
// Atomic < TernaryExpr < BooleanDisExpr < BooleanConExpr
// < CompExpr < AddExpr < MultiExpr < ExpoExpr < UnaryExpr < DefaultExpr
// < FunctionExpr
Operation = _{ Add | Subtract | Power | Multiply | Divide | Mod | And | Or | BitAnd | BitOr | BitXor | ShiftLeft | ShiftRight | CompOp }
Add = { "+" }
Subtract = { "-" }
Multiply = { "*" }
Divide = { "/" }
Mod = { "%" }
Power = { "**" }
And = { "∧" | "&&" | "and" }
Or = { "∨" | "||" | "or" }
Dot = { "." }
BitAnd = { "&" }
BitOr = { "|" }
BitXor = { "^" }
ShiftLeft = { "<<" }
ShiftRight = { ">>" }
UnaryOperation = _{ Add | Subtract | Neg | BitNot }
Neg = { "!" | "¬" }
BitNot = { "~" }
Expr = { (Term ~ ( (Operation ~ Term) | (Dot ~ (IntegerLiteral | FunctionExpr | Ident)) | (OpeningBracket ~ Literal ~ ClosingBracket) )*) }
// TODO Do we need Term to exist for the precedence climber?
Term = _{ MissingExpression | Literal | ParenthesizedExpression | UnaryExpr | TernaryExpr | FunctionExpr | Ident | Tuple}
ParenthesizedExpression = {OpeningParenthesis ~ Expr ~ ClosingParenthesis | OpeningParenthesis ~ Expr ~ MissingClosingParenthesis}
OpeningParenthesis = {"("}
ClosingParenthesis = {")"}
MissingClosingParenthesis = {WHITESPACE* ~ &("then " | "else " |"output " | "input " | "trigger " | "constant " | "Type " | "include " | EOI)}
MissingExpression = {WHITESPACE* ~ &("then " | "else " |")"|"output " | "input " | "trigger " | "constant " | "Type " | "include " | EOI)}
// Functions
FunctionExpr = { FunctionSymbol ~ GenericParam? ~ FunctionArgs }
FunctionSymbol = _{ Ident }
GenericParam = { "<" ~ Type ~ ("," ~ Type)* ~ ">" }
FunctionArgs = { ( "(" ~ ")" ) | ( "(" ~ FunctionArg ~ ("," ~ FunctionArg)* ~ ")" ) }
FunctionArg = { (Ident ~ ":")? ~ Expr }
UnaryExpr = { UnaryOperation~ Term }
TernaryExpr = { "if"~ Expr~ "then"~ Expr~ "else"~ Expr }
Tuple = { "("~ (Expr~ (","~ Expr)+)?~ ")"}
OpeningBracket = { "[" }
ClosingBracket = _{ "]" }
//////////////////////////////////////////////////
//////////// Operators and Functions /////////////
//////////////////////////////////////////////////
LessThan = {"<"}
LessThanOrEqual = {"<=" | "≤"}
MoreThan = {">"}
MoreThanOrEqual = {">=" | "≥"}
NotEqual = {"!=" | "≠"}
Equal = { "="{1,2} }
CompOp = _{LessThanOrEqual | MoreThanOrEqual | LessThan | MoreThan | NotEqual | Equal}
Sum = {"Σ" | "sum"}
Count = {"#" | "count"}
//Product = {"Π" | "prod" | "product"}
Integral = {"∫" | "integral"}
Average = { "avg"| "average" }
WindowOp = _{ Sum | /*Product |*/ Average | Count | Integral | WindowForall | WindowExists }
WindowForall = {"forall" | "∀" | "∧" | "conjunction" }
WindowExists = {"exists" | "∃" | "∨" | "disjunction" }
//////////////////////////////////////////////////
//////////////////// Literals ////////////////////
//////////////////////////////////////////////////
NumberLiteral = ${ NumberLiteralValue ~ NumberPostfix?}
NumberLiteralValue = @{ ("+" | "-")?~ Digit+~ (("." ~ !Letter) ~ Digit*)?~ ("e"~ ("+" | "-")?~ Digit+)? }
// `("." ~ !Letter)` is used to disambiguate from method call, thus,
// floating point numeric values with unit need a digit after period, i.e., `1.0Hz` instead of `1.Hz`
StringLiteral = _{ "\""~ String~ "\""}
String = @{("\\\""|!("\"") ~ ANY)*}
// A raw string literal, e.g., r#"a\"b"#
RawStringLiteral = _{ "r" ~ PUSH("#"*) ~ "\"" ~ RawString ~ "\"" ~ POP }
RawString = @{ (!("\"" ~ PEEK) ~ ANY)* }
BooleanLiteral = _{ True | False}
True = @{"true"| "⊤"}
False = @{"false" | "⊥"}
Literal = { StringLiteral | RawStringLiteral | NumberLiteral | BooleanLiteral}
IntegerLiteral = @{ Digit+ }
SignedIntegerLiteral = @{ ("+" | "-")?~IntegerLiteral }
NumberPostfix = @{ LETTER+ }
Type = {"("~ (Type ~(","~Type)*)?~ ")" | Optional | Ident | "_"} // _ => infer type
Optional = { Ident ~ "?" } // Optional types are written `Int32?`
//////////////////////////////////////////////////
///////////////////// Names //////////////////////
//////////////////////////////////////////////////
Ident = @{ (Letter | "_") ~ (Letter | Digit | "_" | "::")* | WindowOp}
Parameter = { Ident } // Semantic Category
//////////////////////////////////////////////////
//////////////////// Symbols /////////////////////
//////////////////////////////////////////////////
Letter = { 'a'..'z' | 'A'..'Z'}
Digit = _{'0'..'9'}
Number = { '0'..'9'+ }
WHITESPACE = _{ " " | "\t" | "\r" | "\n"}
COMMENT = _{ ("/*" ~ (!"*/" ~ ANY)* ~ "*/") | ("//" ~(!("\n")~ANY)*~ "\n"? ) }