From b99966fe19dbb6653655b294511e7cec913ae41d Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Tue, 29 Sep 2026 14:38:36 +0200 Subject: [PATCH] Name the render's draws apart from the expansion hold: drawMemo, groupDraws, renderDraws --- AGENTS.md | 2 +- README.md | 2 +- doc.go | 12 ++++---- docs/decisions.md | 10 +++---- hold.go => draws.go | 40 +++++++++++++------------- format.go | 4 +-- hold_test.go | 2 +- path.go | 20 ++++++------- perf_test.go | 6 ++-- readsurvey_test.go | 18 ++++++------ record.go | 12 ++++---- render.go | 70 ++++++++++++++++++++++----------------------- render_test.go | 8 +++--- struct.go | 4 +-- todo.md | 3 -- 15 files changed, 106 insertions(+), 107 deletions(-) rename hold.go => draws.go (66%) diff --git a/AGENTS.md b/AGENTS.md index 1d51302..23128d9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -57,7 +57,7 @@ In [docs/decisions.md](docs/decisions.md): - A render shares one reference draw per category, per group - A draw group name is local to its category - The expansion hold and the render's draws are two fences -- A hold set makes its draw maps on the first read +- A render's draws make their maps on the first read - A category never references itself, and a record's fences run at load - A record's column set is fixed before the first draw - Null is a `null` item, not a rate diff --git a/README.md b/README.md index 7e46d43..dac4db8 100644 --- a/README.md +++ b/README.md @@ -883,7 +883,7 @@ record.go records: Record, the JSON/CSV/SQL serializers, and their entry p struct.go structs: FakeStruct, fake tags, and a field's Go type as its column's datatype inline.go inline templates: Template, NewTemplate, FakeTemplate, IsTemplate, and their compile and link format.go the {token} grammar: scanning, tokens, operands, validation, compiling a format -hold.go the hold: one draw per held name; and the hold set and scope a render reads its reference paths through +draws.go the memo a hold or draw group keeps its draws in; and a render's draws and the scope it reads its reference paths through holdfence.go the load fences over the hold: the routes and spellings that read one held name two ways drawfence.go the load fences over reference draws: a drawGroup's, and a render's and a record's columns through the survey readsurvey.go the load-time survey of what a render reads, and what a path reads of a table family, and its verdict on them; and the fence on a table reaching its own family diff --git a/doc.go b/doc.go index efe7a17..6180994 100644 --- a/doc.go +++ b/doc.go @@ -19,15 +19,17 @@ package fejkdata // - draw — one pick from a choice, or one row taken from a table: `pick`, // `resolveChoice`, `table.drawRow`. // - expansion — one render of one format: `expand`. -// - render — what owns one `holdSet`, and so what one reference draw spans: +// - render — what owns one `renderDraws`, 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: -// `formatOps.held`, `hold`, `groupHold`, `readHeld`. -// - draw group — reference draws held apart inside one render: +// - hold — one expansion keeping one draw of a name, so every route to it reads +// that value: `formatOps.held`, `formatOps.keepsHold`, `heldCheck`. +// - memo — what a hold or a draw group keeps its draws in, the variant drawn at +// each level and the value each path read: `drawMemo`, `readMemo`. +// - draw group — reference draws kept apart inside one render: // `template.drawGroup` as the data spells it, `templateLink.drawGroupKey` as a -// render reads it, `holdSet`, `renderScope`. +// render reads it, `renderDraws`, `groupDraws`, `renderScope`. // - pin — fixing which row of a table the render uses, which the draw fences replay: // `pinSet`, `pinSet.pin`, `table.drawIn`. // - family — a table and every table reaching it through a chain of diff --git a/docs/decisions.md b/docs/decisions.md index a4dcd47..4acfdcf 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -242,11 +242,11 @@ groups. They pin different things — an operand pins the value its own render p and stops at a reference, a path pins every level it passes through — so one walk would carry both rules and both scopes anyway, and tell them apart at every step. -## A hold set makes its draw maps on the first read +## A render's draws make their maps on the first read -Every render starts from an empty hold set, its draw groups included, since goal 2 prices -a reader meeting two spellings of one empty set. Making the maps where the set is declared kept a -record's on its frame's stack, but a `Fake`'s set lives on the `Generator`, so there it +Every render starts from empty draws, its draw groups included, since goal 2 prices +a reader meeting two spellings of one empty set. Making the maps where the draws are declared kept a +record's on its frame's stack, but a `Fake`'s draws live on the `Generator`, so there it cost the cheapest render two heap allocations and 240→390 ns, measured 2026-09-28. Lazily, a render reading a reference path pays those two allocations, a record or struct included, and one reading none pays nothing; goal 11 holds either way. @@ -304,7 +304,7 @@ writes as the load check proved it; a `Value any` would hand every caller a type ## A unit takes the stem of what it is, and a file the stem of the units it holds -Goal 2 wants a name to reach one unit: `hold` is what a draw is kept in, `readValue` what +Goal 2 wants a name to reach one unit: `drawMemo` is what a draw is kept in, `readValue` what a read drew. ## One name, one meaning diff --git a/hold.go b/draws.go similarity index 66% rename from hold.go rename to draws.go index e63e517..eb5e4f7 100644 --- a/hold.go +++ b/draws.go @@ -2,18 +2,18 @@ package fejkdata import "strings" -// hold is what one expansion has drawn for the names it holds: the variant each was drawn as, so +// drawMemo is what a hold or a draw group has drawn: the variant each level was drawn as, so // every path under it reads one variant; and the value each read produced, by its path, so the // same read written twice reads one value. -type hold struct { +type drawMemo struct { variant map[string]node value map[string]readValue } -// groupHold is what one render's draw group has drawn for its reference paths: a hold, and the +// groupDraws is what one render's draw group has drawn for its reference paths: a memo, and the // table rows those paths pinned. -type groupHold struct { - hold +type groupDraws struct { + memo drawMemo pins pinSet } @@ -26,20 +26,20 @@ type readValue struct { // renderTrace is a test's view of the fields a render reads; nil outside a test. type renderTrace func(group string, row renderedRow, a arm) -// holdSet is one render's reference draws: the unnamed draw group's, and each named one's; and a -// test's trace of its reads. -type holdSet struct { - unnamed groupHold - named map[string]*groupHold +// renderDraws is one render's reference draws: the unnamed draw group's, and each named one's; +// and a test's trace of its reads. +type renderDraws struct { + unnamed groupDraws + named map[string]*groupDraws trace renderTrace } -// renderScope is where a render reads its reference paths: a hold set, in the draw group of the +// renderScope is where a render reads its reference paths: its reference draws, in the draw group of the // template rendering; and row, the row of the table rendering, which its columns read. It passes // by value, and what is read from it reaches a map key, an interface or a func value only as a -// copy; else sc, and with it every render's hold set, moves to the heap. +// copy; else sc, and with it every render's draws, move to the heap. type renderScope struct { - set *holdSet + draws *renderDraws group string row renderedRow } @@ -77,17 +77,17 @@ func (sc renderScope) rowOf(t *table) int { return sc.row.index } -func (sc renderScope) groupHold() *groupHold { +func (sc renderScope) groupDraws() *groupDraws { if sc.group == "" { - return &sc.set.unnamed + return &sc.draws.unnamed } - d, drew := sc.set.named[sc.group] + d, drew := sc.draws.named[sc.group] if !drew { - if sc.set.named == nil { - sc.set.named = map[string]*groupHold{} + if sc.draws.named == nil { + sc.draws.named = map[string]*groupDraws{} } - d = &groupHold{} - sc.set.named[strings.Clone(sc.group)] = d + d = &groupDraws{} + sc.draws.named[strings.Clone(sc.group)] = d } return d } diff --git a/format.go b/format.go index 599ec75..369917d 100644 --- a/format.go +++ b/format.go @@ -386,7 +386,7 @@ type formatOps struct { ops []op grow int held map[string]firstReach - heldLocal bool // some held name is not a reference path, so expand makes a hold + keepsHold bool // some held name is not a reference path, so expand makes a hold } // firstReach is how a format first reaches a held name: the reader holding it, for @@ -401,7 +401,7 @@ func (c *formatOps) holdName(a arm, label string) { c.held = map[string]firstReach{} } if !isRef(a.head) || len(a.tail) == 0 { - c.heldLocal = true + c.keepsHold = true } h, seen := c.held[a.head] if !seen { diff --git a/hold_test.go b/hold_test.go index b3621d5..df296dc 100644 --- a/hold_test.go +++ b/hold_test.go @@ -771,7 +771,7 @@ func TestReadFieldPanicsOnAPathItCannotHold(t *testing.T) { t.Fatal("not a template") } mustPanic(t, name, func() { - readField(engine(1).rand, tm, nil, renderScope{set: &holdSet{}}, a) + readField(engine(1).rand, tm, nil, renderScope{draws: &renderDraws{}}, a) }) } } diff --git a/path.go b/path.go index 59b7c16..3ce85a2 100644 --- a/path.go +++ b/path.go @@ -348,14 +348,14 @@ func (w *pathProbe) readRow(t *table, tail []string, r tableRoute) error { } // pathDraw draws the rows and variants a proved path reads, pinning the rows in pins; pins is nil -// for a sibling path, which never crosses a table, since a table is only a category. For a held -// read, held keeps the variant drawn at each level of a, so paths sharing a prefix share it. +// for a sibling path, which never crosses a table, since a table is only a category. For a memoized +// read, memo keeps the variant drawn at each level of a, so paths sharing a prefix share it. // Where pathCheck walks every variant of a choice, it walks the one drawn; at a table it selects // as pathCheck does and draws the row the route asks for. type pathDraw struct { s *generatorState pins *pinSet - held *hold + memo *drawMemo a *arm } @@ -400,20 +400,20 @@ func (w *pathDraw) walk(n node, tail []string) (node, error) { return n, nil } -// variant is the variant of c the walk continues into: the one held for this level -// of the read, drawn once, where the walk holds its draws; else one drawn afresh. +// variant is the variant of c the walk continues into: the one memo keeps for this level +// of the read, drawn once, where the walk has a memo; else one drawn afresh. func (w *pathDraw) variant(c *choice, rest []string) node { - if w.held == nil { + if w.memo == nil { return pick(w.s, c) } key := w.a.levels[len(w.a.tail)-len(rest)] - n, drew := w.held.variant[key] + n, drew := w.memo.variant[key] if !drew { n = resolveChoice(w.s, c) - if w.held.variant == nil { - w.held.variant = map[string]node{} + if w.memo.variant == nil { + w.memo.variant = map[string]node{} } - w.held.variant[key] = n + w.memo.variant[key] = n } return n } diff --git a/perf_test.go b/perf_test.go index 03389c1..8a0d2c7 100644 --- a/perf_test.go +++ b/perf_test.go @@ -55,7 +55,7 @@ func TestNoRenderAllocRegression(t *testing.T) { } } -// The repeat shape prices what keeps a hold set off the heap: a copy dropped costs an alloc an iteration. +// The repeat shape prices what keeps a render's draws off the heap: a copy dropped costs an alloc an iteration. func TestNoReferenceAllocRegression(t *testing.T) { word := `{"format":"{w}","w":["alpha","beta","gamma","delta"]}` for _, s := range []struct { @@ -73,12 +73,12 @@ func TestNoReferenceAllocRegression(t *testing.T) { t.Fatalf("New(%s): %v", s.name, err) } if allocs := testing.AllocsPerRun(10000, func() { f.Fake("x") }); allocs > s.base*1.10 { - t.Errorf("%s: %.1f allocs/op regressed past %.1f (baseline %.1f + 10%%); a hold set reaching the heap is the usual cause", s.name, allocs, s.base*1.10, s.base) + t.Errorf("%s: %.1f allocs/op regressed past %.1f (baseline %.1f + 10%%); a render's draws reaching the heap is the usual cause", s.name, allocs, s.base*1.10, s.base) } } } -// A table read pins a row in the render's hold and reads its cells in place. +// A table read pins a row in the render's draws and reads its cells in place. func TestNoTableAllocRegression(t *testing.T) { f, err := New(WithoutShippedData(), WithDataFS(fstest.MapFS{ "region.json": {Data: []byte(`{"format":"{name}","rows":"region.tsv","key":"code","weight":"population"}`)}, diff --git a/readsurvey_test.go b/readsurvey_test.go index d36cf28..a8733b0 100644 --- a/readsurvey_test.go +++ b/readsurvey_test.go @@ -77,17 +77,17 @@ func spellPins(p *pinSet) string { } func renderRoot(s *generatorState, t *template, trace renderTrace) { - set := holdSet{trace: trace} - sc := renderScope{set: &set} + draws := renderDraws{trace: trace} + sc := renderScope{draws: &draws} switch { case t.site.row == formatRow: tbl := t.site.table render(s, tbl, sc) for r := 0; r < tbl.rowCount(); r++ { - set := holdSet{trace: trace} - sc := renderScope{set: &set} - sc.groupHold().pins.pin(tbl, r) - render(s, tbl.rowNode, sc.at(tbl.rowNode, &sc.groupHold().pins)) + draws := renderDraws{trace: trace} + sc := renderScope{draws: &draws} + sc.groupDraws().pins.pin(tbl, r) + render(s, tbl.rowNode, sc.at(tbl.rowNode, &sc.groupDraws().pins)) } case t.repeat > 1: expand(s, t, sc) @@ -97,10 +97,10 @@ func renderRoot(s *generatorState, t *template, trace renderTrace) { } func renderRecordRoot(s *generatorState, t *template, columns []Column, trace renderTrace) { - set := holdSet{trace: trace} - sc := renderScope{set: &set} + draws := renderDraws{trace: trace} + sc := renderScope{draws: &draws} if t.site.row == formatRow { - t.site.table.drawIn(s, &sc.groupHold().pins) + t.site.table.drawIn(s, &sc.groupDraws().pins) } renderRecord(s, t, columns, sc) } diff --git a/record.go b/record.go index e8dee4f..77737c8 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) } - var set holdSet - sc := renderScope{set: &set} + var draws renderDraws + sc := renderScope{draws: &draws} 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) @@ -159,7 +159,7 @@ func tableRecord(s *generatorState, t *table, tail []string, sc renderScope) (no } switch n := n.(type) { case *table: - n.drawIn(s, &sc.groupHold().pins) + n.drawIn(s, &sc.groupDraws().pins) return n, nil case *tableRow: 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() - var set holdSet - return renderRecord(t.g.rand, t.template, t.columns, renderScope{set: &set}) + var draws renderDraws + return renderRecord(t.g.rand, t.template, t.columns, renderScope{draws: &draws}) } // NewRecordTemplate compiles an inline record — a JSON object with a format and @@ -264,7 +264,7 @@ func recordOf(n node) (*template, []Column, error) { func renderRecord(s *generatorState, t *template, columns []Column, sc renderScope) *Record { sc = sc.in(t) if t.site.row == formatRow { - sc = sc.at(t.site.table.rowNode, &sc.groupHold().pins) + sc = sc.at(t.site.table.rowNode, &sc.groupDraws().pins) } r := &Record{columns: append([]Column(nil), columns...)} for i := range r.columns { diff --git a/render.go b/render.go index 86a3aa2..e6f83f7 100644 --- a/render.go +++ b/render.go @@ -6,7 +6,7 @@ import ( ) // rng is the randomness a builtin sample draws from; *rand.Rand satisfies it. The -// render path takes the concrete *generatorState instead, which keeps the hold set of the walk +// render path takes the concrete *generatorState instead, which keeps the draws of the walk // drawing through it off the heap. type rng interface { IntN(n int) int @@ -24,8 +24,8 @@ func (f *Generator) Fake(path string) (string, error) { if err != nil { return "", fmt.Errorf("fejkdata: %w", err) } - var set holdSet - sc := renderScope{set: &set} + var draws renderDraws + sc := renderScope{draws: &draws} n, err := descend(f.rand, &f.root, segments, sc) if err != nil { return "", fmt.Errorf("fejkdata: %s: %w", path, err) @@ -33,7 +33,7 @@ func (f *Generator) Fake(path string) (string, error) { if _, ok := n.(*folder); ok { return "", fmt.Errorf("fejkdata: %s names a folder, not a value", path) } - return render(f.rand, n, sc.at(n, &sc.groupHold().pins)), nil + return render(f.rand, n, sc.at(n, &sc.groupDraws().pins)), nil } // descend walks a caller's path to the node it names, pinning in sc the rows it @@ -43,18 +43,18 @@ func descend(s *generatorState, root node, segments []string, sc renderScope) (n if _, err := (&pathProbe{}).walk(root, segments); err != nil { return nil, err } - return drawPath(root, segments, "", &pathDraw{s: s, pins: &sc.groupHold().pins}), nil + return drawPath(root, segments, "", &pathDraw{s: s, pins: &sc.groupDraws().pins}), nil } -// renderOnce renders n as one render, over a hold set of its own. +// renderOnce renders n as one render, over draws of its own. func renderOnce(s *generatorState, n node) string { - var set holdSet - return render(s, n, renderScope{set: &set}) + var draws renderDraws + return render(s, n, renderScope{draws: &draws}) } // 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 a hold set of its own. +// render shares; each repeat iteration renders over draws of its own. // A child this switch renders is one renderEdges must list too, or the fences miss it. func render(s *generatorState, n node, sc renderScope) string { switch n := n.(type) { @@ -96,12 +96,12 @@ func render(s *generatorState, n node, sc renderScope) string { } // expandAnew expands one repeat iteration of t as a render of its own, in no group. Inlined into -// render's loop, its hold set would move to the heap. +// render's loop, its draws would move to the heap. // //go:noinline func expandAnew(s *generatorState, t *template) string { - var set holdSet - return expand(s, t, renderScope{set: &set}) + var draws renderDraws + return expand(s, t, renderScope{draws: &draws}) } // pick selects one item. Uniform choices are O(1); weighted choices are an @@ -119,10 +119,10 @@ func expand(s *generatorState, t *template, sc renderScope) string { b.Grow(t.compiled.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 hold in sc instead. - var held *hold - if t.compiled.heldLocal { - held = &hold{ + // path reads its draw group's memo in sc instead. + var hold *drawMemo + if t.compiled.keepsHold { + hold = &drawMemo{ variant: make(map[string]node, len(t.compiled.held)), value: make(map[string]readValue, len(t.compiled.held)), } @@ -133,7 +133,7 @@ func expand(s *generatorState, t *template, sc renderScope) string { case literalRun: b.WriteString(o.lit) case nameRead: - b.WriteString(readField(s, t, held, sc, o.arms[s.IntN(len(o.arms))]).text) + b.WriteString(readField(s, t, hold, sc, o.arms[s.IntN(len(o.arms))]).text) case builtinCall: // Read before the call, so the value a calc computes is the value the // format showed. calcVars fixed the order op.operands holds. @@ -141,7 +141,7 @@ func expand(s *generatorState, t *template, sc renderScope) string { if len(o.operands) > 0 { operands = make([]string, len(o.operands)) for j, a := range o.operands { - operands[j] = readField(s, t, held, sc, a).text + operands[j] = readField(s, t, hold, sc, a).text } } b.WriteString(o.call(s, b.String(), operands)) // b.String() is the output so far @@ -152,15 +152,15 @@ func expand(s *generatorState, t *template, sc renderScope) string { // readField renders one arm of a token. A name the expansion holds — a level some // token addresses by a dotted path that is not a reference, or a field an operand -// reads — is drawn once and kept in held, so {place.postal-code} and {place.locality} +// reads — is drawn once and kept in hold, so {place.postal-code} and {place.locality} // read one row, either read twice gives one value, and a shown operand is the operand // computed. Every other name is drawn afresh, so {word} {word} still draws twice. -func readField(s *generatorState, t *template, held *hold, sc renderScope, a arm) readValue { +func readField(s *generatorState, t *template, hold *drawMemo, sc renderScope, a arm) readValue { if isRef(a.head) && len(a.tail) > 0 { return readReference(s, t, sc, a) } - if sc.set.trace != nil { - traceRead(sc.set.trace, t, sc, a) + if sc.draws.trace != nil { + traceRead(sc.draws.trace, t, sc, a) } if _, expansionHolds := t.compiled.held[a.head]; !expansionHolds { if len(a.tail) > 0 { @@ -168,35 +168,35 @@ func readField(s *generatorState, t *template, held *hold, sc renderScope, a arm } return readValue{text: render(s, t.head(a.head), sc)} } - if held == nil { + if hold == nil { panic(internalError("%q reads a name the expansion holds, with no hold to keep it in", a.spelling)) } - return readHeld(s, t, held, nil, sc, a) + return readMemo(s, t, hold, nil, sc, a) } -// readReference reads a reference path, kept in the render's hold for its group, so its draw +// readReference reads a reference path, kept in its draw group's memo, so its draw // spans the render. func readReference(s *generatorState, t *template, sc renderScope, a arm) readValue { - if sc.set.trace != nil { - traceRead(sc.set.trace, t, sc, a) + if sc.draws.trace != nil { + traceRead(sc.draws.trace, t, sc, a) } - group := sc.groupHold() - return readHeld(s, t, &group.hold, &group.pins, sc, a) + group := sc.groupDraws() + return readMemo(s, t, &group.memo, &group.pins, sc, a) } -func readHeld(s *generatorState, t *template, held *hold, pins *pinSet, sc renderScope, a arm) readValue { - if r, done := held.value[a.path]; done { +func readMemo(s *generatorState, t *template, memo *drawMemo, pins *pinSet, sc renderScope, a arm) readValue { + if r, done := memo.value[a.path]; done { return r } - leaf := drawPath(t.head(a.head), a.tail, a.head, &pathDraw{s: s, held: held, pins: pins, a: &a}) + leaf := drawPath(t.head(a.head), a.tail, a.head, &pathDraw{s: s, memo: memo, pins: pins, a: &a}) if pins != nil { sc = sc.at(leaf, pins) } r := renderLeaf(s, leaf, sc) - if held.value == nil { - held.value = map[string]readValue{} + if memo.value == nil { + memo.value = map[string]readValue{} } - held.value[a.path] = r + memo.value[a.path] = r return r } diff --git a/render_test.go b/render_test.go index 28ab84a..6bdea1d 100644 --- a/render_test.go +++ b/render_test.go @@ -42,8 +42,8 @@ func TestGrowIsALowerBound(t *testing.T) { t.Fatalf("format %q did not compile to a template", format) } for i := 0; i < 50; i++ { - var set holdSet - if got := len(expand(f.rand, tmpl, renderScope{set: &set})); got < tmpl.compiled.grow { + var draws renderDraws + if got := len(expand(f.rand, tmpl, renderScope{draws: &draws})); got < tmpl.compiled.grow { t.Errorf("format %q: expand emitted %d bytes, below grow %d", format, got, tmpl.compiled.grow) } } @@ -112,8 +112,8 @@ func TestNodeSwitchesHandleEveryKind(t *testing.T) { "paths": func(n node) { paths(n) }, "prove": func(n node) { (&valueProof{}).prove(n) }, "render": func(n node) { - var set holdSet - render(engine(1).rand, n, renderScope{set: &set, row: renderedRow{tbl, 0}}) + var draws renderDraws + render(engine(1).rand, n, renderScope{draws: &draws, row: renderedRow{tbl, 0}}) }, "renderEdges": func(n node) { renderEdges(n) }, "stepInto": func(n node) { _, _ = stepInto(n, "x") }, diff --git a/struct.go b/struct.go index f51f218..eb38a56 100644 --- a/struct.go +++ b/struct.go @@ -351,8 +351,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(state *generatorState, v reflect.Value) { if s.record != nil { - var set holdSet - for i, c := range renderRecord(state, s.record, s.columns, renderScope{set: &set}).columns { + var draws renderDraws + for i, c := range renderRecord(state, s.record, s.columns, renderScope{draws: &draws}).columns { setColumn(fieldAt(v, s.fieldIndexes[i]), c) } } diff --git a/todo.md b/todo.md index b9c9d49..f0a2a90 100644 --- a/todo.md +++ b/todo.md @@ -34,9 +34,6 @@ ranking `readSurvey.walk` and `edge` hardest and the unit they would least modif This round, in order: -- Give the expansion hold and the render's group hold two names: `groupHold` embeds - `hold`, `held`, `heldLocal` and `holdSet` share its stem, and four seats could not - tell which fence guards which. - Word the three overlap refusals apart, `checkNameHeld`'s, `checkNoOverlap`'s and `overlapError`'s: three seats grepped one message to three fences. - Compile a format in `format.go`: `linkTemplate` calls `compileFormat` in `node.go`,