From d128621fc18165f5e3f42d2805851e547c738d5d Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Mon, 21 Sep 2026 21:41:44 +0200 Subject: [PATCH] Name a render's holds apart from the draw fence --- README.md | 2 +- doc.go | 8 +++---- draw.go | 57 ------------------------------------------------ drawfence.go | 16 +++++++------- family.go | 8 +++---- fejkdata.go | 2 +- hold.go | 36 +++++++++++++++--------------- hold_test.go | 2 +- holdfence.go | 6 ++--- holdset.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ node.go | 2 +- path.go | 6 ++--- record.go | 16 +++++++------- render.go | 30 ++++++++++++------------- struct.go | 4 ++-- table.go | 9 ++++---- table_test.go | 4 ++-- template_test.go | 2 +- todo.md | 12 ++++------ 19 files changed, 138 insertions(+), 141 deletions(-) delete mode 100644 draw.go create mode 100644 holdset.go diff --git a/README.md b/README.md index c49bbd8..d7ac5d0 100644 --- a/README.md +++ b/README.md @@ -1332,7 +1332,7 @@ inline.go inline templates: Template, NewTemplate, FakeTemplate, IsTemplat 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 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 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 diff --git a/doc.go b/doc.go index 2f8e23d..1d8c2fe 100644 --- a/doc.go +++ b/doc.go @@ -19,16 +19,16 @@ package fejkdata // - draw — one pick from a choice, or one row taken from a table, and the value a // read produced: `pick`, `drawn`, `table.draw`, `draw`. // - 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` // start one each, `FakeStruct` one per record, and `expandAnew` one per // iteration of a `template.repeat`. // - 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: // `template.drawGroup` as the data spells it, `template.drawGroupKey` as a -// render reads it, `drawSet`, `drawScope`. -// - pin — fixing which row of a table the render uses: `draws.pin`, `draws.rowOf`. +// render reads it, `holdSet`, `renderScope`. +// - 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 // `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 diff --git a/draw.go b/draw.go deleted file mode 100644 index 0fdb478..0000000 --- a/draw.go +++ /dev/null @@ -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 -} diff --git a/drawfence.go b/drawfence.go index 4e51eaf..64a7e97 100644 --- a/drawfence.go +++ b/drawfence.go @@ -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. type drawWalk struct { reads []pathRead - read map[drawKey]bool - seen map[drawVisit]bool + read map[readKey]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 @@ -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 } -// 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. type rowSet []tablePin @@ -311,26 +311,26 @@ type pathRead struct { tr *tableRead // set where the reference names a table } -type drawVisit struct { +type nodeVisit struct { n node group string alt string } -type drawKey struct { +type readKey struct { group string path string alt string } 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 // there. 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] { return } @@ -377,7 +377,7 @@ func (s rowSet) excludes(cell *template) bool { func (w *drawWalk) edge(from node, e renderEdge, at drawAt) { if a, reads := refRead(from, e.label); reads { 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.reads = append(w.reads, pathRead{at, a, tr}) } diff --git a/family.go b/family.go index 73443e4..a495e0c 100644 --- a/family.go +++ b/family.go @@ -11,7 +11,7 @@ import ( // it lands on a row rendered whole. type tableRead struct { head *table - pins draws + pins hold drawn map[*table]bool sels []tableSel 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) { continue } - var d draws + var d hold if err := r.tr.replay(&d); err != nil { 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. -func (r *tableRead) replay(d *draws) error { +func (r *tableRead) replay(d *hold) error { var err error r.pins.each(func(t *table, row int) { 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. -func (r *tableRead) drawnOf(pins *draws) *table { +func (r *tableRead) drawnOf(pins *hold) *table { var found *table pins.each(func(t *table, _ int) { if found == nil && r.drawn[t] { diff --git a/fejkdata.go b/fejkdata.go index 9fbf0f0..3bcb894 100644 --- a/fejkdata.go +++ b/fejkdata.go @@ -35,7 +35,7 @@ type Generator struct { rand *session categories map[string]node 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 structs map[reflect.Type]structResult } diff --git a/hold.go b/hold.go index a54e183..555e583 100644 --- a/hold.go +++ b/hold.go @@ -11,11 +11,11 @@ type tablePin struct { 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 // 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. -type draws struct { +type hold struct { variant map[string]node 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 @@ -31,7 +31,7 @@ type draw struct { } // 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] { if p.t == t { 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. -func (d *draws) mustRow(t *table) int { +func (d *hold) mustRow(t *table) int { r, ok := d.pinned(t) if !ok { 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. -func (d *draws) pin(t *table, r int) { +func (d *hold) pin(t *table, r int) { for { if _, done := d.pinned(t); done { 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. -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] { 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 // 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. -func (d *draws) rowOf(t *table) int { +func (d *hold) rowOf(t *table) int { if r, ok := d.pinned(t); ok { 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. -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 { 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. -func (d *draws) selectRow(t *table, sel string) error { +func (d *hold) selectRow(t *table, sel string) error { r, err := t.find(sel, d) if err != nil { 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. -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 { pa, ok := d.pinned(a) 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 // drawn afresh, so {word} {word} still draws twice. checkTokens, checkPath and // 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 len(a.tail) > 0 { 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)} } - d := readScope(s, held, sc, a) + d := readHold(s, held, sc, a) if r, done := d.value[a.path]; done { return r } @@ -202,26 +202,26 @@ func readField(s *session, t *template, held *draws, sc drawScope, a arm) draw { return r } -// readScope is the draws 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 +// readHold is the hold a held read keeps its draw in: for a reference that reads a path, +// the render's hold for its group, so its draw spans the render; for a sibling, or a // 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 { - return sc.draws(s) + return sc.hold(s) } return held } // 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. -func renderLeaf(s *session, n node, sc drawScope) draw { +func renderLeaf(s *session, n node, sc renderScope) draw { n = drawn(s, n) if _, isNull := n.(*null); isNull { return draw{null: true} } r := draw{text: render(s, n, sc)} 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 } diff --git a/hold_test.go b/hold_test.go index 53f6f9f..301feca 100644 --- a/hold_test.go +++ b/hold_test.go @@ -766,6 +766,6 @@ func TestReadFieldPanicsOnAnUnheldPath(t *testing.T) { t.Fatal("not a template") } 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"}) }) } diff --git a/holdfence.go b/holdfence.go index 017b6bc..562c915 100644 --- a/holdfence.go +++ b/holdfence.go @@ -45,10 +45,10 @@ func heldHeads(t *template) []string { 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 // 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{} if _, isPath := t.bound[head]; !isPath { 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 // cannot reach it by another route either, so it is walked once. func checkHeadHeld(t *template, head string, readers []reader) error { - held := pinned(t, head, readers) + held := heldNodes(t, head, readers) if len(held) == 0 { return nil // a fixed head holds nothing to reach } diff --git a/holdset.go b/holdset.go new file mode 100644 index 0000000..69d82ca --- /dev/null +++ b/holdset.go @@ -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 +} diff --git a/node.go b/node.go index 6a3f167..c9bb65c 100644 --- a/node.go +++ b/node.go @@ -47,7 +47,7 @@ func (*null) isNode() {} // run: where the format holds a reference, that binds it and compiles the format // again. type template struct { - // Filled by `compileString`, `compileTemplate` and `table.compileFormat`: + // Filled by `compileString`, `compileTemplate` and `table.compileWhole`: format string fields map[string]node // `linkTemplateRefs` adds each bound reference here repeat int diff --git a/path.go b/path.go index 5d85b8b..20fa50e 100644 --- a/path.go +++ b/path.go @@ -135,7 +135,7 @@ type pathWalk struct { choice func(c *choice, rest []string) ([]node, error) level func(t *template, rest []string) 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 @@ -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 // 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 != "" { 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 // that validates here resolves on every render, and a typo is a New-time error. func checkPath(n node, tail []string, level string) error { - var pins draws + var pins hold _, err := walkPath(n, tail, pathWalk{ choice: func(c *choice, rest []string) ([]node, error) { if err := carriedByAll(c, rest); err != nil { diff --git a/record.go b/record.go index 9a2621d..1f61bf1 100644 --- a/record.go +++ b/record.go @@ -130,8 +130,8 @@ func (f *Generator) FakeRecord(path string) (*Record, error) { if err != nil { return nil, fmt.Errorf("fejkdata: %s: %w", path, err) } - set := newDrawSet(f.rand) - sc := drawScope{set: &set} + set := newHoldSet(f.rand) + sc := renderScope{set: &set} if t, isTable := n.(*table); isTable { if n, err = tableRecord(f.rand, t, tail, sc); err != nil { 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, // 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) if err != nil { return nil, err } switch n := n.(type) { case *table: - sc.draws(s).rowOf(n) + sc.hold(s).rowOf(n) return n, nil case *row: return n.t, nil @@ -204,8 +204,8 @@ type RecordTemplate struct { func (t *RecordTemplate) Fake() *Record { t.g.mu.Lock() defer t.g.mu.Unlock() - set := newDrawSet(t.g.rand) - return renderRecord(t.g.rand, t.t, t.columns, drawScope{set: &set}) + set := newHoldSet(t.g.rand) + return renderRecord(t.g.rand, t.t, t.columns, renderScope{set: &set}) } // 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 // 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) 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...)} for i := range r.columns { diff --git a/render.go b/render.go index a1c15c9..649b2c5 100644 --- a/render.go +++ b/render.go @@ -23,8 +23,8 @@ func (f *Generator) Fake(path string) (string, error) { if err != nil { return "", fmt.Errorf("fejkdata: %w", err) } - f.set = drawSet{unnamed: draws{s: f.rand}} - sc := drawScope{set: &f.set} + f.set = holdSet{unnamed: hold{s: f.rand}} + sc := renderScope{set: &f.set} f.root.children = f.categories n, err := descend(f.rand, &f.root, segments, sc) 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 // carries before a variant is picked — a path that resolves at all resolves on // 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. - var probe draws + var probe hold if _, err := walkPath(root, segments, pathWalk{pins: &probe}); err != nil { 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 // 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. -func render(s *session, n node, sc drawScope) string { +func render(s *session, n node, sc renderScope) string { switch n := n.(type) { case *choice: 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) return expand(s, n.format, sc) 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) case *column: 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 { 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 -// render's loop, its draw set would move to the heap. +// render's loop, its hold set would move to the heap. // //go:noinline func expandAnew(s *session, t *template) string { - set := drawSet{unnamed: draws{s: s}} - return expand(s, t, drawScope{set: &set}) + set := holdSet{unnamed: hold{s: s}} + return expand(s, t, renderScope{set: &set}) } // pick selects one item. Uniform choices are O(1); weighted choices are an // 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. // 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 { if c.cum == nil { 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 // cannot fail. -func expand(s *session, t *template, sc drawScope) string { +func expand(s *session, t *template, sc renderScope) string { var b strings.Builder b.Grow(t.grow) // 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 // path reads the render's draws in sc instead. - var held *draws + var held *hold if t.heldLocal { - held = &draws{ + held = &hold{ variant: make(map[string]node, len(t.held)), value: make(map[string]draw, len(t.held)), } diff --git a/struct.go b/struct.go index 617a460..0a9cd91 100644 --- a/struct.go +++ b/struct.go @@ -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. func (s *structShape) fill(sess *session, v reflect.Value) { if s.record != nil { - set := newDrawSet(sess) - for i, c := range renderRecord(sess, s.record, s.columns, drawScope{set: &set}).columns { + set := newHoldSet(sess) + for i, c := range renderRecord(sess, s.record, s.columns, renderScope{set: &set}).columns { setColumn(fieldAt(v, s.fields[i]), c) } } diff --git a/table.go b/table.go index 987fee8..1227bcf 100644 --- a/table.go +++ b/table.go @@ -103,7 +103,7 @@ func compileTable(m map[string]any, category string, files *categoryFiles) (*tab if err := t.checkCells(); err != nil { 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 t, nil @@ -321,8 +321,9 @@ func (t *table) checkCells() error { return nil } -// compileFormat compiles the format over the columns as its fields. -func (t *table) compileFormat(format string) error { +// compileWhole compiles the format a row renders whole through, over the columns +// as its fields. +func (t *table) compileWhole(format string) error { for _, name := range fieldTokens(format) { a := splitArm(name, nil) 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 // 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 { return 0, fmt.Errorf("%s has no key or name column to select a row by", t.path) } diff --git a/table_test.go b/table_test.go index 2f871ed..6b90f75 100644 --- a/table_test.go +++ b/table_test.go @@ -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) } s := rowSet(nil).enter(entered, row, true) - var pinned draws + var pinned hold held := 0 pinned.pin(entered, row) pinned.each(func(tbl *table, r int) { @@ -996,7 +996,7 @@ func TestRowSetSimulatesPinning(t *testing.T) { } for _, cand := range tables { for cr := 0; cr < cand.rows(); cr++ { - var beside draws + var beside hold beside.pin(entered, row) refused := beside.pinRow(cand, cr) != nil if got := s.excludes(&template{cellOf: cand, cellRow: cr}); got != refused { diff --git a/template_test.go b/template_test.go index da8f9ff..89f5e35 100644 --- a/template_test.go +++ b/template_test.go @@ -316,7 +316,7 @@ func TestGrowIsALowerBound(t *testing.T) { t.Fatalf("format %q did not compile to a template", format) } 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) } } diff --git a/todo.md b/todo.md index 19215bf..a639190 100644 --- a/todo.md +++ b/todo.md @@ -21,14 +21,10 @@ name says whether a unit runs at `New` or at `Fake`. This round, in order: -- Name the `draw` stem apart: the free `pinned` in `holdfence.go` is not - `(*draws).pinned` in `hold.go`, `compileFormat` is a method on both `*template` and - `*table`, and `draws`, `drawSet`, `drawScope`, `drawWalk`, `drawAt`, `drawKey`, - `drawVisit` and `drawRoute` no longer tell each other apart. -- 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. +- Spell one fresh hold set one way: `newHoldSet` exists, and `render.go` and + `holdset.go` hand-roll `holdSet{unnamed: hold{s: s}}` at three sites; name the + lazy-map variant if the difference is deliberate, and put the hold set in one file: + `holdset.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 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.