roll-parser 3.0.0-beta.0 → 3.0.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 (159) hide show
  1. package/CHANGELOG.md +107 -9
  2. package/MIGRATION.md +147 -0
  3. package/README.md +960 -154
  4. package/dist/cli/args.d.ts +1 -0
  5. package/dist/cli/args.d.ts.map +1 -1
  6. package/dist/cli/args.js +81 -0
  7. package/dist/cli/args.js.map +1 -0
  8. package/dist/cli/format.d.ts +15 -3
  9. package/dist/cli/format.d.ts.map +1 -1
  10. package/dist/cli/format.js +17 -0
  11. package/dist/cli/format.js.map +1 -0
  12. package/dist/cli/index.d.ts +3 -0
  13. package/dist/cli/index.d.ts.map +1 -1
  14. package/dist/cli/index.js +14 -0
  15. package/dist/cli/index.js.map +1 -0
  16. package/dist/cli/main.d.ts +36 -0
  17. package/dist/cli/main.d.ts.map +1 -0
  18. package/dist/cli/main.js +83 -0
  19. package/dist/cli/main.js.map +1 -0
  20. package/dist/errors.d.ts +332 -17
  21. package/dist/errors.d.ts.map +1 -1
  22. package/dist/errors.js +141 -0
  23. package/dist/errors.js.map +1 -0
  24. package/dist/evaluator/die.d.ts +26 -0
  25. package/dist/evaluator/die.d.ts.map +1 -0
  26. package/dist/evaluator/die.js +19 -0
  27. package/dist/evaluator/die.js.map +1 -0
  28. package/dist/evaluator/env.d.ts +58 -0
  29. package/dist/evaluator/env.d.ts.map +1 -0
  30. package/dist/evaluator/env.js +11 -0
  31. package/dist/evaluator/env.js.map +1 -0
  32. package/dist/evaluator/evaluator.d.ts +41 -75
  33. package/dist/evaluator/evaluator.d.ts.map +1 -1
  34. package/dist/evaluator/evaluator.js +906 -0
  35. package/dist/evaluator/evaluator.js.map +1 -0
  36. package/dist/evaluator/modifiers/compare.js +15 -0
  37. package/dist/evaluator/modifiers/compare.js.map +1 -0
  38. package/dist/evaluator/modifiers/crit-threshold.d.ts +0 -1
  39. package/dist/evaluator/modifiers/crit-threshold.d.ts.map +1 -1
  40. package/dist/evaluator/modifiers/crit-threshold.js +23 -0
  41. package/dist/evaluator/modifiers/crit-threshold.js.map +1 -0
  42. package/dist/evaluator/modifiers/die-bound.d.ts +26 -0
  43. package/dist/evaluator/modifiers/die-bound.d.ts.map +1 -0
  44. package/dist/evaluator/modifiers/die-bound.js +14 -0
  45. package/dist/evaluator/modifiers/die-bound.js.map +1 -0
  46. package/dist/evaluator/modifiers/explode.d.ts +13 -5
  47. package/dist/evaluator/modifiers/explode.d.ts.map +1 -1
  48. package/dist/evaluator/modifiers/explode.js +103 -0
  49. package/dist/evaluator/modifiers/explode.js.map +1 -0
  50. package/dist/evaluator/modifiers/flags.d.ts +37 -0
  51. package/dist/evaluator/modifiers/flags.d.ts.map +1 -0
  52. package/dist/evaluator/modifiers/flags.js +18 -0
  53. package/dist/evaluator/modifiers/flags.js.map +1 -0
  54. package/dist/evaluator/modifiers/keep-drop.d.ts +12 -28
  55. package/dist/evaluator/modifiers/keep-drop.d.ts.map +1 -1
  56. package/dist/evaluator/modifiers/keep-drop.js +82 -0
  57. package/dist/evaluator/modifiers/keep-drop.js.map +1 -0
  58. package/dist/evaluator/modifiers/reroll.d.ts +12 -4
  59. package/dist/evaluator/modifiers/reroll.d.ts.map +1 -1
  60. package/dist/evaluator/modifiers/reroll.js +62 -0
  61. package/dist/evaluator/modifiers/reroll.js.map +1 -0
  62. package/dist/evaluator/modifiers/sort.d.ts +4 -0
  63. package/dist/evaluator/modifiers/sort.d.ts.map +1 -1
  64. package/dist/evaluator/modifiers/sort.js +13 -0
  65. package/dist/evaluator/modifiers/sort.js.map +1 -0
  66. package/dist/evaluator/modifiers/success-count.d.ts +2 -6
  67. package/dist/evaluator/modifiers/success-count.d.ts.map +1 -1
  68. package/dist/evaluator/modifiers/success-count.js +24 -0
  69. package/dist/evaluator/modifiers/success-count.js.map +1 -0
  70. package/dist/index.d.ts +33 -11
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +12 -2560
  73. package/dist/index.js.map +1 -26
  74. package/dist/lexer/lexer.d.ts +50 -5
  75. package/dist/lexer/lexer.d.ts.map +1 -1
  76. package/dist/lexer/lexer.js +260 -0
  77. package/dist/lexer/lexer.js.map +1 -0
  78. package/dist/lexer/tokens.d.ts +31 -6
  79. package/dist/lexer/tokens.d.ts.map +1 -1
  80. package/dist/lexer/tokens.js +42 -0
  81. package/dist/lexer/tokens.js.map +1 -0
  82. package/dist/parser/ast.d.ts +314 -160
  83. package/dist/parser/ast.d.ts.map +1 -1
  84. package/dist/parser/ast.js +52 -0
  85. package/dist/parser/ast.js.map +1 -0
  86. package/dist/parser/guards.d.ts +106 -0
  87. package/dist/parser/guards.d.ts.map +1 -0
  88. package/dist/parser/guards.js +121 -0
  89. package/dist/parser/guards.js.map +1 -0
  90. package/dist/parser/parser.d.ts +118 -14
  91. package/dist/parser/parser.d.ts.map +1 -1
  92. package/dist/parser/parser.js +751 -0
  93. package/dist/parser/parser.js.map +1 -0
  94. package/dist/rng/mock.d.ts +73 -12
  95. package/dist/rng/mock.d.ts.map +1 -1
  96. package/dist/rng/mock.js +30 -0
  97. package/dist/rng/mock.js.map +1 -0
  98. package/dist/rng/seeded.d.ts +141 -9
  99. package/dist/rng/seeded.d.ts.map +1 -1
  100. package/dist/rng/seeded.js +138 -0
  101. package/dist/rng/seeded.js.map +1 -0
  102. package/dist/rng/types.d.ts +57 -0
  103. package/dist/rng/types.d.ts.map +1 -1
  104. package/dist/rng/types.js +2 -0
  105. package/dist/rng/types.js.map +1 -0
  106. package/dist/roll.d.ts +58 -28
  107. package/dist/roll.d.ts.map +1 -1
  108. package/dist/roll.js +8 -0
  109. package/dist/roll.js.map +1 -0
  110. package/dist/testing.d.ts +5 -4
  111. package/dist/testing.d.ts.map +1 -1
  112. package/dist/testing.js +2 -41
  113. package/dist/testing.js.map +1 -11
  114. package/dist/types.d.ts +306 -44
  115. package/dist/types.d.ts.map +1 -1
  116. package/dist/types.js +8 -0
  117. package/dist/types.js.map +1 -0
  118. package/dist/version.d.ts +2 -0
  119. package/dist/version.d.ts.map +1 -0
  120. package/dist/version.js +2 -0
  121. package/dist/version.js.map +1 -0
  122. package/package.json +83 -34
  123. package/src/cli/args.ts +66 -10
  124. package/src/cli/format.ts +22 -3
  125. package/src/cli/index.ts +27 -84
  126. package/src/cli/main.ts +129 -0
  127. package/src/errors.ts +480 -27
  128. package/src/evaluator/die.ts +50 -0
  129. package/src/evaluator/env.ts +73 -0
  130. package/src/evaluator/evaluator.ts +653 -421
  131. package/src/evaluator/modifiers/crit-threshold.ts +3 -6
  132. package/src/evaluator/modifiers/die-bound.ts +39 -0
  133. package/src/evaluator/modifiers/explode.ts +60 -58
  134. package/src/evaluator/modifiers/flags.ts +61 -0
  135. package/src/evaluator/modifiers/keep-drop.ts +124 -126
  136. package/src/evaluator/modifiers/reroll.ts +28 -49
  137. package/src/evaluator/modifiers/sort.ts +14 -1
  138. package/src/evaluator/modifiers/success-count.ts +5 -8
  139. package/src/index.ts +56 -35
  140. package/src/lexer/lexer.ts +107 -34
  141. package/src/lexer/tokens.ts +31 -6
  142. package/src/parser/ast.ts +323 -341
  143. package/src/parser/guards.ts +248 -0
  144. package/src/parser/parser.ts +419 -242
  145. package/src/rng/mock.ts +74 -13
  146. package/src/rng/seeded.ts +299 -64
  147. package/src/rng/types.ts +57 -0
  148. package/src/roll.ts +64 -47
  149. package/src/testing.ts +5 -9
  150. package/src/types.ts +310 -43
  151. package/src/version.ts +2 -0
  152. package/dist/cli.js +0 -2608
  153. package/dist/cli.js.map +0 -28
  154. package/dist/evaluator/index.d.ts +0 -8
  155. package/dist/evaluator/index.d.ts.map +0 -1
  156. package/dist/rng/index.d.ts +0 -8
  157. package/dist/rng/index.d.ts.map +0 -1
  158. package/src/evaluator/index.ts +0 -14
  159. package/src/rng/index.ts +0 -8
