Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
package fejkdata
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// drawSet is one render's reference draws: the unnamed draw group's, and each named one's.
|
||||
type drawSet struct {
|
||||
unnamed draws
|
||||
named map[string]*draws
|
||||
}
|
||||
|
||||
// drawScope is where a render reads its reference paths: a draw set, in the draw group of the
|
||||
// template rendering, and the row a table's format is rendering, which its columns read.
|
||||
type drawScope struct {
|
||||
set *drawSet
|
||||
group string
|
||||
t *table
|
||||
row int
|
||||
}
|
||||
|
||||
// newDrawSet makes the unnamed group's maps where the set is declared, keeping them on that frame's
|
||||
// stack for a render that reads through them; a zero drawSet makes them on its first read instead.
|
||||
func newDrawSet(s *session) drawSet {
|
||||
return drawSet{unnamed: draws{variant: map[string]node{}, value: map[string]draw{}, s: s}}
|
||||
}
|
||||
|
||||
// renderOnce renders n as one render, over draws of its own.
|
||||
func renderOnce(s *session, n node) string {
|
||||
set := drawSet{unnamed: draws{s: s}}
|
||||
return render(s, n, drawScope{set: &set})
|
||||
}
|
||||
|
||||
// in is the scope t renders in: its draw group where it names one, else its caller's.
|
||||
func (sc drawScope) in(t *template) drawScope {
|
||||
if t.drawGroupKey != "" {
|
||||
sc.group = t.drawGroupKey
|
||||
}
|
||||
return sc
|
||||
}
|
||||
|
||||
// draws is the set's draws for sc's draw group. The session is passed in rather than
|
||||
// read out of the set: copying it from there would leak the set's maps to the heap.
|
||||
func (sc drawScope) draws(s *session) *draws {
|
||||
if sc.group == "" {
|
||||
return &sc.set.unnamed
|
||||
}
|
||||
d, drew := sc.set.named[sc.group]
|
||||
if !drew {
|
||||
if sc.set.named == nil {
|
||||
sc.set.named = map[string]*draws{}
|
||||
}
|
||||
d = &draws{variant: map[string]node{}, value: map[string]draw{}, s: s}
|
||||
sc.set.named[strings.Clone(sc.group)] = d // a key from sc would leak sc, and with it every render's draw set, to the heap
|
||||
}
|
||||
return d
|
||||
}
|
||||
+8
-8
@@ -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})
|
||||
}
|
||||
|
||||
@@ -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] {
|
||||
|
||||
+1
-1
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+1
-1
@@ -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"})
|
||||
})
|
||||
}
|
||||
|
||||
+3
-3
@@ -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
|
||||
}
|
||||
|
||||
+57
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)),
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+2
-2
@@ -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 {
|
||||
|
||||
+1
-1
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user