fix(journal): tokenize bare dice notation
The tokenizer only recognized dice when the pattern started with a count (3d6), so a bare d20 fell through to the identifier branch and failed as an unknown identifier. Add a dedicated branch for the bare d-form; DICE_RE already accepted it.
This commit is contained in:
@@ -379,6 +379,20 @@ describe("evaluateExpression", () => {
|
|||||||
expect(result.value.value).toBeLessThanOrEqual(23);
|
expect(result.value.value).toBeLessThanOrEqual(23);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("evaluates bare dice notation", () => {
|
||||||
|
const result = evaluateExpression("d20", { lookup: () => undefined });
|
||||||
|
expect(result.value.kind).toBe("number");
|
||||||
|
expect(result.value.value).toBeGreaterThanOrEqual(1);
|
||||||
|
expect(result.value.value).toBeLessThanOrEqual(20);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("evaluates bare dice alongside modifiers", () => {
|
||||||
|
const result = evaluateExpression("d6 + 5", { lookup: () => undefined });
|
||||||
|
expect(result.value.kind).toBe("number");
|
||||||
|
expect(result.value.value).toBeGreaterThanOrEqual(6);
|
||||||
|
expect(result.value.value).toBeLessThanOrEqual(11);
|
||||||
|
});
|
||||||
|
|
||||||
test("throws on division by zero", () => {
|
test("throws on division by zero", () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
evaluateExpression("10 / 0", { lookup: () => undefined }),
|
evaluateExpression("10 / 0", { lookup: () => undefined }),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* - Number literals (integer or decimal)
|
* - Number literals (integer or decimal)
|
||||||
* - Tagmap literals: #warrior:1;#druid:2 (bare #warrior → #warrior:1)
|
* - Tagmap literals: #warrior:1;#druid:2 (bare #warrior → #warrior:1)
|
||||||
* - $var references (resolved via lookup; auto-detects number vs tagmap)
|
* - $var references (resolved via lookup; auto-detects number vs tagmap)
|
||||||
* - Dice patterns: 3d6, 2d8kh1, etc. (delegates to rollFormula)
|
* - Dice patterns: 3d6, 2d8kh1, d20, etc. (delegates to rollFormula)
|
||||||
* - Arithmetic: + - * / (type-checked via registry)
|
* - Arithmetic: + - * / (type-checked via registry)
|
||||||
* - Functions: floor(x), ceil(x), round(x) (numbers only)
|
* - Functions: floor(x), ceil(x), round(x) (numbers only)
|
||||||
* - Parentheses for grouping
|
* - Parentheses for grouping
|
||||||
@@ -225,6 +225,22 @@ function tokenize(input: string): Token[] {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dice pattern without a leading count: e.g. "d20", "d6kh1". The number
|
||||||
|
// branch above only catches dice that start with a digit ("3d6"), so bare
|
||||||
|
// forms need their own branch before the generic identifier branch.
|
||||||
|
if (/[dD]/.test(ch) && i + 1 < input.length && /[0-9]/.test(input[i + 1])) {
|
||||||
|
let dice = "";
|
||||||
|
while (i < input.length && /[a-zA-Z0-9]/.test(input[i])) {
|
||||||
|
dice += input[i];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (!DICE_RE.test(dice)) {
|
||||||
|
throw new Error(`Invalid dice notation: "${dice}"`);
|
||||||
|
}
|
||||||
|
tokens.push({ kind: "number", value: String(rollDice(dice)), raw: dice });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Tagmap literal: #warrior:1;#druid:2 or bare #warrior
|
// Tagmap literal: #warrior:1;#druid:2 or bare #warrior
|
||||||
if (ch === "#") {
|
if (ch === "#") {
|
||||||
let raw = "";
|
let raw = "";
|
||||||
|
|||||||
Reference in New Issue
Block a user