package/dist/errors.d.ts CHANGED
@@ -1,42 +1,357 @@
1
1
  /**
2
- * Common error base class and error codes for roll-parser.
2
+ * Common error base class, error codes, and span extraction for roll-parser.
3
3
  *
4
4
  * @module errors
5
5
  */
6
+ import type { ASTNode } from './parser/ast.js';
6
7
  /**
7
- * All known roll-parser error codes. Single source of truth — the
8
- * `RollParserErrorCode` type and the runtime `VALID_CODES` set are
9
- * both derived from this array.
8
+ * Every roll-parser error code, as a readonly tuple. Single source of truth —
9
+ * the {@link RollParserErrorCode} union is derived from it, and it is the
10
+ * runtime counterpart for the type: validating an untrusted string, driving an
11
+ * exhaustive UI (a message-catalog completeness check, a settings list), or
12
+ * iterating the codes in tests.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { ROLL_PARSER_ERROR_CODES, type RollParserErrorCode } from 'roll-parser';
17
+ *
18
+ * function isKnownCode(value: string): value is RollParserErrorCode {
19
+ * return (ROLL_PARSER_ERROR_CODES as readonly string[]).includes(value);
20
+ * }
21
+ * ```
22
+ *
23
+ * @category Errors
24
+ */
25
+ export declare const ROLL_PARSER_ERROR_CODES: readonly ['UNEXPECTED_CHARACTER', 'UNEXPECTED_IDENTIFIER', 'UNEXPECTED_TOKEN', 'UNEXPECTED_END', 'EXPECTED_TOKEN', 'INVALID_DICE_COUNT', 'INVALID_DICE_SIDES', 'DICE_LIMIT_EXCEEDED', 'DIVISION_BY_ZERO', 'MODULO_BY_ZERO', 'UNKNOWN_OPERATOR', 'UNKNOWN_NODE_TYPE', 'INVALID_KEEP_DROP_COUNT', 'INVALID_KEEP_DROP_TARGET', 'EXPLODE_LIMIT_EXCEEDED', 'INVALID_EXPLODE_TARGET', 'REROLL_LIMIT_EXCEEDED', 'INVALID_REROLL_TARGET', 'INVALID_SUCCESS_COUNT_TARGET', 'INVALID_SORT_TARGET', 'INVALID_CRIT_THRESHOLD_TARGET', 'INVALID_DIE_BOUND_TARGET', 'INVALID_THRESHOLD', 'NESTED_VERSUS', 'INVALID_FUNCTION_ARITY', 'UNKNOWN_FUNCTION', 'UNDEFINED_VARIABLE', 'INVALID_VARIABLE_VALUE', 'AMBIGUOUS_DICE_CHAIN', 'MAX_DEPTH_EXCEEDED', 'NON_FINITE_RESULT', 'INCOMPATIBLE_RNG_STATE', 'INVALID_EVALUATION_LIMIT', 'INVALID_NOTATION_TYPE'];
26
+ /**
27
+ * Programmatic identifier carried by every roll-parser error, grouped by the
28
+ * stage that raises it. The runtime list behind this union is exported as
29
+ * {@link ROLL_PARSER_ERROR_CODES}.
10
30
  *
11
31
  * Lexer: `UNEXPECTED_CHARACTER`, `UNEXPECTED_IDENTIFIER`
32
+ *
12
33
  * Parser: `UNEXPECTED_TOKEN`, `UNEXPECTED_END`, `EXPECTED_TOKEN`,
13
- * `INVALID_MODIFIER_TARGET`, `INVALID_EXPLODE_TARGET`, `INVALID_REROLL_TARGET`,
14
- * `INVALID_SUCCESS_COUNT_TARGET`, `INVALID_SORT_TARGET`,
15
- * `INVALID_CRIT_THRESHOLD_TARGET`, `NESTED_VERSUS`, `INVALID_FUNCTION_ARITY`,
16
- * `AMBIGUOUS_DICE_CHAIN`, `MAX_DEPTH_EXCEEDED`
34
+ * `INVALID_KEEP_DROP_TARGET`, `INVALID_EXPLODE_TARGET`, `INVALID_REROLL_TARGET`,
35
+ * `INVALID_SUCCESS_COUNT_TARGET`, `INVALID_SORT_TARGET`,
36
+ * `INVALID_CRIT_THRESHOLD_TARGET`, `INVALID_DIE_BOUND_TARGET`, `NESTED_VERSUS`,
37
+ * `INVALID_FUNCTION_ARITY`, `AMBIGUOUS_DICE_CHAIN`, `MAX_DEPTH_EXCEEDED`
38
+ *
17
39
  * Evaluator: `INVALID_DICE_COUNT`, `INVALID_DICE_SIDES`, `DICE_LIMIT_EXCEEDED`,
18
- * `DIVISION_BY_ZERO`, `MODULO_BY_ZERO`, `UNKNOWN_OPERATOR`, `UNKNOWN_NODE_TYPE`,
19
- * `INVALID_MODIFIER_COUNT`, `EXPLODE_LIMIT_EXCEEDED`, `REROLL_LIMIT_EXCEEDED`,
20
- * `INVALID_THRESHOLD`, `NESTED_VERSUS`, `UNKNOWN_FUNCTION`, `UNDEFINED_VARIABLE`,
21
- * `INVALID_VARIABLE_VALUE`, `NON_FINITE_RESULT`
40
+ * `DIVISION_BY_ZERO`, `MODULO_BY_ZERO`, `UNKNOWN_OPERATOR`, `UNKNOWN_NODE_TYPE`,
41
+ * `INVALID_KEEP_DROP_COUNT`, `EXPLODE_LIMIT_EXCEEDED`, `REROLL_LIMIT_EXCEEDED`,
42
+ * `INVALID_THRESHOLD`, `NESTED_VERSUS`, `UNKNOWN_FUNCTION`, `UNDEFINED_VARIABLE`,
43
+ * `INVALID_VARIABLE_VALUE`, `NON_FINITE_RESULT`
44
+ *
45
+ * RNG: `INCOMPATIBLE_RNG_STATE`
46
+ *
47
+ * Options: `INVALID_EVALUATION_LIMIT` — raised before evaluation begins, from
48
+ * the options object rather than from the notation, so it carries no span.
49
+ *
50
+ * Input: `INVALID_NOTATION_TYPE` — raised before lexing, when `notation` is not
51
+ * a string, so it carries no span either.
52
+ *
53
+ * New codes are only ever introduced in a minor release, never a patch. Treat
54
+ * the union as open when you switch over it: give the switch a `default` arm
55
+ * rather than relying on exhaustiveness, or a minor upgrade turns a new code
56
+ * into a silent fall-through.
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * import { isRollParserError, roll, type RollParserErrorCode } from 'roll-parser';
61
+ *
62
+ * const MESSAGES: Partial<Record<RollParserErrorCode, string>> = {
63
+ * DICE_LIMIT_EXCEEDED: 'That is too many dice.',
64
+ * DIVISION_BY_ZERO: 'Cannot divide by zero.',
65
+ * UNEXPECTED_CHARACTER: 'That is not valid dice notation.',
66
+ * };
67
+ *
68
+ * try {
69
+ * roll(userInput);
70
+ * } catch (error) {
71
+ * if (isRollParserError(error)) {
72
+ * reply(MESSAGES[error.code] ?? error.message);
73
+ * }
74
+ * }
75
+ * ```
76
+ *
77
+ * @category Errors
22
78
  */
