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.
- package/CHANGELOG.md +91 -0
- package/README.md +175 -170
- package/dist/cli/args.d.ts +35 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/format.d.ts +20 -0
- package/dist/cli/format.d.ts.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli.js +2608 -0
- package/dist/cli.js.map +28 -0
- package/dist/errors.d.ts +42 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/evaluator/evaluator.d.ts +121 -0
- package/dist/evaluator/evaluator.d.ts.map +1 -0
- package/dist/evaluator/index.d.ts +8 -0
- package/dist/evaluator/index.d.ts.map +1 -0
- package/dist/evaluator/modifiers/compare.d.ts +15 -0
- package/dist/evaluator/modifiers/compare.d.ts.map +1 -0
- package/dist/evaluator/modifiers/crit-threshold.d.ts +28 -0
- package/dist/evaluator/modifiers/crit-threshold.d.ts.map +1 -0
- package/dist/evaluator/modifiers/explode.d.ts +48 -0
- package/dist/evaluator/modifiers/explode.d.ts.map +1 -0
- package/dist/evaluator/modifiers/keep-drop.d.ts +44 -0
- package/dist/evaluator/modifiers/keep-drop.d.ts.map +1 -0
- package/dist/evaluator/modifiers/reroll.d.ts +33 -0
- package/dist/evaluator/modifiers/reroll.d.ts.map +1 -0
- package/dist/evaluator/modifiers/sort.d.ts +23 -0
- package/dist/evaluator/modifiers/sort.d.ts.map +1 -0
- package/dist/evaluator/modifiers/success-count.d.ts +28 -0
- package/dist/evaluator/modifiers/success-count.d.ts.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2560 -0
- package/dist/index.js.map +26 -0
- package/dist/lexer/lexer.d.ts +90 -0
- package/dist/lexer/lexer.d.ts.map +1 -0
- package/dist/lexer/tokens.d.ts +107 -0
- package/dist/lexer/tokens.d.ts.map +1 -0
- package/dist/parser/ast.d.ts +368 -0
- package/dist/parser/ast.d.ts.map +1 -0
- package/dist/parser/parser.d.ts +145 -0
- package/dist/parser/parser.d.ts.map +1 -0
- package/dist/rng/index.d.ts +8 -0
- package/dist/rng/index.d.ts.map +1 -0
- package/dist/rng/mock.d.ts +36 -0
- package/dist/rng/mock.d.ts.map +1 -0
- package/dist/rng/seeded.d.ts +40 -0
- package/dist/rng/seeded.d.ts.map +1 -0
- package/dist/rng/types.d.ts +24 -0
- package/dist/rng/types.d.ts.map +1 -0
- package/dist/roll.d.ts +51 -0
- package/dist/roll.d.ts.map +1 -0
- package/dist/testing.d.ts +11 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +41 -0
- package/dist/testing.js.map +11 -0
- package/dist/types.d.ts +246 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +66 -43
- package/src/cli/args.ts +73 -0
- package/src/cli/format.ts +49 -0
- package/src/cli/index.ts +92 -0
- package/src/errors.ts +90 -0
- package/src/evaluator/evaluator.ts +1552 -0
- package/src/evaluator/index.ts +14 -0
- package/src/evaluator/modifiers/compare.ts +29 -0
- package/src/evaluator/modifiers/crit-threshold.ts +59 -0
- package/src/evaluator/modifiers/explode.ts +219 -0
- package/src/evaluator/modifiers/keep-drop.ts +151 -0
- package/src/evaluator/modifiers/reroll.ts +165 -0
- package/src/evaluator/modifiers/sort.ts +30 -0
- package/src/evaluator/modifiers/success-count.ts +59 -0
- package/src/index.ts +90 -0
- package/src/lexer/lexer.ts +374 -0
- package/src/lexer/tokens.ts +187 -0
- package/src/parser/ast.ts +628 -0
- package/src/parser/parser.ts +1239 -0
- package/src/rng/index.ts +8 -0
- package/src/rng/mock.ts +65 -0
- package/src/rng/seeded.ts +157 -0
- package/src/rng/types.ts +25 -0
- package/src/roll.ts +74 -0
- package/src/testing.ts +16 -0
- package/src/types.ts +237 -0
- package/bin/help.txt +0 -19
- package/bin/roll-parser.js +0 -23
- package/dist/roll-parser.js +0 -697
- package/dist/roll-parser.min.js +0 -1
- package/index.js +0 -24
- package/src/complex/parse.js +0 -37
- package/src/complex/parseAndRoll.js +0 -21
- package/src/complex/parseAndRollClassic.js +0 -20
- package/src/complex/parseAndRollSimple.js +0 -19
- package/src/complex/parseAndRollWod.js +0 -20
- package/src/complex/parseClassicRoll.js +0 -22
- package/src/complex/parseSimpleRoll.js +0 -21
- package/src/complex/parseWodRoll.js +0 -21
- package/src/converter.js +0 -45
- package/src/grammar.js +0 -14
- package/src/mapper.js +0 -50
- package/src/normalizer.js +0 -51
- package/src/object/Result.js +0 -23
- package/src/object/Roll.js +0 -26
- package/src/object/Type.js +0 -5
- package/src/object/WodRoll.js +0 -29
- package/src/parser.js +0 -43
- package/src/random.js +0 -22
- package/src/roller.js +0 -106
- package/src/stringifier.js +0 -33
package/package.json
CHANGED
|
@@ -1,18 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roll-parser",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
3
|
+
"version": "3.0.0-beta.0",
|
|
4
|
+
"description": "High-performance dice notation parser for tabletop RPGs. TypeScript-first, Bun-optimized.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"roll-parser": "./dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"sideEffects": false,
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./testing": {
|
|
18
|
+
"types": "./dist/testing.d.ts",
|
|
19
|
+
"default": "./dist/testing.js"
|
|
20
|
+
},
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"src",
|
|
26
|
+
"CHANGELOG.md",
|
|
27
|
+
"!src/**/*.test.ts"
|
|
28
|
+
],
|
|
7
29
|
"keywords": [
|
|
8
30
|
"parser",
|
|
9
31
|
"dice",
|
|
10
32
|
"roll",
|
|
11
33
|
"rpg",
|
|
12
34
|
"dnd",
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
35
|
+
"d20",
|
|
36
|
+
"pratt-parser",
|
|
37
|
+
"typescript",
|
|
38
|
+
"bun"
|
|
16
39
|
],
|
|
17
40
|
"homepage": "https://github.com/edloidas/roll-parser#readme",
|
|
18
41
|
"bugs": {
|
|
@@ -20,50 +43,50 @@
|
|
|
20
43
|
},
|
|
21
44
|
"license": "MIT",
|
|
22
45
|
"author": "Mikita Taukachou <edloidas@gmail.com> (https://edloidas.com)",
|
|
23
|
-
"files": [
|
|
24
|
-
"index.js",
|
|
25
|
-
"bin",
|
|
26
|
-
"dist",
|
|
27
|
-
"src"
|
|
28
|
-
],
|
|
29
46
|
"repository": {
|
|
30
47
|
"type": "git",
|
|
31
48
|
"url": "git+https://github.com/edloidas/roll-parser.git"
|
|
32
49
|
},
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"cover": "jest --coverage",
|
|
36
|
-
"report": "node_modules/.bin/jest --coverage && cat coverage/lcov.info | node_modules/coveralls/bin/coveralls.js",
|
|
37
|
-
"fix": "eslint --fix .",
|
|
38
|
-
"api": "rimraf api/ && jsdoc -c .jsdocrc",
|
|
39
|
-
"clean": "rimraf dist/*",
|
|
40
|
-
"build:bundle": "browserify index.js --debug --standalone rollParser -o dist/roll-parser.js",
|
|
41
|
-
"build:minify": "babili dist/roll-parser.js --out-file dist/roll-parser.min.js",
|
|
42
|
-
"build": "npm run build:bundle && npm run build:minify",
|
|
43
|
-
"prepush": "npm test",
|
|
44
|
-
"prepublishOnly": "npm run fix && npm test && npm run clean && npm run build"
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
45
52
|
},
|
|
46
|
-
"
|
|
47
|
-
"
|
|
53
|
+
"scripts": {
|
|
54
|
+
"typecheck": "tsc --noEmit",
|
|
55
|
+
"lint": "biome lint .",
|
|
56
|
+
"lint:fix": "biome lint --write .",
|
|
57
|
+
"format": "biome format --write .",
|
|
58
|
+
"format:check": "biome format .",
|
|
59
|
+
"check": "bun typecheck && bun lint && bun format:check",
|
|
60
|
+
"check:fix": "bun typecheck && bun lint:fix && bun format",
|
|
61
|
+
"check:version": "bun scripts/check-version.ts",
|
|
62
|
+
"check:changelog": "bun scripts/check-changelog.ts",
|
|
63
|
+
"clean": "rm -rf dist coverage",
|
|
64
|
+
"build": "bun run clean && bun run build:lib && bun run build:testing && bun run build:cli && bun run build:types",
|
|
65
|
+
"build:lib": "bun build src/index.ts --outdir dist --target node --sourcemap=linked",
|
|
66
|
+
"build:testing": "bun build src/testing.ts --outdir dist --target node --sourcemap=linked",
|
|
67
|
+
"build:cli": "bun build src/cli/index.ts --outdir dist --entry-naming 'cli.[ext]' --target node --sourcemap=linked",
|
|
68
|
+
"build:types": "tsc --emitDeclarationOnly -p tsconfig.build.json",
|
|
69
|
+
"check:package": "attw --pack . --profile esm-only && publint",
|
|
70
|
+
"bench": "bun run bench/roll.bench.ts",
|
|
71
|
+
"test": "bun test",
|
|
72
|
+
"test:node": "node -e \"import('./dist/index.js').then(m => { const r = m.roll('1d6'); if (!(r.total >= 1 && r.total <= 6)) throw new Error('ESM smoke failed: total=' + r.total); console.log('Node.js ESM smoke: ok'); })\" && node -e \"const m = require('./dist/index.js'); const r = m.roll('1d6'); if (!(r.total >= 1 && r.total <= 6)) throw new Error('require(esm) smoke failed: total=' + r.total); console.log('Node.js require(esm) smoke: ok');\"",
|
|
73
|
+
"test:watch": "bun test --watch",
|
|
74
|
+
"test:ci": "bun test --bail --coverage",
|
|
75
|
+
"validate": "bun run check && bun run build && bun run check:package && bun test:ci",
|
|
76
|
+
"coverage": "bun test --coverage",
|
|
77
|
+
"release:dry": "bun run check:version && bun run check:changelog && bun run check && bun run build && bun run check:package && bun test",
|
|
78
|
+
"prepublishOnly": "bun run release:dry"
|
|
48
79
|
},
|
|
49
80
|
"devDependencies": {
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"eslint-config-airbnb-base": "^11.2.0",
|
|
58
|
-
"eslint-plugin-import": "^2.3.0",
|
|
59
|
-
"husky": "^0.13.4",
|
|
60
|
-
"istanbul": "^0.4.5",
|
|
61
|
-
"jest": "^20.0.4",
|
|
62
|
-
"jsdoc": "^3.4.3",
|
|
63
|
-
"rimraf": "^2.6.1"
|
|
81
|
+
"@arethetypeswrong/cli": "^0.18.4",
|
|
82
|
+
"@biomejs/biome": "^2.4.9",
|
|
83
|
+
"@types/bun": "^1.3.0",
|
|
84
|
+
"fast-check": "^4.6.0",
|
|
85
|
+
"mitata": "^1.0.34",
|
|
86
|
+
"publint": "^0.3.21",
|
|
87
|
+
"typescript": "^6.0.2"
|
|
64
88
|
},
|
|
65
89
|
"engines": {
|
|
66
|
-
"node": ">=
|
|
67
|
-
"npm": ">= 3.8.6"
|
|
90
|
+
"node": ">= 22.0.0"
|
|
68
91
|
}
|
|
69
92
|
}
|
package/src/cli/args.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI argument parser.
|
|
3
|
+
*
|
|
4
|
+
* Pure function that parses process.argv-style string arrays into typed options.
|
|
5
|
+
*
|
|
6
|
+
* @module cli/args
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Parsed CLI arguments.
|
|
11
|
+
*/
|
|
12
|
+
export type CliArgs = {
|
|
13
|
+
notation: string | undefined;
|
|
14
|
+
verbose: boolean;
|
|
15
|
+
seed: string | undefined;
|
|
16
|
+
showHelp: boolean;
|
|
17
|
+
showVersion: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Result of parsing CLI arguments — either success or a usage error.
|
|
22
|
+
*/
|
|
23
|
+
export type ParseArgsResult = { ok: true; args: CliArgs } | { ok: false; error: string };
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Parses a raw argument array into typed CLI options.
|
|
27
|
+
*
|
|
28
|
+
* @param argv - Arguments to parse (typically `process.argv.slice(2)`)
|
|
29
|
+
* @returns Parsed result or an error message for usage errors
|
|
30
|
+
*/
|
|
31
|
+
export function parseArgs(argv: string[]): ParseArgsResult {
|
|
32
|
+
let verbose = false;
|
|
33
|
+
let seed: string | undefined;
|
|
34
|
+
let showHelp = false;
|
|
35
|
+
let showVersion = false;
|
|
36
|
+
const positional: string[] = [];
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < argv.length; i++) {
|
|
39
|
+
const arg = argv[i] as string;
|
|
40
|
+
|
|
41
|
+
if (arg === '--help' || arg === '-h') {
|
|
42
|
+
showHelp = true;
|
|
43
|
+
} else if (arg === '--version') {
|
|
44
|
+
showVersion = true;
|
|
45
|
+
} else if (arg === '--verbose' || arg === '-v') {
|
|
46
|
+
verbose = true;
|
|
47
|
+
} else if (arg === '--seed') {
|
|
48
|
+
const next = argv[i + 1];
|
|
49
|
+
if (next === undefined || (next.startsWith('-') && !/^-\d/.test(next))) {
|
|
50
|
+
return { ok: false, error: 'Missing value for --seed' };
|
|
51
|
+
}
|
|
52
|
+
seed = next;
|
|
53
|
+
i++;
|
|
54
|
+
} else if (arg.startsWith('--seed=')) {
|
|
55
|
+
const value = arg.slice('--seed='.length);
|
|
56
|
+
if (value === '') {
|
|
57
|
+
return { ok: false, error: 'Missing value for --seed' };
|
|
58
|
+
}
|
|
59
|
+
seed = value;
|
|
60
|
+
} else if (arg.startsWith('--')) {
|
|
61
|
+
return { ok: false, error: `Unknown option: ${arg}` };
|
|
62
|
+
} else if (arg.startsWith('-') && arg.length > 1 && !/^[\ddD(]/.test(arg.slice(1))) {
|
|
63
|
+
// Negative prefix notation (-3, -d6, -D6, -dF, -(2d6)) stays positional
|
|
64
|
+
return { ok: false, error: `Unknown option: ${arg}` };
|
|
65
|
+
} else {
|
|
66
|
+
positional.push(arg);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const notation = positional.length > 0 ? positional.join(' ') : undefined;
|
|
71
|
+
|
|
72
|
+
return { ok: true, args: { notation, verbose, seed, showHelp, showVersion } };
|
|
73
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI output formatting.
|
|
3
|
+
*
|
|
4
|
+
* Transforms RollResult into terminal-appropriate strings.
|
|
5
|
+
*
|
|
6
|
+
* @module cli/format
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { RollResult } from '../types.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Formats a roll result for terminal display.
|
|
13
|
+
*
|
|
14
|
+
* In normal mode, returns just the total. In verbose mode, returns the
|
|
15
|
+
* rendered breakdown with terminal-safe formatting for dropped dice.
|
|
16
|
+
*
|
|
17
|
+
* @param result - The roll result to format
|
|
18
|
+
* @param verbose - Whether to show the detailed breakdown
|
|
19
|
+
* @returns Formatted string for terminal output
|
|
20
|
+
*/
|
|
21
|
+
export function formatResult(result: RollResult, verbose: boolean): string {
|
|
22
|
+
if (!verbose) {
|
|
23
|
+
return String(result.total);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return formatRendered(result.rendered);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Converts markdown-style dice markers to terminal-friendly forms.
|
|
31
|
+
*
|
|
32
|
+
* The evaluator uses markdown syntax in the rendered field:
|
|
33
|
+
* `~~value~~` — dropped dice or dropped group sub-rolls
|
|
34
|
+
* `**value**` — dice counted as success
|
|
35
|
+
* `__value__` — dice counted as failure
|
|
36
|
+
*
|
|
37
|
+
* For plain terminals these become `(value)`, `[value]`, and `{value}` so
|
|
38
|
+
* the per-die classification stays visible without any markup dependency.
|
|
39
|
+
* Dropped spans can wrap a whole sub-roll (e.g. `~~1d8[2]~~` from
|
|
40
|
+
* `{1d8, 1d10}kh1`), so the strikethrough pattern accepts any tilde-free
|
|
41
|
+
* content, not just a single number. The evaluator never nests `~~`
|
|
42
|
+
* (see `stripInnerMarkers`), so the tilde-free span match is safe.
|
|
43
|
+
*/
|
|
44
|
+
function formatRendered(rendered: string): string {
|
|
45
|
+
return rendered
|
|
46
|
+
.replace(/\*\*(-?\d+)\*\*/g, '[$1]')
|
|
47
|
+
.replace(/__(-?\d+)__/g, '{$1}')
|
|
48
|
+
.replace(/~~([^~]+)~~/g, '($1)');
|
|
49
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CLI entry point for roll-parser.
|
|
5
|
+
*
|
|
6
|
+
* @module cli/index
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { isRollParserError } from '../errors.js';
|
|
10
|
+
import { VERSION } from '../index.js';
|
|
11
|
+
import { roll } from '../roll.js';
|
|
12
|
+
import { parseArgs } from './args.js';
|
|
13
|
+
import { formatResult } from './format.js';
|
|
14
|
+
|
|
15
|
+
const HELP_TEXT = `roll-parser v${VERSION}
|
|
16
|
+
|
|
17
|
+
Usage: roll-parser <notation> [options]
|
|
18
|
+
|
|
19
|
+
Options:
|
|
20
|
+
-h, --help Show this help message
|
|
21
|
+
--version Show version number
|
|
22
|
+
-v, --verbose Show detailed roll breakdown
|
|
23
|
+
--seed <value> Use seed for reproducible rolls
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
roll-parser 2d6+3
|
|
27
|
+
roll-parser 4d6kh3 --verbose
|
|
28
|
+
roll-parser 4d6dl1 --seed "character-str"
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Prints the notation with a caret under the error position for errors that
|
|
33
|
+
* carry one (LexerError/ParseError `position`, EvaluatorError `start`).
|
|
34
|
+
* Skipped for multi-line notations and out-of-range positions to keep the
|
|
35
|
+
* caret honest.
|
|
36
|
+
*/
|
|
37
|
+
function writeErrorContext(notation: string, error: Error): void {
|
|
38
|
+
const position =
|
|
39
|
+
'position' in error ? error.position : 'start' in error ? error.start : undefined;
|
|
40
|
+
if (typeof position !== 'number' || !Number.isInteger(position)) return;
|
|
41
|
+
if (notation.includes('\n') || position < 0 || position > notation.length) return;
|
|
42
|
+
|
|
43
|
+
process.stderr.write(` ${notation}\n`);
|
|
44
|
+
process.stderr.write(` ${' '.repeat(position)}^\n`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function main(): void {
|
|
48
|
+
const parsed = parseArgs(process.argv.slice(2));
|
|
49
|
+
|
|
50
|
+
if (!parsed.ok) {
|
|
51
|
+
process.stderr.write(`Error: ${parsed.error}\n`);
|
|
52
|
+
process.stderr.write('Run "roll-parser --help" for usage.\n');
|
|
53
|
+
process.exitCode = 2;
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const { args } = parsed;
|
|
58
|
+
|
|
59
|
+
if (args.showHelp) {
|
|
60
|
+
process.stdout.write(HELP_TEXT);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (args.showVersion) {
|
|
65
|
+
process.stdout.write(`${VERSION}\n`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (args.notation == null) {
|
|
70
|
+
process.stderr.write('Error: No dice notation provided.\n');
|
|
71
|
+
process.stderr.write('Run "roll-parser --help" for usage.\n');
|
|
72
|
+
process.exitCode = 2;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const options = args.seed != null ? { seed: args.seed } : {};
|
|
78
|
+
const result = roll(args.notation, options);
|
|
79
|
+
const output = formatResult(result, args.verbose);
|
|
80
|
+
process.stdout.write(`${output}\n`);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (isRollParserError(error)) {
|
|
83
|
+
process.stderr.write(`Error: ${error.message}\n`);
|
|
84
|
+
writeErrorContext(args.notation, error);
|
|
85
|
+
process.exitCode = 1;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
main();
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common error base class and error codes for roll-parser.
|
|
3
|
+
*
|
|
4
|
+
* @module errors
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* All known roll-parser error codes. Single source of truth — the
|
|
9
|
+
* `RollParserErrorCode` type and the runtime `VALID_CODES` set are
|
|
10
|
+
* both derived from this array.
|
|
11
|
+
*
|
|
12
|
+
* Lexer: `UNEXPECTED_CHARACTER`, `UNEXPECTED_IDENTIFIER`
|
|
13
|
+
* Parser: `UNEXPECTED_TOKEN`, `UNEXPECTED_END`, `EXPECTED_TOKEN`,
|
|
14
|
+
* `INVALID_MODIFIER_TARGET`, `INVALID_EXPLODE_TARGET`, `INVALID_REROLL_TARGET`,
|
|
15
|
+
* `INVALID_SUCCESS_COUNT_TARGET`, `INVALID_SORT_TARGET`,
|
|
16
|
+
* `INVALID_CRIT_THRESHOLD_TARGET`, `NESTED_VERSUS`, `INVALID_FUNCTION_ARITY`,
|
|
17
|
+
* `AMBIGUOUS_DICE_CHAIN`, `MAX_DEPTH_EXCEEDED`
|
|
18
|
+
* Evaluator: `INVALID_DICE_COUNT`, `INVALID_DICE_SIDES`, `DICE_LIMIT_EXCEEDED`,
|
|
19
|
+
* `DIVISION_BY_ZERO`, `MODULO_BY_ZERO`, `UNKNOWN_OPERATOR`, `UNKNOWN_NODE_TYPE`,
|
|
20
|
+
* `INVALID_MODIFIER_COUNT`, `EXPLODE_LIMIT_EXCEEDED`, `REROLL_LIMIT_EXCEEDED`,
|
|
21
|
+
* `INVALID_THRESHOLD`, `NESTED_VERSUS`, `UNKNOWN_FUNCTION`, `UNDEFINED_VARIABLE`,
|
|
22
|
+
* `INVALID_VARIABLE_VALUE`, `NON_FINITE_RESULT`
|
|
23
|
+
*/
|
|
24
|
+
const ROLL_PARSER_ERROR_CODES = [
|
|
25
|
+
'UNEXPECTED_CHARACTER',
|
|
26
|
+
'UNEXPECTED_IDENTIFIER',
|
|
27
|
+
'UNEXPECTED_TOKEN',
|
|
28
|
+
'UNEXPECTED_END',
|
|
29
|
+
'EXPECTED_TOKEN',
|
|
30
|
+
'INVALID_DICE_COUNT',
|
|
31
|
+
'INVALID_DICE_SIDES',
|
|
32
|
+
'DICE_LIMIT_EXCEEDED',
|
|
33
|
+
'DIVISION_BY_ZERO',
|
|
34
|
+
'MODULO_BY_ZERO',
|
|
35
|
+
'UNKNOWN_OPERATOR',
|
|
36
|
+
'UNKNOWN_NODE_TYPE',
|
|
37
|
+
'INVALID_MODIFIER_COUNT',
|
|
38
|
+
'INVALID_MODIFIER_TARGET',
|
|
39
|
+
'EXPLODE_LIMIT_EXCEEDED',
|
|
40
|
+
'INVALID_EXPLODE_TARGET',
|
|
41
|
+
'REROLL_LIMIT_EXCEEDED',
|
|
42
|
+
'INVALID_REROLL_TARGET',
|
|
43
|
+
'INVALID_SUCCESS_COUNT_TARGET',
|
|
44
|
+
'INVALID_SORT_TARGET',
|
|
45
|
+
'INVALID_CRIT_THRESHOLD_TARGET',
|
|
46
|
+
'INVALID_THRESHOLD',
|
|
47
|
+
'NESTED_VERSUS',
|
|
48
|
+
'INVALID_FUNCTION_ARITY',
|
|
49
|
+
'UNKNOWN_FUNCTION',
|
|
50
|
+
'UNDEFINED_VARIABLE',
|
|
51
|
+
'INVALID_VARIABLE_VALUE',
|
|
52
|
+
'AMBIGUOUS_DICE_CHAIN',
|
|
53
|
+
'MAX_DEPTH_EXCEEDED',
|
|
54
|
+
'NON_FINITE_RESULT',
|
|
55
|
+
] as const;
|
|
56
|
+
|
|
57
|
+
export type RollParserErrorCode = (typeof ROLL_PARSER_ERROR_CODES)[number];
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Base error class for all roll-parser errors.
|
|
61
|
+
*
|
|
62
|
+
* Provides a typed `code` field for programmatic error handling.
|
|
63
|
+
* All library errors (`LexerError`, `ParseError`, `EvaluatorError`)
|
|
64
|
+
* extend this class.
|
|
65
|
+
*/
|
|
66
|
+
export class RollParserError extends Error {
|
|
67
|
+
readonly code: RollParserErrorCode;
|
|
68
|
+
|
|
69
|
+
constructor(message: string, code: RollParserErrorCode) {
|
|
70
|
+
super(message);
|
|
71
|
+
this.name = 'RollParserError';
|
|
72
|
+
this.code = code;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const VALID_CODES: Set<string> = new Set<string>(ROLL_PARSER_ERROR_CODES);
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Type guard for roll-parser errors. Checks `instanceof` first, then
|
|
80
|
+
* falls back to duck-typing for cross-realm safety.
|
|
81
|
+
*/
|
|
82
|
+
export function isRollParserError(value: unknown): value is RollParserError {
|
|
83
|
+
if (value instanceof RollParserError) return true;
|
|
84
|
+
return (
|
|
85
|
+
value instanceof Error &&
|
|
86
|
+
'code' in value &&
|
|
87
|
+
typeof (value as RollParserError).code === 'string' &&
|
|
88
|
+
VALID_CODES.has((value as RollParserError).code)
|
|
89
|
+
);
|
|
90
|
+
}
|