Files
Foundry-VTT-Docker/resources/app/client-esm/dice/grammar.pegjs

92 lines
3.8 KiB
Plaintext
Raw Normal View History

2025-01-04 00:34:03 +01:00
{
/*
This is a per-parser initialization block. It runs whenever the parser is invoked. Any variables declared here are
available in any javascript blocks in the rest of the grammar.
In addition to the parser generated by peggy, we allow for certain parts of the process to be delegated to client
code. A class implementing this 'parser' API may be passed-in here as an option when the peggy parser is invoked,
otherwise we use the one configured at CONFIG.Dice.parser.
*/
const Parser = options.parser ?? CONFIG.Dice.parser;
const parser = new Parser(input);
}
Expression = _ leading:(_ @Additive)* _ head:Term tail:(_ @Operators _ @Term)* _ {
/*
The grammar rules are matched in order of precedence starting at the top of the file, so the rules that match the
largest portions of a string should generally go at the top, with matches for smaller substrings going at the bottom.
Here we have a rule that matches the overall roll formula. If a given formula does not match this rule, it means that
it is an invalid formula and will throw a parsing error.
Prefixing a pattern with 'foo:' is a way to give a name to the sub-match in the associated javascript code. We use
this fairly heavily since we want to forward these sub-matches onto the 'parser'. We can think of them like named
capture groups.
Prefixing a pattern with '@' is called 'plucking', and is used to identify sub-matches that should be assigned to the
overall capture name (like 'foo:'), ignoring any that are not 'plucked'.
For example 'tail:(_ @Operators _ @Term)*' matches operators followed by terms, with any amount of whitespace
in-between, however only the operator and term matches are assigned to the 'tail' variable, the whitespace is ignored.
The 'head:A tail:(Delimiter B)*' pattern is a way of representing a string of things separated by a delimiter,
like 'A + B + C' for formulas, or 'A, B, C' for Pool and Math terms.
In each of these cases we follow the same pattern: We match a term, then we call 'parser.on*', and that method is
responsible for taking the raw matched sub-strings and returning a 'parse node', i.e. a plain javascript object that
contains all the information we need to instantiate a real `RollTerm`.
*/
return parser._onExpression(head, tail, leading, text(), error);
}
Term = FunctionTerm / DiceTerm / NumericTerm / PoolTerm / Parenthetical / StringTerm
FunctionTerm = fn:FunctionName "(" _ head:Expression? _ tail:(_ "," _ @Expression)* _ ")" flavor:Flavor? {
return parser._onFunctionTerm(fn, head, tail, flavor, text());
}
DiceTerm = number:(Parenthetical / Constant)? [dD] faces:([a-z]i / Parenthetical / Constant) modifiers:Modifiers? flavor:Flavor? {
return parser._onDiceTerm(number, faces, modifiers, flavor, text());
}
NumericTerm = number:Constant flavor:Flavor? !StringTerm { return parser._onNumericTerm(number, flavor); }
PoolTerm = "{" _ head:Expression tail:("," _ @Expression)* "}" modifiers:Modifiers? flavor:Flavor? {
return parser._onPoolTerm(head, tail, modifiers, flavor, text());
}
Parenthetical = "(" _ term:Expression _ ")" flavor:Flavor? { return parser._onParenthetical(term, flavor, text()); }
StringTerm = term:(ReplacedData / PlainString) flavor:Flavor? { return parser._onStringTerm(term, flavor); }
ReplacedData = $("$" $[^$]+ "$")
PlainString = $[^ (){}[\]$,+\-*%/]+
FunctionName = $([a-z$_]i [a-z$_0-9]i*)
Modifiers = $([^ (){}[\]$+\-*/,]+)
Constant = _ [0-9]+ ("." [0-9]+)? { return Number(text()); }
Operators = MultiplicativeFirst / AdditiveOnly
MultiplicativeFirst = head:Multiplicative tail:(_ @Additive)* { return [head, ...tail]; }
AdditiveOnly = head:Additive tail:(_ @Additive)* { return [null, head, ...tail]; }
Multiplicative = "*" / "/" / "%"
Additive = "+" / "-"
Flavor = "[" @$[^[\]]+ "]"
_ "whitespace" = [ ]*