roll-parser 2.3.2 → 3.0.0-beta.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.
Files changed (109) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/README.md +175 -170
  3. package/dist/cli/args.d.ts +35 -0
  4. package/dist/cli/args.d.ts.map +1 -0
  5. package/dist/cli/format.d.ts +20 -0
  6. package/dist/cli/format.d.ts.map +1 -0
  7. package/dist/cli/index.d.ts +8 -0
  8. package/dist/cli/index.d.ts.map +1 -0
  9. package/dist/cli.js +2608 -0
  10. package/dist/cli.js.map +28 -0
  11. package/dist/errors.d.ts +42 -0
  12. package/dist/errors.d.ts.map +1 -0
  13. package/dist/evaluator/evaluator.d.ts +121 -0
  14. package/dist/evaluator/evaluator.d.ts.map +1 -0
  15. package/dist/evaluator/index.d.ts +8 -0
  16. package/dist/evaluator/index.d.ts.map +1 -0
  17. package/dist/evaluator/modifiers/compare.d.ts +15 -0
  18. package/dist/evaluator/modifiers/compare.d.ts.map +1 -0
  19. package/dist/evaluator/modifiers/crit-threshold.d.ts +28 -0
  20. package/dist/evaluator/modifiers/crit-threshold.d.ts.map +1 -0
  21. package/dist/evaluator/modifiers/explode.d.ts +48 -0
  22. package/dist/evaluator/modifiers/explode.d.ts.map +1 -0
  23. package/dist/evaluator/modifiers/keep-drop.d.ts +44 -0
  24. package/dist/evaluator/modifiers/keep-drop.d.ts.map +1 -0
  25. package/dist/evaluator/modifiers/reroll.d.ts +33 -0
  26. package/dist/evaluator/modifiers/reroll.d.ts.map +1 -0
  27. package/dist/evaluator/modifiers/sort.d.ts +23 -0
  28. package/dist/evaluator/modifiers/sort.d.ts.map +1 -0
  29. package/dist/evaluator/modifiers/success-count.d.ts +28 -0
  30. package/dist/evaluator/modifiers/success-count.d.ts.map +1 -0
  31. package/dist/index.d.ts +20 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +2560 -0
  34. package/dist/index.js.map +26 -0
  35. package/dist/lexer/lexer.d.ts +90 -0
  36. package/dist/lexer/lexer.d.ts.map +1 -0
  37. package/dist/lexer/tokens.d.ts +107 -0
  38. package/dist/lexer/tokens.d.ts.map +1 -0
  39. package/dist/parser/ast.d.ts +368 -0
  40. package/dist/parser/ast.d.ts.map +1 -0
  41. package/dist/parser/parser.d.ts +145 -0
  42. package/dist/parser/parser.d.ts.map +1 -0
  43. package/dist/rng/index.d.ts +8 -0
  44. package/dist/rng/index.d.ts.map +1 -0
  45. package/dist/rng/mock.d.ts +36 -0
  46. package/dist/rng/mock.d.ts.map +1 -0
  47. package/dist/rng/seeded.d.ts +40 -0
  48. package/dist/rng/seeded.d.ts.map +1 -0
  49. package/dist/rng/types.d.ts +24 -0
  50. package/dist/rng/types.d.ts.map +1 -0
  51. package/dist/roll.d.ts +51 -0
  52. package/dist/roll.d.ts.map +1 -0
  53. package/dist/testing.d.ts +11 -0
  54. package/dist/testing.d.ts.map +1 -0
  55. package/dist/testing.js +41 -0
  56. package/dist/testing.js.map +11 -0
  57. package/dist/types.d.ts +246 -0
  58. package/dist/types.d.ts.map +1 -0
  59. package/package.json +66 -43
  60. package/src/cli/args.ts +73 -0
  61. package/src/cli/format.ts +49 -0
  62. package/src/cli/index.ts +92 -0
  63. package/src/errors.ts +90 -0
  64. package/src/evaluator/evaluator.ts +1552 -0
  65. package/src/evaluator/index.ts +14 -0
  66. package/src/evaluator/modifiers/compare.ts +29 -0
  67. package/src/evaluator/modifiers/crit-threshold.ts +59 -0
  68. package/src/evaluator/modifiers/explode.ts +219 -0
  69. package/src/evaluator/modifiers/keep-drop.ts +151 -0
  70. package/src/evaluator/modifiers/reroll.ts +165 -0
  71. package/src/evaluator/modifiers/sort.ts +30 -0
  72. package/src/evaluator/modifiers/success-count.ts +59 -0
  73. package/src/index.ts +90 -0
  74. package/src/lexer/lexer.ts +374 -0
  75. package/src/lexer/tokens.ts +187 -0
  76. package/src/parser/ast.ts +628 -0
  77. package/src/parser/parser.ts +1239 -0
  78. package/src/rng/index.ts +8 -0
  79. package/src/rng/mock.ts +65 -0
  80. package/src/rng/seeded.ts +157 -0
  81. package/src/rng/types.ts +25 -0
  82. package/src/roll.ts +74 -0
  83. package/src/testing.ts +16 -0
  84. package/src/types.ts +237 -0
  85. package/bin/help.txt +0 -19
  86. package/bin/roll-parser.js +0 -23
  87. package/dist/roll-parser.js +0 -697
  88. package/dist/roll-parser.min.js +0 -1
  89. package/index.js +0 -24
  90. package/src/complex/parse.js +0 -37
  91. package/src/complex/parseAndRoll.js +0 -21
  92. package/src/complex/parseAndRollClassic.js +0 -20
  93. package/src/complex/parseAndRollSimple.js +0 -19
  94. package/src/complex/parseAndRollWod.js +0 -20
  95. package/src/complex/parseClassicRoll.js +0 -22
  96. package/src/complex/parseSimpleRoll.js +0 -21
  97. package/src/complex/parseWodRoll.js +0 -21
  98. package/src/converter.js +0 -45
  99. package/src/grammar.js +0 -14
  100. package/src/mapper.js +0 -50
  101. package/src/normalizer.js +0 -51
  102. package/src/object/Result.js +0 -23
  103. package/src/object/Roll.js +0 -26
  104. package/src/object/Type.js +0 -5
  105. package/src/object/WodRoll.js +0 -29
  106. package/src/parser.js +0 -43
  107. package/src/random.js +0 -22
  108. package/src/roller.js +0 -106
  109. package/src/stringifier.js +0 -33
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Dice notation lexer with character-by-character scanning.
3
+ *
4
+ * @module lexer/lexer
5
+ */
6
+ import type { RollParserErrorCode } from '../errors.js';
7
+ import { RollParserError } from '../errors.js';
8
+ import { type Token } from './tokens.js';
9
+ /**
10
+ * Error thrown when the lexer encounters an invalid character.
11
+ */
12
+ export declare class LexerError extends RollParserError {
13
+ readonly position: number;
14
+ readonly character: string;
15
+ constructor(message: string, code: RollParserErrorCode, position: number, character: string);
16
+ }
17
+ /**
18
+ * Lexer for dice notation.
19
+ *
20
+ * Produces a stream of tokens from an input string using character-by-character
21
+ * scanning with maximal munch for multi-character tokens.
22
+ */
23
+ export declare class Lexer {
24
+ private pos;
25
+ private readonly input;
26
+ constructor(input: string);
27
+ /**
28
+ * Tokenize the entire input and return all tokens.
29
+ */
30
+ tokenize(): Token[];
31
+ /**
32
+ * Get the next token from the input.
33
+ */
34
+ nextToken(): Token;
35
+ private skipWhitespace;
36
+ private scanNumber;
37
+ /**
38
+ * Scans an identifier using full-accumulation: collects all consecutive
39
+ * alpha characters, then classifies the result against known keywords.
40
+ *
41
+ * Special cases run before/after the accumulation loop:
42
+ * - `dF` / `Df` / `dF` / `DF` produces DICE_FATE. Must be handled BEFORE
43
+ * the loop because `F` is alpha and would otherwise be greedily merged
44
+ * into identifiers like `dfkh` (from `4dFkh2`) or `dfdf` (from `dFdF`).
45
+ * Reserves the `d[fF]` prefix namespace for Fate dice.
46
+ * - Bare `d` followed by `%` produces DICE_PERCENT. `%` is not alpha so the
47
+ * accumulation loop stops naturally and the post-loop check handles it.
48
+ */
49
+ private scanIdentifier;
50
+ /**
51
+ * Scans a variable reference introduced by `@`.
52
+ *
53
+ * Two forms:
54
+ * - Bare: `@name` where `name` matches `[A-Za-z_][A-Za-z0-9_]*` (case preserved).
55
+ * - Braced: `@{name}` where `name` is any run of printable characters except
56
+ * `}` and newline (permits spaces, hyphens, digits).
57
+ *
58
+ * Case is preserved — distinct from `scanIdentifier`, which lowercases the
59
+ * captured value. The emitted token's `value` is the variable name without
60
+ * the leading `@` or the surrounding braces.
61
+ */
62
+ private scanAt;
63
+ private peek;
64
+ private peekNext;
65
+ private advance;
66
+ private match;
67
+ private isAtEnd;
68
+ private isDigit;
69
+ private isAlpha;
70
+ private isIdentifierStart;
71
+ private isIdentifierPart;
72
+ private isWhitespace;
73
+ private createToken;
74
+ private createTokenAt;
75
+ }
76
+ /**
77
+ * Tokenize a dice notation string.
78
+ *
79
+ * @param input - The dice notation to tokenize
80
+ * @returns Array of tokens including EOF
81
+ * @throws {LexerError} If an invalid character is encountered
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * const tokens = lex('2d20+5');
86
+ * // [NUMBER(2), DICE, NUMBER(20), PLUS, NUMBER(5), EOF]
87
+ * ```
88
+ */
89
+ export declare function lex(input: string): Token[];
90
+ //# sourceMappingURL=lexer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lexer.d.ts","sourceRoot":"","sources":["../../src/lexer/lexer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,KAAK,KAAK,EAAa,MAAM,aAAa,CAAC;AAEpD;;GAEG;AACH,qBAAa,UAAW,SAAQ,eAAe;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;CAM5F;AA2CD;;;;;GAKG;AACH,qBAAa,KAAK;IAChB,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAEnB,KAAK,EAAE,MAAM;IAIzB;;OAEG;IACH,QAAQ,IAAI,KAAK,EAAE;IAYnB;;OAEG;IACH,SAAS,IAAI,KAAK;IAuFlB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,UAAU;IAoBlB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,cAAc;IAqCtB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,MAAM;IA+Bd,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,KAAK;IAOb,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,aAAa;CAGtB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,EAAE,CAE1C"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Token type definitions for the dice notation lexer.
3
+ *
4
+ * @module lexer/tokens
5
+ */
6
+ /**
7
+ * Token types for dice notation.
8
+ *
9
+ * Grouped semantically. Numeric values are stable identifiers — the specific
10
+ * numbers don't matter, but they must be unique.
11
+ */
12
+ export declare enum TokenType {
13
+ /** Numeric literal: integer or decimal */
14
+ NUMBER = 0,
15
+ /** Dice operator: 'd' or 'D' */
16
+ DICE = 1,
17
+ /** Percentile dice operator: 'd%' (alias for d100) */
18
+ DICE_PERCENT = 2,
19
+ /** Fate/Fudge dice operator: 'dF' */
20
+ DICE_FATE = 3,
21
+ /** Addition operator: '+' */
22
+ PLUS = 4,
23
+ /** Subtraction operator: '-' */
24
+ MINUS = 5,
25
+ /** Multiplication operator: '*' */
26
+ MULTIPLY = 6,
27
+ /** Division operator: '/' */
28
+ DIVIDE = 7,
29
+ /** Modulo operator: '%' */
30
+ MODULO = 8,
31
+ /** Power operator: '**' or '^' */
32
+ POWER = 9,
33
+ /** Greater than: '>' */
34
+ GREATER = 10,
35
+ /** Greater than or equal: '>=' */
36
+ GREATER_EQUAL = 11,
37
+ /** Less than: '<' */
38
+ LESS = 12,
39
+ /** Less than or equal: '<=' */
40
+ LESS_EQUAL = 13,
41
+ /** Equal: '=' */
42
+ EQUAL = 14,
43
+ /** Left parenthesis: '(' */
44
+ LPAREN = 15,
45
+ /** Right parenthesis: ')' */
46
+ RPAREN = 16,
47
+ /** Argument separator: ',' */
48
+ COMMA = 17,
49
+ /** Keep highest modifier: 'kh' or 'k' */
50
+ KEEP_HIGH = 18,
51
+ /** Keep lowest modifier: 'kl' */
52
+ KEEP_LOW = 19,
53
+ /** Drop highest modifier: 'dh' */
54
+ DROP_HIGH = 20,
55
+ /** Drop lowest modifier: 'dl' */
56
+ DROP_LOW = 21,
57
+ /** Standard explode: '!' */
58
+ EXPLODE = 22,
59
+ /** Compounding explode: '!!' */
60
+ EXPLODE_COMPOUND = 23,
61
+ /** Penetrating explode: '!p' */
62
+ EXPLODE_PENETRATING = 24,
63
+ /** Recursive reroll: 'r' */
64
+ REROLL = 25,
65
+ /** Reroll once: 'ro' */
66
+ REROLL_ONCE = 26,
67
+ /** Fail marker: 'f' */
68
+ FAIL = 27,
69
+ /** Math function: 'floor', 'ceil', 'round', 'abs', 'max', 'min' */
70
+ FUNCTION = 28,
71
+ /** Versus operator: 'vs' */
72
+ VS = 29,
73
+ /** Left brace: '{' */
74
+ LBRACE = 30,
75
+ /** Right brace: '}' */
76
+ RBRACE = 31,
77
+ /** Variable reference prefix: '@' */
78
+ AT = 32,
79
+ /** Ascending sort: 's' or 'sa' */
80
+ SORT_ASC = 33,
81
+ /** Descending sort: 'sd' */
82
+ SORT_DESC = 34,
83
+ /** Critical success threshold: 'cs' */
84
+ CRIT_SUCCESS = 35,
85
+ /** Critical failure threshold: 'cf' */
86
+ CRIT_FAIL = 36,
87
+ /** End of input marker */
88
+ EOF = 37
89
+ }
90
+ /**
91
+ * A token produced by the lexer.
92
+ */
93
+ export type Token = {
94
+ /** The type of this token */
95
+ type: TokenType;
96
+ /** The raw string value from input (lowercased for identifiers) */
97
+ value: string;
98
+ /** Zero-based start offset in the input string (UTF-16 code units) */
99
+ position: number;
100
+ /**
101
+ * Zero-based end offset (exclusive). Not always `position + value.length` —
102
+ * braced variables (`@{name}` stores only `name`) and case-normalized
103
+ * identifiers consume more input than their `value` retains.
104
+ */
105
+ end: number;
106
+ };
107
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/lexer/tokens.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;GAKG;AACH,oBAAY,SAAS;IAKnB,0CAA0C;IAC1C,MAAM,IAAI;IAMV,gCAAgC;IAChC,IAAI,IAAI;IACR,sDAAsD;IACtD,YAAY,IAAI;IAChB,qCAAqC;IACrC,SAAS,IAAI;IAMb,6BAA6B;IAC7B,IAAI,IAAI;IACR,gCAAgC;IAChC,KAAK,IAAI;IACT,mCAAmC;IACnC,QAAQ,IAAI;IACZ,6BAA6B;IAC7B,MAAM,IAAI;IACV,2BAA2B;IAC3B,MAAM,IAAI;IACV,kCAAkC;IAClC,KAAK,IAAI;IAMT,wBAAwB;IACxB,OAAO,KAAK;IACZ,kCAAkC;IAClC,aAAa,KAAK;IAClB,qBAAqB;IACrB,IAAI,KAAK;IACT,+BAA+B;IAC/B,UAAU,KAAK;IACf,iBAAiB;IACjB,KAAK,KAAK;IAMV,4BAA4B;IAC5B,MAAM,KAAK;IACX,6BAA6B;IAC7B,MAAM,KAAK;IACX,8BAA8B;IAC9B,KAAK,KAAK;IAMV,yCAAyC;IACzC,SAAS,KAAK;IACd,iCAAiC;IACjC,QAAQ,KAAK;IACb,kCAAkC;IAClC,SAAS,KAAK;IACd,iCAAiC;IACjC,QAAQ,KAAK;IAMb,4BAA4B;IAC5B,OAAO,KAAK;IACZ,gCAAgC;IAChC,gBAAgB,KAAK;IACrB,gCAAgC;IAChC,mBAAmB,KAAK;IAMxB,4BAA4B;IAC5B,MAAM,KAAK;IACX,wBAAwB;IACxB,WAAW,KAAK;IAMhB,uBAAuB;IACvB,IAAI,KAAK;IAMT,mEAAmE;IACnE,QAAQ,KAAK;IAMb,4BAA4B;IAC5B,EAAE,KAAK;IAMP,sBAAsB;IACtB,MAAM,KAAK;IACX,uBAAuB;IACvB,MAAM,KAAK;IAMX,qCAAqC;IACrC,EAAE,KAAK;IAMP,kCAAkC;IAClC,QAAQ,KAAK;IACb,4BAA4B;IAC5B,SAAS,KAAK;IAMd,uCAAuC;IACvC,YAAY,KAAK;IACjB,uCAAuC;IACvC,SAAS,KAAK;IAMd,0BAA0B;IAC1B,GAAG,KAAK;CACT;AAED;;GAEG;AACH,MAAM,MAAM,KAAK,GAAG;IAClB,6BAA6B;IAC7B,IAAI,EAAE,SAAS,CAAC;IAChB,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC;CACb,CAAC"}
@@ -0,0 +1,368 @@
1
+ /**
2
+ * AST node type definitions for the dice notation parser.
3
+ *
4
+ * @module parser/ast
5
+ */
6
+ import type { ComparePoint } from '../types.js';
7
+ /**
8
+ * Source span carried by every AST node.
9
+ *
10
+ * `start` is inclusive, `end` exclusive, both in UTF-16 code units into the
11
+ * original notation string. The parser sets both on every node it produces;
12
+ * they are typed optional so hand-constructed ASTs (tests, programmatic
13
+ * consumers) remain valid without positions.
14
+ */
15
+ export type NodeSpan = {
16
+ start?: number;
17
+ end?: number;
18
+ };
19
+ /**
20
+ * Numeric literal node.
21
+ */
22
+ export type LiteralNode = NodeSpan & {
23
+ type: 'Literal';
24
+ value: number;
25
+ };
26
+ /**
27
+ * Dice roll node.
28
+ * Count and sides can be expressions to support computed dice like (1+1)d(3*2).
29
+ */
30
+ export type DiceNode = NodeSpan & {
31
+ type: 'Dice';
32
+ count: ASTNode;
33
+ sides: ASTNode;
34
+ };
35
+ /**
36
+ * Fate/Fudge dice node (`dF`).
37
+ * Each die produces a result in {-1, 0, +1}. No configurable sides.
38
+ */
39
+ export type FateDiceNode = NodeSpan & {
40
+ type: 'FateDice';
41
+ count: ASTNode;
42
+ };
43
+ /**
44
+ * Binary operation node.
45
+ */
46
+ export type BinaryOpNode = NodeSpan & {
47
+ type: 'BinaryOp';
48
+ operator: '+' | '-' | '*' | '/' | '%' | '**';
49
+ left: ASTNode;
50
+ right: ASTNode;
51
+ };
52
+ /**
53
+ * Unary operation node.
54
+ */
55
+ export type UnaryOpNode = NodeSpan & {
56
+ type: 'UnaryOp';
57
+ operator: '-';
58
+ operand: ASTNode;
59
+ };
60
+ /**
61
+ * Keep/drop modifier node.
62
+ * Wraps a dice expression with keep highest/lowest or drop highest/lowest.
63
+ */
64
+ export type ModifierNode = NodeSpan & {
65
+ type: 'Modifier';
66
+ modifier: 'keep' | 'drop';
67
+ selector: 'highest' | 'lowest';
68
+ count: ASTNode;
69
+ target: ASTNode;
70
+ };
71
+ /**
72
+ * Exploding dice node (`!`, `!!`, `!p`, `!>Y`).
73
+ * Wraps a dice expression with a standard, compounding, or penetrating
74
+ * explosion. An absent `threshold` means "explode on the die's maximum face".
75
+ */
76
+ export type ExplodeNode = NodeSpan & {
77
+ type: 'Explode';
78
+ variant: 'standard' | 'compound' | 'penetrating';
79
+ threshold?: ComparePoint;
80
+ target: ASTNode;
81
+ };
82
+ /**
83
+ * Reroll node (`r<COND>`, `ro<COND>`).
84
+ * Re-rolls dice that match a comparison condition. `once: true` for `ro`
85
+ * keeps the second result regardless of match; `once: false` for `r`
86
+ * re-rolls recursively until the condition no longer matches.
87
+ */
88
+ export type RerollNode = NodeSpan & {
89
+ type: 'Reroll';
90
+ once: boolean;
91
+ condition: ComparePoint;
92
+ target: ASTNode;
93
+ };
94
+ /**
95
+ * Success counting node (`>=T`, `>T`, `<T`, `<=T`, `=T`, with optional `f=F`).
96
+ *
97
+ * Transforms a dice pool into a success count: each die meeting `threshold`
98
+ * adds +1, each die meeting `failThreshold` subtracts 1. Terminal — a
99
+ * `SuccessCountNode` may not be wrapped by any postfix modifier, binary
100
+ * operator, unary operator, versus operand, or function argument. The
101
+ * `failThreshold` accepts any `CompareOp`; bare `fN` defaults to `operator: '='`.
102
+ */
103
+ export type SuccessCountNode = NodeSpan & {
104
+ type: 'SuccessCount';
105
+ target: ASTNode;
106
+ threshold: ComparePoint;
107
+ failThreshold?: ComparePoint;
108
+ };
109
+ /**
110
+ * Versus node (`<roll> vs <dc>`) — PF2e Degrees of Success.
111
+ *
112
+ * Both sides are full expressions. The `roll` side is evaluated and compared
113
+ * against the `dc` side total, producing a `DegreeOfSuccess` with natural
114
+ * d20 upgrade/downgrade applied when exactly one kept d20 appears on the
115
+ * roll side. Lowest-precedence operator — chaining (`a vs b vs c`) is
116
+ * rejected at parse time; nesting via parens (`a vs (b vs c)`) is rejected
117
+ * at evaluation time.
118
+ */
119
+ export type VersusNode = NodeSpan & {
120
+ type: 'Versus';
121
+ roll: ASTNode;
122
+ dc: ASTNode;
123
+ };
124
+ /**
125
+ * Math function call node (`floor(expr)`, `max(a, b, ...)`, etc.).
126
+ *
127
+ * Supports the fixed-arity functions `floor`, `ceil`, `round`, `abs`, and the
128
+ * variadic functions `max`, `min` (minimum 2 args). Arity is validated at
129
+ * parse time against a static table; by the time the evaluator sees a
130
+ * `FunctionCallNode`, `args.length` is guaranteed to match the function.
131
+ */
132
+ export type FunctionCallNode = NodeSpan & {
133
+ type: 'FunctionCall';
134
+ name: string;
135
+ args: ASTNode[];
136
+ };
137
+ /**
138
+ * Parenthesized group node (`(<expr>)`).
139
+ *
140
+ * Preserves explicit grouping typed by the user so that
141
+ * `RollResult.expression` and `RollResult.rendered` round-trip through
142
+ * `parse` without losing precedence information. Semantically transparent:
143
+ * evaluation returns the inner expression's value unchanged.
144
+ */
145
+ export type GroupedNode = NodeSpan & {
146
+ type: 'Grouped';
147
+ expression: ASTNode;
148
+ };
149
+ /**
150
+ * Variable reference node (`@name` or `@{name with spaces}`).
151
+ *
152
+ * Resolves to a numeric value from the evaluator's `context` map at
153
+ * evaluation time. Names are case-sensitive (`@StrMod` ≠ `@strmod`) — the
154
+ * lexer preserves case in the `AT` token's `value`, distinct from other
155
+ * identifier tokens which lowercase. Leaf node — no LED, never wraps a
156
+ * sub-expression.
157
+ */
158
+ export type VariableNode = NodeSpan & {
159
+ type: 'Variable';
160
+ name: string;
161
+ };
162
+ /**
163
+ * Grouped-roll node (`{expr}`, `{expr1, expr2, ...}`).
164
+ *
165
+ * Distinct from `GroupedNode` (parenthesized wrapper) — a `GroupNode`
166
+ * collects one or more sub-expressions whose evaluation semantics change
167
+ * with the sub-roll count. `expressions.length === 1` is a passthrough
168
+ * (flat-pool when wrapped by keep/drop); `expressions.length >= 2` treats
169
+ * each sub-roll's subtotal as a compound die for keep/drop selection.
170
+ */
171
+ export type GroupNode = NodeSpan & {
172
+ type: 'Group';
173
+ expressions: ASTNode[];
174
+ };
175
+ /**
176
+ * Sort modifier node (`s`, `sa`, `sd`).
177
+ *
178
+ * Cosmetically reorders the dice produced by `target` in ascending or
179
+ * descending order. Purely visual — does not affect `total`,
180
+ * `successes`/`failures`, or any die-level flag (`kept`/`dropped`/
181
+ * `critical`/`fumble`). Dropped dice retain their `dropped` flag and
182
+ * appear in sorted position alongside kept dice.
183
+ */
184
+ export type SortNode = NodeSpan & {
185
+ type: 'Sort';
186
+ order: 'ascending' | 'descending';
187
+ target: ASTNode;
188
+ };
189
+ /**
190
+ * Sentinel for bare `cs` / `cf` without a ComparePoint. Resolved to
191
+ * `result === sides` (for critical) or `result === 1` (for fumble) at
192
+ * evaluation time, using each die's own `sides`.
193
+ */
194
+ export type CritThreshold = ComparePoint | 'default';
195
+ /**
196
+ * Critical threshold modifier node (`cs`, `cf`).
197
+ *
198
+ * Overrides the default `critical`/`fumble` flag logic for the dice
199
+ * produced by `target`. Bare `cs`/`cf` uses the `'default'` sentinel
200
+ * (max face / 1). Custom thresholds accept any ComparePoint. Chaining
201
+ * collapses into a single node — `1d20cs=20cs=1cf>18` has two success
202
+ * and one fail threshold. Display-only: does not change `total`,
203
+ * explosion triggers, or success counting. `cs` and `cf` are independent
204
+ * overrides — a side with no explicit thresholds keeps the default rule
205
+ * (the evaluator substitutes the `'default'` sentinel at apply time).
206
+ */
207
+ export type CritThresholdNode = NodeSpan & {
208
+ type: 'CritThreshold';
209
+ successThresholds: CritThreshold[];
210
+ failThresholds: CritThreshold[];
211
+ target: ASTNode;
212
+ };
213
+ /**
214
+ * Union type of all AST nodes.
215
+ */
216
+ export type ASTNode = LiteralNode | DiceNode | FateDiceNode | BinaryOpNode | UnaryOpNode | ModifierNode | ExplodeNode | RerollNode | SuccessCountNode | VersusNode | FunctionCallNode | GroupedNode | VariableNode | GroupNode | SortNode | CritThresholdNode;
217
+ /**
218
+ * Type guard for LiteralNode.
219
+ */
220
+ export declare function isLiteral(node: ASTNode): node is LiteralNode;
221
+ /**
222
+ * Type guard for DiceNode.
223
+ */
224
+ export declare function isDice(node: ASTNode): node is DiceNode;
225
+ /**
226
+ * Type guard for FateDiceNode.
227
+ */
228
+ export declare function isFateDice(node: ASTNode): node is FateDiceNode;
229
+ /**
230
+ * Type guard for BinaryOpNode.
231
+ */
232
+ export declare function isBinaryOp(node: ASTNode): node is BinaryOpNode;
233
+ /**
234
+ * Type guard for UnaryOpNode.
235
+ */
236
+ export declare function isUnaryOp(node: ASTNode): node is UnaryOpNode;
237
+ /**
238
+ * Type guard for ModifierNode.
239
+ */
240
+ export declare function isModifier(node: ASTNode): node is ModifierNode;
241
+ /**
242
+ * Type guard for ExplodeNode.
243
+ */
244
+ export declare function isExplode(node: ASTNode): node is ExplodeNode;
245
+ /**
246
+ * Type guard for RerollNode.
247
+ */
248
+ export declare function isReroll(node: ASTNode): node is RerollNode;
249
+ /**
250
+ * Type guard for SuccessCountNode.
251
+ */
252
+ export declare function isSuccessCount(node: ASTNode): node is SuccessCountNode;
253
+ /**
254
+ * Type guard for VersusNode.
255
+ */
256
+ export declare function isVersus(node: ASTNode): node is VersusNode;
257
+ /**
258
+ * Type guard for FunctionCallNode.
259
+ */
260
+ export declare function isFunctionCall(node: ASTNode): node is FunctionCallNode;
261
+ /**
262
+ * Type guard for GroupedNode.
263
+ */
264
+ export declare function isGrouped(node: ASTNode): node is GroupedNode;
265
+ /**
266
+ * Type guard for VariableNode.
267
+ */
268
+ export declare function isVariable(node: ASTNode): node is VariableNode;
269
+ /**
270
+ * Type guard for GroupNode.
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.
283
+ *
284
+ * "Transparent" is relative to the question being asked. `Modifier`/`Sort`/
285
+ * `CritThreshold` are transparent for "what is the underlying operand?" when
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.
296
+ *
297
+ * Used by parser reject helpers to look past wrappers when deciding whether
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`).
300
+ */
301
+ export declare function unwrapTransparent(node: ASTNode, kinds: readonly TransparentWrapperKind[]): ASTNode;
302
+ /**
303
+ * Returns `true` only when `node`'s direct result is a dice pool —
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.
307
+ *
308
+ * Used by the parser to reject postfix pool-modifier targets (kh/kl/dh/dl,
309
+ * !/!!/!p, r/ro) that wrap a non-pool expression. Operating on the inner
310
+ * dice pool would silently drop the surrounding arithmetic.
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).
319
+ */
320
+ export declare function deepContainsDicePool(node: ASTNode): boolean;
321
+ /**
322
+ * Returns `true` if the pool this node resolves to is (or wraps) a `FateDice`
323
+ * pool. Walks through chained pool modifiers (`Modifier` / `Explode` /
324
+ * `Reroll`) but not arithmetic wrappers — callers should run
325
+ * `containsDicePool` first to reject those.
326
+ *
327
+ * Used by the parser to reject `!`, `!!`, `!p` applied to Fate pools
328
+ * (`4dF!`, `(4dF)kh2!`, etc.). Fate explosion semantics are undefined, so
329
+ * parse-time rejection is preferred over a silent evaluator no-op.
330
+ *
331
+ * Inside a `Group`, recursion uses `deepContainsFatePool` to mirror
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.
335
+ */
336
+ export declare function containsFatePool(node: ASTNode): boolean;
337
+ /**
338
+ * Deeper variant of `containsFatePool` that recurses through arithmetic and
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.
342
+ *
343
+ * Outside a `Group`, ordinary parenthesized arithmetic (`(4dF+1d6)cf`) is
344
+ * already rejected upstream by shallow `containsDicePool`, so this helper
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.
354
+ *
355
+ * Without this walk, the unwrap inside `rejectGroupTarget` only peels
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.
366
+ */
367
+ export declare function containsVersus(node: ASTNode): boolean;
368
+ //# sourceMappingURL=ast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../../src/parser/ast.ts"],"names":[],"mappings":"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"}