Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33

Open
lilleman wants to merge 966 commits from worktree-todo-relative-dates-exact-counts into main
19 changed files with 138 additions and 141 deletions
Showing only changes of commit d128621fc1 - Show all commits
+1 -1
View File
@@ -1332,7 +1332,7 @@ inline.go inline templates: Template, NewTemplate, FakeTemplate, IsTemplat
template.go the {token} grammar: scanning, tokens, operands, validation, compiling a format template.go the {token} grammar: scanning, tokens, operands, validation, compiling a format
hold.go the hold: one draw per expansion for paths and operands, and the rows it pins hold.go the hold: one draw per expansion for paths and operands, and the rows it pins
holdfence.go the load fences over the hold: the routes and spellings that read one held name two ways holdfence.go the load fences over the hold: the routes and spellings that read one held name two ways
draw.go one reference draw per render and group: the draw set a render reads its paths through holdset.go one reference draw per render and group: the hold set a render reads its paths through
drawfence.go the load fences over reference draws: the drawGroup option, and the walk over a render's reads drawfence.go the load fences over reference draws: the drawGroup option, and the walk over a render's reads
family.go a family of linked tables: what a reference path reads of one, and the fence over paths into it family.go a family of linked tables: what a reference path reads of one, and the fence over paths into it
reference.go reference sigils, and binding references across the tree reference.go reference sigils, and binding references across the tree
+4 -4
View File
@@ -19,16 +19,16 @@ package fejkdata
// - draw — one pick from a choice, or one row taken from a table, and the value a // - draw — one pick from a choice, or one row taken from a table, and the value a
// read produced: `pick`, `drawn`, `table.draw`, `draw`. // read produced: `pick`, `drawn`, `table.draw`, `draw`.
// - expansion — one render of one format: `expand`. // - expansion — one render of one format: `expand`.
// - render — what owns one `drawSet`, and so what one reference draw spans: // - render — what owns one `holdSet`, and so what one reference draw spans:
// `Generator.Fake`, `FakeRecord`, `Template.Fake` and `RecordTemplate.Fake` // `Generator.Fake`, `FakeRecord`, `Template.Fake` and `RecordTemplate.Fake`
// start one each, `FakeStruct` one per record, and `expandAnew` one per // start one each, `FakeStruct` one per record, and `expandAnew` one per
// iteration of a `template.repeat`. // iteration of a `template.repeat`.
// - hold — keeping one draw of a name, so every route to it reads that value: // - hold — keeping one draw of a name, so every route to it reads that value:
// `template.held`, `draws`, `readField`. // `template.held`, `hold`, `readField`.
// - draw group — reference draws held apart inside one render: // - draw group — reference draws held apart inside one render:
// `template.drawGroup` as the data spells it, `template.drawGroupKey` as a // `template.drawGroup` as the data spells it, `template.drawGroupKey` as a
// render reads it, `drawSet`, `drawScope`. // render reads it, `holdSet`, `renderScope`.
// - pin — fixing which row of a table the render uses: `draws.pin`, `draws.rowOf`. // - pin — fixing which row of a table the render uses: `hold.pin`, `hold.rowOf`.
// - family — a table and every table reaching it through a chain of // - family — a table and every table reaching it through a chain of
// `table.parentT`, named by the root that chain ends at: `table.family`. // `table.parentT`, named by the root that chain ends at: `table.family`.
// - whole — a read that lands on a `row`, so the row renders through its table's // - whole — a read that lands on a `row`, so the row renders through its table's
-57
View File
@@ -1,57 +0,0 @@
package fejkdata
import (
"strings"
)
// drawSet is one render's reference draws: the unnamed draw group's, and each named one's.
type drawSet struct {
unnamed draws
named map[string]*draws
}
// drawScope is where a render reads its reference paths: a draw set, in the draw group of the
// template rendering, and the row a table's format is rendering, which its columns read.
type drawScope struct {
set *drawSet
group string
t *table
row int
}
// newDrawSet makes the unnamed group's maps where the set is declared, keeping them on that frame's
// stack for a render that reads through them; a zero drawSet makes them on its first read instead.
func newDrawSet(s *session) drawSet {
return drawSet{unnamed: draws{variant: map[string]node{}, value: map[string]draw{}, s: s}}
}
// renderOnce renders n as one render, over draws of its own.
func renderOnce(s *session, n node) string {
set := drawSet{unnamed: draws{s: s}}
return render(s, n, drawScope{set: &set})
}
// in is the scope t renders in: its draw group where it names one, else its caller's.
func (sc drawScope) in(t *template) drawScope {
if t.drawGroupKey != "" {
sc.group = t.drawGroupKey
}
return sc
}
// draws is the set's draws for sc's draw group. The session is passed in rather than
// read out of the set: copying it from there would leak the set's maps to the heap.
func (sc drawScope) draws(s *session) *draws {
if sc.group == "" {
return &sc.set.unnamed
}
d, drew := sc.set.named[sc.group]
if !drew {
if sc.set.named == nil {
sc.set.named = map[string]*draws{}
}
d = &draws{variant: map[string]node{}, value: map[string]draw{}, s: s}
sc.set.named[strings.Clone(sc.group)] = d // a key from sc would leak sc, and with it every render's draw set, to the heap
}
return d
}
+8 -8
View File
@@ -235,8 +235,8 @@ func checkColumnDraws(t *template, columns []string) error {
// drawWalk gathers the references one render reads, by draw group, for check to compare. // drawWalk gathers the references one render reads, by draw group, for check to compare.
type drawWalk struct { type drawWalk struct {
reads []pathRead reads []pathRead
read map[drawKey]bool read map[readKey]bool
seen map[drawVisit]bool seen map[nodeVisit]bool
} }
// drawAt is where a walk stands: the draw group it draws in, how the render's root reached it, the // drawAt is where a walk stands: the draw group it draws in, how the render's root reached it, the
@@ -248,7 +248,7 @@ type drawAt struct {
drawn *table // left out of the visit keys: only this table's own cells compare against it, which the own-family fence keeps true drawn *table // left out of the visit keys: only this table's own cells compare against it, which the own-family fence keeps true
} }
// rowSet stands in at load for the rows (*draws).pin holds at render: a path read enters a row with // rowSet stands in at load for the rows (*hold).pin holds at render: a path read enters a row with
// its ancestors, a whole read enters the drawn row alone. // its ancestors, a whole read enters the drawn row alone.
type rowSet []tablePin type rowSet []tablePin
@@ -311,26 +311,26 @@ type pathRead struct {
tr *tableRead // set where the reference names a table tr *tableRead // set where the reference names a table
} }
type drawVisit struct { type nodeVisit struct {
n node n node
group string group string
alt string alt string
} }
type drawKey struct { type readKey struct {
group string group string
path string path string
alt string alt string
} }
func newDrawWalk() *drawWalk { func newDrawWalk() *drawWalk {
return &drawWalk{read: map[drawKey]bool{}, seen: map[drawVisit]bool{}} return &drawWalk{read: map[readKey]bool{}, seen: map[nodeVisit]bool{}}
} }
// walk follows what rendering n renders. A repeat renders over draws of its own, so the walk stops // walk follows what rendering n renders. A repeat renders over draws of its own, so the walk stops
// there. // there.
func (w *drawWalk) walk(n node, at drawAt) { func (w *drawWalk) walk(n node, at drawAt) {
v := drawVisit{n, at.group, at.alt.key()} v := nodeVisit{n, at.group, at.alt.key()}
if w.seen[v] { if w.seen[v] {
return return
} }
@@ -377,7 +377,7 @@ func (s rowSet) excludes(cell *template) bool {
func (w *drawWalk) edge(from node, e renderEdge, at drawAt) { func (w *drawWalk) edge(from node, e renderEdge, at drawAt) {
if a, reads := refRead(from, e.label); reads { if a, reads := refRead(from, e.label); reads {
tr := tableReadOf(from.(*template).fields[a.key], a, e.to) tr := tableReadOf(from.(*template).fields[a.key], a, e.to)
if k := (drawKey{at.group, a.path, at.alt.key()}); !w.read[k] { if k := (readKey{at.group, a.path, at.alt.key()}); !w.read[k] {
w.read[k] = true w.read[k] = true
w.reads = append(w.reads, pathRead{at, a, tr}) w.reads = append(w.reads, pathRead{at, a, tr})
} }
+4 -4
View File
@@ -11,7 +11,7 @@ import (
// it lands on a row rendered whole. // it lands on a row rendered whole.
type tableRead struct { type tableRead struct {
head *table head *table
pins draws pins hold
drawn map[*table]bool drawn map[*table]bool
sels []tableSel sels []tableSel
whole bool whole bool
@@ -125,7 +125,7 @@ func replayPairs(reads []pathRead) error {
if o.tr == nil || o.at.group != r.at.group || alternatives(r.at, o.at) { if o.tr == nil || o.at.group != r.at.group || alternatives(r.at, o.at) {
continue continue
} }
var d draws var d hold
if err := r.tr.replay(&d); err != nil { if err := r.tr.replay(&d); err != nil {
return conflict(r, err) return conflict(r, err)
} }
@@ -142,7 +142,7 @@ func conflict(r pathRead, err error) error {
} }
// replay pins the read's rows into d, where they agree with the rows pinned before. // replay pins the read's rows into d, where they agree with the rows pinned before.
func (r *tableRead) replay(d *draws) error { func (r *tableRead) replay(d *hold) error {
var err error var err error
r.pins.each(func(t *table, row int) { r.pins.each(func(t *table, row int) {
if err == nil { if err == nil {
@@ -178,7 +178,7 @@ func checkFamilyPair(a, b pathRead) error {
} }
// drawnOf is a table the read draws that pins holds a row of, if any. // drawnOf is a table the read draws that pins holds a row of, if any.
func (r *tableRead) drawnOf(pins *draws) *table { func (r *tableRead) drawnOf(pins *hold) *table {
var found *table var found *table
pins.each(func(t *table, _ int) { pins.each(func(t *table, _ int) {
if found == nil && r.drawn[t] { if found == nil && r.drawn[t] {
+1 -1
View File
@@ -35,7 +35,7 @@ type Generator struct {
rand *session rand *session
categories map[string]node categories map[string]node
root folder // the categories as the node a path walks from, owned here so a walk allocates none root folder // the categories as the node a path walks from, owned here so a walk allocates none
set drawSet // one Fake's draws, owned here so a walk pinning rows keeps them off the heap set holdSet // one Fake's hold set, owned here so a walk pinning rows keeps them off the heap
records map[node]recordShape records map[node]recordShape
structs map[reflect.Type]structResult structs map[reflect.Type]structResult
} }
+18 -18
View File
@@ -11,11 +11,11 @@ type tablePin struct {
row int row int
} }
// draws is what has already been drawn for held names: the variant each was drawn as, // hold is what has already been drawn for held names: the variant each was drawn as,
// so every path under it reads one row, and the draw each read made, by its one // so every path under it reads one row, and the draw each read made, by its one
// spelling, so the same read written twice reads one draw. An expansion keeps one for // spelling, so the same read written twice reads one draw. An expansion keeps one for
// its sibling names, and a render one per group for its reference paths. // its sibling names, and a render one per group for its reference paths.
type draws struct { type hold struct {
variant map[string]node variant map[string]node
value map[string]draw value map[string]draw
pins [8]tablePin // the rows pinned, inline so a render over a country's five-deep geo tree stays off the heap pins [8]tablePin // the rows pinned, inline so a render over a country's five-deep geo tree stays off the heap
@@ -31,7 +31,7 @@ type draw struct {
} }
// pinned is the row the render pinned for t, if any. // pinned is the row the render pinned for t, if any.
func (d *draws) pinned(t *table) (int, bool) { func (d *hold) pinned(t *table) (int, bool) {
for _, p := range d.pins[:d.npins] { for _, p := range d.pins[:d.npins] {
if p.t == t { if p.t == t {
return p.row, true return p.row, true
@@ -42,7 +42,7 @@ func (d *draws) pinned(t *table) (int, bool) {
} }
// mustRow is the row pinned for t, which the walk reaching a column pinned. // mustRow is the row pinned for t, which the walk reaching a column pinned.
func (d *draws) mustRow(t *table) int { func (d *hold) mustRow(t *table) int {
r, ok := d.pinned(t) r, ok := d.pinned(t)
if !ok { if !ok {
panic(fmt.Sprintf("fejkdata: a column of %s is rendered with no row pinned", t.category)) panic(fmt.Sprintf("fejkdata: a column of %s is rendered with no row pinned", t.category))
@@ -51,7 +51,7 @@ func (d *draws) mustRow(t *table) int {
} }
// pin pins row r of t, and the rows of t's ancestors it links to. // pin pins row r of t, and the rows of t's ancestors it links to.
func (d *draws) pin(t *table, r int) { func (d *hold) pin(t *table, r int) {
for { for {
if _, done := d.pinned(t); done { if _, done := d.pinned(t); done {
return return
@@ -73,7 +73,7 @@ func (d *draws) pin(t *table, r int) {
} }
// each calls fn for every pinned row, in pin order, the spilled ones by table name. // each calls fn for every pinned row, in pin order, the spilled ones by table name.
func (d *draws) each(fn func(t *table, r int)) { func (d *hold) each(fn func(t *table, r int)) {
for _, p := range d.pins[:d.npins] { for _, p := range d.pins[:d.npins] {
fn(p.t, p.row) fn(p.t, p.row)
} }
@@ -90,7 +90,7 @@ func (d *draws) each(fn func(t *table, r int)) {
// rowOf is the render's row of t: the one pinned, else one drawn inside the // rowOf is the render's row of t: the one pinned, else one drawn inside the
// nearest pinned ancestor — its parent drawn inside that first where the ancestor // nearest pinned ancestor — its parent drawn inside that first where the ancestor
// is further up — or over the whole table, and pinned with its ancestors. // is further up — or over the whole table, and pinned with its ancestors.
func (d *draws) rowOf(t *table) int { func (d *hold) rowOf(t *table) int {
if r, ok := d.pinned(t); ok { if r, ok := d.pinned(t); ok {
return r return r
} }
@@ -113,7 +113,7 @@ func (d *draws) rowOf(t *table) int {
} }
// pinRow pins row r of t where it agrees with the rows pinned before it. // pinRow pins row r of t where it agrees with the rows pinned before it.
func (d *draws) pinRow(t *table, r int) error { func (d *hold) pinRow(t *table, r int) error {
if pr, ok := d.pinned(t); ok && pr != r { if pr, ok := d.pinned(t); ok && pr != r {
return fmt.Errorf("%s and %s are two rows of %s", t.selectorSpelling(pr), t.selectorSpelling(r), t.path) return fmt.Errorf("%s and %s are two rows of %s", t.selectorSpelling(pr), t.selectorSpelling(r), t.path)
} }
@@ -127,7 +127,7 @@ func (d *draws) pinRow(t *table, r int) error {
} }
// selectRow pins the row a selector names. // selectRow pins the row a selector names.
func (d *draws) selectRow(t *table, sel string) error { func (d *hold) selectRow(t *table, sel string) error {
r, err := t.find(sel, d) r, err := t.find(sel, d)
if err != nil { if err != nil {
return err return err
@@ -136,7 +136,7 @@ func (d *draws) selectRow(t *table, sel string) error {
} }
// inside keeps the rows of t that sit inside every pinned ancestor. // inside keeps the rows of t that sit inside every pinned ancestor.
func (d *draws) inside(t *table, rows []int) []int { func (d *hold) inside(t *table, rows []int) []int {
for a := t.parentT; a != nil; a = a.parentT { for a := t.parentT; a != nil; a = a.parentT {
pa, ok := d.pinned(a) pa, ok := d.pinned(a)
if !ok { if !ok {
@@ -160,14 +160,14 @@ func (d *draws) inside(t *table, rows []int) []int {
// gives one value, and a shown operand is the operand computed. Every other name is // gives one value, and a shown operand is the operand computed. Every other name is
// drawn afresh, so {word} {word} still draws twice. checkTokens, checkPath and // drawn afresh, so {word} {word} still draws twice. checkTokens, checkPath and
// linkRefs prove every step, so the walk cannot fail. // linkRefs prove every step, so the walk cannot fail.
func readField(s *session, t *template, held *draws, sc drawScope, a arm) draw { func readField(s *session, t *template, held *hold, sc renderScope, a arm) draw {
if !t.held[a.key] { if !t.held[a.key] {
if len(a.tail) > 0 { if len(a.tail) > 0 {
panic(fmt.Sprintf("fejkdata: %q reads a path into %q, which the expansion does not hold", a.name, a.key)) panic(fmt.Sprintf("fejkdata: %q reads a path into %q, which the expansion does not hold", a.name, a.key))
} }
return draw{text: render(s, t.fields[a.key], sc)} return draw{text: render(s, t.fields[a.key], sc)}
} }
d := readScope(s, held, sc, a) d := readHold(s, held, sc, a)
if r, done := d.value[a.path]; done { if r, done := d.value[a.path]; done {
return r return r
} }
@@ -202,26 +202,26 @@ func readField(s *session, t *template, held *draws, sc drawScope, a arm) draw {
return r return r
} }
// readScope is the draws a held read keeps its draw in: for a reference that reads a path, // readHold is the hold a held read keeps its draw in: for a reference that reads a path,
// the render's draws for its group, so its draw spans the render; for a sibling, or a // the render's hold for its group, so its draw spans the render; for a sibling, or a
// reference read whole, held. // reference read whole, held.
func readScope(s *session, held *draws, sc drawScope, a arm) *draws { func readHold(s *session, held *hold, sc renderScope, a arm) *hold {
if isRef(a.key) && len(a.tail) > 0 { if isRef(a.key) && len(a.tail) > 0 {
return sc.draws(s) return sc.hold(s)
} }
return held return held
} }
// renderLeaf draws and renders what a read lands on: null on a null item, or on a column of one // renderLeaf draws and renders what a read lands on: null on a null item, or on a column of one
// reference alone whose read drew null. // reference alone whose read drew null.
func renderLeaf(s *session, n node, sc drawScope) draw { func renderLeaf(s *session, n node, sc renderScope) draw {
n = drawn(s, n) n = drawn(s, n)
if _, isNull := n.(*null); isNull { if _, isNull := n.(*null); isNull {
return draw{null: true} return draw{null: true}
} }
r := draw{text: render(s, n, sc)} r := draw{text: render(s, n, sc)}
if t, _ := n.(*template); t != nil && t.readsColumn != nil { if t, _ := n.(*template); t != nil && t.readsColumn != nil {
r.null = sc.in(t).draws(s).value[t.readsColumn.a.path].null r.null = sc.in(t).hold(s).value[t.readsColumn.a.path].null
} }
return r return r
} }
+1 -1
View File
@@ -766,6 +766,6 @@ func TestReadFieldPanicsOnAnUnheldPath(t *testing.T) {
t.Fatal("not a template") t.Fatal("not a template")
} }
mustPanic(t, "unheld arm with a path", func() { mustPanic(t, "unheld arm with a path", func() {
readField(engine(1).rand, tm, nil, drawScope{}, arm{name: "w.x", key: "w", tail: []string{"x"}, path: "w.x"}) readField(engine(1).rand, tm, nil, renderScope{}, arm{name: "w.x", key: "w", tail: []string{"x"}, path: "w.x"})
}) })
} }
+3 -3
View File
@@ -45,10 +45,10 @@ func heldHeads(t *template) []string {
return heads return heads
} }
// pinned collects what the hold of head answers for: a path pins the levels it // heldNodes is what the hold of head answers for: a path pins the levels it
// passes through and the leaf it lands on, an operand exactly the value its render // passes through and the leaf it lands on, an operand exactly the value its render
// produces. // produces.
func pinned(t *template, head string, readers []reader) map[node]bool { func heldNodes(t *template, head string, readers []reader) map[node]bool {
held := map[node]bool{} held := map[node]bool{}
if _, isPath := t.bound[head]; !isPath { if _, isPath := t.bound[head]; !isPath {
operandDraw(t.fields[head], held) operandDraw(t.fields[head], held)
@@ -66,7 +66,7 @@ func pinned(t *template, head string, readers []reader) map[node]bool {
// holding it. One seen set across the edges: a node that cannot reach the level // holding it. One seen set across the edges: a node that cannot reach the level
// cannot reach it by another route either, so it is walked once. // cannot reach it by another route either, so it is walked once.
func checkHeadHeld(t *template, head string, readers []reader) error { func checkHeadHeld(t *template, head string, readers []reader) error {
held := pinned(t, head, readers) held := heldNodes(t, head, readers)
if len(held) == 0 { if len(held) == 0 {
return nil // a fixed head holds nothing to reach return nil // a fixed head holds nothing to reach
} }
+57
View File
@@ -0,0 +1,57 @@
package fejkdata
import (
"strings"
)
// holdSet is one render's reference draws: the unnamed draw group's, and each named one's.
type holdSet struct {
unnamed hold
named map[string]*hold
}
// renderScope is where a render reads its reference paths: a hold set, in the draw group of the
// template rendering, and the row a table's format is rendering, which its columns read.
type renderScope struct {
set *holdSet
group string
t *table
row int
}
// newHoldSet makes the unnamed group's maps where the set is declared, keeping them on that frame's
// stack for a render that reads through them; a zero holdSet makes them on its first read instead.
func newHoldSet(s *session) holdSet {
return holdSet{unnamed: hold{variant: map[string]node{}, value: map[string]draw{}, s: s}}
}
// renderOnce renders n as one render, over draws of its own.
func renderOnce(s *session, n node) string {
set := holdSet{unnamed: hold{s: s}}
return render(s, n, renderScope{set: &set})
}
// in is the scope t renders in: its draw group where it names one, else its caller's.
func (sc renderScope) in(t *template) renderScope {
if t.drawGroupKey != "" {
sc.group = t.drawGroupKey
}
return sc
}
// hold is the set's hold for sc's draw group. The session is passed in rather than
// read out of the set: copying it from there would leak the set's maps to the heap.
func (sc renderScope) hold(s *session) *hold {
if sc.group == "" {
return &sc.set.unnamed
}
d, drew := sc.set.named[sc.group]
if !drew {
if sc.set.named == nil {
sc.set.named = map[string]*hold{}
}
d = &hold{variant: map[string]node{}, value: map[string]draw{}, s: s}
sc.set.named[strings.Clone(sc.group)] = d // a key from sc would leak sc, and with it every render's hold set, to the heap
}
return d
}
+1 -1
View File
@@ -47,7 +47,7 @@ func (*null) isNode() {}
// run: where the format holds a reference, that binds it and compiles the format // run: where the format holds a reference, that binds it and compiles the format
// again. // again.
type template struct { type template struct {
// Filled by `compileString`, `compileTemplate` and `table.compileFormat`: // Filled by `compileString`, `compileTemplate` and `table.compileWhole`:
format string format string
fields map[string]node // `linkTemplateRefs` adds each bound reference here fields map[string]node // `linkTemplateRefs` adds each bound reference here
repeat int repeat int
+3 -3
View File
@@ -135,7 +135,7 @@ type pathWalk struct {
choice func(c *choice, rest []string) ([]node, error) choice func(c *choice, rest []string) ([]node, error)
level func(t *template, rest []string) error level func(t *template, rest []string) error
leaf func(n node) error leaf func(n node) error
pins *draws pins *hold
} }
// walkPath descends tail from n and returns the node it ends at: a folder or // walkPath descends tail from n and returns the node it ends at: a folder or
@@ -272,7 +272,7 @@ func (t *table) step(tail []string) (column node, child *table, err error) {
// readRow pins the row a path reads of t: the one its selector names, or, where the // readRow pins the row a path reads of t: the one its selector names, or, where the
// walk draws, one drawn where the path reads into the table. // walk draws, one drawn where the path reads into the table.
func readRow(d *draws, t *table, sel string, draw bool) error { func readRow(d *hold, t *table, sel string, draw bool) error {
if sel != "" { if sel != "" {
return d.selectRow(t, sel) return d.selectRow(t, sel)
} }
@@ -312,7 +312,7 @@ func unreachableInChoice(c *choice, want string) error {
// carries a repeat or a drawGroup, which one draw of it could not apply. So a path // carries a repeat or a drawGroup, which one draw of it could not apply. So a path
// that validates here resolves on every render, and a typo is a New-time error. // that validates here resolves on every render, and a typo is a New-time error.
func checkPath(n node, tail []string, level string) error { func checkPath(n node, tail []string, level string) error {
var pins draws var pins hold
_, err := walkPath(n, tail, pathWalk{ _, err := walkPath(n, tail, pathWalk{
choice: func(c *choice, rest []string) ([]node, error) { choice: func(c *choice, rest []string) ([]node, error) {
if err := carriedByAll(c, rest); err != nil { if err := carriedByAll(c, rest); err != nil {
+8 -8
View File
@@ -130,8 +130,8 @@ func (f *Generator) FakeRecord(path string) (*Record, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("fejkdata: %s: %w", path, err) return nil, fmt.Errorf("fejkdata: %s: %w", path, err)
} }
set := newDrawSet(f.rand) set := newHoldSet(f.rand)
sc := drawScope{set: &set} sc := renderScope{set: &set}
if t, isTable := n.(*table); isTable { if t, isTable := n.(*table); isTable {
if n, err = tableRecord(f.rand, t, tail, sc); err != nil { if n, err = tableRecord(f.rand, t, tail, sc); err != nil {
return nil, fmt.Errorf("fejkdata: %s: %w", path, err) return nil, fmt.Errorf("fejkdata: %s: %w", path, err)
@@ -152,14 +152,14 @@ func (f *Generator) FakeRecord(path string) (*Record, error) {
// tableRecord walks a path's tail from a table to the table whose row is the record, // tableRecord walks a path's tail from a table to the table whose row is the record,
// pinning the rows it selects or draws. // pinning the rows it selects or draws.
func tableRecord(s *session, t *table, tail []string, sc drawScope) (node, error) { func tableRecord(s *session, t *table, tail []string, sc renderScope) (node, error) {
n, err := descend(s, t, tail, sc) n, err := descend(s, t, tail, sc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
switch n := n.(type) { switch n := n.(type) {
case *table: case *table:
sc.draws(s).rowOf(n) sc.hold(s).rowOf(n)
return n, nil return n, nil
case *row: case *row:
return n.t, nil return n.t, nil
@@ -204,8 +204,8 @@ type RecordTemplate struct {
func (t *RecordTemplate) Fake() *Record { func (t *RecordTemplate) Fake() *Record {
t.g.mu.Lock() t.g.mu.Lock()
defer t.g.mu.Unlock() defer t.g.mu.Unlock()
set := newDrawSet(t.g.rand) set := newHoldSet(t.g.rand)
return renderRecord(t.g.rand, t.t, t.columns, drawScope{set: &set}) return renderRecord(t.g.rand, t.t, t.columns, renderScope{set: &set})
} }
// NewRecordTemplate compiles an inline record — a JSON object with a format and // NewRecordTemplate compiles an inline record — a JSON object with a format and
@@ -262,10 +262,10 @@ func recordOf(n node) (*template, []Column, error) {
// renderRecord draws each column once, in the name order recordOf fixed, as one render // renderRecord draws each column once, in the name order recordOf fixed, as one render
// over sc's draws; a table's columns read the row pinned there. // over sc's draws; a table's columns read the row pinned there.
func renderRecord(s *session, t *template, columns []Column, sc drawScope) *Record { func renderRecord(s *session, t *template, columns []Column, sc renderScope) *Record {
sc = sc.in(t) sc = sc.in(t)
if t.table != nil { if t.table != nil {
sc.t, sc.row = t.table, sc.draws(s).mustRow(t.table) sc.t, sc.row = t.table, sc.hold(s).mustRow(t.table)
} }
r := &Record{columns: append([]Column(nil), columns...)} r := &Record{columns: append([]Column(nil), columns...)}
for i := range r.columns { for i := range r.columns {
+15 -15
View File
@@ -23,8 +23,8 @@ func (f *Generator) Fake(path string) (string, error) {
if err != nil { if err != nil {
return "", fmt.Errorf("fejkdata: %w", err) return "", fmt.Errorf("fejkdata: %w", err)
} }
f.set = drawSet{unnamed: draws{s: f.rand}} f.set = holdSet{unnamed: hold{s: f.rand}}
sc := drawScope{set: &f.set} sc := renderScope{set: &f.set}
f.root.children = f.categories f.root.children = f.categories
n, err := descend(f.rand, &f.root, segments, sc) n, err := descend(f.rand, &f.root, segments, sc)
if err != nil { if err != nil {
@@ -42,19 +42,19 @@ func (f *Generator) Fake(path string) (string, error) {
// choice consumes no segment, so the rest of the path must be one every variant // choice consumes no segment, so the rest of the path must be one every variant
// carries before a variant is picked — a path that resolves at all resolves on // carries before a variant is picked — a path that resolves at all resolves on
// every call. // every call.
func descend(s *session, root node, segments []string, sc drawScope) (node, error) { func descend(s *session, root node, segments []string, sc renderScope) (node, error) {
// Walked once without drawing first, so a path that fails moves no seeded stream. // Walked once without drawing first, so a path that fails moves no seeded stream.
var probe draws var probe hold
if _, err := walkPath(root, segments, pathWalk{pins: &probe}); err != nil { if _, err := walkPath(root, segments, pathWalk{pins: &probe}); err != nil {
return nil, err return nil, err
} }
return walkPath(root, segments, pathWalk{pins: sc.draws(s)}) return walkPath(root, segments, pathWalk{pins: sc.hold(s)})
} }
// render evaluates a compiled node to a string. compile validates every node up // render evaluates a compiled node to a string. compile validates every node up
// front, so rendering a compiled tree cannot fail. sc holds the reference draws the // front, so rendering a compiled tree cannot fail. sc holds the reference draws the
// render shares; each repeat iteration renders over draws of its own. // render shares; each repeat iteration renders over draws of its own.
func render(s *session, n node, sc drawScope) string { func render(s *session, n node, sc renderScope) string {
switch n := n.(type) { switch n := n.(type) {
case *choice: case *choice:
return render(s, pick(s, n), sc) return render(s, pick(s, n), sc)
@@ -64,11 +64,11 @@ func render(s *session, n node, sc drawScope) string {
sc.t, sc.row = n, n.draw(s) sc.t, sc.row = n, n.draw(s)
return expand(s, n.format, sc) return expand(s, n.format, sc)
case *row: case *row:
sc.t, sc.row = n.t, sc.draws(s).mustRow(n.t) sc.t, sc.row = n.t, sc.hold(s).mustRow(n.t)
return expand(s, n.t.format, sc) return expand(s, n.t.format, sc)
case *column: case *column:
if sc.t != n.t { if sc.t != n.t {
sc.t, sc.row = n.t, sc.draws(s).mustRow(n.t) sc.t, sc.row = n.t, sc.hold(s).mustRow(n.t)
} }
if cell := n.t.cellNode(sc.row, n.i); cell != nil { if cell := n.t.cellNode(sc.row, n.i); cell != nil {
return render(s, cell, sc) return render(s, cell, sc)
@@ -97,19 +97,19 @@ func render(s *session, n node, sc drawScope) string {
} }
// expandAnew expands one repeat iteration of t as a render of its own, in no group. Inlined into // expandAnew expands one repeat iteration of t as a render of its own, in no group. Inlined into
// render's loop, its draw set would move to the heap. // render's loop, its hold set would move to the heap.
// //
//go:noinline //go:noinline
func expandAnew(s *session, t *template) string { func expandAnew(s *session, t *template) string {
set := drawSet{unnamed: draws{s: s}} set := holdSet{unnamed: hold{s: s}}
return expand(s, t, drawScope{set: &set}) return expand(s, t, renderScope{set: &set})
} }
// pick selects one item. Uniform choices are O(1); weighted choices are an // pick selects one item. Uniform choices are O(1); weighted choices are an
// O(log n) search over precomputed cumulative weights. compile guarantees a // O(log n) search over precomputed cumulative weights. compile guarantees a
// non-empty choice and a finite positive total, so the index is always in range. // non-empty choice and a finite positive total, so the index is always in range.
// The session is concrete rather than the rng interface, which would make the // The session is concrete rather than the rng interface, which would make the
// walk that draws through it leak its draw set to the heap. // walk that draws through it leak its hold set to the heap.
func pick(s *session, c *choice) node { func pick(s *session, c *choice) node {
if c.cum == nil { if c.cum == nil {
return c.items[s.IntN(len(c.items))] return c.items[s.IntN(len(c.items))]
@@ -119,15 +119,15 @@ func pick(s *session, c *choice) node {
// expand renders a template's compiled ops. compile validated every token, so this // expand renders a template's compiled ops. compile validated every token, so this
// cannot fail. // cannot fail.
func expand(s *session, t *template, sc drawScope) string { func expand(s *session, t *template, sc renderScope) string {
var b strings.Builder var b strings.Builder
b.Grow(t.grow) b.Grow(t.grow)
// One draw per held name, for this expansion only: a nested template and each // One draw per held name, for this expansion only: a nested template and each
// repeat iteration get their own, since each is its own expansion. A reference // repeat iteration get their own, since each is its own expansion. A reference
// path reads the render's draws in sc instead. // path reads the render's draws in sc instead.
var held *draws var held *hold
if t.heldLocal { if t.heldLocal {
held = &draws{ held = &hold{
variant: make(map[string]node, len(t.held)), variant: make(map[string]node, len(t.held)),
value: make(map[string]draw, len(t.held)), value: make(map[string]draw, len(t.held)),
} }
+2 -2
View File
@@ -349,8 +349,8 @@ func (p *valueProof) checkField(label string, ft reflect.Type, column node) erro
// fill draws the record into v's tagged fields, then each nested struct as a record of its own. // fill draws the record into v's tagged fields, then each nested struct as a record of its own.
func (s *structShape) fill(sess *session, v reflect.Value) { func (s *structShape) fill(sess *session, v reflect.Value) {
if s.record != nil { if s.record != nil {
set := newDrawSet(sess) set := newHoldSet(sess)
for i, c := range renderRecord(sess, s.record, s.columns, drawScope{set: &set}).columns { for i, c := range renderRecord(sess, s.record, s.columns, renderScope{set: &set}).columns {
setColumn(fieldAt(v, s.fields[i]), c) setColumn(fieldAt(v, s.fields[i]), c)
} }
} }
+5 -4
View File
@@ -103,7 +103,7 @@ func compileTable(m map[string]any, category string, files *categoryFiles) (*tab
if err := t.checkCells(); err != nil { if err := t.checkCells(); err != nil {
return nil, fmt.Errorf("%s: %w", o.rows, err) return nil, fmt.Errorf("%s: %w", o.rows, err)
} }
if err := t.compileFormat(o.format); err != nil { if err := t.compileWhole(o.format); err != nil {
return nil, err return nil, err
} }
return t, nil return t, nil
@@ -321,8 +321,9 @@ func (t *table) checkCells() error {
return nil return nil
} }
// compileFormat compiles the format over the columns as its fields. // compileWhole compiles the format a row renders whole through, over the columns
func (t *table) compileFormat(format string) error { // as its fields.
func (t *table) compileWhole(format string) error {
for _, name := range fieldTokens(format) { for _, name := range fieldTokens(format) {
a := splitArm(name, nil) a := splitArm(name, nil)
if _, ok := t.col[a.key]; !ok && !isRef(a.key) && a.key != "" { if _, ok := t.col[a.key]; !ok && !isRef(a.key) && a.key != "" {
@@ -524,7 +525,7 @@ func (t *table) under(r int, a *table, pr int) bool {
// find is the row a selector names: by key first, then by name, where a name // find is the row a selector names: by key first, then by name, where a name
// naming several rows resolves inside the ancestors pinned in d. // naming several rows resolves inside the ancestors pinned in d.
func (t *table) find(sel string, d *draws) (int, error) { func (t *table) find(sel string, d *hold) (int, error) {
if t.key < 0 && t.name < 0 { if t.key < 0 && t.name < 0 {
return 0, fmt.Errorf("%s has no key or name column to select a row by", t.path) return 0, fmt.Errorf("%s has no key or name column to select a row by", t.path)
} }
+2 -2
View File
@@ -982,7 +982,7 @@ func TestRowSetSimulatesPinning(t *testing.T) {
t.Errorf("a drawn %s enters %v, want its own row alone", entered.selectorSpelling(row), drawn) t.Errorf("a drawn %s enters %v, want its own row alone", entered.selectorSpelling(row), drawn)
} }
s := rowSet(nil).enter(entered, row, true) s := rowSet(nil).enter(entered, row, true)
var pinned draws var pinned hold
held := 0 held := 0
pinned.pin(entered, row) pinned.pin(entered, row)
pinned.each(func(tbl *table, r int) { pinned.each(func(tbl *table, r int) {
@@ -996,7 +996,7 @@ func TestRowSetSimulatesPinning(t *testing.T) {
} }
for _, cand := range tables { for _, cand := range tables {
for cr := 0; cr < cand.rows(); cr++ { for cr := 0; cr < cand.rows(); cr++ {
var beside draws var beside hold
beside.pin(entered, row) beside.pin(entered, row)
refused := beside.pinRow(cand, cr) != nil refused := beside.pinRow(cand, cr) != nil
if got := s.excludes(&template{cellOf: cand, cellRow: cr}); got != refused { if got := s.excludes(&template{cellOf: cand, cellRow: cr}); got != refused {
+1 -1
View File
@@ -316,7 +316,7 @@ func TestGrowIsALowerBound(t *testing.T) {
t.Fatalf("format %q did not compile to a template", format) t.Fatalf("format %q did not compile to a template", format)
} }
for i := 0; i < 50; i++ { for i := 0; i < 50; i++ {
if got := len(expand(f.rand, tmpl, drawScope{set: &drawSet{}})); got < tmpl.grow { if got := len(expand(f.rand, tmpl, renderScope{set: &holdSet{}})); got < tmpl.grow {
t.Errorf("format %q: expand emitted %d bytes, below grow %d", format, got, tmpl.grow) t.Errorf("format %q: expand emitted %d bytes, below grow %d", format, got, tmpl.grow)
} }
} }
+4 -8
View File
@@ -21,14 +21,10 @@ name says whether a unit runs at `New` or at `Fake`.
This round, in order: This round, in order:
- Name the `draw` stem apart: the free `pinned` in `holdfence.go` is not - Spell one fresh hold set one way: `newHoldSet` exists, and `render.go` and
`(*draws).pinned` in `hold.go`, `compileFormat` is a method on both `*template` and `holdset.go` hand-roll `holdSet{unnamed: hold{s: s}}` at three sites; name the
`*table`, and `draws`, `drawSet`, `drawScope`, `drawWalk`, `drawAt`, `drawKey`, lazy-map variant if the difference is deliberate, and put the hold set in one file:
`drawVisit` and `drawRoute` no longer tell each other apart. `holdset.go` holds 57 lines after the split, and `render.go` two of the three sites.
- Spell one fresh draw set one way: `newDrawSet` exists, and `render.go` and `draw.go`
hand-roll `drawSet{unnamed: draws{s: s}}` at three sites; name the lazy-map variant
if the difference is deliberate, and put the draw set in one file: `draw.go` holds 57
lines after the split, and `render.go` two of the three sites.
- Run the package doc's Go example under test, as `readme_test.go` runs the README's - Run the package doc's Go example under test, as `readme_test.go` runs the README's
JSON blocks: `doc.go` and the README both print `Järvedsvägen 43\n891 77 Järved` JSON blocks: `doc.go` and the README both print `Järvedsvägen 43\n891 77 Järved`
for seed 42, and nothing renders either, so a draw that moves leaves both false. for seed 42, and nothing renders either, so a draw that moves leaves both false.