diff --git a/src/cli/completions/variable-system.test.ts b/src/cli/completions/variable-system.test.ts index 7989084..97bcfbf 100644 --- a/src/cli/completions/variable-system.test.ts +++ b/src/cli/completions/variable-system.test.ts @@ -379,6 +379,20 @@ describe("evaluateExpression", () => { 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", () => { expect(() => evaluateExpression("10 / 0", { lookup: () => undefined }), diff --git a/src/components/journal/variable-expression.ts b/src/components/journal/variable-expression.ts index 0cb21d4..9ad2362 100644 --- a/src/components/journal/variable-expression.ts +++ b/src/components/journal/variable-expression.ts @@ -6,7 +6,7 @@ * - Number literals (integer or decimal) * - Tagmap literals: #warrior:1;#druid:2 (bare #warrior → #warrior:1) * - $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) * - Functions: floor(x), ceil(x), round(x) (numbers only) * - Parentheses for grouping @@ -225,6 +225,22 @@ function tokenize(input: string): Token[] { 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 if (ch === "#") { let raw = "";