roll-parser 3.3.0 → 3.4.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 +42 -1
- package/README.md +60 -32
- package/dist/cli/args.d.ts +11 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +67 -42
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/format.d.ts +8 -1
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +4 -3
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/main.d.ts +3 -3
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +50 -19
- package/dist/cli/main.js.map +1 -1
- package/dist/evaluator/evaluator.d.ts +15 -3
- package/dist/evaluator/evaluator.d.ts.map +1 -1
- package/dist/evaluator/evaluator.js +134 -105
- package/dist/evaluator/evaluator.js.map +1 -1
- package/dist/evaluator/modifiers/flags.d.ts +7 -1
- package/dist/evaluator/modifiers/flags.d.ts.map +1 -1
- package/dist/evaluator/modifiers/flags.js +9 -1
- package/dist/evaluator/modifiers/flags.js.map +1 -1
- package/dist/evaluator/modifiers/keep-drop.d.ts.map +1 -1
- package/dist/evaluator/modifiers/keep-drop.js +19 -16
- package/dist/evaluator/modifiers/keep-drop.js.map +1 -1
- package/dist/lexer/lexer.d.ts +5 -0
- package/dist/lexer/lexer.d.ts.map +1 -1
- package/dist/lexer/lexer.js +3 -0
- package/dist/lexer/lexer.js.map +1 -1
- package/dist/parser/parser.d.ts +1 -0
- package/dist/parser/parser.d.ts.map +1 -1
- package/dist/parser/parser.js +23 -20
- package/dist/parser/parser.js.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +2 -1
- package/dist/render.js.map +1 -1
- package/dist/rng/seeded.d.ts.map +1 -1
- package/dist/rng/seeded.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +13 -14
- package/src/cli/args.ts +112 -40
- package/src/cli/format.ts +11 -3
- package/src/cli/main.ts +97 -22
- package/src/evaluator/evaluator.ts +189 -150
- package/src/evaluator/modifiers/flags.ts +18 -2
- package/src/evaluator/modifiers/keep-drop.ts +42 -16
- package/src/lexer/lexer.ts +8 -0
- package/src/parser/parser.ts +36 -32
- package/src/render.ts +2 -1
- package/src/rng/seeded.ts +4 -0
- package/src/version.ts +1 -1
package/src/cli/main.ts
CHANGED
|
@@ -8,10 +8,15 @@
|
|
|
8
8
|
* @module cli/main
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
type ErrorSpan,
|
|
13
|
+
getErrorSpan,
|
|
14
|
+
isRollParserError,
|
|
15
|
+
type RollParserErrorCode,
|
|
16
|
+
} from '../errors.js';
|
|
12
17
|
import { VERSION } from '../index.js';
|
|
13
18
|
import { roll } from '../roll.js';
|
|
14
|
-
import { parseArgs } from './args.js';
|
|
19
|
+
import { isKnownOption, parseArgs } from './args.js';
|
|
15
20
|
import { formatResult } from './format.js';
|
|
16
21
|
|
|
17
22
|
const HELP_TEXT = `roll-parser v${VERSION}
|
|
@@ -24,12 +29,23 @@ Options:
|
|
|
24
29
|
-v, --verbose Show detailed roll breakdown
|
|
25
30
|
--json Print the whole result as compact JSON (wins over --verbose)
|
|
26
31
|
--seed <value> Use seed for reproducible rolls
|
|
27
|
-
--
|
|
32
|
+
-- Notation that looks like an option — put options before it
|
|
28
33
|
|
|
29
34
|
JSON output:
|
|
30
|
-
Emits the complete result, including the structured "parts" tree
|
|
31
|
-
"
|
|
32
|
-
|
|
35
|
+
Emits the complete result, including the structured "parts" tree, plus the
|
|
36
|
+
"seed" that produced it and the "version" that fixes the seed-to-dice
|
|
37
|
+
mapping — rerun with --seed <seed> on the same major to replay the roll.
|
|
38
|
+
When --seed is omitted the CLI mints one. The "degree" field
|
|
39
|
+
(DegreeOfSuccess) serializes as a number: 0 critical failure, 1 failure,
|
|
40
|
+
2 success, 3 critical success.
|
|
41
|
+
|
|
42
|
+
JSON errors:
|
|
43
|
+
Every diagnostic is one JSON line on stderr:
|
|
44
|
+
{"error":{"message":...,"code":...,"span":{"start":N}},...}. A roll error
|
|
45
|
+
adds the "notation" and "seed" that produced it, so a failure replays like
|
|
46
|
+
a result does; "code" and "span" are absent when the failure carries
|
|
47
|
+
neither. Usage errors are covered too, but only where --json still reads as
|
|
48
|
+
the flag: it is a seed value in --seed --json, and notation after --.
|
|
33
49
|
|
|
34
50
|
Exit codes:
|
|
35
51
|
0 Success
|
|
@@ -41,9 +57,12 @@ Examples:
|
|
|
41
57
|
roll-parser 4d6kh3 --verbose
|
|
42
58
|
roll-parser 4d6dl1 --seed "character-str"
|
|
43
59
|
roll-parser "1d20+7 vs 25" --json
|
|
44
|
-
roll-parser
|
|
60
|
+
roll-parser -1d6+3
|
|
45
61
|
`;
|
|
46
62
|
|
|
63
|
+
// The build sets `types: []`, so no ambient runtime globals are declared.
|
|
64
|
+
declare const crypto: { randomUUID(): string };
|
|
65
|
+
|
|
47
66
|
/** Sink for one stream's worth of CLI output. */
|
|
48
67
|
export type WriteFn = (text: string) => void;
|
|
49
68
|
|
|
@@ -74,26 +93,73 @@ export function writeErrorContext(notation: string, error: unknown, write: Write
|
|
|
74
93
|
write(` ${' '.repeat(column)}^\n`);
|
|
75
94
|
}
|
|
76
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Names the ordering rule when `--` swallowed an option. Gated on `terminated`,
|
|
98
|
+
* never on `argv` holding a `--`: `--seed --` makes that `--` a seed value, so a
|
|
99
|
+
* textual scan would name a terminator that never ran.
|
|
100
|
+
*/
|
|
101
|
+
function writeOptionHint(terminated: boolean, notation: string, write: WriteFn): void {
|
|
102
|
+
if (terminated && notation.split(' ').some(isKnownOption)) {
|
|
103
|
+
write('Hint: options must come before "--"\n');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** The `error` member of the `--json` failure record. */
|
|
108
|
+
type JsonErrorBody = {
|
|
109
|
+
message: string;
|
|
110
|
+
code?: RollParserErrorCode;
|
|
111
|
+
span?: ErrorSpan | undefined;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* What the failure record needs to replay a roll that got as far as the
|
|
116
|
+
* dice. A usage error passes no context — it never reached the dice, so
|
|
117
|
+
* there is no roll to reproduce.
|
|
118
|
+
*/
|
|
119
|
+
type RollContext = {
|
|
120
|
+
notation: string;
|
|
121
|
+
seed: string;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Writes one failure record as compact JSON. `JSON.stringify` drops the
|
|
126
|
+
* absent members, so a lexer error carries no `end` and a usage error carries
|
|
127
|
+
* neither `code` nor `span` — the key is missing rather than null.
|
|
128
|
+
*/
|
|
129
|
+
function writeJsonError(write: WriteFn, error: JsonErrorBody, context?: RollContext): void {
|
|
130
|
+
write(`${JSON.stringify({ error, ...context, version: VERSION })}\n`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Reports a usage error and returns the exit code for one. */
|
|
134
|
+
function failUsage(stderr: WriteFn, message: string, json: boolean): number {
|
|
135
|
+
if (json) {
|
|
136
|
+
writeJsonError(stderr, { message });
|
|
137
|
+
} else {
|
|
138
|
+
stderr(`Error: ${message}\n`);
|
|
139
|
+
stderr('Run "roll-parser --help" for usage.\n');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return 2;
|
|
143
|
+
}
|
|
144
|
+
|
|
77
145
|
/**
|
|
78
146
|
* Runs one CLI invocation and returns the process exit code: `0` on success,
|
|
79
147
|
* `1` for a roll-parser error, `2` for a usage error. Anything that is not a
|
|
80
148
|
* `RollParserError` propagates so the runtime reports it with a stack.
|
|
81
149
|
*
|
|
82
|
-
* `--json` swaps the success payload
|
|
83
|
-
* stderr
|
|
84
|
-
* before
|
|
150
|
+
* `--json` swaps the success payload on stdout and every diagnostic on
|
|
151
|
+
* stderr, usage errors included. Exit codes are identical either way, so
|
|
152
|
+
* scripts can still branch on the code before reading a stream.
|
|
85
153
|
*/
|
|
86
154
|
export function main(env: CliEnv): number {
|
|
87
155
|
const { argv, stdout, stderr } = env;
|
|
88
156
|
const parsed = parseArgs(argv);
|
|
89
157
|
|
|
90
158
|
if (!parsed.ok) {
|
|
91
|
-
stderr
|
|
92
|
-
stderr('Run "roll-parser --help" for usage.\n');
|
|
93
|
-
return 2;
|
|
159
|
+
return failUsage(stderr, parsed.error, parsed.json);
|
|
94
160
|
}
|
|
95
161
|
|
|
96
|
-
const { args } = parsed;
|
|
162
|
+
const { args, terminated } = parsed;
|
|
97
163
|
|
|
98
164
|
if (args.showHelp) {
|
|
99
165
|
stdout(HELP_TEXT);
|
|
@@ -106,20 +172,29 @@ export function main(env: CliEnv): number {
|
|
|
106
172
|
}
|
|
107
173
|
|
|
108
174
|
if (args.notation == null) {
|
|
109
|
-
stderr
|
|
110
|
-
stderr('Run "roll-parser --help" for usage.\n');
|
|
111
|
-
return 2;
|
|
175
|
+
return failUsage(stderr, 'No dice notation provided.', args.json);
|
|
112
176
|
}
|
|
113
177
|
|
|
178
|
+
// `SeededRNG`'s auto-seed is unreachable, so mint one that can be echoed back.
|
|
179
|
+
const seed = args.seed ?? crypto.randomUUID();
|
|
180
|
+
|
|
114
181
|
try {
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
const output = formatResult(result, { json: args.json, verbose: args.verbose });
|
|
182
|
+
const result = roll(args.notation, { seed });
|
|
183
|
+
const output = formatResult(result, { json: args.json, verbose: args.verbose, seed });
|
|
118
184
|
stdout(`${output}\n`);
|
|
119
185
|
} catch (error) {
|
|
120
186
|
if (isRollParserError(error)) {
|
|
121
|
-
|
|
122
|
-
|
|
187
|
+
if (args.json) {
|
|
188
|
+
writeJsonError(
|
|
189
|
+
stderr,
|
|
190
|
+
{ message: error.message, code: error.code, span: getErrorSpan(error) },
|
|
191
|
+
{ notation: args.notation, seed },
|
|
192
|
+
);
|
|
193
|
+
} else {
|
|
194
|
+
stderr(`Error: ${error.message}\n`);
|
|
195
|
+
writeErrorContext(args.notation, error, stderr);
|
|
196
|
+
writeOptionHint(terminated, args.notation, stderr);
|
|
197
|
+
}
|
|
123
198
|
return 1;
|
|
124
199
|
}
|
|
125
200
|
throw error;
|