23
- declare const ROLL_PARSER_ERROR_CODES: readonly ["UNEXPECTED_CHARACTER", "UNEXPECTED_IDENTIFIER", "UNEXPECTED_TOKEN", "UNEXPECTED_END", "EXPECTED_TOKEN", "INVALID_DICE_COUNT", "INVALID_DICE_SIDES", "DICE_LIMIT_EXCEEDED", "DIVISION_BY_ZERO", "MODULO_BY_ZERO", "UNKNOWN_OPERATOR", "UNKNOWN_NODE_TYPE", "INVALID_MODIFIER_COUNT", "INVALID_MODIFIER_TARGET", "EXPLODE_LIMIT_EXCEEDED", "INVALID_EXPLODE_TARGET", "REROLL_LIMIT_EXCEEDED", "INVALID_REROLL_TARGET", "INVALID_SUCCESS_COUNT_TARGET", "INVALID_SORT_TARGET", "INVALID_CRIT_THRESHOLD_TARGET", "INVALID_THRESHOLD", "NESTED_VERSUS", "INVALID_FUNCTION_ARITY", "UNKNOWN_FUNCTION", "UNDEFINED_VARIABLE", "INVALID_VARIABLE_VALUE", "AMBIGUOUS_DICE_CHAIN", "MAX_DEPTH_EXCEEDED", "NON_FINITE_RESULT"];
24
79
  export type RollParserErrorCode = (typeof ROLL_PARSER_ERROR_CODES)[number];
