// 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 ~ GlobalTagLists ~ ImportStmts ~ Declaration* }
Spec = { SOI~ (BOM)? ~ GlobalTagLists ~ ImportStmts ~ Declaration* ~ EOI }
ImportStmts = _{ ImportStmt* }
ImportStmt = { "import " ~ Ident }
Declaration = _{IncludeStatement | TypeDecl | ConstantStream | InputStream | OutputStream | MirrorStream | SimpleTrigger }
BOM = _{"\u{FEFF}"}
// ///////////////// Statements ///////////////////
ParamListDecl = _{ "(" ~ ParamList ~")"}
LambdaParamListDecl = _{ Ident | "(" ~ ParamList ~")"}
ParamList = { (ParameterDecl ~("," ~ParameterDecl)*)? }
ParameterDecl = { Ident ~ (":" ~ Type)? }
SpawnDecl = { "spawn"~ ActivationCondition? ~ (SpawnWhen | SpawnWith){0,2} }
SpawnWith = { "with " ~Expr }
SpawnWhen = { "when " ~Expr }
ActivationCondition = { "@" ~ (TopLevelAC | "(" ~ TopLevelAC ~ ")")}
TopLevelAC = _{GlobalActivationCondition | LocalActivationCondition | AcExpr}
GlobalActivationCondition = { "Global" ~ "(" ~ AcExpr ~ ")"}
LocalActivationCondition = {"Local" ~ "(" ~ AcExpr ~ ")"}
AcExpr = {NumberLiteral | PositiveBooleanExpr}
PositiveBooleanExpr = {
True
| (Ident | "(" ~ PositiveBooleanExpr ~ ")") ~ ((And | BitAnd | Or | BitOr) ~ PositiveBooleanExpr)*
| "(" ~ PositiveBooleanExpr ~ ")"
}
CloseDecl = { "close" ~ ActivationCondition? ~ ("when" ~Expr)? }
EvalDecl = { "eval" ~ ActivationCondition? ~ (EvalWhen | EvalWith){0,2} }
EvalWhen = { "when " ~ Expr}
EvalWith = { "with " ~ Expr}
SimpleEvalDecl = { ActivationCondition? ~ ":=" ~ Expr }
IncludeStatement = { "include"~ StringLiteral}
TypeDecl = { "type " ~ Ident ~"{" ~Ident~ ":"~Type~ ("," ~Ident~ ":"~Type)* ~ "}"}
ConstantStream = { "constant " ~ Ident ~ ":" ~ Type ~":=" ~ ConstantLiteral }
InputStream = { TagLists? ~ "input " ~ Ident ~ ParamListDecl? ~ ":" ~ Type ~(","~ Ident~ ParamListDecl? ~ ":" ~ Type)*}
OutputStream = { TagLists? ~ (TriggerDecl | NamedOutputDecl) ~ ParamListDecl?~ (":" ~ Type)? ~
(((SpawnDecl| CloseDecl)* ~ EvalDecl ~ (SpawnDecl | EvalDecl | CloseDecl)*)| SimpleEvalDecl)
}
TriggerDecl = { "trigger" }
NamedOutputDecl = { "output " ~ Ident }
MirrorStream = { "output " ~ Ident ~ "mirrors " ~ Ident ~ "when " ~ Expr}
SimpleTriggerDecl = { "trigger " }
SimpleTrigger = { TagLists? ~ SimpleTriggerDecl ~ ActivationCondition? ~ Expr ~ StringLiteral? }
IdentList = {"(" ~(Ident ~("," ~Ident)*) ~")"}
TagLists = { TagList ~ TagList* }
TagList = { "#" ~ "[" ~ TagItem ~ ("," ~ TagItem)* ~ "]" }
GlobalTagLists = _{ GlobalTagList* }
GlobalTagList = { "#!" ~ "[" ~ TagItem ~ ("," ~ TagItem)* ~ "]" }
TagItem = { Ident ~ ("=" ~ StringLiteral)? }
// //////////////// 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 = _{ And | Or | Implies | Add | Subtract | Power | Multiply | Divide | Mod | BitAnd | BitOr | BitXor | ShiftLeft | ShiftRight | CompOp }
Add = { "+" }
Subtract = { "-" }
Multiply = { "*" }
Divide = { "/" }
Mod = { "%" }
Power = { "**" }
And = { "∧" | "&&" | "and" }
Or = { "∨" | "||" | "or" }
Implies = { "->" | "→" | "implies"}
Dot = { "." }
BitAnd = { "&" }
BitOr = { "|" }
BitXor = { "^" }
ShiftLeft = { "<<" }
ShiftRight = { ">>" }
UnaryOperation = _{ Add | Subtract | Neg | BitNot }
Neg = { "!" | "¬" | "not"}
BitNot = { "~" }
Expr = { (Term ~ ( (Operation ~ Term) | (Dot ~ (IntegerLiteral | FunctionExpr | Ident)) | (OpeningBracket ~ Literal ~ ClosingBracket) )*) }
// TODO Do we need Term to exist for the precedence climber?
Term = _{ LambdaExpr | 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 }
LambdaExpr = { LambdaParamListDecl ~ "=>" ~ Expr}
Tuple = { "("~ (Expr~ (","~ Expr)+)?~ ")"}
OpeningBracket = { "[" }
ClosingBracket = _{ "]" }
// ////////// Operators and Functions /////////////
LessThan = {"<"}
LessThanOrEqual = {"<=" | "≤"}
MoreThan = {">"}
MoreThanOrEqual = {">=" | "≥"}
NotEqual = {"!=" | "≠"}
Equal = { "="{1,2} }
CompOp = _{LessThanOrEqual | MoreThanOrEqual | LessThan | MoreThan | NotEqual | Equal}
// ////////////////// 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"| "⊤") ~ !(Ident)}
False = @{("false" | "⊥") ~ !(Ident)}
Literal = { StringLiteral | RawStringLiteral | NumberLiteral | BooleanLiteral }
ConstantLiteral = { StringLiteral | RawStringLiteral | NumberLiteral | BooleanLiteral | TupleLiteral }
TupleLiteral = {"(" ~ ConstantLiteral ~(","~ ConstantLiteral)+ ~ ")"}
IntegerLiteral = @{ Digit+ }
SignedIntegerLiteral = @{ ("+" | "-")?~IntegerLiteral }
NumberPostfix = @{ LETTER+ }
Type = {"("~ (Type ~(","~Type)*)?~ ")" | Optional | Ident | "_"} // _ => infer type
Optional = { Ident ~ "?" } // Optional types are written `Int32?`
// /////////////////// Names //////////////////////
// look at https://www.unicode.org/reports/tr31/#R1 and https://www.unicode.org/reports/tr31/#D1
Ident = @{ VarianceAggregation | (XID_START | "_") ~ (XID_CONTINUE | "_" | "::" | "'")* }
VarianceAggregation = _{ "σ²" }
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"? ) }