diff --git a/CHANGELOG.md b/CHANGELOG.md index 581ffa8..f41a89e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. 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 - `repeat` binds picks again on every iteration. Refused at `New`: a name bound twice in - one category, or inside a `repeat` and outside it; a name that is a field or an option; + `repeat` binds picks again on every iteration. Refused at `New`: a name bound twice + 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 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 `. diff --git a/README.md b/README.md index 12fa13d..96f071f 100644 --- a/README.md +++ b/README.md @@ -711,15 +711,16 @@ row: 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 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 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 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 -it; a name that is a field or an option; a binding of anything but a reference; a binding in +Rejected at load: a name bound twice 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, 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 table's format or cell. A field, column, category or folder name may not contain ` as `. diff --git a/docs/decisions.md b/docs/decisions.md index 3f44575..40bd461 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -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 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 -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. diff --git a/draws.go b/draws.go index 3695b45..38072b4 100644 --- a/draws.go +++ b/draws.go @@ -51,9 +51,10 @@ type renderScope struct { entry *drawMemo base int // 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 pickKey string + refs *renderDraws } type renderedRow struct { @@ -90,16 +91,20 @@ func (sc renderScope) rowOf(t *table) int { } func (sc renderScope) groupDraws() *groupDraws { - if sc.group == "" { - return &sc.draws.unnamed + draws := sc.draws + 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 sc.draws.named == nil { - sc.draws.named = map[string]*groupDraws{} + if draws.named == nil { + draws.named = map[string]*groupDraws{} } d = &groupDraws{} - sc.draws.named[strings.Clone(sc.group)] = d + draws.named[strings.Clone(sc.group)] = d } return d } diff --git a/name.go b/name.go index fb4dc6a..5f40896 100644 --- a/name.go +++ b/name.go @@ -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 -// 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 { memo drawMemo pins pinSet + refs renderDraws } // 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 - if memo == nil { - memo = &sc.groupDraws().memo + return &sc.entryMemo().frameOf(b.scope).picks[b.index] +} + +// 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 { 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)) } diff --git a/render.go b/render.go index 779a6a3..78c6848 100644 --- a/render.go +++ b/render.go @@ -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 -// rendering t, where a name bound outside t keeps its pick: a scope sc's category was entered -// past keeps its frame in that read's memo, else in sc's render's. +// rendering t, where a name bound outside t keeps its pick in the frame sc.entryMemo keeps. func renderRepeat(s *generatorState, t *template, sc renderScope) string { - entry := sc.entry - if entry == nil { - entry = &sc.groupDraws().memo - } + entry := sc.entryMemo() var b strings.Builder b.Grow(t.repeat * (t.compiled.grow + len(t.separator))) for i := 0; i < t.repeat; i++ {