From 2bb795e08c1a12d075d7db6aab1fc9de468ce727 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sat, 3 Oct 2026 14:16:57 +0200 Subject: [PATCH] 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 --- README.md | 22 +-- docs/decisions.md | 7 +- name.go | 333 ---------------------------------------------- namefence.go | 138 +++++++++++++++++++ node.go | 6 +- path.go | 11 +- pick.go | 209 +++++++++++++++++++++++++++++ render.go | 3 +- table.go | 14 +- todo.md | 1 + 10 files changed, 375 insertions(+), 369 deletions(-) create mode 100644 namefence.go create mode 100644 pick.go diff --git a/README.md b/README.md index c34c82d..be03692 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/decisions.md b/docs/decisions.md index 7572ab8..b97fed0 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -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. diff --git a/name.go b/name.go index 1228790..5c6ae05 100644 --- a/name.go +++ b/name.go @@ -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 -} diff --git a/namefence.go b/namefence.go new file mode 100644 index 0000000..52dac90 --- /dev/null +++ b/namefence.go @@ -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 +} diff --git a/node.go b/node.go index 4f91007..15487e4 100644 --- a/node.go +++ b/node.go @@ -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 } diff --git a/path.go b/path.go index 0666214..eabadcb 100644 --- a/path.go +++ b/path.go @@ -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) { diff --git a/pick.go b/pick.go new file mode 100644 index 0000000..95ff8e7 --- /dev/null +++ b/pick.go @@ -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 +} diff --git a/render.go b/render.go index 855afcf..2943416 100644 --- a/render.go +++ b/render.go @@ -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)} } diff --git a/table.go b/table.go index 983765b..c23a513 100644 --- a/table.go +++ b/table.go @@ -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 } diff --git a/todo.md b/todo.md index 919a074..cc197a4 100644 --- a/todo.md +++ b/todo.md @@ -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`.