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.
- package/CHANGELOG.md +107 -9
- package/MIGRATION.md +147 -0
- package/README.md +960 -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 +17 -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 +26 -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 +58 -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 +906 -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 +0 -1
- package/dist/evaluator/modifiers/crit-threshold.d.ts.map +1 -1
- package/dist/evaluator/modifiers/crit-threshold.js +23 -0
- package/dist/evaluator/modifiers/crit-threshold.js.map +1 -0
- package/dist/evaluator/modifiers/die-bound.d.ts +26 -0
- package/dist/evaluator/modifiers/die-bound.d.ts.map +1 -0
- package/dist/evaluator/modifiers/die-bound.js +14 -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 +103 -0
- package/dist/evaluator/modifiers/explode.js.map +1 -0
- package/dist/evaluator/modifiers/flags.d.ts +37 -0
- package/dist/evaluator/modifiers/flags.d.ts.map +1 -0
- package/dist/evaluator/modifiers/flags.js +18 -0
- package/dist/evaluator/modifiers/flags.js.map +1 -0
- package/dist/evaluator/modifiers/keep-drop.d.ts +12 -28
- 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 +62 -0
- package/dist/evaluator/modifiers/reroll.js.map +1 -0
- package/dist/evaluator/modifiers/sort.d.ts +4 -0
- 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 +2 -6
- package/dist/evaluator/modifiers/success-count.d.ts.map +1 -1
- package/dist/evaluator/modifiers/success-count.js +24 -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 +314 -160
- 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/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 +306 -44
- 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 +83 -34
- package/src/cli/args.ts +66 -10
- package/src/cli/format.ts +22 -3
- 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 +50 -0
- package/src/evaluator/env.ts +73 -0
- package/src/evaluator/evaluator.ts +653 -421
- package/src/evaluator/modifiers/crit-threshold.ts +3 -6
- package/src/evaluator/modifiers/die-bound.ts +39 -0
- package/src/evaluator/modifiers/explode.ts +60 -58
- package/src/evaluator/modifiers/flags.ts +61 -0
- package/src/evaluator/modifiers/keep-drop.ts +124 -126
- package/src/evaluator/modifiers/reroll.ts +28 -49
- package/src/evaluator/modifiers/sort.ts +14 -1
- package/src/evaluator/modifiers/success-count.ts +5 -8
- 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 +323 -341
- package/src/parser/guards.ts +248 -0
- package/src/parser/parser.ts +419 -242
- 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 +310 -43
- 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/errors.d.ts
CHANGED
|
@@ -1,42 +1,357 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Common error base class
|
|
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
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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
|
-
*
|
|
38
|
-
*
|
|
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
|
-
|
|
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
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE/C;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,uBAAuB,YAClC,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EACvB,uBAAuB,EACvB,8BAA8B,EAC9B,qBAAqB,EACrB,+BAA+B,EAC/B,0BAA0B,EAC1B,mBAAmB,EACnB,eAAe,EACf,wBAAwB,EACxB,kBAAkB,EAClB,oBAAoB,EACpB,wBAAwB,EACxB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,wBAAwB,EACxB,0BAA0B,EAC1B,uBAAuB,CACf,CAAC;AAEX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,oBAAoB,YAC/B,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,gBAAgB,EAChB,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,EACxB,wBAAwB,EACxB,uBAAuB,EACvB,uBAAuB,EACvB,8BAA8B,EAC9B,qBAAqB,EACrB,+BAA+B,EAC/B,0BAA0B,EAC1B,mBAAmB,EACnB,eAAe,EACf,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,uBAAuB,CACf,CAAC;AAEX;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAatE;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IACxC;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IAEnC,YAAY,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,EAAE,OAAO,CAAC,EAAE,YAAY,EAI7E;CAeF;AAoBD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,cAAe,SAAQ,eAAe;;IACjD;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;IAK/C,YACE,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,mBAAmB,EACzB,QAAQ,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,EAC1B,OAAO,CAAC,EAAE,YAAY,EAKvB;IAED;;;OAGG;IACH,IAAI,KAAK,IAAI,MAAM,GAAG,SAAS,CAE9B;IAED,6DAA6D;IAC7D,IAAI,GAAG,IAAI,MAAM,GAAG,SAAS,CAE5B;CASF;AAED;;;;;;GAMG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,eAAe,CAO1E;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,eAAe,GAAG;IAAE,IAAI,EAAE,iBAAiB,CAAA;CAAE,CAExD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAOpD;AAOD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAclE"}
|
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"}
|