80
+ /**
81
+ * The subset of {@link ROLL_PARSER_ERROR_CODES} the *input* is answerable for,
82
+ * as a readonly tuple. Runtime counterpart of {@link NotationErrorCode} and the
83
+ * list {@link isNotationError} matches against.
84
+ *
85
+ * A code is in when `roll(notation)` can raise it for some notation string,
86
+ * given valid options and a valid `context` — so the right response is to tell
87
+ * whoever supplied the notation that it was rejected. Six codes are out, because
88
+ * for each of them the notation is innocent:
89
+ *
90
+ * Calling code: `INVALID_EVALUATION_LIMIT` (a bad `maxDice`,
91
+ * `maxExplodeIterations`, or `maxRerollIterations`), `INVALID_VARIABLE_VALUE` (a
92
+ * non-finite entry in `context`), `INCOMPATIBLE_RNG_STATE` (a snapshot from
93
+ * another version)
94
+ *
95
+ * Library invariant: `UNKNOWN_NODE_TYPE`, `UNKNOWN_OPERATOR`,
96
+ * `UNKNOWN_FUNCTION` — the lexer and parser only ever hand the evaluator shapes
97
+ * it already covers, so reaching one means a hand-built AST or a bug in here.
98
+ *
99
+ * Two boundaries are worth knowing. `DIVISION_BY_ZERO`, `MODULO_BY_ZERO`, and
100
+ * `NON_FINITE_RESULT` are in because notation alone reaches them (`1d6/0`,
101
+ * `10**400`), but a `context` variable reaches them too, so a `true` is not
102
+ * proof the notation was at fault. `INVALID_NOTATION_TYPE` is in even though no
103
+ * user can type a non-string: it means the `notation` you were handed was
104
+ * `null` or `undefined` — an absent slash-command option, a missing JSON field —
105
+ * and "give me a dice expression" is the reply that fits.
106
+ *
107
+ * @example
108
+ * ```typescript
109
+ * import { NOTATION_ERROR_CODES } from 'roll-parser';
110
+ *
111
+ * // Prompt copy is only worth writing for the codes a user can actually cause.
112
+ * const needsCopy = new Set(NOTATION_ERROR_CODES);
113
+ * ```
114
+ *
115
+ * @category Errors
116
+ */
117
+ export declare const NOTATION_ERROR_CODES: readonly ['UNEXPECTED_CHARACTER', 'UNEXPECTED_IDENTIFIER', 'UNEXPECTED_TOKEN', 'UNEXPECTED_END', 'EXPECTED_TOKEN', 'INVALID_DICE_COUNT', 'INVALID_DICE_SIDES', 'DICE_LIMIT_EXCEEDED', 'DIVISION_BY_ZERO', 'MODULO_BY_ZERO', 'INVALID_KEEP_DROP_COUNT', 'INVALID_KEEP_DROP_TARGET', 'EXPLODE_LIMIT_EXCEEDED', 'INVALID_EXPLODE_TARGET', 'REROLL_LIMIT_EXCEEDED', 'INVALID_REROLL_TARGET', 'INVALID_SUCCESS_COUNT_TARGET', 'INVALID_SORT_TARGET', 'INVALID_CRIT_THRESHOLD_TARGET', 'INVALID_DIE_BOUND_TARGET', 'INVALID_THRESHOLD', 'NESTED_VERSUS', 'INVALID_FUNCTION_ARITY', 'UNDEFINED_VARIABLE', 'AMBIGUOUS_DICE_CHAIN', 'MAX_DEPTH_EXCEEDED', 'NON_FINITE_RESULT', 'INVALID_NOTATION_TYPE'];
118
+ /**
119
+ * A {@link RollParserErrorCode} the input is answerable for. The runtime list
120
+ * behind this union is exported as {@link NOTATION_ERROR_CODES}, which documents
121
+ * where the line falls.
122
+ *
123
+ * @category Errors
124
+ */
125
+ export type NotationErrorCode = (typeof NOTATION_ERROR_CODES)[number];
25
126
  /**
26
127
  * Base error class for all roll-parser errors.
27
128
  *
28
129
  * Provides a typed `code` field for programmatic error handling.
29
130
  * All library errors (`LexerError`, `ParseError`, `EvaluatorError`)
30
131
  * extend this class.
132
+ *
133
+ * Error messages never embed the source position — every subclass reports it
134
+ * through structured fields instead, readable uniformly via
135
+ * {@link getErrorSpan}. Prefer {@link isRollParserError} over `instanceof`: it
136
+ * also matches errors from another realm or a duplicate copy of the library.
137
+ *
138
+ * @example
139
+ * ```typescript
140
+ * import { roll, RollParserError } from 'roll-parser';
141
+ *
142
+ * try {
143
+ * roll('1d6/0');
144
+ * } catch (error) {
145
+ * const typed = error as RollParserError;
146
+ * typed.name; // 'EvaluatorError'
147
+ * typed.code; // 'DIVISION_BY_ZERO'
148
+ * typed.message; // 'Division by zero'
149
+ * }
150
+ * ```
151
+ *
152
+ * @category Errors
31
153
  */
