Commit Graph

6 Commits

Author SHA1 Message Date
M 9ed0d3bf55 Hold a calc operand's draw for the expansion
A field the format rendered and a calc read was drawn twice, so the operand
shown could differ from the operand computed — the same disagreement the
dotted-path rule already fences, in its plainest spelling. The README
carried it as a caveat, which is what a rule like this exists to remove.

A calc operand now joins the names an expansion holds, so it is drawn once
and every later read of it — the calc, and any bare token spelling it —
sees that draw. The hold stays per expansion: each repeat iteration and each
nested template draws its own.

expand reads a calc's operands before the call and hands over their values,
so a builtin takes (emitted, operands) rather than the sibling fields, and
the evaluator indexes that slice instead of walking the node tree. That is
what keeps the draws a local. Handing them to a builtin instead lets them
escape through an indirect call, which put two maps on the heap for every
held format, calc or not: BenchmarkBound went 56 B/5 allocs -> 744 B/10.

Measured against main: Bound 359 -> 349 ns at 56 B/5 allocs, unchanged;
Calc 416 -> 577 ns and one more alloc, which is what the correlation costs.

Validation is unchanged: t.bound still carries only the levels a dotted
token reads, so the overlap fences fence exactly what they did.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 13:33:05 +02:00
M 5a69b02ee0 Find a format's bound readers in one ordered scan 2026-08-30 22:31:54 +02:00
Mikael Göransson 37759f773f Pin the builtin registry shape and grow as a lower bound, and correct stale comments 2026-08-28 13:20:07 +02:00
lilleman ab9846c2ff Compile format strings at New instead of re-scanning them on every render 2026-08-28 13:20:07 +02:00
lilleman 5b6480a094 Updates from reviewers 2026-06-09 10:03:53 +02:00
lilleman cee2f35a2c Add {calc()}: arithmetic over number literals and sibling fields 2026-06-09 07:59:29 +02:00