Split the picks and the name fences out of name.go, state the repeat refusal and the naming rule where they hold, and draw a memo's variants one way

This commit is contained in:
2026-10-03 14:16:57 +02:00
committed by Lillem4n
parent 8c3ee104f6
commit 2bb795e08c
10 changed files with 375 additions and 369 deletions
-333
View File
@@ -287,336 +287,3 @@ func linkName(t *template, a *arm) error {
}
return nil
}
// checkNameReads refuses each binding of t that checkUses refuses, and a read such as {n} whose
// pick renders twice a field another read, {n.w}, reads once: the pick keeps one draw of it.
func checkNameReads(path string, t *template) error {
for _, tok := range t.tokens {
if tok.kind != nameBind {
continue
}
if err := t.nameScope.bindings[tok.bound].checkUses(); err != nil {
return fmt.Errorf("%s: token {%s}: %w", t.site.label(path), tok.body, err)
}
}
return namedReads(t, func(o *op, a *arm) error {
for _, leaf := range a.leaves {
if err := a.named.checkOnce(a.spelling, leaf, a.path); err != nil {
return fmt.Errorf("%s: token {%s}: %w", t.site.label(path), o.body, err)
}
}
return nil
})
}
// checkUses refuses a binding of a category read once whole, and reads of b in two draw groups,
// or inside a repeat, where what b names reads a reference path.
func (b *nameBinding) checkUses() error {
if r := b.uses[0]; len(b.uses) == 1 && r.tail == "" && !r.nested && len(b.tail) == 0 {
spelling := b.ref
if !r.operand {
spelling = "{" + spelling + "}"
}
return fmt.Errorf("name %q is read once, whole, which the bare reference draws the same way; write %s where it is read, and drop the token", b.name, spelling)
}
if !readsHeld(b.head, map[node]bool{}) {
return nil
}
for _, u := range b.uses {
if u.nested {
return fmt.Errorf("name %q is read inside a repeat, and what it names reads a reference path, which each iteration draws apart; bind the name inside the repeat, or bind a second name there", b.name)
}
if u.group != b.uses[0].group {
return fmt.Errorf("name %q is read in two draw groups, %s and %s, and what it names reads a reference path, which each draw group draws apart; read the name in one draw group, or bind a name in each", b.name, groupSpelling(b.uses[0].group), groupSpelling(u.group))
}
}
return nil
}
func groupSpelling(group string) string {
if group == "" {
return "the unnamed one"
}
return fmt.Sprintf("%q", group)
}
// readsHeld reports whether rendering n, or any field under it, can read a reference path.
func readsHeld(n node, seen map[node]bool) bool {
if seen[n] {
return false
}
seen[n] = true
if t, isTemplate := n.(*template); isTemplate {
for _, o := range t.compiled.ops {
for _, a := range append(o.arms[:len(o.arms):len(o.arms)], o.operands...) {
if a.kind == refPathRead {
return true
}
}
}
}
for _, c := range contained(n) {
if readsHeld(c.node, seen) {
return true
}
}
for _, e := range renderEdges(n) {
if readsHeld(e.to, seen) {
return true
}
}
return false
}
// checkOnce walks n, rendering at key under b's pick, into each level a read of b addresses.
func (b *nameBinding) checkOnce(read string, n node, key string) error {
switch n := n.(type) {
case *choice:
for _, item := range n.items {
if err := b.checkOnce(read, item, key); err != nil {
return err
}
}
case *template:
return b.checkTemplateOnce(read, n, key)
}
return nil
}
func (b *nameBinding) checkTemplateOnce(read string, t *template, key string) error {
fresh := map[string]int{}
var into []arm
for _, o := range t.compiled.ops {
for _, a := range append(o.arms[:len(o.arms):len(o.arms)], o.operands...) {
if isRef(a.head) || a.kind == namedRead {
continue
}
if _, kept := b.addressed[join(key, a.path)]; kept {
into = append(into, a)
}
if a.kind == freshRead {
fresh[a.head]++
}
}
}
heads := make([]string, 0, len(fresh))
for head := range fresh {
heads = append(heads, head)
}
sort.Strings(heads)
for _, head := range heads {
if by, kept := b.addressed[join(key, head)]; kept && fresh[head] > 1 {
return fmt.Errorf("{%s} renders field %q twice, so {%s} cannot say which draw it reads; drop {%s} or {%s}", read, head, by, read, by)
}
}
for _, a := range into {
for _, leaf := range a.leaves {
if err := b.checkOnce(read, leaf, join(key, a.path)); err != nil {
return err
}
}
}
return nil
}
// namedPick is one draw of a name: the variant drawn at each level a read of it addresses, the
// value each read there produced, keyed by the path from the name, and the table rows they
// pinned. A reference read under it draws in the draw group of the read of the name, which the
// draw fences survey it in.
type namedPick struct {
named *nameBinding
memo drawMemo
pins pinSet
}
// pickFrame is one render of a name scope: a pick per binding, each drawn on its first read.
type pickFrame struct {
scope *nameScope
picks []namedPick
}
func newPickFrame(scope *nameScope) *pickFrame {
f := &pickFrame{scope: scope, picks: make([]namedPick, len(scope.order))}
for i, b := range scope.order {
f.picks[i].named = b
}
return f
}
// frameStack is the frames of the name scopes rendering, innermost last. A render's draws point
// at it, and a repeat iteration's share it: holding the frames in the draws, or in a renderScope,
// would move every render's draws to the heap.
type frameStack struct {
frames []*pickFrame
}
// pushFrame opens f until popFrames closes it, returning the mark popFrames takes.
func (d *renderDraws) pushFrame(f *pickFrame) int {
mark := d.depth()
if d.frameStack == nil {
d.frameStack = &frameStack{}
}
d.frameStack.frames = append(d.frameStack.frames, f)
return mark
}
func (d *renderDraws) popFrames(mark int) {
if d.frameStack != nil {
d.frameStack.frames = d.frameStack.frames[:mark]
}
}
func (d *renderDraws) depth() int {
if d.frameStack == nil {
return 0
}
return len(d.frameStack.frames)
}
// enter starts a read landing on n: the read sees no frame opened before it, and gets from memo a
// frame for each name scope around n, so reads sharing memo read one pick of each name. It
// returns the mark that closes those frames.
func (sc renderScope) enter(n node, memo *drawMemo) (renderScope, int) {
sc.base, sc.pick = sc.draws.depth(), nil
for scope := scopeAround(n); scope != nil; scope = scope.up {
if len(scope.order) > 0 {
sc.draws.pushFrame(memo.enteredFrame(scope))
}
}
return sc, sc.base
}
// scopeAround is the innermost name scope a render of n reads names in, short of the frames n
// renders itself, one per iteration of a repeat.
func scopeAround(n node) *nameScope {
switch n := n.(type) {
case *template:
if n.repeat > 1 && n.nameScope != nil && n.nameScope.owner == n {
return n.nameScope.up
}
return n.nameScope
case *choice:
return n.nameScope
}
return nil
}
func (m *drawMemo) enteredFrame(scope *nameScope) *pickFrame {
f, ok := m.enteredFrames[scope]
if !ok {
f = newPickFrame(scope)
if m.enteredFrames == nil {
m.enteredFrames = map[*nameScope]*pickFrame{}
}
m.enteredFrames[scope] = f
}
return f
}
// renderFrame opens a fresh frame of t's scope, where t binds names and no read entering the
// category opened one, returning the mark that closes it, or -1.
func (sc renderScope) renderFrame(t *template) int {
if t.ownNameScope == nil || sc.frameOf(t.ownNameScope) != nil {
return -1
}
return sc.draws.pushFrame(newPickFrame(t.ownNameScope))
}
// frameOf is the frame of scope rendering since the read entering the category, nil where none is.
func (sc renderScope) frameOf(scope *nameScope) *pickFrame {
if stack := sc.draws.frameStack; stack != nil {
for i := len(stack.frames) - 1; i >= sc.base; i-- {
if f := stack.frames[i]; f.scope == scope {
return f
}
}
}
return nil
}
func readName(s *generatorState, sc renderScope, a arm) readValue {
f := sc.frameOf(a.named.scope)
if f == nil {
panic(internalError("name %q is read where no frame of its scope renders", a.named.name))
}
p := &f.picks[a.named.index]
if r, done := p.memo.value[a.path]; done {
return r
}
leaf := p.draw(s, a.named.head, a.steps, a.levels, a.path)
sc, mark := sc.enter(leaf, &p.memo)
r := p.renderAt(s, leaf, a.path, sc)
sc.draws.popFrames(mark)
return r
}
// drawRowOf draws the row a render of t reads: inside the pick's rows where t renders as part of one.
func (sc renderScope) drawRowOf(s *generatorState, t *table) int {
if sc.pick != nil {
return t.drawIn(s, &sc.pick.pins)
}
return t.drawRow(s)
}
// keeps reports whether a read of sc's name addresses the level a starts at.
func (sc renderScope) keeps(a arm) bool {
_, kept := sc.pick.named.addressed[underKey(sc.pickKey, a.head)]
return kept
}
// readUnder reads a of t, which renders as part of the pick sc.pick at sc.pickKey, where a read of
// the name addresses it: once per pick, by its path from the name.
func readUnder(s *generatorState, t *template, sc renderScope, a arm) readValue {
p, key := sc.pick, underKey(sc.pickKey, a.path)
if r, done := p.memo.value[key]; done {
return r
}
levels := make([]string, len(a.levels))
for i, l := range a.levels {
levels[i] = underKey(sc.pickKey, l)
}
return p.renderAt(s, p.draw(s, t.head(a.head), a.steps, levels, key), key, sc)
}
// underKey is the key of path under the pick key prefix. It never returns prefix itself, which a
// memo would keep: anything a renderScope holds reaching the heap moves every render's draws there.
func underKey(prefix, path string) string {
if prefix == "" {
return path
}
return prefix + "." + path
}
// draw draws the path key names under p, its variant at key kept too.
func (p *namedPick) draw(s *generatorState, head node, steps []pathStep, levels []string, key string) node {
leaf := drawSteps(s, head, steps, &p.pins, &p.memo, levels)
if c, isChoice := leaf.(*choice); isChoice {
leaf = p.memo.variantOf(s, c, key)
}
return leaf
}
// renderAt renders leaf, drawn at key, as part of p, and keeps what it rendered.
func (p *namedPick) renderAt(s *generatorState, leaf node, key string, sc renderScope) readValue {
sc = sc.at(leaf, &p.pins)
sc.pick, sc.pickKey = p, key
r := renderLeaf(s, leaf, sc)
if p.memo.value == nil {
p.memo.value = map[string]readValue{}
}
p.memo.value[key] = r
return r
}
// variantOf is the variant of c drawn at level, drawn now where none was.
func (m *drawMemo) variantOf(s *generatorState, c *choice, level string) node {
n, drew := m.variant[level]
if !drew {
n = resolveChoice(s, c)
if m.variant == nil {
m.variant = map[string]node{}
}
m.variant[strings.Clone(level)] = n
}
return n
}