Name the render's draws apart from the expansion hold: drawMemo, groupDraws, renderDraws
This commit is contained in:
@@ -57,7 +57,7 @@ In [docs/decisions.md](docs/decisions.md):
|
|||||||
- A render shares one reference draw per category, per group
|
- A render shares one reference draw per category, per group
|
||||||
- A draw group name is local to its category
|
- A draw group name is local to its category
|
||||||
- The expansion hold and the render's draws are two fences
|
- 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 category never references itself, and a record's fences run at load
|
||||||
- A record's column set is fixed before the first draw
|
- A record's column set is fixed before the first draw
|
||||||
- Null is a `null` item, not a rate
|
- Null is a `null` item, not a rate
|
||||||
|
|||||||
@@ -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
|
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
|
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
|
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
|
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
|
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
|
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
|
||||||
|
|||||||
@@ -19,15 +19,17 @@ package fejkdata
|
|||||||
// - draw — one pick from a choice, or one row taken from a table: `pick`,
|
// - draw — one pick from a choice, or one row taken from a table: `pick`,
|
||||||
// `resolveChoice`, `table.drawRow`.
|
// `resolveChoice`, `table.drawRow`.
|
||||||
// - expansion — one render of one format: `expand`.
|
// - 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`
|
// `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 — one expansion keeping one draw of a name, so every route to it reads
|
||||||
// `formatOps.held`, `hold`, `groupHold`, `readHeld`.
|
// that value: `formatOps.held`, `formatOps.keepsHold`, `heldCheck`.
|
||||||
// - draw group — reference draws held apart inside one render:
|
// - 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
|
// `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:
|
// - pin — fixing which row of a table the render uses, which the draw fences replay:
|
||||||
// `pinSet`, `pinSet.pin`, `table.drawIn`.
|
// `pinSet`, `pinSet.pin`, `table.drawIn`.
|
||||||
// - family — a table and every table reaching it through a chain of
|
// - family — a table and every table reaching it through a chain of
|
||||||
|
|||||||
+5
-5
@@ -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
|
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.
|
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
|
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 set is declared kept a
|
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 set lives on the `Generator`, so there it
|
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.
|
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
|
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.
|
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
|
## 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.
|
a read drew.
|
||||||
|
|
||||||
## One name, one meaning
|
## One name, one meaning
|
||||||
|
|||||||
+20
-20
@@ -2,18 +2,18 @@ package fejkdata
|
|||||||
|
|
||||||
import "strings"
|
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
|
// 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.
|
// same read written twice reads one value.
|
||||||
type hold struct {
|
type drawMemo struct {
|
||||||
variant map[string]node
|
variant map[string]node
|
||||||
value map[string]readValue
|
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.
|
// table rows those paths pinned.
|
||||||
type groupHold struct {
|
type groupDraws struct {
|
||||||
hold
|
memo drawMemo
|
||||||
pins pinSet
|
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.
|
// 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)
|
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
|
// renderDraws is one render's reference draws: the unnamed draw group's, and each named one's;
|
||||||
// test's trace of its reads.
|
// and a test's trace of its reads.
|
||||||
type holdSet struct {
|
type renderDraws struct {
|
||||||
unnamed groupHold
|
unnamed groupDraws
|
||||||
named map[string]*groupHold
|
named map[string]*groupDraws
|
||||||
trace renderTrace
|
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
|
// 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
|
// 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 {
|
type renderScope struct {
|
||||||
set *holdSet
|
draws *renderDraws
|
||||||
group string
|
group string
|
||||||
row renderedRow
|
row renderedRow
|
||||||
}
|
}
|
||||||
@@ -77,17 +77,17 @@ func (sc renderScope) rowOf(t *table) int {
|
|||||||
return sc.row.index
|
return sc.row.index
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc renderScope) groupHold() *groupHold {
|
func (sc renderScope) groupDraws() *groupDraws {
|
||||||
if sc.group == "" {
|
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 !drew {
|
||||||
if sc.set.named == nil {
|
if sc.draws.named == nil {
|
||||||
sc.set.named = map[string]*groupHold{}
|
sc.draws.named = map[string]*groupDraws{}
|
||||||
}
|
}
|
||||||
d = &groupHold{}
|
d = &groupDraws{}
|
||||||
sc.set.named[strings.Clone(sc.group)] = d
|
sc.draws.named[strings.Clone(sc.group)] = d
|
||||||
}
|
}
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
@@ -386,7 +386,7 @@ type formatOps struct {
|
|||||||
ops []op
|
ops []op
|
||||||
grow int
|
grow int
|
||||||
held map[string]firstReach
|
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
|
// 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{}
|
c.held = map[string]firstReach{}
|
||||||
}
|
}
|
||||||
if !isRef(a.head) || len(a.tail) == 0 {
|
if !isRef(a.head) || len(a.tail) == 0 {
|
||||||
c.heldLocal = true
|
c.keepsHold = true
|
||||||
}
|
}
|
||||||
h, seen := c.held[a.head]
|
h, seen := c.held[a.head]
|
||||||
if !seen {
|
if !seen {
|
||||||
|
|||||||
+1
-1
@@ -771,7 +771,7 @@ func TestReadFieldPanicsOnAPathItCannotHold(t *testing.T) {
|
|||||||
t.Fatal("not a template")
|
t.Fatal("not a template")
|
||||||
}
|
}
|
||||||
mustPanic(t, name, func() {
|
mustPanic(t, name, func() {
|
||||||
readField(engine(1).rand, tm, nil, renderScope{set: &holdSet{}}, a)
|
readField(engine(1).rand, tm, nil, renderScope{draws: &renderDraws{}}, a)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
// 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
|
// for a sibling path, which never crosses a table, since a table is only a category. For a memoized
|
||||||
// read, held keeps the variant drawn at each level of a, so paths sharing a prefix share it.
|
// 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
|
// 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.
|
// as pathCheck does and draws the row the route asks for.
|
||||||
type pathDraw struct {
|
type pathDraw struct {
|
||||||
s *generatorState
|
s *generatorState
|
||||||
pins *pinSet
|
pins *pinSet
|
||||||
held *hold
|
memo *drawMemo
|
||||||
a *arm
|
a *arm
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,20 +400,20 @@ func (w *pathDraw) walk(n node, tail []string) (node, error) {
|
|||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// variant is the variant of c the walk continues into: the one held for this level
|
// 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 holds its draws; else one drawn afresh.
|
// of the read, drawn once, where the walk has a memo; else one drawn afresh.
|
||||||
func (w *pathDraw) variant(c *choice, rest []string) node {
|
func (w *pathDraw) variant(c *choice, rest []string) node {
|
||||||
if w.held == nil {
|
if w.memo == nil {
|
||||||
return pick(w.s, c)
|
return pick(w.s, c)
|
||||||
}
|
}
|
||||||
key := w.a.levels[len(w.a.tail)-len(rest)]
|
key := w.a.levels[len(w.a.tail)-len(rest)]
|
||||||
n, drew := w.held.variant[key]
|
n, drew := w.memo.variant[key]
|
||||||
if !drew {
|
if !drew {
|
||||||
n = resolveChoice(w.s, c)
|
n = resolveChoice(w.s, c)
|
||||||
if w.held.variant == nil {
|
if w.memo.variant == nil {
|
||||||
w.held.variant = map[string]node{}
|
w.memo.variant = map[string]node{}
|
||||||
}
|
}
|
||||||
w.held.variant[key] = n
|
w.memo.variant[key] = n
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -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) {
|
func TestNoReferenceAllocRegression(t *testing.T) {
|
||||||
word := `{"format":"{w}","w":["alpha","beta","gamma","delta"]}`
|
word := `{"format":"{w}","w":["alpha","beta","gamma","delta"]}`
|
||||||
for _, s := range []struct {
|
for _, s := range []struct {
|
||||||
@@ -73,12 +73,12 @@ func TestNoReferenceAllocRegression(t *testing.T) {
|
|||||||
t.Fatalf("New(%s): %v", s.name, err)
|
t.Fatalf("New(%s): %v", s.name, err)
|
||||||
}
|
}
|
||||||
if allocs := testing.AllocsPerRun(10000, func() { f.Fake("x") }); allocs > s.base*1.10 {
|
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) {
|
func TestNoTableAllocRegression(t *testing.T) {
|
||||||
f, err := New(WithoutShippedData(), WithDataFS(fstest.MapFS{
|
f, err := New(WithoutShippedData(), WithDataFS(fstest.MapFS{
|
||||||
"region.json": {Data: []byte(`{"format":"{name}","rows":"region.tsv","key":"code","weight":"population"}`)},
|
"region.json": {Data: []byte(`{"format":"{name}","rows":"region.tsv","key":"code","weight":"population"}`)},
|
||||||
|
|||||||
+9
-9
@@ -77,17 +77,17 @@ func spellPins(p *pinSet) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func renderRoot(s *generatorState, t *template, trace renderTrace) {
|
func renderRoot(s *generatorState, t *template, trace renderTrace) {
|
||||||
set := holdSet{trace: trace}
|
draws := renderDraws{trace: trace}
|
||||||
sc := renderScope{set: &set}
|
sc := renderScope{draws: &draws}
|
||||||
switch {
|
switch {
|
||||||
case t.site.row == formatRow:
|
case t.site.row == formatRow:
|
||||||
tbl := t.site.table
|
tbl := t.site.table
|
||||||
render(s, tbl, sc)
|
render(s, tbl, sc)
|
||||||
for r := 0; r < tbl.rowCount(); r++ {
|
for r := 0; r < tbl.rowCount(); r++ {
|
||||||
set := holdSet{trace: trace}
|
draws := renderDraws{trace: trace}
|
||||||
sc := renderScope{set: &set}
|
sc := renderScope{draws: &draws}
|
||||||
sc.groupHold().pins.pin(tbl, r)
|
sc.groupDraws().pins.pin(tbl, r)
|
||||||
render(s, tbl.rowNode, sc.at(tbl.rowNode, &sc.groupHold().pins))
|
render(s, tbl.rowNode, sc.at(tbl.rowNode, &sc.groupDraws().pins))
|
||||||
}
|
}
|
||||||
case t.repeat > 1:
|
case t.repeat > 1:
|
||||||
expand(s, t, sc)
|
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) {
|
func renderRecordRoot(s *generatorState, t *template, columns []Column, trace renderTrace) {
|
||||||
set := holdSet{trace: trace}
|
draws := renderDraws{trace: trace}
|
||||||
sc := renderScope{set: &set}
|
sc := renderScope{draws: &draws}
|
||||||
if t.site.row == formatRow {
|
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)
|
renderRecord(s, t, columns, sc)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
var set holdSet
|
var draws renderDraws
|
||||||
sc := renderScope{set: &set}
|
sc := renderScope{draws: &draws}
|
||||||
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)
|
||||||
@@ -159,7 +159,7 @@ func tableRecord(s *generatorState, t *table, tail []string, sc renderScope) (no
|
|||||||
}
|
}
|
||||||
switch n := n.(type) {
|
switch n := n.(type) {
|
||||||
case *table:
|
case *table:
|
||||||
n.drawIn(s, &sc.groupHold().pins)
|
n.drawIn(s, &sc.groupDraws().pins)
|
||||||
return n, nil
|
return n, nil
|
||||||
case *tableRow:
|
case *tableRow:
|
||||||
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()
|
||||||
var set holdSet
|
var draws renderDraws
|
||||||
return renderRecord(t.g.rand, t.template, t.columns, renderScope{set: &set})
|
return renderRecord(t.g.rand, t.template, t.columns, renderScope{draws: &draws})
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRecordTemplate compiles an inline record — a JSON object with a format and
|
// 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 {
|
func renderRecord(s *generatorState, t *template, columns []Column, sc renderScope) *Record {
|
||||||
sc = sc.in(t)
|
sc = sc.in(t)
|
||||||
if t.site.row == formatRow {
|
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...)}
|
r := &Record{columns: append([]Column(nil), columns...)}
|
||||||
for i := range r.columns {
|
for i := range r.columns {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// rng is the randomness a builtin sample draws from; *rand.Rand satisfies it. The
|
// 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.
|
// drawing through it off the heap.
|
||||||
type rng interface {
|
type rng interface {
|
||||||
IntN(n int) int
|
IntN(n int) int
|
||||||
@@ -24,8 +24,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)
|
||||||
}
|
}
|
||||||
var set holdSet
|
var draws renderDraws
|
||||||
sc := renderScope{set: &set}
|
sc := renderScope{draws: &draws}
|
||||||
n, err := descend(f.rand, &f.root, segments, sc)
|
n, err := descend(f.rand, &f.root, segments, sc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("fejkdata: %s: %w", path, err)
|
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 {
|
if _, ok := n.(*folder); ok {
|
||||||
return "", fmt.Errorf("fejkdata: %s names a folder, not a value", path)
|
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
|
// 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 {
|
if _, err := (&pathProbe{}).walk(root, segments); err != nil {
|
||||||
return nil, err
|
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 {
|
func renderOnce(s *generatorState, n node) string {
|
||||||
var set holdSet
|
var draws renderDraws
|
||||||
return render(s, n, renderScope{set: &set})
|
return render(s, n, renderScope{draws: &draws})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 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.
|
// 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 {
|
func render(s *generatorState, n node, sc renderScope) string {
|
||||||
switch n := n.(type) {
|
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
|
// 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
|
//go:noinline
|
||||||
func expandAnew(s *generatorState, t *template) string {
|
func expandAnew(s *generatorState, t *template) string {
|
||||||
var set holdSet
|
var draws renderDraws
|
||||||
return expand(s, t, renderScope{set: &set})
|
return expand(s, t, renderScope{draws: &draws})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@@ -119,10 +119,10 @@ func expand(s *generatorState, t *template, sc renderScope) string {
|
|||||||
b.Grow(t.compiled.grow)
|
b.Grow(t.compiled.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 hold in sc instead.
|
// path reads its draw group's memo in sc instead.
|
||||||
var held *hold
|
var hold *drawMemo
|
||||||
if t.compiled.heldLocal {
|
if t.compiled.keepsHold {
|
||||||
held = &hold{
|
hold = &drawMemo{
|
||||||
variant: make(map[string]node, len(t.compiled.held)),
|
variant: make(map[string]node, len(t.compiled.held)),
|
||||||
value: make(map[string]readValue, 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:
|
case literalRun:
|
||||||
b.WriteString(o.lit)
|
b.WriteString(o.lit)
|
||||||
case nameRead:
|
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:
|
case builtinCall:
|
||||||
// Read before the call, so the value a calc computes is the value the
|
// Read before the call, so the value a calc computes is the value the
|
||||||
// format showed. calcVars fixed the order op.operands holds.
|
// 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 {
|
if len(o.operands) > 0 {
|
||||||
operands = make([]string, len(o.operands))
|
operands = make([]string, len(o.operands))
|
||||||
for j, a := range 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
|
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
|
// 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
|
// 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
|
// 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.
|
// 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 {
|
if isRef(a.head) && len(a.tail) > 0 {
|
||||||
return readReference(s, t, sc, a)
|
return readReference(s, t, sc, a)
|
||||||
}
|
}
|
||||||
if sc.set.trace != nil {
|
if sc.draws.trace != nil {
|
||||||
traceRead(sc.set.trace, t, sc, a)
|
traceRead(sc.draws.trace, t, sc, a)
|
||||||
}
|
}
|
||||||
if _, expansionHolds := t.compiled.held[a.head]; !expansionHolds {
|
if _, expansionHolds := t.compiled.held[a.head]; !expansionHolds {
|
||||||
if len(a.tail) > 0 {
|
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)}
|
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))
|
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.
|
// spans the render.
|
||||||
func readReference(s *generatorState, t *template, sc renderScope, a arm) readValue {
|
func readReference(s *generatorState, t *template, sc renderScope, a arm) readValue {
|
||||||
if sc.set.trace != nil {
|
if sc.draws.trace != nil {
|
||||||
traceRead(sc.set.trace, t, sc, a)
|
traceRead(sc.draws.trace, t, sc, a)
|
||||||
}
|
}
|
||||||
group := sc.groupHold()
|
group := sc.groupDraws()
|
||||||
return readHeld(s, t, &group.hold, &group.pins, sc, a)
|
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 {
|
func readMemo(s *generatorState, t *template, memo *drawMemo, pins *pinSet, sc renderScope, a arm) readValue {
|
||||||
if r, done := held.value[a.path]; done {
|
if r, done := memo.value[a.path]; done {
|
||||||
return r
|
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 {
|
if pins != nil {
|
||||||
sc = sc.at(leaf, pins)
|
sc = sc.at(leaf, pins)
|
||||||
}
|
}
|
||||||
r := renderLeaf(s, leaf, sc)
|
r := renderLeaf(s, leaf, sc)
|
||||||
if held.value == nil {
|
if memo.value == nil {
|
||||||
held.value = map[string]readValue{}
|
memo.value = map[string]readValue{}
|
||||||
}
|
}
|
||||||
held.value[a.path] = r
|
memo.value[a.path] = r
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -42,8 +42,8 @@ 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++ {
|
||||||
var set holdSet
|
var draws renderDraws
|
||||||
if got := len(expand(f.rand, tmpl, renderScope{set: &set})); got < tmpl.compiled.grow {
|
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)
|
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) },
|
"paths": func(n node) { paths(n) },
|
||||||
"prove": func(n node) { (&valueProof{}).prove(n) },
|
"prove": func(n node) { (&valueProof{}).prove(n) },
|
||||||
"render": func(n node) {
|
"render": func(n node) {
|
||||||
var set holdSet
|
var draws renderDraws
|
||||||
render(engine(1).rand, n, renderScope{set: &set, row: renderedRow{tbl, 0}})
|
render(engine(1).rand, n, renderScope{draws: &draws, row: renderedRow{tbl, 0}})
|
||||||
},
|
},
|
||||||
"renderEdges": func(n node) { renderEdges(n) },
|
"renderEdges": func(n node) { renderEdges(n) },
|
||||||
"stepInto": func(n node) { _, _ = stepInto(n, "x") },
|
"stepInto": func(n node) { _, _ = stepInto(n, "x") },
|
||||||
|
|||||||
@@ -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.
|
// 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) {
|
func (s *structShape) fill(state *generatorState, v reflect.Value) {
|
||||||
if s.record != nil {
|
if s.record != nil {
|
||||||
var set holdSet
|
var draws renderDraws
|
||||||
for i, c := range renderRecord(state, s.record, s.columns, renderScope{set: &set}).columns {
|
for i, c := range renderRecord(state, s.record, s.columns, renderScope{draws: &draws}).columns {
|
||||||
setColumn(fieldAt(v, s.fieldIndexes[i]), c)
|
setColumn(fieldAt(v, s.fieldIndexes[i]), c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,9 +34,6 @@ ranking `readSurvey.walk` and `edge` hardest and the unit they would least modif
|
|||||||
|
|
||||||
This round, in order:
|
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
|
- Word the three overlap refusals apart, `checkNameHeld`'s, `checkNoOverlap`'s and
|
||||||
`overlapError`'s: three seats grepped one message to three fences.
|
`overlapError`'s: three seats grepped one message to three fences.
|
||||||
- Compile a format in `format.go`: `linkTemplate` calls `compileFormat` in `node.go`,
|
- Compile a format in `format.go`: `linkTemplate` calls `compileFormat` in `node.go`,
|
||||||
|
|||||||
Reference in New Issue
Block a user