From 2b61433258f53d7636aa1ab72adcf77e60f06eac Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Tue, 29 Sep 2026 21:46:25 +0200 Subject: [PATCH] Name a read's context its group, route and branches, deleting surveyAt --- doc.go | 2 +- drawfence.go | 40 ++++++++++++++-------------- drawfence_test.go | 2 +- reads.go | 67 ++++++++++++++++++++++++----------------------- reads_test.go | 14 +++++----- table_test.go | 8 +++--- todo.md | 3 --- 7 files changed, 67 insertions(+), 69 deletions(-) diff --git a/doc.go b/doc.go index 38a3e51..a075162 100644 --- a/doc.go +++ b/doc.go @@ -37,7 +37,7 @@ package fejkdata // - family — a table and every table reaching it through a chain of // `table.parentT`, named by the root that chain ends at: `table.familyRoot`. // - whole — a read of a table with no selector and no descent, {/city}, landing on -// the `table` itself, so it draws a row apart from every pin: `surveyAt.wholePins`. +// the `table` itself, so it draws a row apart from every pin: `branches.wholePins`. // - fence — a load-time check, so rendering a compiled tree cannot fail: `New` // runs them over the data set, `NewTemplate` and `FakeStruct` over what those // compile. The draw fences are `heldCheck`, `drawFence` and `checkFamilies`. diff --git a/drawfence.go b/drawfence.go index 926ed57..bbb2b4b 100644 --- a/drawfence.go +++ b/drawfence.go @@ -34,12 +34,12 @@ func survey(reads []pathRead, group string) *readSurvey { for _, r := range reads { if r.clash != nil { if s.clash == nil { - s.clash = &pinClash{r.at.route, r.a.spelling, r.clash} + s.clash = &pinClash{r.route, r.a.spelling, r.clash} } continue } - if r.at.group == "" { - r.at.group = group + if r.group == "" { + r.group = group } s.reads = append(s.reads, r) } @@ -55,7 +55,7 @@ func (f *drawFence) checkDrawGroup(path string, n node) error { return nil } for _, r := range f.fold.rootReads(t) { - if r.at.group == "" && r.draws() { + if r.group == "" && r.draws() { return nil } } @@ -157,18 +157,18 @@ func (s *readSurvey) check() error { return fmt.Errorf("%s: %w; read the family from a template beside it, or add the value as a column", c.route.spelled(c.spelling), c.err) } sort.SliceStable(s.reads, func(i, j int) bool { - if s.reads[i].at.group != s.reads[j].at.group { - return s.reads[i].at.group < s.reads[j].at.group + if s.reads[i].group != s.reads[j].group { + return s.reads[i].group < s.reads[j].group } return s.reads[i].a.path < s.reads[j].a.path }) for i, level := range s.reads { for _, into := range s.reads[i+1:] { - if into.at.group != level.at.group || alternatives(level.at, into.at) { + if into.group != level.group || alternatives(level.branches, into.branches) { continue } if strings.HasPrefix(into.a.path, level.a.path+".") && !(level.tr != nil && level.tr.landsRow) { - return overlapError(level.at.route, level.a.spelling, into) + return overlapError(level.route, level.a.spelling, into) } } } @@ -185,7 +185,7 @@ func checkFamilies(reads []pathRead) error { continue } for _, o := range reads[:i] { - if o.tr == nil || o.at.group != r.at.group || alternatives(o.at, r.at) || o.tr.headTable.familyRoot() != r.tr.headTable.familyRoot() { + if o.tr == nil || o.group != r.group || alternatives(o.branches, r.branches) || o.tr.headTable.familyRoot() != r.tr.headTable.familyRoot() { continue } if err := checkFamilyPair(o, r); err != nil { @@ -204,7 +204,7 @@ func checkFamilyPair(a, b pathRead) error { for _, pair := range [][2]pathRead{{a, b}, {b, a}} { x, y := pair[0], pair[1] if len(x.a.tail) == 0 && len(y.a.tail) > 0 { - return overlapError(x.at.route, x.a.spelling, y) + return overlapError(x.route, x.a.spelling, y) } if drawn := x.tr.drawnOf(&y.tr.pins); drawn != nil { if s, ok := y.tr.selected(x.tr.headTable); ok && len(x.tr.sels) == 0 { @@ -213,10 +213,10 @@ func checkFamilyPair(a, b pathRead) error { tail = append([]string{x.tr.headTable.segment}, tail...) } return fmt.Errorf("%s draws %s, which %s selects a row of; write {%s.%s}, or draw them apart with a drawGroup", - x.at.route.spelled(x.a.spelling), drawn.segment, y.at.route.spelled(y.a.spelling), s.spelling, joinSegments(tail)) + x.route.spelled(x.a.spelling), drawn.segment, y.route.spelled(y.a.spelling), s.spelling, joinSegments(tail)) } return fmt.Errorf("%s draws %s, which %s selects a row of; select that row in both, or draw them apart with a drawGroup", - x.at.route.spelled(x.a.spelling), drawn.segment, y.at.route.spelled(y.a.spelling)) + x.route.spelled(x.a.spelling), drawn.segment, y.route.spelled(y.a.spelling)) } } return nil @@ -232,7 +232,7 @@ func replayPairs(reads []pathRead) error { continue } for _, o := range reads[i+1:] { - if o.tr == nil || o.at.group != r.at.group || alternatives(r.at, o.at) { + if o.tr == nil || o.group != r.group || alternatives(r.branches, o.branches) { continue } d := r.tr.pins.clone() @@ -245,24 +245,24 @@ func replayPairs(reads []pathRead) error { } func conflict(r pathRead, err error) error { - return fmt.Errorf("%s: %w; select the same rows in every path into the family, or draw them apart with a drawGroup", r.at.route.spelled(r.a.spelling), err) + return fmt.Errorf("%s: %w; select the same rows in every path into the family, or draw them apart with a drawGroup", r.route.spelled(r.a.spelling), err) } // alternatives reports whether two reads never render together: one read renders one row of a // table, so reads under different rows the walks pinned, or under different rows of one whole // draw, never meet. -func alternatives(a, b surveyAt) bool { +func alternatives(a, b branches) bool { return a.pins.differs(&b.pins) || a.wholePins.differs(&b.wholePins) } func overlapError(route surveyRoute, ref string, into pathRead) error { apart := "" switch { - case route.spelling == into.at.route.spelling: - case isRef(into.at.route.label): - apart = fmt.Sprintf(", or move {%s} into a field with a drawGroup", into.at.route.label) + case route.spelling == into.route.spelling: + case isRef(into.route.label): + apart = fmt.Sprintf(", or move {%s} into a field with a drawGroup", into.route.label) default: - apart = fmt.Sprintf(", or give %s a drawGroup", into.at.route.spelling) + apart = fmt.Sprintf(", or give %s a drawGroup", into.route.spelling) } - return fmt.Errorf("%s renders its own draw of what %s reads a path through; name the fields you want instead%s", route.spelled(ref), into.at.route.spelled(into.a.spelling), apart) + return fmt.Errorf("%s renders its own draw of what %s reads a path through; name the fields you want instead%s", route.spelled(ref), into.route.spelled(into.a.spelling), apart) } diff --git a/drawfence_test.go b/drawfence_test.go index bed94b0..4916102 100644 --- a/drawfence_test.go +++ b/drawfence_test.go @@ -153,7 +153,7 @@ func wantPairsFindAll(t *testing.T, label string, reads []pathRead) int { // eachRenderSet calls fn with every maximal set of reads that render together, by Bron–Kerbosch. func eachRenderSet(reads []pathRead, fn func([]pathRead)) { - together := func(a, b pathRead) bool { return a.at.group == b.at.group && !alternatives(a.at, b.at) } + together := func(a, b pathRead) bool { return a.group == b.group && !alternatives(a.branches, b.branches) } var grow func(set, candidates, excluded []pathRead) grow = func(set, candidates, excluded []pathRead) { if len(candidates) == 0 && len(excluded) == 0 { diff --git a/reads.go b/reads.go index d56e33d..89bdb97 100644 --- a/reads.go +++ b/reads.go @@ -2,25 +2,26 @@ package fejkdata import "fmt" -// surveyAt is where a read sits in its render: the draw group it draws in ("" until the -// root names one), how the render's root reached it, the rows pinned above it, and the rows -// of whole draws whose cells it sits in. -type surveyAt struct { - group string - route surveyRoute +// surveyRoute is how a render reaches a read: as its author spells it, and the root edge's label. +type surveyRoute struct{ spelling, label string } + +// branches is the rows a read sits under: those pinned above it, and those of whole draws whose +// cells it sits in. +type branches struct { pins pinSet wholePins pinSet } -// surveyRoute is how a render reaches a read: as its author spells it, and the root edge's label. -type surveyRoute struct{ spelling, label string } - -// pathRead is one reference a render reads: a path, or a bare reference with no tail. +// pathRead is one reference a render reads, a path or a bare reference with no tail: in its draw +// group ("" until the root names one), by the route the render's root reached it, under its +// branches. type pathRead struct { - at surveyAt - a arm - tr *tableRead // set where the reference names a table - clash error // set where its selectors clash with a row its route pinned + group string + route surveyRoute + branches branches + a arm + tr *tableRead // set where the reference names a table + clash error // set where its selectors clash with a row its route pinned } type readKey struct { @@ -79,21 +80,21 @@ func (f *readFold) reads(n node) []pathRead { } if n.link.drawGroupKey != "" { for i := range out { - if out[i].at.group == "" { - out[i].at.group = n.link.drawGroupKey + if out[i].group == "" { + out[i].group = n.link.drawGroupKey } } } case *table: - out = f.rowReads(n, func(p *surveyAt, row int) bool { - p.wholePins = p.wholePins.clone() - p.wholePins.add(n, row) + out = f.rowReads(n, func(b *branches, row int) bool { + b.wholePins = b.wholePins.clone() + b.wholePins.add(n, row) return true }) case *tableRow: - out = f.rowReads(n.t, func(p *surveyAt, row int) bool { return p.enter(n.t, row) }) + out = f.rowReads(n.t, func(b *branches, row int) bool { return b.enter(n.t, row) }) case *tableColumn: - out = f.cellReads(n, func(p *surveyAt, row int) bool { return p.enter(n.t, row) }) + out = f.cellReads(n, func(b *branches, row int) bool { return b.enter(n.t, row) }) } out = distinct(out) f.memo[n] = out @@ -106,7 +107,7 @@ func distinct(reads []pathRead) []pathRead { seen := make(map[readKey]bool, len(reads)) out := reads[:0:0] for _, r := range reads { - k := readKey{r.at.group, r.a.path, r.at.pins.key(), r.at.wholePins.key(), r.clash != nil} + k := readKey{r.group, r.a.path, r.branches.pins.key(), r.branches.wholePins.key(), r.clash != nil} if !seen[k] { seen[k] = true out = append(out, r) @@ -123,7 +124,7 @@ func (f *readFold) viaEdge(n node, e renderEdge, route surveyRoute) []pathRead { } a := e.read tr := tableReadOf(n.(*template).head(a.head), a, e.to) - out := []pathRead{{at: surveyAt{route: route}, a: a, tr: tr}} + out := []pathRead{{route: route, a: a, tr: tr}} below := f.reads(e.to) if tr != nil { below = pinBelow(below, tr) @@ -137,7 +138,7 @@ func (f *readFold) viaEdge(n node, e renderEdge, route surveyRoute) []pathRead { func pinBelow(reads []pathRead, tr *tableRead) []pathRead { kept := reads[:0:0] for _, r := range reads { - pins := r.at.pins.clone() + pins := r.branches.pins.clone() if err := tr.replay(&pins); err != nil { continue } @@ -145,7 +146,7 @@ func pinBelow(reads []pathRead, tr *tableRead) []pathRead { check := pins.clone() r.clash = r.tr.replay(&check) } - r.at.pins = pins + r.branches.pins = pins kept = append(kept, r) } return kept @@ -154,7 +155,7 @@ func pinBelow(reads []pathRead, tr *tableRead) []pathRead { // rowReads is what the rows of t read: the cells of the columns its format renders, each tagged // with its row, and the format's other reads, which every row shares. A format reaching its own // cells through another category is checkOwnFamily's refusal, which runs first. -func (f *readFold) rowReads(t *table, tag func(*surveyAt, int) bool) []pathRead { +func (f *readFold) rowReads(t *table, tag func(*branches, int) bool) []pathRead { var out []pathRead for _, e := range renderEdges(t.formatTemplate) { if c, isColumn := e.to.(*tableColumn); isColumn && !e.readsRef() { @@ -167,7 +168,7 @@ func (f *readFold) rowReads(t *table, tag func(*surveyAt, int) bool) []pathRead } // cellReads is what the cells of column c read, each tagged with its row. -func (f *readFold) cellReads(c *tableColumn, tag func(*surveyAt, int) bool) []pathRead { +func (f *readFold) cellReads(c *tableColumn, tag func(*branches, int) bool) []pathRead { var out []pathRead for r := 0; r < c.t.rowCount(); r++ { cell := c.t.cellTemplate(r, c.i) @@ -175,7 +176,7 @@ func (f *readFold) cellReads(c *tableColumn, tag func(*surveyAt, int) bool) []pa continue } for _, read := range f.reads(cell) { - if tag(&read.at, r) { + if tag(&read.branches, r) { out = append(out, read) } } @@ -184,11 +185,11 @@ func (f *readFold) cellReads(c *tableColumn, tag func(*surveyAt, int) bool) []pa } // enter pins row r of t above a read, or reports that the rows already pinned keep it out. -func (at *surveyAt) enter(t *table, r int) bool { - if at.pins.clash(t, r) != nil { +func (b *branches) enter(t *table, r int) bool { + if b.pins.clash(t, r) != nil { return false } - at.pins = at.pins.entered(t, r) + b.pins = b.pins.entered(t, r) return true } @@ -199,8 +200,8 @@ func routed(reads []pathRead, route surveyRoute) []pathRead { } out := make([]pathRead, len(reads)) for i, r := range reads { - if r.at.route == (surveyRoute{}) { - r.at.route = route + if r.route == (surveyRoute{}) { + r.route = route } out[i] = r } diff --git a/reads_test.go b/reads_test.go index 3b33c7e..2d0ffe7 100644 --- a/reads_test.go +++ b/reads_test.go @@ -39,9 +39,9 @@ func wantGathered(t *testing.T, label string, gathered []pathRead, renderWith fu if !isRef(a.head) { return } - r := pathRead{at: surveyAt{group: group}, a: a} + r := pathRead{group: group, a: a} if row.t != nil { - r.at.pins.add(row.t, row.index) + r.branches.pins.add(row.t, row.index) } if k := (traceKey{group, a.path, row}); !seen[k] { seen[k] = true @@ -53,7 +53,7 @@ func wantGathered(t *testing.T, label string, gathered []pathRead, renderWith fu } for _, r := range reads { if !gathers(gathered, r) { - t.Errorf("%s: the render read {%s} in draw group %q from %s, and readSurvey gathered no such read", label, r.a.spelling, r.at.group, spellPins(&r.at.pins)) + t.Errorf("%s: the render read {%s} in draw group %q from %s, and the fold gathered no such read", label, r.a.spelling, r.group, spellPins(&r.branches.pins)) } } return len(reads) @@ -61,7 +61,7 @@ func wantGathered(t *testing.T, label string, gathered []pathRead, renderWith fu func gathers(gathered []pathRead, r pathRead) bool { for _, g := range gathered { - if g.at.group == r.at.group && g.a.path == r.a.path && !g.at.pins.differs(&r.at.pins) && !g.at.wholePins.differs(&r.at.pins) { + if g.group == r.group && g.a.path == r.a.path && !g.branches.pins.differs(&r.branches.pins) && !g.branches.wholePins.differs(&r.branches.pins) { return true } } @@ -106,7 +106,7 @@ func renderRecordRoot(s *generatorState, t *template, columns []Column, trace re renderRecord(s, t, columns, sc) } -func TestReadSurveyWalksACellByEveryRoute(t *testing.T) { +func TestReadFoldWalksACellByEveryRoute(t *testing.T) { fsys := fstest.MapFS{ "g.json": {Data: []byte(`{"format":"{/x.v}","drawGroup":"g2"}`)}, "r.json": {Data: []byte(`"{/g} {/x}"`)}, @@ -130,8 +130,8 @@ func TestReadSurveyWalksACellByEveryRoute(t *testing.T) { if read.a.path != "/z" { continue } - pinned = pinned || read.at.pins.used > 0 - whole = whole || read.at.wholePins.used > 0 + pinned = pinned || read.branches.pins.used > 0 + whole = whole || read.branches.wholePins.used > 0 } if !pinned || !whole { t.Errorf("{/z} gathered under a pinned row %v and a whole read's row %v, want both: {/g} reaches x's cells from the root, and {/x} renders them whole", pinned, whole) diff --git a/table_test.go b/table_test.go index 9cce34e..779c127 100644 --- a/table_test.go +++ b/table_test.go @@ -1005,7 +1005,7 @@ func TestEnteredRowsAgreeWithPinning(t *testing.T) { var none pinSet for _, entered := range tables { for row := 0; row < entered.rowCount(); row++ { - whole := surveyAt{} + whole := branches{} whole.wholePins.add(entered, row) s := none.entered(entered, row) for _, cand := range tables { @@ -1018,14 +1018,14 @@ func TestEnteredRowsAgreeWithPinning(t *testing.T) { for cr := 0; cr < cand.rowCount(); cr++ { beside := s.clone() refused := beside.pinRow(cand, cr) != nil - candAt := surveyAt{pins: none.entered(cand, cr)} - if got := alternatives(surveyAt{pins: s}, candAt); got != refused { + candAt := branches{pins: none.entered(cand, cr)} + if got := alternatives(branches{pins: s}, candAt); got != refused { t.Errorf("alternatives(%s, %s) = %v, but pinning both refuses = %v", entered.selectorSpelling(row), cand.selectorSpelling(cr), got, refused) } if alternatives(whole, candAt) || alternatives(candAt, whole) { t.Errorf("%s rendered whole is an alternative to %s pinned, though the whole draw ignores the pin", entered.selectorSpelling(row), cand.selectorSpelling(cr)) } - other := surveyAt{} + other := branches{} other.wholePins.add(cand, cr) if want := cand == entered && cr != row; alternatives(whole, other) != want { t.Errorf("alternatives(%s, %s), both rendered whole, = %v, want %v", entered.selectorSpelling(row), cand.selectorSpelling(cr), !want, want) diff --git a/todo.md b/todo.md index 59efc5e..88c46b7 100644 --- a/todo.md +++ b/todo.md @@ -36,9 +36,6 @@ on 2026-09-29 a fifth time (Navigation 6.9, Locality 5.2, Shape 6.1, Self-suffic This round, in order: -- Move the fold to `reads.go` and the verdicts to `drawfence.go`, deleting - `readsurvey.go`; name a read's context its `group`, `route` and `branches`, deleting - `surveyAt`, and let the README's Layout and `doc.go` follow. - Name what a read sits under `branches`, the rows pinned with their ancestors and the rows of whole draws without, both load-bearing, and fold `check`, `checkFamilies`, `replayPairs`, `checkFamilyPair` and `alternatives` into one pairwise loop over