Compile format strings at New instead of re-scanning them on every render

This commit is contained in:
lilleman
2026-08-27 23:09:35 +02:00
committed by lilleman-tw
parent 5282f37a59
commit ab9846c2ff
8 changed files with 202 additions and 46 deletions
+66
View File
@@ -69,6 +69,8 @@ func eachToken(format string, fn func(ftoken) error) error {
// compile time (their values, beyond the count). The registry lives in builtins.go.
type builtin struct {
arity int
// prep parses validated args once, at compile time, into the closure expand calls.
prep func(args []string) callFn
check func(fields map[string]node, args []string) error
call func(s *session, emitted string, fields map[string]node, args []string) string
}
@@ -159,3 +161,67 @@ func fieldTokens(format string) []string {
})
return names
}
// op is one compiled unit of a format string: a literal run, a class char, a field
// alternation, or a builtin already bound to its args. compile builds these so
// render never re-scans the format.
// callFn is a builtin bound to one call site: its args already parsed.
type callFn func(s *session, emitted string, fields map[string]node) string
type op struct {
kind byte // 'l' literal run, 'c' class char, 'f' field alternation, 'b' builtin
lit string // kind 'l'
r rune // kind 'c'
names []string // kind 'f': the '|' arms, split once
call callFn
}
// compileOps turns a validated format string into ops, and returns the smallest
// output it can produce (literals plus one byte per class char) to size the buffer.
func compileOps(format string, fields map[string]node) ([]op, int) {
var ops []op
var lit strings.Builder
grow := 0
flush := func() {
if lit.Len() > 0 {
ops = append(ops, op{kind: 'l', lit: lit.String()})
lit.Reset()
}
}
_ = eachToken(format, func(t ftoken) error {
switch t.kind {
case 'l':
lit.WriteRune(t.r)
case 'c':
flush()
grow++
ops = append(ops, op{kind: 'c', r: t.r})
case 'b':
flush()
if name, args, ok := funcCall(t.body); ok {
ops = append(ops, op{kind: 'b', call: builtins[name].bind(args)})
} else {
ops = append(ops, op{kind: 'f', names: strings.Split(t.body, "|")})
}
}
return nil
})
flush()
for _, o := range ops {
if o.kind == 'l' {
grow += len(o.lit)
}
}
return ops, grow
}
// bind returns the closure for one call site, parsing args once via prep if present.
func (b builtin) bind(args []string) callFn {
if b.prep != nil {
return b.prep(args)
}
call := b.call
return func(s *session, emitted string, fields map[string]node) string {
return call(s, emitted, fields, args)
}
}