Add d% as an alias for d100. The notation d% and 2d% should produce DiceNode with sides = Literal(100).
Currently d% throws a parse error because % after d is scanned as DICE + MODULO. With the Stage 2 foundation (#34), the lexer already emits DICE_PERCENT for d%. This issue covers the parser and test work.
Parser Changes
- NUD (prefix
d%): create DiceNode(Literal(1), Literal(100))
- LED (infix
2d%): create DiceNode(left, Literal(100))
getLeftBp: DICE_PERCENT → BP.DICE_LEFT (40)
- No
getRightBp needed — sides are always 100, no right-hand expression to parse
No new AST node type. Reuses DiceNode with synthetic sides = Literal(100).
Evaluator Changes
None. DiceNode with sides = 100 is already handled correctly by evalDice. Critical detection (result === 100 && 100 > 1) and fumble detection (result === 1) work naturally.
Expression/Rendered Output
expression: shows 1d100 (canonical form)
notation: preserves d% (original input)
rendered: 1d100[42] = 42
Edge Cases
| Expression |
Expected |
d% |
1d100 |
2d% |
2d100 |
D% |
1d100 (case-insensitive) |
d%+5 |
1d100 + 5 |
2d%kh1 |
Roll 2d100, keep highest |
(2)d% |
2d100 (computed count) |
d % 3 |
Error — whitespace breaks the d% token, producing DICE + MODULO |
d%% |
Error — d% consumed as DICE_PERCENT, then bare % is MODULO with no NUD |
10%3 |
Modulo (unchanged) — % after digit is still MODULO in the operator switch |
Test Plan
- Parser tests for AST structure:
d%, 2d%, d%+5, 2d%kh1, (2)d%
- Evaluator tests with MockRNG: verify
sides = 100, total in [1, 100]
- Integration tests via
roll(): d%, 2d%, d%+5
- Property test:
Nd% total is in [N, N*100]
- Error tests:
d%%, standalone d% followed by invalid token
Drafted with AI assistance
Add
d%as an alias ford100. The notationd%and2d%should produceDiceNodewithsides = Literal(100).Currently
d%throws a parse error because%afterdis scanned asDICE + MODULO. With the Stage 2 foundation (#34), the lexer already emitsDICE_PERCENTford%. This issue covers the parser and test work.Parser Changes
d%): createDiceNode(Literal(1), Literal(100))2d%): createDiceNode(left, Literal(100))getLeftBp:DICE_PERCENT→BP.DICE_LEFT(40)getRightBpneeded — sides are always 100, no right-hand expression to parseNo new AST node type. Reuses
DiceNodewith syntheticsides = Literal(100).Evaluator Changes
None.
DiceNodewithsides = 100is already handled correctly byevalDice. Critical detection (result === 100 && 100 > 1) and fumble detection (result === 1) work naturally.Expression/Rendered Output
expression: shows1d100(canonical form)notation: preservesd%(original input)rendered:1d100[42] = 42Edge Cases
d%2d%D%d%+52d%kh1(2)d%d % 3d%token, producingDICE + MODULOd%%d%consumed asDICE_PERCENT, then bare%isMODULOwith no NUD10%3%after digit is stillMODULOin the operator switchTest Plan
d%,2d%,d%+5,2d%kh1,(2)d%sides = 100, total in[1, 100]roll():d%,2d%,d%+5Nd% total is in [N, N*100]d%%, standaloned%followed by invalid tokenDrafted with AI assistance