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
+12 -10
View File
@@ -156,8 +156,8 @@ work with no data on disk. A directory is a namespace: each JSON file is a
category named after the file, each subdirectory a dot-path segment, so
`mydata/sv_SE/person.json` is `sv_SE.person` and replaces the shipped one.
Sources merge in order; matching folders combine, any other clash is won by the
last loaded. Names may not use `.`, `|`, `(`, `{`, `}`, `[`, `]`, `"` or `/`, nor be
`-`, which a struct tag reserves; dot-prefixed entries are skipped, so a data directory can also be a checkout.
last loaded. Names may not use `.`, `|`, `(`, `{`, `}`, `[`, `]`, `"`, `/` or ` as `,
which binds a [name](#names), nor be `-`, which a struct tag reserves; dot-prefixed entries are skipped, so a data directory can also be a checkout.
Each locale carries `address`, `color`, `company`, `date`, `email`, `first-name`,
`ip`, `last-name`, `person`, `phone`, `price`, `sentence`, `sex`, `time`, `url`,
@@ -735,10 +735,10 @@ Refused at `New`, each error naming what to write instead:
bound, which the bare reference spells: `{/word as w}{w}` is `{/word}`;
- a path through a name that selects a row;
- `{n}` beside `{n.w}` where `n`'s category renders `w` twice, as in `{w}-{w}`;
- reads of a name in two draw groups, or inside a `repeat`, where what it names reads a
reference path, which each draw group and iteration draws apart;
- a binding in a table's format or cell;
- a field, column, category or folder name containing ` as `.
- reads of a name in two draw groups, or inside a `repeat` nested where the name is bound,
where what it names reads a reference path, which each draw group and iteration draws
apart;
- a binding in a table's format or cell.
### Draw group
@@ -816,8 +816,8 @@ The target is about a microsecond per value (goal 12).
Each file is parsed, validated and weight-indexed once, in `New`, bar a table's name and
parent columns, which are mapped on the first `Fake` that selects by name or descends
through it. Proving the draw fences adds a pass over the loaded tree per
fence, and walks what a render reads only where data binds a reference, so a set that
binds none pays for the passes alone. A `Fake` call then costs about what its output
fence, and walks what a render reads only where data holds a reference, so a set that
holds none pays for the passes alone. A `Fake` call then costs about what its output
costs: an unweighted pick is O(1) whatever the list's length, a weighted one
O(log n), and long formats, deep nesting and many tokens add cost in proportion to
the output.
@@ -933,7 +933,9 @@ doc.go the package doc, and the vocabulary the package is written in
fejkdata.go Generator, New, options, the embedded data set, List
node.go the node model and JSON -> node compilation, with its fence on a nested drawGroup
table.go tables: the rows TSV, its options and links, row selection and draws
name.go names: {ref as n} bindings, the scopes they live in, and the picks a render keeps under them
name.go names: {ref as n} bindings, the scopes they live in, and the reads they answer
namefence.go the load fences over names: reads in two draw groups or a nested repeat, and a field rendered twice under a pick
pick.go a render's named picks: the frames of the name scopes rendering, and the reads kept under a name
path.go the dotted-path walks with their selectors, and proving a path resolves
render.go Fake, the recursive renderer (choices, format strings, expansions) and the reads its tokens make
record.go records: Record, the JSON/CSV/SQL serializers, and their entry points
@@ -945,7 +947,7 @@ holdfence.go the load fences over the hold: the routes and spellings that rea
drawfence.go the load fences over reference draws and their refusals: a drawGroup's, a render's and a record's columns judged on what they read, and a table reaching its own family
reads.go the load-time fold of what a render reads, and what a path reads of a table family
pins.go the pin set: the table rows a render fixes, which the draw fences replay
reference.go reference sigils, and binding references across the tree
reference.go reference sigils, and resolving references across the tree
graph.go the node graph: tree walks, containment and render edges, and the cycle and repeat fences
builtins.go the {name()} function registry and its implementations
layout.go date and time layouts: the instants one is proved against, and the two samples
+4 -3
View File
@@ -905,15 +905,16 @@ it.
The scope is lexical. A read entering a category, by a reference, through a name or from
`Fake`, sees no frame (one render of a scope's picks) opened above it, so a category renders
the same whoever references it. That read opens the frames around where it lands from its own
memo, so reads sharing a memo, `{n}` and `{n.path}` alike, read one pick. A reference path's
the same whoever references it. A reference path, a read through a name or `Fake` opens the
frames around where it lands from its own memo, so reads sharing a memo, `{n}` and `{n.path}`
alike, read one pick; a bare reference lands on a category's root, which opens fresh ones. A reference path's
memo lasts per render and draw group, a name's per pick, and `Fake("cat.field")`'s per render.
A pick keeps only the levels a read of the name addresses, so a template draws the same way
under a name as anywhere else (goal 5.2). A reference read under a pick draws in the draw group
of the read of the name, the group the draw fences survey it in, so two names bound to one
category share what it reads by reference path. Where what a name names reads a reference
path, its reads stay in one draw group and outside any repeat: a reference path is held per
path, its reads stay in one draw group and outside any repeat nested where it is bound: a reference path is held per
draw group and per iteration, so what the pick kept and what it renders afresh would come from
two draws of it. `todo.md` item 4 ends both: once every `{…}` draws afresh, nothing is held for
the render.
-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
}
+138
View File
@@ -0,0 +1,138 @@
package fejkdata
import (
"fmt"
"slices"
"sort"
)
// 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 slices.Concat(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 slices.Concat(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
}
+4 -2
View File
@@ -235,10 +235,12 @@ func compileString(s string, site tableSite) (*template, error) {
return nil, err
}
if site.table != nil {
if err := checkTableToks(toks, unbound); err != nil {
if err := refuseTableBinding(toks); err != nil {
return nil, err
}
unbound = nil
if len(unbound) > 0 {
return nil, unbound[0].err
}
}
return &template{format: s, tokens: toks, repeat: 1, fromString: true, site: site, unbound: unbound}, nil
}
+1 -10
View File
@@ -427,16 +427,7 @@ func drawVariant(s *generatorState, c *choice, memo *drawMemo, levels []string,
if memo == nil {
return resolveChoice(s, c)
}
level := levels[at]
n, drew := memo.variant[level]
if !drew {
n = resolveChoice(s, c)
if memo.variant == nil {
memo.variant = map[string]node{}
}
memo.variant[level] = n
}
return n
return memo.variantOf(s, c, levels[at])
}
func (t *table) selector(tail []string) (sel string, rest []string, err error) {
+209
View File
@@ -0,0 +1,209 @@
package fejkdata
// 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 = sc.entering()
for scope := scopeAround(n); scope != nil; scope = scope.up {
if len(scope.order) > 0 {
sc.draws.pushFrame(memo.enteredFrame(scope))
}
}
return sc, sc.base
}
// entering is sc as a read entering a category sees it: no frame opened before it, and no pick.
func (sc renderScope) entering() renderScope {
sc.base, sc.pick = sc.draws.depth(), nil
return sc
}
// 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[level] = n
}
return n
}
+1 -2
View File
@@ -187,8 +187,7 @@ func readField(s *generatorState, t *template, hold *drawMemo, sc renderScope, a
sc.pick = nil
switch {
case a.kind == freshRead && isRef(a.head):
sc.base = sc.draws.depth()
return readValue{text: render(s, t.head(a.head), sc)}
return readValue{text: render(s, t.head(a.head), sc.entering())}
case a.kind == freshRead:
return readValue{text: render(s, t.head(a.head), sc)}
}
+5 -9
View File
@@ -323,7 +323,7 @@ func (t *table) compileRowFormat(format string) error {
if err != nil {
return err
}
if err := checkTableToks(toks, nil); err != nil {
if err := refuseTableBinding(toks); err != nil {
return err
}
for _, tok := range toks {
@@ -345,25 +345,21 @@ func (t *table) compileRowFormat(format string) error {
if err != nil {
return err
}
if err := checkTableToks(nil, unbound); err != nil {
return err
if len(unbound) > 0 {
return unbound[0].err
}
t.formatTemplate = &template{format: format, tokens: toks, fields: fields, repeat: 1, isRecord: true, site: tableSite{t, formatRow}}
t.rowNode = &tableRow{t}
return nil
}
// checkTableToks refuses a table's format or cell binding a name, or reading a head no column
// holds: a table holds no names.
func checkTableToks(toks []formatToken, unbound []unboundRead) error {
// refuseTableBinding refuses a table's format or cell binding a name.
func refuseTableBinding(toks []formatToken) error {
for _, tok := range toks {
if tok.kind == nameBind {
return fmt.Errorf("token {%s}: a table binds no name; bind it in a template reading the table", tok.body)
}
}
if len(unbound) > 0 {
return unbound[0].err
}
return nil
}
+1
View File
@@ -203,6 +203,7 @@ Shape: T = table, t = template, c = choice.
- Deletes the token-order rules (`drawsApart`, `readFold`, `branches`, the pairwise replay), the expansion hold fence, `drawGroup`, the own-family fence and the cell-reference fences, and revises the decisions that point here. The remaining draw checks are names, paths through names, cycles and the repeat cap.
- Fixes on the way: `{/sel}|{/sel}` panics out of `Fake` today where two draws land on rows whose cells select different rows of another table; it must render.
- Rewrites the README's References, Draw group, Correlated fields, Linked tables, Names and "One draw, one spelling" sections; the Linked tables section says which pick's weights govern a family, since the bound row decides.
- Widens the refusal of a name read once to every binding, a path into it or a reference path bound included, since `{n.x}` read once is then `{/ref.x}`, and deletes the names fence over draw groups and nested repeats (`checkUses`, `readsHeld`).
- Re-pin seeded output and `testdata/shipped_shape.txt` in their own commits; the CHANGELOG names the grammar change.
### 25. Draw `en_US.phone`'s `exch` as a NANP central office code, and assert the rule in `TestShippedUSPhone`.