//! Parser rules for Actions expressions.
//!
//! See: <https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/evaluate-expressions-in-workflows-and-actions>
/// Whitespace handling
WHITESPACE = _{ " " | NEWLINE }
// Misc notes:
// I have pretty low confidence in this grammar -- it should parse the
// overwhelming majority of normal looking Actions expressions, but
// it makes up for a lack of left-recursion with some hacky rules that
// probably aren't sufficient for capturing all possible grammar productions.
// I've noted some of these with `HACK` below.
// I've also noted some potentials TODOs with `TODO`.
expression = { SOI ~ or_expr ~ EOI }
/// Logical OR
or_expr = { (and_expr ~ ("||" ~ and_expr)*) }
/// Logical AND
and_expr = { (eq_expr ~ ("&&" ~ eq_expr)*) }
/// Structural equality/inequality
eq_expr = { (comp_expr ~ (eq_op ~ comp_expr)*) }
eq_op = { "==" | "!=" }
/// Structural comparison
comp_expr = {
unary_expr ~ (comp_op ~ unary_expr)*
}
comp_op = { ">=" | ">" | "<=" | "<" }
/// Unary operations, including the base case for expressions.
// HACK: `unary_op ~ or_expr` ensures that we handle non-trivial
// negation productions, like `!(true || false)`.
unary_expr = { (unary_op? ~ primary_expr) | unary_op ~ or_expr }
unary_op = { "!" }
primary_expr = {
number
| string
| boolean
| null
| context
}
/// Numbers
/// Supports decimal (with optional sign and scientific notation), hex (0x), and octal (0o).
/// See: <https://github.com/actions/runner/blob/main/src/Sdk/DTExpressions2/Expressions2/Sdk/ExpressionUtility.cs>
number = @{
"0x" ~ ASCII_HEX_DIGIT+
| "0o" ~ ('0'..'7')+
| "NaN"
| ("+" | "-")? ~ "Infinity"
| ("+" | "-")? ~ (ASCII_DIGIT+ ~ ("." ~ ASCII_DIGIT*)? | "." ~ ASCII_DIGIT+) ~ (("e" | "E") ~ ("+" | "-")? ~ ASCII_DIGIT+)?
}
/// Strings
string = ${ "'" ~ string_inner ~ "'" }
string_inner = @{ string_char* }
string_char = @{
!("'") ~ ANY
| "'" ~ "'"
}
/// Booleans
boolean = { "true" | "false" }
/// Null
null = { "null" }
/// Context references (e.g., github.event.issue.number)
context = { (function_call | identifier | "(" ~ or_expr ~ ")") ~ (("." ~ (identifier | star)) | index)* }
/// A star within a context component.
star = { "*" }
/// A single identifier within a context component.
identifier = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_" | "-")* }
/// An index within a context component.
index = !{ ("[" ~ (or_expr | star) ~ "]") }
/// Function calls
function_call = !{ identifier ~ "(" ~ (or_expr ~ ("," ~ or_expr)*)? ~ ")" }