32
154
  export declare class RollParserError extends Error {
155
+ /**
156
+ * Stable programmatic identifier for the failure. Branch on this rather
157
+ * than on `message`, which is free to change between releases.
158
+ */
33
159
  readonly code: RollParserErrorCode;
34
- constructor(message: string, code: RollParserErrorCode);
160
+ constructor(message: string, code: RollParserErrorCode, options?: ErrorOptions);
161
+ }
162
+ /**
163
+ * Error thrown during AST evaluation.
164
+ *
165
+ * Lives here rather than in `evaluator/evaluator.ts` so the modifier
166
+ * implementations can throw it without importing back into the evaluator —
167
+ * that value-level round trip was a genuine ESM cycle. `evaluator.ts`
168
+ * re-exports the class for existing importers.
169
+ *
170
+ * Unlike `LexerError` / `ParseError` — which point at a single offset — an
171
+ * `EvaluatorError` carries a full `start`/`end` span covering the failing
172
+ * sub-expression, because by evaluation time the AST knows its own extent.
173
+ *
174
+ * @example
175
+ * ```typescript
176
+ * import { getErrorSpan, roll } from 'roll-parser';
177
+ *
178
+ * try {
179
+ * roll('2d6+1d0+3');
180
+ * } catch (error) {
181
+ * (error as Error).name; // 'EvaluatorError'
182
+ * getErrorSpan(error); // { start: 4, end: 7 } — the '1d0' sub-expression
183
+ * }
184
+ * ```
185
+ *
186
+ * @category Errors
187
+ */
188
+ export declare class EvaluatorError extends RollParserError {
189
+ #private;
190
+ /**
191
+ * `ASTNode.type` of the node that raised the error (`'Dice'`, `'BinaryOp'`,
192
+ * …). `undefined` for failures raised outside a node context, such as the
193
+ * whole-expression dice budget.
194
+ */
195
+ readonly nodeType: ASTNode['type'] | undefined;
196
+ constructor(message: string, code: RollParserErrorCode, nodeType?: ASTNode['type'], options?: ErrorOptions);
197
+ /**
198
+ * Start offset of the tightest AST node that was being evaluated when the
199
+ * error was thrown. `undefined` when the AST was built without parser spans.
200
+ */
201
+ get start(): number | undefined;
202
+ /** Exclusive end offset of the span described by `start`. */
203
+ get end(): number | undefined;
35
204
  }
36
205
  /**
37
- * Type guard for roll-parser errors. Checks `instanceof` first, then
38
- * falls back to duck-typing for cross-realm safety.
206
+ * Source span of an error, in UTF-16 code units into the original notation.
207
+ * `start` is inclusive; `end` is exclusive and present only when the error
208
+ * carries a full span (evaluator errors) rather than a single offset.
209
+ *
210
+ * @category Errors
211
+ */
212
+ export type ErrorSpan = {
213
+ start: number;
214
+ end?: number;
215
+ };
216
+ /**
217
+ * Type guard for roll-parser errors. Checks `instanceof` first, then a brand
218
+ * carried on the error's prototype — so it still matches when `instanceof`
219
+ * cannot, namely an error from an iframe or `vm` context, or from a second copy
220
+ * of the library in `node_modules` at this version or newer.
221
+ *
222
+ * Use it as the outer filter in every `catch`: anything it rejects came from
223
+ * somewhere else and should be rethrown. The brand is what makes that sound — a
224
+ * foreign error is never accepted just for carrying a `code` that happens to
225
+ * collide with one of ours.
226
+ *
227
+ * What it answers is "did this come from us", not "whose fault was it". A `true`
228
+ * covers bad notation, a bad options object, and a broken invariant in here
229
+ * alike, so it is the wrong test to hang a user-facing message on. Reach for
230
+ * {@link isNotationError} for that.
231
+ *
232
+ * Only this library's own error prototype carries the brand, so holding it is
233
+ * proof of origin, and the `code` is trusted rather than re-validated: an error
234
+ * from a newer minor passes with a code this build has never heard of, which is
235
+ * what {@link RollParserErrorCode} being an open union already implies. A value
236
+ * that forges the brand is out of scope, as it is for any brand check.
237
+ *
238
+ * The one boundary it cannot cross is a worker. `postMessage` and
239
+ * `structuredClone` rebuild an `Error` from `message` and `stack` alone,
240
+ * discarding `code`, `name`, and the prototype with it, so the value that
241
+ * arrives is no longer recognizable as anything. Send `error.code` yourself as
242
+ * part of the message payload if the other side needs it.
243
+ *
244
+ * @param value - The caught value, of unknown type
245
+ * @returns `true` when `value` is a roll-parser error
246
+ *
247
+ * @example
248
+ * ```typescript
249
+ * import { isRollParserError, roll } from 'roll-parser';
250
+ *
251
+ * try {
252
+ * roll('2d6+&');
253
+ * } catch (error) {
254
+ * if (!isRollParserError(error)) throw error;
255
+ * error.code; // 'UNEXPECTED_CHARACTER'
256
+ * error.message; // "Unexpected character: '&'"
257
+ * }
258
+ * ```
259
+ *
260
+ * @category Errors
39
261
  */
40
262
  export declare function isRollParserError(value: unknown): value is RollParserError;
