Fix a render's fallback frame to its unnamed group, and draw a pick's references in draw groups of its own

This commit is contained in:
2026-10-03 02:12:24 +02:00
committed by Lillem4n
parent 30d18bf485
commit 4362905b82
6 changed files with 36 additions and 24 deletions
+2 -2
View File
@@ -12,8 +12,8 @@ replacement, and each removed path, column or flag.
and prints nothing, and `{t}`, `{t.capital}` and `{t.calling-code}` read that one row. and prints nothing, and `{t}`, `{t.capital}` and `{t.calling-code}` read that one row.
A name lives in the category binding it, so every field and record column of the A name lives in the category binding it, so every field and record column of the
category reads one pick, a transform reads it as `{lowercase(t.name)}`, and a name a category reads one pick, a transform reads it as `{lowercase(t.name)}`, and a name a
`repeat` binds picks again on every iteration. Refused at `New`: a name bound twice in `repeat` binds picks again on every iteration. Refused at `New`: a name bound twice
one category, or inside a `repeat` and outside it; a name that is a field or an option; outside any `repeat`, twice in one `repeat`, or both inside a `repeat` and outside it; a name that is a field or an option;
a binding of anything but a reference; a binding in a choice's item; a binding nothing a binding of anything but a reference; a binding in a choice's item; a binding nothing
reads; a path through a name that selects a row; and a binding in a table. A field, reads; a path through a name that selects a row; and a binding in a table. A field,
column, category or folder name may not contain ` as `. column, category or folder name may not contain ` as `.
+4 -3
View File
@@ -711,15 +711,16 @@ row:
Renders e.g. `🇳🇴 Norway: capital Oslo, calling code +47`. Everything under a name is drawn Renders e.g. `🇳🇴 Norway: capital Oslo, calling code +47`. Everything under a name is drawn
once, so `{n}` and `{n.path}` agree, a [transform](#transforms) of `n.path` reads the same once, so `{n}` and `{n.path}` agree, a [transform](#transforms) of `n.path` reads the same
value, and a field `n` renders twice prints the same text twice. A name is a pick of its own: value, and a field `n` renders twice prints the same text twice. A name is a pick of its own:
`{t.name}` and `{/misc.territory.name}` beside it are two draws. `{t.name}` and `{/misc.territory.name}` beside it are two draws, as are two names bound to one
category.
A name is drawn on its first read, and lives in the category binding it: any field of the A name is drawn on its first read, and lives in the category binding it: any field of the
category reads it, and a record's columns read one pick. Each render of the category picks category reads it, and a record's columns read one pick. Each render of the category picks
anew, and so does each bare reference to it. A name a `repeat` binds picks again on every anew, and so does each bare reference to it. A name a `repeat` binds picks again on every
iteration, and one bound outside the repeat keeps its pick on every line. iteration, and one bound outside the repeat keeps its pick on every line.
Rejected at load: a name bound twice in one category, or both inside a `repeat` and outside Rejected at load: a name bound twice outside any `repeat`, twice in one `repeat`, or both
it; a name that is a field or an option; a binding of anything but a reference; a binding in inside a `repeat` and outside it; a name that is a field or an option; a binding of anything but a reference; a binding in
a choice's item, which every other item would leave unbound; a binding nothing reads; a path a choice's item, which every other item would leave unbound; a binding nothing reads; a path
through a name that selects a row, which the reference selects instead; and a binding in a through a name that selects a row, which the reference selects instead; and a binding in a
table's format or cell. A field, column, category or folder name may not contain ` as `. table's format or cell. A field, column, category or folder name may not contain ` as `.
+2 -1
View File
@@ -903,4 +903,5 @@ pick is drawn on its first read, so a record's columns, rendered in name order,
whichever binds it. A read entering a category keeps the frame of each scope it enters in its whichever binds it. A read entering a category keeps the frame of each scope it enters in its
own memo, and the category's root renders that frame when the read lands on the root, so own memo, and the category's root renders that frame when the read lands on the root, so
`{n}` and `{n.path}` read one pick: per render and group for a reference path, per pick for a `{n}` and `{n.path}` read one pick: per render and group for a reference path, per pick for a
path through a name, and per render for `Fake("cat.field")`. path through a name, and per render for `Fake("cat.field")`. A reference read under a pick
draws in the pick's own draw groups, so two names bound to one category are two picks of it.
+12 -7
View File
@@ -51,9 +51,10 @@ type renderScope struct {
entry *drawMemo entry *drawMemo
base int base int
// pick is the named pick what renders is part of, at pickKey, its path from the name; nil // pick is the named pick what renders is part of, at pickKey, its path from the name; nil
// outside one. // outside one. refs is the draw groups of the last pick entered, nil before one.
pick *namedPick pick *namedPick
pickKey string pickKey string
refs *renderDraws
} }
type renderedRow struct { type renderedRow struct {
@@ -90,16 +91,20 @@ func (sc renderScope) rowOf(t *table) int {
} }
func (sc renderScope) groupDraws() *groupDraws { func (sc renderScope) groupDraws() *groupDraws {
if sc.group == "" { draws := sc.draws
return &sc.draws.unnamed if sc.refs != nil {
draws = sc.refs
} }
d, drew := sc.draws.named[sc.group] if sc.group == "" {
return &draws.unnamed
}
d, drew := draws.named[sc.group]
if !drew { if !drew {
if sc.draws.named == nil { if draws.named == nil {
sc.draws.named = map[string]*groupDraws{} draws.named = map[string]*groupDraws{}
} }
d = &groupDraws{} d = &groupDraws{}
sc.draws.named[strings.Clone(sc.group)] = d draws.named[strings.Clone(sc.group)] = d
} }
return d return d
} }
+14 -5
View File
@@ -219,10 +219,12 @@ func linkName(t *template, a *arm) error {
} }
// namedPick is one draw of a name: the variant drawn at each level under it, the value each // namedPick is one draw of a name: the variant drawn at each level under it, the value each
// path through it read, keyed by the path from the name, and the table rows they pinned. // path through it read, keyed by the path from the name, and the table rows they pinned; and the
// draw groups of the references read under it, so two names on one category are two picks of it.
type namedPick struct { type namedPick struct {
memo drawMemo memo drawMemo
pins pinSet pins pinSet
refs renderDraws
} }
// pickFrame is one render of a name scope: a pick per binding, each drawn on its first read. // pickFrame is one render of a name scope: a pick per binding, each drawn on its first read.
@@ -299,15 +301,22 @@ func (sc renderScope) pickOf(b *nameBinding) *namedPick {
} }
} }
} }
memo := sc.entry return &sc.entryMemo().frameOf(b.scope).picks[b.index]
if memo == nil { }
memo = &sc.groupDraws().memo
// entryMemo is the memo of the read that entered the category rendering, else the unnamed draw
// group's of the render: one memo for a render begun below a category's root, whatever draw group
// its templates name.
func (sc renderScope) entryMemo() *drawMemo {
if sc.entry != nil {
return sc.entry
} }
return &memo.frameOf(b.scope).picks[b.index] return &sc.draws.unnamed.memo
} }
func readName(s *generatorState, sc renderScope, a arm) readValue { func readName(s *generatorState, sc renderScope, a arm) readValue {
p := sc.pickOf(a.bind) p := sc.pickOf(a.bind)
sc.refs, sc.group = &p.refs, ""
return readPicked(s, p, a.bind.head, a.steps, a.levels, a.path, sc.entering(&p.memo)) return readPicked(s, p, a.bind.head, a.steps, a.levels, a.path, sc.entering(&p.memo))
} }
+2 -6
View File
@@ -93,13 +93,9 @@ func render(s *generatorState, n node, sc renderScope) string {
} }
// renderRepeat renders each iteration of t as a render of its own, inside the name scopes // renderRepeat renders each iteration of t as a render of its own, inside the name scopes
// rendering t, where a name bound outside t keeps its pick: a scope sc's category was entered // rendering t, where a name bound outside t keeps its pick in the frame sc.entryMemo keeps.
// past keeps its frame in that read's memo, else in sc's render's.
func renderRepeat(s *generatorState, t *template, sc renderScope) string { func renderRepeat(s *generatorState, t *template, sc renderScope) string {
entry := sc.entry entry := sc.entryMemo()
if entry == nil {
entry = &sc.groupDraws().memo
}
var b strings.Builder var b strings.Builder
b.Grow(t.repeat * (t.compiled.grow + len(t.separator))) b.Grow(t.repeat * (t.compiled.grow + len(t.separator)))
for i := 0; i < t.repeat; i++ { for i := 0; i < t.repeat; i++ {