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>
This commit is contained in:
@@ -47,9 +47,12 @@ type template struct {
|
||||
ops []op // format compiled once (see compileOps); what expand walks
|
||||
grow int // minimum output size, to size the render buffer
|
||||
// bound maps each field the format addresses by dotted path to one path token
|
||||
// reading it: drawn once per expansion (see compileOps and expand), and the
|
||||
// token names the other half of an overlap. nil when the format takes no path.
|
||||
// reading it, which is the half of an overlap the fences name. nil when the
|
||||
// format takes no path.
|
||||
bound map[string]string
|
||||
// held is every name drawn once per expansion: the bound levels above, plus the
|
||||
// siblings a {calc()} reads. nil when the format holds nothing (see expand).
|
||||
held map[string]bool
|
||||
}
|
||||
|
||||
func (*template) isNode() {}
|
||||
@@ -160,7 +163,7 @@ func compileTemplate(m map[string]any) (node, error) {
|
||||
if err := checkTokens(format, t.fields); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.ops, t.grow, t.bound = compileOps(format)
|
||||
t.ops, t.grow, t.bound, t.held = compileOps(format)
|
||||
if err := checkNoOverlap(format, t.bound); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user