41
- export {};
263
+ /**
264
+ * Type guard for the failures the input is answerable for: a roll-parser error
265
+ * whose `code` is one of {@link NOTATION_ERROR_CODES}. This is the test to hang
266
+ * a user-facing message on — {@link isRollParserError} only establishes origin,
267
+ * and answers `true` for a bad options object and a broken library invariant
268
+ * too, both of which should page you instead.
269
+ *
270
+ * Pair the two: the outer filter rethrows what is not ours, and this one splits
271
+ * what is left into "tell the user" and "report a bug".
272
+ *
273
+ * Unlike {@link isRollParserError}, this one has to read the `code` against the
274
+ * list this build carries — attribution is not something a brand can express. So
275
+ * where the outer filter accepts a code it has never heard of, this one rejects
276
+ * it: an error from a newer minor carrying a notation code added after this build
277
+ * reads as `false` and is misfiled as internal. That is the safe direction — it
278
+ * pages a developer rather than blaming a user — but keep the library and its
279
+ * consumers on one version when the distinction drives more than a message.
280
+ *
281
+ * @param value - The caught value, of unknown type
282
+ * @returns `true` when `value` is a roll-parser error the input caused
283
+ *
284
+ * @example Two channels, one catch
285
+ * ```typescript
286
+ * import { isNotationError, isRollParserError, roll } from 'roll-parser';
287
+ *
288
+ * try {
289
+ * roll(userInput);
290
+ * } catch (error) {
291
+ * if (!isRollParserError(error)) throw error;
292
+ * if (isNotationError(error)) reply(`Bad notation: ${error.message}`);
293
+ * else report(error); // our bug or yours — never the user's
294
+ * }
295
+ * ```
296
+ *
297
+ * @category Errors
298
+ */
299
+ export declare function isNotationError(value: unknown): value is RollParserError & {
300
+ code: NotationErrorCode;
301
+ };
302
+ /**
303
+ * Renders a rejected value for an error message, never coercing an object:
304
+ * `String(Object.create(null))` throws, and a hostile `toString` can too —
305
+ * either would replace the typed error with a raw `TypeError`.
306
+ *
307
+ * Module-level export, deliberately absent from `src/index.ts` — the package
308
+ * surface never mentions it.
309
+ */
310
+ export declare function describeValue(value: unknown): string;
311
+ /**
312
+ * Normalizes the three error position shapes into one span.
313
+ *
314
+ * `LexerError` and `ParseError` expose a single `position`; `EvaluatorError`
315
+ * exposes `start`/`end`. Returns `undefined` for errors that are not
316
+ * roll-parser errors, or that carry no usable offset (an `EvaluatorError`
317
+ * raised on a hand-built AST, for instance).
318
+ *
319
+ * @param error - The caught value, of unknown type
320
+ * @returns The span, or `undefined` when none is available
321
+ *
322
+ * @example Rendering a caret, the way the CLI does
323
+ * ```typescript
324
+ * import { getErrorSpan, isRollParserError, roll } from 'roll-parser';
325
+ *
326
+ * function explain(notation: string): string | undefined {
327
+ * try {
328
+ * roll(notation);
329
+ * return undefined;
330
+ * } catch (error) {
331
+ * if (!isRollParserError(error)) throw error;
332
+ * const span = getErrorSpan(error);
333
+ * if (span == null) return error.message;
334
+ * const width = (span.end ?? span.start + 1) - span.start;
335
+ * return [
336
+ * error.message,
337
+ * notation,
338
+ * ' '.repeat(span.start) + '^'.repeat(width),
339
+ * ].join('\n');
340
+ * }
341
+ * }
342
+ *
343
+ * explain('2d6+&');
344
+ * // Unexpected character: '&'
345
+ * // 2d6+&
346
+ * // ^
347
+ *
348
+ * explain('2d6+1d0+3');
349
+ * // Invalid dice sides: 0
350
+ * // 2d6+1d0+3
351
+ * // ^^^
352
+ * ```
353
+ *
354
+ * @category Errors
355
+ */
356
+ export declare function getErrorSpan(error: unknown): ErrorSpan | undefined;
42
357
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;;;;;;;GAgBG;AACH,QAAA,MAAM,uBAAuB,gsBA+BnB,CAAC;AAEX,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3E;;;;;;GAMG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;gBAEvB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB;CAKvD;AAID;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,eAAe,CAQ1E"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
package/dist/errors.js ADDED
@@ -0,0 +1,141 @@
1
+ export const ROLL_PARSER_ERROR_CODES = [
2
+ 'UNEXPECTED_CHARACTER',
3
+ 'UNEXPECTED_IDENTIFIER',
4
+ 'UNEXPECTED_TOKEN',
5
+ 'UNEXPECTED_END',
6
+ 'EXPECTED_TOKEN',
7
+ 'INVALID_DICE_COUNT',
8
+ 'INVALID_DICE_SIDES',
9
+ 'DICE_LIMIT_EXCEEDED',
10
+ 'DIVISION_BY_ZERO',
11
+ 'MODULO_BY_ZERO',
12
+ 'UNKNOWN_OPERATOR',
13
+ 'UNKNOWN_NODE_TYPE',
14
+ 'INVALID_KEEP_DROP_COUNT',
15
+ 'INVALID_KEEP_DROP_TARGET',
16
+ 'EXPLODE_LIMIT_EXCEEDED',
17
+ 'INVALID_EXPLODE_TARGET',
18
+ 'REROLL_LIMIT_EXCEEDED',
19
+ 'INVALID_REROLL_TARGET',
20
+ 'INVALID_SUCCESS_COUNT_TARGET',
21
+ 'INVALID_SORT_TARGET',
22
+ 'INVALID_CRIT_THRESHOLD_TARGET',
23
+ 'INVALID_DIE_BOUND_TARGET',
24
+ 'INVALID_THRESHOLD',
25
+ 'NESTED_VERSUS',
26
+ 'INVALID_FUNCTION_ARITY',
27
+ 'UNKNOWN_FUNCTION',
28
+ 'UNDEFINED_VARIABLE',
29
+ 'INVALID_VARIABLE_VALUE',
30
+ 'AMBIGUOUS_DICE_CHAIN',
31
+ 'MAX_DEPTH_EXCEEDED',
32
+ 'NON_FINITE_RESULT',
33
+ 'INCOMPATIBLE_RNG_STATE',
34
+ 'INVALID_EVALUATION_LIMIT',
35
+ 'INVALID_NOTATION_TYPE',
36
+ ];
37
+ export const NOTATION_ERROR_CODES = [
38
+ 'UNEXPECTED_CHARACTER',
39
+ 'UNEXPECTED_IDENTIFIER',
40
+ 'UNEXPECTED_TOKEN',
41
+ 'UNEXPECTED_END',
42
+ 'EXPECTED_TOKEN',
43
+ 'INVALID_DICE_COUNT',
44
+ 'INVALID_DICE_SIDES',
45
+ 'DICE_LIMIT_EXCEEDED',
46
+ 'DIVISION_BY_ZERO',
47
+ 'MODULO_BY_ZERO',
48
+ 'INVALID_KEEP_DROP_COUNT',
49
+ 'INVALID_KEEP_DROP_TARGET',
50
+ 'EXPLODE_LIMIT_EXCEEDED',
51
+ 'INVALID_EXPLODE_TARGET',
52
+ 'REROLL_LIMIT_EXCEEDED',
53
+ 'INVALID_REROLL_TARGET',
54
+ 'INVALID_SUCCESS_COUNT_TARGET',
55
+ 'INVALID_SORT_TARGET',
56
+ 'INVALID_CRIT_THRESHOLD_TARGET',
57
+ 'INVALID_DIE_BOUND_TARGET',
58
+ 'INVALID_THRESHOLD',
59
+ 'NESTED_VERSUS',
60
+ 'INVALID_FUNCTION_ARITY',
61
+ 'UNDEFINED_VARIABLE',
62
+ 'AMBIGUOUS_DICE_CHAIN',
63
+ 'MAX_DEPTH_EXCEEDED',
64
+ 'NON_FINITE_RESULT',
65
+ 'INVALID_NOTATION_TYPE',
66
+ ];
67
+ const ERROR_BRAND = Symbol.for('roll-parser.error');
68
+ export class RollParserError extends Error {
69
+ code;
70
+ constructor(message, code, options) {
71
+ super(message, options);
72
+ this.name = 'RollParserError';
73
+ this.code = code;
74
+ }
75
+ get [ERROR_BRAND]() {
76
+ return true;
77
+ }
78
+ }
79
+ export let stampEvaluatorSpan;
80
+ export class EvaluatorError extends RollParserError {
81
+ nodeType;
82
+ #start;
83
+ #end;
84
+ constructor(message, code, nodeType, options) {
85
+ super(message, code, options);
86
+ this.name = 'EvaluatorError';
87
+ this.nodeType = nodeType;
88
+ }
89
+ get start() {
90
+ return this.#start;
91
+ }
92
+ get end() {
93
+ return this.#end;
94
+ }
95
+ static {
96
+ stampEvaluatorSpan = (error, start, end) => {
97
+ if (error.#start != null)
98
+ return;
99
+ error.#start = start;
100
+ error.#end = end;
101
+ };
102
+ }
103
+ }
104
+ export function isRollParserError(value) {
105
+ if (value instanceof RollParserError)
106
+ return true;
107
+ return (typeof value === 'object' &&
108
+ value !== null &&
109
+ value[ERROR_BRAND] === true);
110
+ }
111
+ const NOTATION_CODES = new Set(NOTATION_ERROR_CODES);
112
+ export function isNotationError(value) {
113
+ return isRollParserError(value) && NOTATION_CODES.has(value.code);
114
+ }
115
+ export function describeValue(value) {
116
+ if (typeof value === 'string')
117
+ return JSON.stringify(value);
118
+ if (value === null)
119
+ return 'null';
120
+ if (typeof value === 'object' || typeof value === 'function')
121
+ return typeof value;
122
+ return String(value);
123
+ }
124
+ function isOffset(value) {
125
+ return typeof value === 'number' && Number.isInteger(value) && value >= 0;
126
+ }
127
+ export function getErrorSpan(error) {
128
+ if (!isRollParserError(error))
129
+ return undefined;
130
+ if ('position' in error && isOffset(error.position)) {
131
+ return { start: error.position };
132
+ }
133
+ if ('start' in error && isOffset(error.start)) {
134
+ const span = { start: error.start };
135
+ if ('end' in error && isOffset(error.end))
136
+ span.end = error.end;
137
+ return span;
138
+ }
139
+ return undefined;
140
+ }
141
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Die-result construction shared by the evaluator and the modifier
3
+ * implementations.
4
+ *
5
+ * Lives in its own module so the crit/fumble rule has exactly one definition —
6
+ * pool rolling (`evaluator.ts`), explosion continuation dice (`modifiers/
7
+ * explode.ts`), and reroll replacements (`modifiers/reroll.ts`) all build dice
8
+ * through here.
9
+ *
10
+ * @module evaluator/die
11
+ */
12
+ import type { DieModifier, DieResult } from '../types.js';
13
+ /**
14
+ * Creates a die result with critical/fumble detection.
15
+ *
16
+ * `modifiers` is taken by the caller because the flags depend on how the die
17
+ * entered the pool (a fresh pool die starts bare, an explosion continuation
18
+ * die starts `['exploded', 'kept']`).
19
+ */
20
+ export declare function createDieResult(sides: number, result: number, modifiers: DieModifier[]): DieResult;
21
+ /**
22
+ * Creates a Fate/Fudge die result. Uses `sides = 0` as a sentinel — Fate dice
23
+ * have no max-face concept, so `critical` and `fumble` are always `false`.
24
+ */
25
+ export declare function createFateDieResult(result: number, modifiers: DieModifier[]): DieResult;
26
+ //# sourceMappingURL=die.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"die.d.ts","sourceRoot":"","sources":["../../src/evaluator/die.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE1D;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,WAAW,EAAE,GACvB,SAAS,CAUX;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,SAAS,CAQvF"}
@@ -0,0 +1,19 @@
1
+ export function createDieResult(sides, result, modifiers) {
2
+ return {
3
+ sides,
4
+ result,
5
+ modifiers,
6
+ critical: result === sides && sides > 1,
7
+ fumble: result === 1 && sides > 1,
8
+ };
9
+ }
10
+ export function createFateDieResult(result, modifiers) {
11
+ return {
12
+ sides: 0,
13
+ result,
14
+ modifiers,
15
+ critical: false,
16
+ fumble: false,
17
+ };
18
+ }
19
+ //# sourceMappingURL=die.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"die.js","sourceRoot":"","sources":["../../src/evaluator/die.ts"],"names":[],"mappings":"AAqBA,MAAM,UAAU,eAAe,CAC7B,KAAa,EACb,MAAc,EACd,SAAwB;IAIxB,OAAO;QACL,KAAK;QACL,MAAM;QACN,SAAS;QACT,QAAQ,EAAE,MAAM,KAAK,KAAK,IAAI,KAAK,GAAG,CAAC;QACvC,MAAM,EAAE,MAAM,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;KAClC,CAAC;AACJ,CAAC;AAMD,MAAM,UAAU,mBAAmB,CAAC,MAAc,EAAE,SAAwB;IAC1E,OAAO;QACL,KAAK,EAAE,CAAC;QACR,MAAM;QACN,SAAS;QACT,QAAQ,EAAE,KAAK;QACf,MAAM,EAAE,KAAK;KACd,CAAC;AACJ,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Per-evaluation shared environment.
3
+ *
4
+ * Lives in its own module so the modifier implementations under
5
+ * `./modifiers/*` can type their `env` parameter without importing
6
+ * `./evaluator.js`, which imports them back.
7
+ *
8
+ * @module evaluator/env
9
+ */
10
+ import type { ASTNode } from '../parser/ast.js';
11
+ /**
12
+ * Per-evaluation shared environment (created once, shared across all branches).
13
+ *
14
+ * Exported for use by modifier implementations under `./modifiers/*`. Not part
15
+ * of the public library API.
16
+ */
17
+ export type EvalEnv = {
18
+ readonly maxDice: number;
19
+ readonly maxExplodeIterations: number;
20
+ readonly maxRerollIterations: number;
21
+ totalDiceRolled: number;
22
+ /**
23
+ * Set to `true` by `evalSuccessCount`. Propagates through the shared env
24
+ * so `evaluate()` can include `successes`/`failures` fields even when no
25
+ * die was tagged (impossible threshold).
26
+ */
27
+ hasSuccessCount: boolean;
28
+ /**
29
+ * `true` while the evaluator is inside a `VersusNode`'s roll or DC
30
+ * sub-evaluation. `evalVersus` rejects nesting via this flag — catches
31
+ * paren-nested versus (`1d20 vs (5 vs 3)`) that slip past the parser's
32
+ * left-chain check.
33
+ */
34
+ insideVersus: boolean;
35
+ /**
36
+ * User-supplied variable map for `@name` / `@{name}` references. Always
37
+ * defined — `evaluate()` defaults to an empty object so lookups can be
38
+ * branch-free on presence.
39
+ */
40
+ readonly context: Readonly<Record<string, number>>;
41
+ /**
42
+ * Behavior when a referenced variable is missing from `context`. Always
43
+ * defined — `evaluate()` defaults to `'throw'`.
44
+ */
45
+ readonly onMissingVariable: 'throw' | 'zero';
46
+ };
47
+ /**
48
+ * Reserves `count` dice against the global `maxDice` budget, throwing
49
+ * `DICE_LIMIT_EXCEEDED` when the reservation would overshoot.
50
+ *
51
+ * Every path that consumes RNG draws for dice charges through here — initial
52
+ * pools (`evalDice` / `evalFateDice`), explosion continuations, and reroll
53
+ * replacements — so the limit and its message have one definition.
54
+ */
55
+ export declare function chargeDice(env: EvalEnv, count: number, nodeType: ASTNode['type']): void;
56
+ /** Single-die shorthand for {@link chargeDice}. */
57
+ export declare function chargeDie(env: EvalEnv, nodeType: ASTNode['type']): void;
58
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/evaluator/env.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,eAAe,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,eAAe,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD;;;OAGG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,GAAG,MAAM,CAAC;CAC9C,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CASvF;AAED,mDAAmD;AACnD,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CAEvE"}
@@ -0,0 +1,11 @@
1
+ import { EvaluatorError } from '../errors.js';
2
+ export function chargeDice(env, count, nodeType) {
3
+ if (env.totalDiceRolled + count > env.maxDice) {
4
+ throw new EvaluatorError(`Total dice count ${env.totalDiceRolled + count} exceeds limit of ${env.maxDice}`, 'DICE_LIMIT_EXCEEDED', nodeType);
5
+ }
6
+ env.totalDiceRolled += count;
7
+ }
8
+ export function chargeDie(env, nodeType) {
9
+ chargeDice(env, 1, nodeType);
10
+ }
11
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/evaluator/env.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAgD9C,MAAM,UAAU,UAAU,CAAC,GAAY,EAAE,KAAa,EAAE,QAAyB;IAC/E,IAAI,GAAG,CAAC,eAAe,GAAG,KAAK,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;QAC9C,MAAM,IAAI,cAAc,CACtB,oBAAoB,GAAG,CAAC,eAAe,GAAG,KAAK,qBAAqB,GAAG,CAAC,OAAO,EAAE,EACjF,qBAAqB,EACrB,QAAQ,CACT,CAAC;IACJ,CAAC;IACD,GAAG,CAAC,eAAe,IAAI,KAAK,CAAC;AAC/B,CAAC;AAGD,MAAM,UAAU,SAAS,CAAC,GAAY,EAAE,QAAyB;IAC/D,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;AAC/B,CAAC"}