roll-parser 3.0.0-beta.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +142 -9
- package/MIGRATION.md +219 -0
- package/README.md +1026 -154
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +81 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/format.d.ts +15 -3
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +18 -0
- package/dist/cli/format.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +14 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/main.d.ts +36 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +83 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/errors.d.ts +332 -17
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +141 -0
- package/dist/errors.js.map +1 -0
- package/dist/evaluator/die.d.ts +27 -0
- package/dist/evaluator/die.d.ts.map +1 -0
- package/dist/evaluator/die.js +19 -0
- package/dist/evaluator/die.js.map +1 -0
- package/dist/evaluator/env.d.ts +89 -0
- package/dist/evaluator/env.d.ts.map +1 -0
- package/dist/evaluator/env.js +11 -0
- package/dist/evaluator/env.js.map +1 -0
- package/dist/evaluator/evaluator.d.ts +41 -75
- package/dist/evaluator/evaluator.d.ts.map +1 -1
- package/dist/evaluator/evaluator.js +914 -0
- package/dist/evaluator/evaluator.js.map +1 -0
- package/dist/evaluator/modifiers/compare.js +15 -0
- package/dist/evaluator/modifiers/compare.js.map +1 -0
- package/dist/evaluator/modifiers/crit-threshold.d.ts +57 -5
- package/dist/evaluator/modifiers/crit-threshold.d.ts.map +1 -1
- package/dist/evaluator/modifiers/crit-threshold.js +42 -0
- package/dist/evaluator/modifiers/crit-threshold.js.map +1 -0
- package/dist/evaluator/modifiers/die-bound.d.ts +29 -0
- package/dist/evaluator/modifiers/die-bound.d.ts.map +1 -0
- package/dist/evaluator/modifiers/die-bound.js +17 -0
- package/dist/evaluator/modifiers/die-bound.js.map +1 -0
- package/dist/evaluator/modifiers/explode.d.ts +13 -5
- package/dist/evaluator/modifiers/explode.d.ts.map +1 -1
- package/dist/evaluator/modifiers/explode.js +105 -0
- package/dist/evaluator/modifiers/explode.js.map +1 -0
- package/dist/evaluator/modifiers/flags.d.ts +47 -0
- package/dist/evaluator/modifiers/flags.d.ts.map +1 -0
- package/dist/evaluator/modifiers/flags.js +25 -0
- package/dist/evaluator/modifiers/flags.js.map +1 -0
- package/dist/evaluator/modifiers/keep-drop.d.ts +15 -29
- package/dist/evaluator/modifiers/keep-drop.d.ts.map +1 -1
- package/dist/evaluator/modifiers/keep-drop.js +82 -0
- package/dist/evaluator/modifiers/keep-drop.js.map +1 -0
- package/dist/evaluator/modifiers/reroll.d.ts +12 -4
- package/dist/evaluator/modifiers/reroll.d.ts.map +1 -1
- package/dist/evaluator/modifiers/reroll.js +68 -0
- package/dist/evaluator/modifiers/reroll.js.map +1 -0
- package/dist/evaluator/modifiers/sort.d.ts +5 -1
- package/dist/evaluator/modifiers/sort.d.ts.map +1 -1
- package/dist/evaluator/modifiers/sort.js +13 -0
- package/dist/evaluator/modifiers/sort.js.map +1 -0
- package/dist/evaluator/modifiers/success-count.d.ts +6 -7
- package/dist/evaluator/modifiers/success-count.d.ts.map +1 -1
- package/dist/evaluator/modifiers/success-count.js +25 -0
- package/dist/evaluator/modifiers/success-count.js.map +1 -0
- package/dist/index.d.ts +33 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2560
- package/dist/index.js.map +1 -26
- package/dist/lexer/lexer.d.ts +50 -5
- package/dist/lexer/lexer.d.ts.map +1 -1
- package/dist/lexer/lexer.js +260 -0
- package/dist/lexer/lexer.js.map +1 -0
- package/dist/lexer/tokens.d.ts +31 -6
- package/dist/lexer/tokens.d.ts.map +1 -1
- package/dist/lexer/tokens.js +42 -0
- package/dist/lexer/tokens.js.map +1 -0
- package/dist/parser/ast.d.ts +324 -165
- package/dist/parser/ast.d.ts.map +1 -1
- package/dist/parser/ast.js +52 -0
- package/dist/parser/ast.js.map +1 -0
- package/dist/parser/guards.d.ts +106 -0
- package/dist/parser/guards.d.ts.map +1 -0
- package/dist/parser/guards.js +121 -0
- package/dist/parser/guards.js.map +1 -0
- package/dist/parser/parser.d.ts +118 -14
- package/dist/parser/parser.d.ts.map +1 -1
- package/dist/parser/parser.js +751 -0
- package/dist/parser/parser.js.map +1 -0
- package/dist/render.d.ts +95 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +227 -0
- package/dist/render.js.map +1 -0
- package/dist/rng/mock.d.ts +73 -12
- package/dist/rng/mock.d.ts.map +1 -1
- package/dist/rng/mock.js +30 -0
- package/dist/rng/mock.js.map +1 -0
- package/dist/rng/seeded.d.ts +141 -9
- package/dist/rng/seeded.d.ts.map +1 -1
- package/dist/rng/seeded.js +138 -0
- package/dist/rng/seeded.js.map +1 -0
- package/dist/rng/types.d.ts +57 -0
- package/dist/rng/types.d.ts.map +1 -1
- package/dist/rng/types.js +2 -0
- package/dist/rng/types.js.map +1 -0
- package/dist/roll.d.ts +58 -28
- package/dist/roll.d.ts.map +1 -1
- package/dist/roll.js +8 -0
- package/dist/roll.js.map +1 -0
- package/dist/testing.d.ts +5 -4
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +2 -41
- package/dist/testing.js.map +1 -11
- package/dist/types.d.ts +349 -47
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +93 -34
- package/src/cli/args.ts +66 -10
- package/src/cli/format.ts +37 -26
- package/src/cli/index.ts +27 -84
- package/src/cli/main.ts +129 -0
- package/src/errors.ts +480 -27
- package/src/evaluator/die.ts +51 -0
- package/src/evaluator/env.ts +105 -0
- package/src/evaluator/evaluator.ts +693 -434
- package/src/evaluator/modifiers/crit-threshold.ts +96 -14
- package/src/evaluator/modifiers/die-bound.ts +48 -0
- package/src/evaluator/modifiers/explode.ts +70 -62
- package/src/evaluator/modifiers/flags.ts +78 -0
- package/src/evaluator/modifiers/keep-drop.ts +129 -127
- package/src/evaluator/modifiers/reroll.ts +44 -56
- package/src/evaluator/modifiers/sort.ts +21 -2
- package/src/evaluator/modifiers/success-count.ts +24 -12
- package/src/index.ts +56 -35
- package/src/lexer/lexer.ts +107 -34
- package/src/lexer/tokens.ts +31 -6
- package/src/parser/ast.ts +333 -346
- package/src/parser/guards.ts +248 -0
- package/src/parser/parser.ts +419 -242
- package/src/render.ts +392 -0
- package/src/rng/mock.ts +74 -13
- package/src/rng/seeded.ts +299 -64
- package/src/rng/types.ts +57 -0
- package/src/roll.ts +64 -47
- package/src/testing.ts +5 -9
- package/src/types.ts +353 -46
- package/src/version.ts +2 -0
- package/dist/cli.js +0 -2608
- package/dist/cli.js.map +0 -28
- package/dist/evaluator/index.d.ts +0 -8
- package/dist/evaluator/index.d.ts.map +0 -1
- package/dist/rng/index.d.ts +0 -8
- package/dist/rng/index.d.ts.map +0 -1
- package/src/evaluator/index.ts +0 -14
- package/src/rng/index.ts +0 -8
package/dist/parser/ast.d.ts
CHANGED
|
@@ -11,84 +11,173 @@ import type { ComparePoint } from '../types.js';
|
|
|
11
11
|
* original notation string. The parser sets both on every node it produces;
|
|
12
12
|
* they are typed optional so hand-constructed ASTs (tests, programmatic
|
|
13
13
|
* consumers) remain valid without positions.
|
|
14
|
+
*
|
|
15
|
+
* `notation.slice(start, end)` recovers the source text of any parsed node,
|
|
16
|
+
* which is how the CLI underlines the failing sub-expression.
|
|
17
|
+
*
|
|
18
|
+
* @category AST
|
|
14
19
|
*/
|
|
15
20
|
export type NodeSpan = {
|
|
16
|
-
start?: number;
|
|
17
|
-
end?: number;
|
|
21
|
+
readonly start?: number;
|
|
22
|
+
readonly end?: number;
|
|
18
23
|
};
|
|
19
24
|
/**
|
|
20
|
-
* Numeric literal node
|
|
25
|
+
* Numeric literal node — an integer or decimal written directly in the
|
|
26
|
+
* notation (`3`, `2.5`). Leaf node.
|
|
27
|
+
*
|
|
28
|
+
* Decimals are accepted and carried through arithmetic exactly as written;
|
|
29
|
+
* only dice counts and sides are required to resolve to integers.
|
|
30
|
+
*
|
|
31
|
+
* @category AST
|
|
21
32
|
*/
|
|
22
33
|
export type LiteralNode = NodeSpan & {
|
|
23
|
-
type: 'Literal';
|
|
24
|
-
value: number;
|
|
34
|
+
readonly type: 'Literal';
|
|
35
|
+
readonly value: number;
|
|
25
36
|
};
|
|
26
37
|
/**
|
|
27
|
-
* Dice roll node.
|
|
28
|
-
*
|
|
38
|
+
* Dice roll node (`NdX`, `dX`, `d%`).
|
|
39
|
+
*
|
|
40
|
+
* `count` and `sides` are full sub-expressions, not numbers, which is what
|
|
41
|
+
* makes computed dice like `(1+1)d(3*2)` and `(1d4)d6` expressible. Both are
|
|
42
|
+
* evaluated first and must resolve to integers; `d%` desugars to a `sides`
|
|
43
|
+
* literal of `100`, and an omitted count to a `count` literal of `1`.
|
|
44
|
+
*
|
|
45
|
+
* Dice rolled by the `count`/`sides` sub-expressions are meta-expression dice
|
|
46
|
+
* — they land in `RollResult.rolls` tagged `'meta'` and belong to no pool.
|
|
47
|
+
*
|
|
48
|
+
* @category AST
|
|
29
49
|
*/
|
|
30
50
|
export type DiceNode = NodeSpan & {
|
|
31
|
-
type: 'Dice';
|
|
32
|
-
count: ASTNode;
|
|
33
|
-
sides: ASTNode;
|
|
51
|
+
readonly type: 'Dice';
|
|
52
|
+
readonly count: ASTNode;
|
|
53
|
+
readonly sides: ASTNode;
|
|
34
54
|
};
|
|
35
55
|
/**
|
|
36
56
|
* Fate/Fudge dice node (`dF`).
|
|
37
57
|
* Each die produces a result in {-1, 0, +1}. No configurable sides.
|
|
58
|
+
*
|
|
59
|
+
* Fate dice carry `sides: 0` as a sentinel in their `DieResult`, so the
|
|
60
|
+
* default `critical`/`fumble` rule never fires — it is guarded on `sides > 1`,
|
|
61
|
+
* which also stops a `+1` face from reading as a fumbled 1. An explicit
|
|
62
|
+
* threshold still applies: `4dFcs>0` and `4dFcf=-1` set the flags, while the
|
|
63
|
+
* bare `cs`/`cf` forms are rejected at parse time.
|
|
64
|
+
*
|
|
65
|
+
* @category AST
|
|
38
66
|
*/
|
|
39
67
|
export type FateDiceNode = NodeSpan & {
|
|
40
|
-
type: 'FateDice';
|
|
41
|
-
count: ASTNode;
|
|
68
|
+
readonly type: 'FateDice';
|
|
69
|
+
readonly count: ASTNode;
|
|
42
70
|
};
|
|
43
71
|
/**
|
|
44
|
-
* Binary operation node.
|
|
72
|
+
* Binary operation node — the arithmetic backbone of an expression.
|
|
73
|
+
*
|
|
74
|
+
* | Operator | Meaning | Precedence | Associativity |
|
|
75
|
+
* |----------|---------|-----------:|---------------|
|
|
76
|
+
* | `+` `-` | add, subtract | 10 | left |
|
|
77
|
+
* | `*` `/` `%` | multiply, divide, modulo | 20 | left |
|
|
78
|
+
* | `**` | power (also spelled `^`) | 30 | right |
|
|
79
|
+
*
|
|
80
|
+
* Dice bind tighter than all of them (40), and postfix modifiers sit at 35,
|
|
81
|
+
* so `2d6+3` is `(2d6)+3` and `4d6kh3*2` is `(4d6kh3)*2`. The `vs` operator
|
|
82
|
+
* binds loosest of all (2) and has its own {@link VersusNode}.
|
|
83
|
+
*
|
|
84
|
+
* `/` and `%` throw `DIVISION_BY_ZERO` / `MODULO_BY_ZERO` on a zero right
|
|
85
|
+
* side; any operator producing a non-finite total throws `NON_FINITE_RESULT`.
|
|
86
|
+
* Division is not rounded — wrap it in `floor()` if you need an integer.
|
|
87
|
+
*
|
|
88
|
+
* @category AST
|
|
45
89
|
*/
|
|
46
90
|
export type BinaryOpNode = NodeSpan & {
|
|
47
|
-
type: 'BinaryOp';
|
|
48
|
-
operator: '+' | '-' | '*' | '/' | '%' | '**';
|
|
49
|
-
left: ASTNode;
|
|
50
|
-
right: ASTNode;
|
|
91
|
+
readonly type: 'BinaryOp';
|
|
92
|
+
readonly operator: '+' | '-' | '*' | '/' | '%' | '**';
|
|
93
|
+
readonly left: ASTNode;
|
|
94
|
+
readonly right: ASTNode;
|
|
51
95
|
};
|
|
52
96
|
/**
|
|
53
|
-
* Unary operation node.
|
|
97
|
+
* Unary operation node — prefix negation, the only unary operator.
|
|
98
|
+
*
|
|
99
|
+
* Binding power 25 sits between multiplication and power, so `-1d4` is
|
|
100
|
+
* `-(1d4)` (negate the roll) rather than `(-1)d4`, while `-2**2` is `-(2**2)`.
|
|
101
|
+
*
|
|
102
|
+
* @category AST
|
|
54
103
|
*/
|
|
55
104
|
export type UnaryOpNode = NodeSpan & {
|
|
56
|
-
type: 'UnaryOp';
|
|
57
|
-
operator: '-';
|
|
58
|
-
operand: ASTNode;
|
|
105
|
+
readonly type: 'UnaryOp';
|
|
106
|
+
readonly operator: '-';
|
|
107
|
+
readonly operand: ASTNode;
|
|
59
108
|
};
|
|
60
109
|
/**
|
|
61
|
-
* Keep/drop modifier node
|
|
110
|
+
* Keep/drop modifier node (`khN`, `klN`, `dhN`, `dlN`, and the `kN`
|
|
111
|
+
* shorthand for `khN`).
|
|
112
|
+
*
|
|
62
113
|
* Wraps a dice expression with keep highest/lowest or drop highest/lowest.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
114
|
+
* An omitted count defaults to 1, so `4d6kh` is `4d6kh1`. `count` is a full
|
|
115
|
+
* sub-expression, so `4d6kh(1d2)` is legal — and its dice are drawn *before*
|
|
116
|
+
* the pool.
|
|
117
|
+
*
|
|
118
|
+
* Chained modifiers (`4d6kh3dl1`) do not nest: the evaluator flattens the
|
|
119
|
+
* chain and applies each spec independently to the same pool, unioning the
|
|
120
|
+
* dropped sets, which is the Roll20 rule.
|
|
121
|
+
*
|
|
122
|
+
* @category AST
|
|
123
|
+
*/
|
|
124
|
+
export type KeepDropNode = NodeSpan & {
|
|
125
|
+
readonly type: 'KeepDrop';
|
|
126
|
+
readonly kind: 'keep' | 'drop';
|
|
127
|
+
readonly selector: 'highest' | 'lowest';
|
|
128
|
+
readonly count: ASTNode;
|
|
129
|
+
readonly target: ASTNode;
|
|
70
130
|
};
|
|
71
131
|
/**
|
|
72
132
|
* Exploding dice node (`!`, `!!`, `!p`, `!>Y`).
|
|
73
133
|
* Wraps a dice expression with a standard, compounding, or penetrating
|
|
74
134
|
* explosion. An absent `threshold` means "explode on the die's maximum face".
|
|
135
|
+
*
|
|
136
|
+
* Thresholds accept any comparator. In notation like `1d10!=10` (the
|
|
137
|
+
* Storyteller "10-again" rule) the `!` is the explode marker and `=10` the
|
|
138
|
+
* equality threshold — there is no `!=` comparator. Per-die explosion count
|
|
139
|
+
* is capped by `EvaluationOptions.maxExplodeIterations`.
|
|
140
|
+
*
|
|
141
|
+
* @category AST
|
|
75
142
|
*/
|
|
76
143
|
export type ExplodeNode = NodeSpan & {
|
|
77
|
-
type: 'Explode';
|
|
78
|
-
variant: 'standard' | 'compound' | 'penetrating';
|
|
79
|
-
threshold?: ComparePoint;
|
|
80
|
-
target: ASTNode;
|
|
144
|
+
readonly type: 'Explode';
|
|
145
|
+
readonly variant: 'standard' | 'compound' | 'penetrating';
|
|
146
|
+
readonly threshold?: ComparePoint;
|
|
147
|
+
readonly target: ASTNode;
|
|
81
148
|
};
|
|
82
149
|
/**
|
|
83
150
|
* Reroll node (`r<COND>`, `ro<COND>`).
|
|
84
151
|
* Re-rolls dice that match a comparison condition. `once: true` for `ro`
|
|
85
152
|
* keeps the second result regardless of match; `once: false` for `r`
|
|
86
|
-
* re-rolls recursively until the condition no longer matches
|
|
153
|
+
* re-rolls recursively until the condition no longer matches, bounded by
|
|
154
|
+
* `EvaluationOptions.maxRerollIterations`.
|
|
155
|
+
*
|
|
156
|
+
* @category AST
|
|
87
157
|
*/
|
|
88
158
|
export type RerollNode = NodeSpan & {
|
|
89
|
-
type: 'Reroll';
|
|
90
|
-
once: boolean;
|
|
91
|
-
condition: ComparePoint;
|
|
159
|
+
readonly type: 'Reroll';
|
|
160
|
+
readonly once: boolean;
|
|
161
|
+
readonly condition: ComparePoint;
|
|
162
|
+
readonly target: ASTNode;
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* Per-die clamp node (`minN`, `maxN`).
|
|
166
|
+
* `4d6min2` raises every die below 2 to 2; `4d6max5` lowers every die above
|
|
167
|
+
* 5 to 5. The clamp rewrites `DieResult.result` (preserving the raw face in
|
|
168
|
+
* `initialResult`) and re-sums the pool; `critical`/`fumble` keep reflecting
|
|
169
|
+
* the natural face. The bound is a full sub-expression (`4d6min(1d2)`),
|
|
170
|
+
* drawn *after* the pool like other threshold arguments.
|
|
171
|
+
*
|
|
172
|
+
* Chained bounds nest and apply left to right: `4d6min2max5` clamps into
|
|
173
|
+
* [2, 5].
|
|
174
|
+
*
|
|
175
|
+
* @category AST
|
|
176
|
+
*/
|
|
177
|
+
export type DieBoundNode = NodeSpan & {
|
|
178
|
+
type: 'DieBound';
|
|
179
|
+
bound: 'min' | 'max';
|
|
180
|
+
value: ASTNode;
|
|
92
181
|
target: ASTNode;
|
|
93
182
|
};
|
|
94
183
|
/**
|
|
@@ -99,12 +188,14 @@ export type RerollNode = NodeSpan & {
|
|
|
99
188
|
* `SuccessCountNode` may not be wrapped by any postfix modifier, binary
|
|
100
189
|
* operator, unary operator, versus operand, or function argument. The
|
|
101
190
|
* `failThreshold` accepts any `CompareOp`; bare `fN` defaults to `operator: '='`.
|
|
191
|
+
*
|
|
192
|
+
* @category AST
|
|
102
193
|
*/
|
|
103
194
|
export type SuccessCountNode = NodeSpan & {
|
|
104
|
-
type: 'SuccessCount';
|
|
105
|
-
target: ASTNode;
|
|
106
|
-
threshold: ComparePoint;
|
|
107
|
-
failThreshold?: ComparePoint;
|
|
195
|
+
readonly type: 'SuccessCount';
|
|
196
|
+
readonly target: ASTNode;
|
|
197
|
+
readonly threshold: ComparePoint;
|
|
198
|
+
readonly failThreshold?: ComparePoint;
|
|
108
199
|
};
|
|
109
200
|
/**
|
|
110
201
|
* Versus node (`<roll> vs <dc>`) — PF2e Degrees of Success.
|
|
@@ -115,11 +206,13 @@ export type SuccessCountNode = NodeSpan & {
|
|
|
115
206
|
* roll side. Lowest-precedence operator — chaining (`a vs b vs c`) is
|
|
116
207
|
* rejected at parse time; nesting via parens (`a vs (b vs c)`) is rejected
|
|
117
208
|
* at evaluation time.
|
|
209
|
+
*
|
|
210
|
+
* @category AST
|
|
118
211
|
*/
|
|
119
212
|
export type VersusNode = NodeSpan & {
|
|
120
|
-
type: 'Versus';
|
|
121
|
-
roll: ASTNode;
|
|
122
|
-
dc: ASTNode;
|
|
213
|
+
readonly type: 'Versus';
|
|
214
|
+
readonly roll: ASTNode;
|
|
215
|
+
readonly dc: ASTNode;
|
|
123
216
|
};
|
|
124
217
|
/**
|
|
125
218
|
* Math function call node (`floor(expr)`, `max(a, b, ...)`, etc.).
|
|
@@ -128,11 +221,16 @@ export type VersusNode = NodeSpan & {
|
|
|
128
221
|
* variadic functions `max`, `min` (minimum 2 args). Arity is validated at
|
|
129
222
|
* parse time against a static table; by the time the evaluator sees a
|
|
130
223
|
* `FunctionCallNode`, `args.length` is guaranteed to match the function.
|
|
224
|
+
*
|
|
225
|
+
* `max`/`min` are variadic with no upper bound, so `max(1d20, 1d20, 1d20)`
|
|
226
|
+
* is a valid three-way advantage roll.
|
|
227
|
+
*
|
|
228
|
+
* @category AST
|
|
131
229
|
*/
|
|
132
230
|
export type FunctionCallNode = NodeSpan & {
|
|
133
|
-
type: 'FunctionCall';
|
|
134
|
-
name: string;
|
|
135
|
-
args: ASTNode[];
|
|
231
|
+
readonly type: 'FunctionCall';
|
|
232
|
+
readonly name: string;
|
|
233
|
+
readonly args: readonly ASTNode[];
|
|
136
234
|
};
|
|
137
235
|
/**
|
|
138
236
|
* Parenthesized group node (`(<expr>)`).
|
|
@@ -141,10 +239,12 @@ export type FunctionCallNode = NodeSpan & {
|
|
|
141
239
|
* `RollResult.expression` and `RollResult.rendered` round-trip through
|
|
142
240
|
* `parse` without losing precedence information. Semantically transparent:
|
|
143
241
|
* evaluation returns the inner expression's value unchanged.
|
|
242
|
+
*
|
|
243
|
+
* @category AST
|
|
144
244
|
*/
|
|
145
245
|
export type GroupedNode = NodeSpan & {
|
|
146
|
-
type: 'Grouped';
|
|
147
|
-
expression: ASTNode;
|
|
246
|
+
readonly type: 'Grouped';
|
|
247
|
+
readonly expression: ASTNode;
|
|
148
248
|
};
|
|
149
249
|
/**
|
|
150
250
|
* Variable reference node (`@name` or `@{name with spaces}`).
|
|
@@ -154,10 +254,15 @@ export type GroupedNode = NodeSpan & {
|
|
|
154
254
|
* lexer preserves case in the `AT` token's `value`, distinct from other
|
|
155
255
|
* identifier tokens which lowercase. Leaf node — no LED, never wraps a
|
|
156
256
|
* sub-expression.
|
|
257
|
+
*
|
|
258
|
+
* A name missing from `context` throws `UNDEFINED_VARIABLE` unless
|
|
259
|
+
* `onMissingVariable: 'zero'` is set.
|
|
260
|
+
*
|
|
261
|
+
* @category AST
|
|
157
262
|
*/
|
|
158
263
|
export type VariableNode = NodeSpan & {
|
|
159
|
-
type: 'Variable';
|
|
160
|
-
name: string;
|
|
264
|
+
readonly type: 'Variable';
|
|
265
|
+
readonly name: string;
|
|
161
266
|
};
|
|
162
267
|
/**
|
|
163
268
|
* Grouped-roll node (`{expr}`, `{expr1, expr2, ...}`).
|
|
@@ -167,10 +272,12 @@ export type VariableNode = NodeSpan & {
|
|
|
167
272
|
* with the sub-roll count. `expressions.length === 1` is a passthrough
|
|
168
273
|
* (flat-pool when wrapped by keep/drop); `expressions.length >= 2` treats
|
|
169
274
|
* each sub-roll's subtotal as a compound die for keep/drop selection.
|
|
275
|
+
*
|
|
276
|
+
* @category AST
|
|
170
277
|
*/
|
|
171
278
|
export type GroupNode = NodeSpan & {
|
|
172
|
-
type: 'Group';
|
|
173
|
-
expressions: ASTNode[];
|
|
279
|
+
readonly type: 'Group';
|
|
280
|
+
readonly expressions: readonly ASTNode[];
|
|
174
281
|
};
|
|
175
282
|
/**
|
|
176
283
|
* Sort modifier node (`s`, `sa`, `sd`).
|
|
@@ -180,189 +287,241 @@ export type GroupNode = NodeSpan & {
|
|
|
180
287
|
* `successes`/`failures`, or any die-level flag (`kept`/`dropped`/
|
|
181
288
|
* `critical`/`fumble`). Dropped dice retain their `dropped` flag and
|
|
182
289
|
* appear in sorted position alongside kept dice.
|
|
290
|
+
*
|
|
291
|
+
* `s` and `sa` both mean ascending; `sd` is descending.
|
|
292
|
+
*
|
|
293
|
+
* @category AST
|
|
183
294
|
*/
|
|
184
295
|
export type SortNode = NodeSpan & {
|
|
185
|
-
type: 'Sort';
|
|
186
|
-
order: 'ascending' | 'descending';
|
|
187
|
-
target: ASTNode;
|
|
296
|
+
readonly type: 'Sort';
|
|
297
|
+
readonly order: 'ascending' | 'descending';
|
|
298
|
+
readonly target: ASTNode;
|
|
188
299
|
};
|
|
189
300
|
/**
|
|
190
|
-
* Sentinel for bare `cs` / `cf` without a ComparePoint. Resolved
|
|
191
|
-
*
|
|
192
|
-
*
|
|
301
|
+
* Sentinel for bare `cs` / `cf` without a ComparePoint. Resolved at
|
|
302
|
+
* evaluation time against each die's own `sides` and its natural face
|
|
303
|
+
* (`initialResult ?? result`): critical when that face equals `sides`,
|
|
304
|
+
* fumble when it equals 1.
|
|
305
|
+
*
|
|
306
|
+
* @category AST
|
|
193
307
|
*/
|
|
194
308
|
export type CritThreshold = ComparePoint | 'default';
|
|
195
309
|
/**
|
|
196
310
|
* Critical threshold modifier node (`cs`, `cf`).
|
|
197
311
|
*
|
|
198
|
-
* Overrides the default `critical`/`fumble` flag logic for
|
|
199
|
-
*
|
|
312
|
+
* Overrides the default `critical`/`fumble` flag logic for `target`'s dice
|
|
313
|
+
* pool — including dice an enclosing `!` or `r` adds to it afterwards, which
|
|
314
|
+
* `target` never produced. Bare `cs`/`cf` uses the `'default'` sentinel
|
|
200
315
|
* (max face / 1). Custom thresholds accept any ComparePoint. Chaining
|
|
201
316
|
* collapses into a single node — `1d20cs=20cs=1cf>18` has two success
|
|
202
317
|
* and one fail threshold. Display-only: does not change `total`,
|
|
203
318
|
* explosion triggers, or success counting. `cs` and `cf` are independent
|
|
204
319
|
* overrides — a side with no explicit thresholds keeps the default rule
|
|
205
320
|
* (the evaluator substitutes the `'default'` sentinel at apply time).
|
|
321
|
+
*
|
|
322
|
+
* @category AST
|
|
206
323
|
*/
|
|
207
324
|
export type CritThresholdNode = NodeSpan & {
|
|
208
|
-
type: 'CritThreshold';
|
|
209
|
-
successThresholds: CritThreshold[];
|
|
210
|
-
failThresholds: CritThreshold[];
|
|
211
|
-
target: ASTNode;
|
|
325
|
+
readonly type: 'CritThreshold';
|
|
326
|
+
readonly successThresholds: readonly CritThreshold[];
|
|
327
|
+
readonly failThresholds: readonly CritThreshold[];
|
|
328
|
+
readonly target: ASTNode;
|
|
212
329
|
};
|
|
213
330
|
/**
|
|
214
|
-
*
|
|
331
|
+
* Discriminated union of all 17 AST node types — what {@link parse} returns
|
|
332
|
+
* and what {@link evaluate} consumes.
|
|
333
|
+
*
|
|
334
|
+
* Narrow it either by switching on `node.type` (PascalCase discriminants, as
|
|
335
|
+
* opposed to the camelCase ones on {@link RollPart}) or with the exported
|
|
336
|
+
* type guards: {@link isLiteral}, {@link isDice}, {@link isFateDice},
|
|
337
|
+
* {@link isBinaryOp}, {@link isUnaryOp}, {@link isKeepDrop},
|
|
338
|
+
* {@link isExplode}, {@link isReroll}, {@link isDieBound},
|
|
339
|
+
* {@link isSuccessCount}, {@link isVersus}, {@link isFunctionCall},
|
|
340
|
+
* {@link isGrouped}, {@link isVariable}, {@link isGroup}, {@link isSort},
|
|
341
|
+
* {@link isCritThreshold}.
|
|
342
|
+
*
|
|
343
|
+
* Nodes are plain data with no methods, so they are structurally clonable and
|
|
344
|
+
* safe to cache. Every parser-produced node carries a {@link NodeSpan}.
|
|
345
|
+
*
|
|
346
|
+
* @example A guard-based walker — count the dice an expression can roll
|
|
347
|
+
* ```typescript
|
|
348
|
+
* import {
|
|
349
|
+
* type ASTNode, isBinaryOp, isDice, isFateDice, isGroup, isGrouped,
|
|
350
|
+
* isFunctionCall, isLiteral, isUnaryOp, isVersus, parse,
|
|
351
|
+
* } from 'roll-parser';
|
|
352
|
+
*
|
|
353
|
+
* function countPools(node: ASTNode): number {
|
|
354
|
+
* if (isDice(node) || isFateDice(node)) return 1;
|
|
355
|
+
* if (isLiteral(node)) return 0;
|
|
356
|
+
* if (isBinaryOp(node)) return countPools(node.left) + countPools(node.right);
|
|
357
|
+
* if (isUnaryOp(node)) return countPools(node.operand);
|
|
358
|
+
* if (isGrouped(node)) return countPools(node.expression);
|
|
359
|
+
* if (isVersus(node)) return countPools(node.roll) + countPools(node.dc);
|
|
360
|
+
* if (isGroup(node)) return node.expressions.reduce((n, e) => n + countPools(e), 0);
|
|
361
|
+
* if (isFunctionCall(node)) return node.args.reduce((n, a) => n + countPools(a), 0);
|
|
362
|
+
* // Every remaining variant is a postfix modifier wrapping `target`.
|
|
363
|
+
* return 'target' in node ? countPools(node.target) : 0;
|
|
364
|
+
* }
|
|
365
|
+
*
|
|
366
|
+
* countPools(parse('2d6+3')); // 1
|
|
367
|
+
* countPools(parse('{2d20kh1+5, 3d8!}kh1')); // 2
|
|
368
|
+
* countPools(parse('1+2')); // 0
|
|
369
|
+
* ```
|
|
370
|
+
*
|
|
371
|
+
* @category AST
|
|
215
372
|
*/
|
|
216
|
-
export type ASTNode = LiteralNode | DiceNode | FateDiceNode | BinaryOpNode | UnaryOpNode |
|
|
373
|
+
export type ASTNode = LiteralNode | DiceNode | FateDiceNode | BinaryOpNode | UnaryOpNode | KeepDropNode | ExplodeNode | RerollNode | DieBoundNode | SuccessCountNode | VersusNode | FunctionCallNode | GroupedNode | VariableNode | GroupNode | SortNode | CritThresholdNode;
|
|
217
374
|
/**
|
|
218
|
-
*
|
|
375
|
+
* Narrows an {@link ASTNode} to a numeric literal (`3`, `2.5`).
|
|
376
|
+
*
|
|
377
|
+
* @param node - Any AST node
|
|
378
|
+
* @returns `true` when `node` is a {@link LiteralNode}
|
|
379
|
+
*
|
|
380
|
+
* @category AST
|
|
219
381
|
*/
|
|
220
382
|
export declare function isLiteral(node: ASTNode): node is LiteralNode;
|
|
221
383
|
/**
|
|
222
|
-
*
|
|
384
|
+
* Narrows an {@link ASTNode} to a dice pool (`2d6`, `d%`, `(1d4)d6`).
|
|
385
|
+
*
|
|
386
|
+
* @param node - Any AST node
|
|
387
|
+
* @returns `true` when `node` is a {@link DiceNode}
|
|
388
|
+
*
|
|
389
|
+
* @category AST
|
|
223
390
|
*/
|
|
224
391
|
export declare function isDice(node: ASTNode): node is DiceNode;
|
|
225
392
|
/**
|
|
226
|
-
*
|
|
393
|
+
* Narrows an {@link ASTNode} to a Fate/Fudge pool (`4dF`).
|
|
394
|
+
*
|
|
395
|
+
* @param node - Any AST node
|
|
396
|
+
* @returns `true` when `node` is a {@link FateDiceNode}
|
|
397
|
+
*
|
|
398
|
+
* @category AST
|
|
227
399
|
*/
|
|
228
400
|
export declare function isFateDice(node: ASTNode): node is FateDiceNode;
|
|
229
401
|
/**
|
|
230
|
-
*
|
|
402
|
+
* Narrows an {@link ASTNode} to an arithmetic operation (`+ - * / % **`).
|
|
403
|
+
*
|
|
404
|
+
* @param node - Any AST node
|
|
405
|
+
* @returns `true` when `node` is a {@link BinaryOpNode}
|
|
406
|
+
*
|
|
407
|
+
* @category AST
|
|
231
408
|
*/
|
|
232
409
|
export declare function isBinaryOp(node: ASTNode): node is BinaryOpNode;
|
|
233
410
|
/**
|
|
234
|
-
*
|
|
411
|
+
* Narrows an {@link ASTNode} to a prefix negation (`-1d4`).
|
|
412
|
+
*
|
|
413
|
+
* @param node - Any AST node
|
|
414
|
+
* @returns `true` when `node` is a {@link UnaryOpNode}
|
|
415
|
+
*
|
|
416
|
+
* @category AST
|
|
235
417
|
*/
|
|
236
418
|
export declare function isUnaryOp(node: ASTNode): node is UnaryOpNode;
|
|
237
419
|
/**
|
|
238
|
-
*
|
|
420
|
+
* Narrows an {@link ASTNode} to a keep/drop modifier (`kh`, `kl`, `dh`, `dl`).
|
|
421
|
+
*
|
|
422
|
+
* @param node - Any AST node
|
|
423
|
+
* @returns `true` when `node` is a {@link KeepDropNode}
|
|
424
|
+
*
|
|
425
|
+
* @category AST
|
|
239
426
|
*/
|
|
240
|
-
export declare function
|
|
427
|
+
export declare function isKeepDrop(node: ASTNode): node is KeepDropNode;
|
|
241
428
|
/**
|
|
242
|
-
*
|
|
429
|
+
* Narrows an {@link ASTNode} to an explosion (`!`, `!!`, `!p`).
|
|
430
|
+
*
|
|
431
|
+
* @param node - Any AST node
|
|
432
|
+
* @returns `true` when `node` is a {@link ExplodeNode}
|
|
433
|
+
*
|
|
434
|
+
* @category AST
|
|
243
435
|
*/
|
|
244
436
|
export declare function isExplode(node: ASTNode): node is ExplodeNode;
|
|
245
437
|
/**
|
|
246
|
-
*
|
|
438
|
+
* Narrows an {@link ASTNode} to a reroll (`r`, `ro`).
|
|
439
|
+
*
|
|
440
|
+
* @param node - Any AST node
|
|
441
|
+
* @returns `true` when `node` is a {@link RerollNode}
|
|
442
|
+
*
|
|
443
|
+
* @category AST
|
|
247
444
|
*/
|
|
248
445
|
export declare function isReroll(node: ASTNode): node is RerollNode;
|
|
249
446
|
/**
|
|
250
|
-
*
|
|
447
|
+
* Narrows an {@link ASTNode} to a success count (`>=6`, with optional `f1`).
|
|
448
|
+
*
|
|
449
|
+
* @param node - Any AST node
|
|
450
|
+
* @returns `true` when `node` is a {@link SuccessCountNode}
|
|
451
|
+
*
|
|
452
|
+
* @category AST
|
|
251
453
|
*/
|
|
252
454
|
export declare function isSuccessCount(node: ASTNode): node is SuccessCountNode;
|
|
253
455
|
/**
|
|
254
|
-
*
|
|
456
|
+
* Narrows an {@link ASTNode} to a PF2e degree-of-success check (`vs`).
|
|
457
|
+
*
|
|
458
|
+
* @param node - Any AST node
|
|
459
|
+
* @returns `true` when `node` is a {@link VersusNode}
|
|
460
|
+
*
|
|
461
|
+
* @category AST
|
|
255
462
|
*/
|
|
256
463
|
export declare function isVersus(node: ASTNode): node is VersusNode;
|
|
257
464
|
/**
|
|
258
|
-
*
|
|
465
|
+
* Narrows an {@link ASTNode} to a math function call (`floor`, `max`, …).
|
|
466
|
+
*
|
|
467
|
+
* @param node - Any AST node
|
|
468
|
+
* @returns `true` when `node` is a {@link FunctionCallNode}
|
|
469
|
+
*
|
|
470
|
+
* @category AST
|
|
259
471
|
*/
|
|
260
472
|
export declare function isFunctionCall(node: ASTNode): node is FunctionCallNode;
|
|
261
473
|
/**
|
|
262
|
-
*
|
|
474
|
+
* Narrows an {@link ASTNode} to a parenthesized group (`(1d6+2)`).
|
|
475
|
+
*
|
|
476
|
+
* @param node - Any AST node
|
|
477
|
+
* @returns `true` when `node` is a {@link GroupedNode}
|
|
478
|
+
*
|
|
479
|
+
* @category AST
|
|
263
480
|
*/
|
|
264
481
|
export declare function isGrouped(node: ASTNode): node is GroupedNode;
|
|
265
482
|
/**
|
|
266
|
-
*
|
|
483
|
+
* Narrows an {@link ASTNode} to a variable reference (`@str`).
|
|
484
|
+
*
|
|
485
|
+
* @param node - Any AST node
|
|
486
|
+
* @returns `true` when `node` is a {@link VariableNode}
|
|
487
|
+
*
|
|
488
|
+
* @category AST
|
|
267
489
|
*/
|
|
268
490
|
export declare function isVariable(node: ASTNode): node is VariableNode;
|
|
269
491
|
/**
|
|
270
|
-
*
|
|
271
|
-
*/
|
|
272
|
-
export declare function isGroup(node: ASTNode): node is GroupNode;
|
|
273
|
-
/**
|
|
274
|
-
* Type guard for SortNode.
|
|
275
|
-
*/
|
|
276
|
-
export declare function isSort(node: ASTNode): node is SortNode;
|
|
277
|
-
/**
|
|
278
|
-
* Type guard for CritThresholdNode.
|
|
279
|
-
*/
|
|
280
|
-
export declare function isCritThreshold(node: ASTNode): node is CritThresholdNode;
|
|
281
|
-
/**
|
|
282
|
-
* Wrapper kinds that `unwrapTransparent` can peel.
|
|
492
|
+
* Narrows an {@link ASTNode} to a braced grouped roll (`{1d8, 1d10}`).
|
|
283
493
|
*
|
|
284
|
-
*
|
|
285
|
-
* `
|
|
286
|
-
* deciding whether to reject a `Group` target — they preserve `containsDicePool`'s
|
|
287
|
-
* answer for whatever they wrap. They are NOT transparent for "is this a
|
|
288
|
-
* `SuccessCount`?" or "is this a `Versus`?", because the parsers that build
|
|
289
|
-
* those wrappers already reject `SuccessCount`/`Versus` operands upstream.
|
|
290
|
-
*/
|
|
291
|
-
export type TransparentWrapperKind = 'Grouped' | 'Modifier' | 'Sort' | 'CritThreshold';
|
|
292
|
-
/**
|
|
293
|
-
* Walks `node` while its `.type` is in `kinds`, returning the first descendant
|
|
294
|
-
* that is not one of the listed wrappers. Each caller picks the subset that
|
|
295
|
-
* matches its rejection semantics — see `TransparentWrapperKind` for guidance.
|
|
494
|
+
* @param node - Any AST node
|
|
495
|
+
* @returns `true` when `node` is a {@link GroupNode}
|
|
296
496
|
*
|
|
297
|
-
*
|
|
298
|
-
* an operand violates a rule (e.g., `Group` cannot be the target of `cs`/`cf`,
|
|
299
|
-
* even when wrapped in `Modifier` like `{1d6}kh1cs>5`).
|
|
497
|
+
* @category AST
|
|
300
498
|
*/
|
|
301
|
-
export declare function
|
|
499
|
+
export declare function isGroup(node: ASTNode): node is GroupNode;
|
|
302
500
|
/**
|
|
303
|
-
*
|
|
304
|
-
* `Dice`, `FateDice`, or a chained pool modifier (`Modifier` / `Explode` /
|
|
305
|
-
* `Reroll`). Does NOT recurse through arithmetic wrappers (`BinaryOp`,
|
|
306
|
-
* `UnaryOp`, `FunctionCall`), so `(1d6+5)` and `floor(1d6/2)` are rejected.
|
|
501
|
+
* Narrows an {@link ASTNode} to a sort modifier (`s`, `sa`, `sd`).
|
|
307
502
|
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
|
|
312
|
-
export declare function containsDicePool(node: ASTNode): boolean;
|
|
313
|
-
/**
|
|
314
|
-
* Deeper variant of `containsDicePool` that recurses through arithmetic and
|
|
315
|
-
* function wrappers. Used from the `Group` case above (ordinary parenthesized
|
|
316
|
-
* arithmetic `(1d6+5)kh1` must still reject, so the shallow `containsDicePool`
|
|
317
|
-
* handles those directly) and from the Sort parser guard (sort accepts
|
|
318
|
-
* `(1d6+2d8)s` per Stage 3 spec).
|
|
503
|
+
* @param node - Any AST node
|
|
504
|
+
* @returns `true` when `node` is a {@link SortNode}
|
|
505
|
+
*
|
|
506
|
+
* @category AST
|
|
319
507
|
*/
|
|
320
|
-
export declare function
|
|
508
|
+
export declare function isSort(node: ASTNode): node is SortNode;
|
|
321
509
|
/**
|
|
322
|
-
*
|
|
323
|
-
* pool. Walks through chained pool modifiers (`Modifier` / `Explode` /
|
|
324
|
-
* `Reroll`) but not arithmetic wrappers — callers should run
|
|
325
|
-
* `containsDicePool` first to reject those.
|
|
510
|
+
* Narrows an {@link ASTNode} to a crit-threshold override (`cs`, `cf`).
|
|
326
511
|
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
* parse-time rejection is preferred over a silent evaluator no-op.
|
|
512
|
+
* @param node - Any AST node
|
|
513
|
+
* @returns `true` when `node` is a {@link CritThresholdNode}
|
|
330
514
|
*
|
|
331
|
-
*
|
|
332
|
-
* `containsDicePool`'s deep walk through the same case — otherwise
|
|
333
|
-
* `{4dF+1d6}cf` slips past the bare-Fate guard and the default fumble
|
|
334
|
-
* check (`result === 1`) flips `+1` faces into fumbles.
|
|
515
|
+
* @category AST
|
|
335
516
|
*/
|
|
336
|
-
export declare function
|
|
517
|
+
export declare function isCritThreshold(node: ASTNode): node is CritThresholdNode;
|
|
337
518
|
/**
|
|
338
|
-
*
|
|
339
|
-
* function wrappers. Mirrors `deepContainsDicePool`. Used from
|
|
340
|
-
* `containsFatePool`'s `Group` case so single-sub-roll groups containing
|
|
341
|
-
* arithmetic-wrapped Fate (`{4dF+1d6}cf`) still trip the bare-Fate guard.
|
|
519
|
+
* Narrows an {@link ASTNode} to a per-die clamp (`min2`, `max5`).
|
|
342
520
|
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
* intentionally stays Group-internal.
|
|
346
|
-
*/
|
|
347
|
-
export declare function deepContainsFatePool(node: ASTNode): boolean;
|
|
348
|
-
/**
|
|
349
|
-
* Deep-walks a node to find any descendant `Group` with two or more
|
|
350
|
-
* sub-expressions. Used by `rejectGroupTarget`'s single-sub-roll
|
|
351
|
-
* passthrough so a multi-sub Group buried under arithmetic
|
|
352
|
-
* (`{{1d6,2d8}+0}cs>5`), function calls (`{abs({1d6,2d8})}cs>5`), or any
|
|
353
|
-
* other non-transparent wrapper still rejects with the same error code.
|
|
521
|
+
* @param node - Any AST node
|
|
522
|
+
* @returns `true` when `node` is a {@link DieBoundNode}
|
|
354
523
|
*
|
|
355
|
-
*
|
|
356
|
-
* `Grouped`/`Modifier`/`Sort`/`CritThreshold` — a multi-sub Group cloaked
|
|
357
|
-
* in a `BinaryOp`/`UnaryOp`/`FunctionCall` revives issue #97.
|
|
358
|
-
*/
|
|
359
|
-
export declare function containsMultiSubGroup(node: ASTNode): boolean;
|
|
360
|
-
/**
|
|
361
|
-
* Deep-walks a node to find any descendant `Versus`. Used by
|
|
362
|
-
* `rejectVersusTarget`'s single-sub-roll Group passthrough so a buried
|
|
363
|
-
* Versus (`{1+(1d20 vs 15)}cs>18`, `{abs(1d20 vs 15)}cs>18`,
|
|
364
|
-
* `{-(1d20 vs 15)}kh1`) still rejects with `NESTED_VERSUS` instead of
|
|
365
|
-
* silently dropping `versusMetadata` at the modifier consumer site.
|
|
524
|
+
* @category AST
|
|
366
525
|
*/
|
|
367
|
-
export declare function
|
|
526
|
+
export declare function isDieBound(node: ASTNode): node is DieBoundNode;
|
|
368
527
|
//# sourceMappingURL=ast.d.ts.map
|