Gather a render's reads as a fold memoised per node, deleting readSurvey.walk and edge
This commit is contained in:
@@ -37,8 +37,7 @@ package fejkdata
|
|||||||
// - family — a table and every table reaching it through a chain of
|
// - family — a table and every table reaching it through a chain of
|
||||||
// `table.parentT`, named by the root that chain ends at: `table.familyRoot`.
|
// `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
|
// - 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:
|
// the `table` itself, so it draws a row apart from every pin: `surveyAt.wholePins`.
|
||||||
// `surveyAt.wholeTable`, `surveyAt.wholePins`.
|
|
||||||
// - fence — a load-time check, so rendering a compiled tree cannot fail: `New`
|
// - 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
|
// runs them over the data set, `NewTemplate` and `FakeStruct` over what those
|
||||||
// compile. The draw fences are `heldCheck`, `drawFence` and `checkFamilies`.
|
// compile. The draw fences are `heldCheck`, `drawFence` and `checkFamilies`.
|
||||||
|
|||||||
+196
-88
@@ -2,11 +2,9 @@ package fejkdata
|
|||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// readSurvey gathers, at load, the references one render reads, by draw group, for check to compare.
|
// readSurvey is what one render reads, gathered at load for check to compare.
|
||||||
type readSurvey struct {
|
type readSurvey struct {
|
||||||
reads []pathRead
|
reads []pathRead
|
||||||
read map[readKey]bool
|
|
||||||
seen map[nodeVisit]bool
|
|
||||||
clash *pinClash
|
clash *pinClash
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,15 +15,14 @@ type pinClash struct {
|
|||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
// surveyAt is where a survey stands: the draw group its reads draw in, how the render's root reached it, the
|
// surveyAt is where a read sits in its render: the draw group it draws in ("" until the
|
||||||
// table rows it pinned, the table a whole read draws a row of, and the rows of whole draws whose
|
// root names one), how the render's root reached it, the rows pinned above it, and the rows
|
||||||
// cells the walk is in.
|
// of whole draws whose cells it sits in.
|
||||||
type surveyAt struct {
|
type surveyAt struct {
|
||||||
group string
|
group string
|
||||||
route surveyRoute
|
route surveyRoute
|
||||||
pins pinSet
|
pins pinSet
|
||||||
wholeTable *table
|
wholePins pinSet
|
||||||
wholePins pinSet
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// surveyRoute is how a render reaches a read: as its author spells it, and the root edge's label.
|
// surveyRoute is how a render reaches a read: as its author spells it, and the root edge's label.
|
||||||
@@ -33,17 +30,10 @@ 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.
|
||||||
type pathRead struct {
|
type pathRead struct {
|
||||||
at surveyAt
|
at surveyAt
|
||||||
a arm
|
a arm
|
||||||
tr *tableRead // set where the reference names a table
|
tr *tableRead // set where the reference names a table
|
||||||
}
|
clash error // set where its selectors clash with a row its route pinned
|
||||||
|
|
||||||
type nodeVisit struct {
|
|
||||||
n node
|
|
||||||
group string
|
|
||||||
wholeTable *table
|
|
||||||
pins string
|
|
||||||
wholePins string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type readKey struct {
|
type readKey struct {
|
||||||
@@ -51,92 +41,210 @@ type readKey struct {
|
|||||||
path string
|
path string
|
||||||
pins string
|
pins string
|
||||||
wholePins string
|
wholePins string
|
||||||
|
clash bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newReadSurvey() *readSurvey {
|
// readFold gathers, once per node, the reference reads rendering it makes.
|
||||||
return &readSurvey{read: map[readKey]bool{}, seen: map[nodeVisit]bool{}}
|
type readFold struct {
|
||||||
|
memo map[node][]pathRead
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newReadFold() *readFold { return &readFold{memo: map[node][]pathRead{}} }
|
||||||
|
|
||||||
func surveyRender(t *template) *readSurvey {
|
func surveyRender(t *template) *readSurvey {
|
||||||
s := newReadSurvey()
|
f := newReadFold()
|
||||||
for _, e := range renderEdges(t) {
|
return survey(f.rootReads(t), t.link.drawGroupKey)
|
||||||
s.edge(t, e, surveyAt{group: t.link.drawGroupKey, route: surveyRoute{e.reached(), e.label}})
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func surveyColumns(t *template, columns []string) *readSurvey {
|
func surveyColumns(t *template, columns []string) *readSurvey {
|
||||||
s := newReadSurvey()
|
f := newReadFold()
|
||||||
for _, name := range columns {
|
return survey(f.columnReads(t, columns), t.link.drawGroupKey)
|
||||||
s.walk(t.fields[name], surveyAt{group: t.link.drawGroupKey, route: surveyRoute{spelling: fmt.Sprintf("column %q", name)}})
|
}
|
||||||
|
|
||||||
|
// rootReads is what rendering t as a render of its own reads, each read named by the edge of t
|
||||||
|
// that reaches it, and in "" where no template below t names a draw group.
|
||||||
|
func (f *readFold) rootReads(t *template) []pathRead {
|
||||||
|
var reads []pathRead
|
||||||
|
for _, e := range renderEdges(t) {
|
||||||
|
reads = append(reads, f.viaEdge(t, e, surveyRoute{e.reached(), e.label})...)
|
||||||
}
|
}
|
||||||
|
return reads
|
||||||
|
}
|
||||||
|
|
||||||
|
// columnReads is what rendering t's columns as one record reads.
|
||||||
|
func (f *readFold) columnReads(t *template, columns []string) []pathRead {
|
||||||
|
var reads []pathRead
|
||||||
|
for _, name := range columns {
|
||||||
|
reads = append(reads, routed(f.reads(t.fields[name]), surveyRoute{spelling: fmt.Sprintf("column %q", name)})...)
|
||||||
|
}
|
||||||
|
return reads
|
||||||
|
}
|
||||||
|
|
||||||
|
// survey is the fold's result as a render in group: every read not inside a nested draw group
|
||||||
|
// draws in it, and one read gathered by several routes is kept once.
|
||||||
|
func survey(reads []pathRead, group string) *readSurvey {
|
||||||
|
s := &readSurvey{}
|
||||||
|
for _, r := range reads {
|
||||||
|
if r.clash != nil {
|
||||||
|
if s.clash == nil {
|
||||||
|
s.clash = &pinClash{r.at.route, r.a.spelling, r.clash}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if r.at.group == "" {
|
||||||
|
r.at.group = group
|
||||||
|
}
|
||||||
|
s.reads = append(s.reads, r)
|
||||||
|
}
|
||||||
|
s.reads = distinct(s.reads)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// walk follows what rendering n renders. A repeat renders over draws of its own, so the walk stops
|
// reads is what rendering n reads, short of a repeat, which renders over draws of its own: each
|
||||||
// there.
|
// read with the rows pinned above it inside n, and in its draw group where a template inside n
|
||||||
func (s *readSurvey) walk(n node, at surveyAt) {
|
// names one.
|
||||||
v := nodeVisit{n, at.group, at.wholeTable, at.pins.key(), at.wholePins.key()}
|
func (f *readFold) reads(n node) []pathRead {
|
||||||
if s.seen[v] {
|
if r, done := f.memo[n]; done {
|
||||||
return
|
return r
|
||||||
}
|
}
|
||||||
s.seen[v] = true
|
var out []pathRead
|
||||||
if repeats(n) {
|
switch n := n.(type) {
|
||||||
return
|
case *choice:
|
||||||
}
|
for _, it := range n.items {
|
||||||
if t, isTemplate := n.(*template); isTemplate && t.link.drawGroupKey != "" {
|
out = append(out, f.reads(it)...)
|
||||||
at.group = t.link.drawGroupKey
|
|
||||||
}
|
|
||||||
if t, isTable := n.(*table); isTable {
|
|
||||||
at.wholeTable = t
|
|
||||||
}
|
|
||||||
for _, e := range renderEdges(n) {
|
|
||||||
to, isTemplate := e.to.(*template)
|
|
||||||
var cell tableSite
|
|
||||||
if isTemplate {
|
|
||||||
cell = to.site
|
|
||||||
}
|
}
|
||||||
switch {
|
case *template:
|
||||||
case !cell.isCell():
|
if n.repeat > 1 {
|
||||||
s.edge(n, e, at)
|
break
|
||||||
case cell.table == at.wholeTable:
|
|
||||||
in := at
|
|
||||||
in.wholePins = at.wholePins.clone()
|
|
||||||
in.wholePins.add(cell.table, cell.row)
|
|
||||||
s.edge(n, e, in)
|
|
||||||
case at.pins.clash(cell.table, cell.row) == nil:
|
|
||||||
in := at
|
|
||||||
in.pins = at.pins.entered(cell.table, cell.row)
|
|
||||||
s.edge(n, e, in)
|
|
||||||
default:
|
|
||||||
// A pinned table renders its pinned row alone, and an unpinned one draws a row inside its
|
|
||||||
// nearest pinned ancestor's, so a cell of a row the pins clash with never renders on this route.
|
|
||||||
}
|
}
|
||||||
|
for _, e := range renderEdges(n) {
|
||||||
|
out = append(out, f.viaEdge(n, e, surveyRoute{})...)
|
||||||
|
}
|
||||||
|
if n.link.drawGroupKey != "" {
|
||||||
|
for i := range out {
|
||||||
|
if out[i].at.group == "" {
|
||||||
|
out[i].at.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)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
case *tableRow:
|
||||||
|
out = f.rowReads(n.t, func(p *surveyAt, row int) bool { return p.enter(n.t, row) })
|
||||||
|
case *tableColumn:
|
||||||
|
out = f.cellReads(n, func(p *surveyAt, row int) bool { return p.enter(n.t, row) })
|
||||||
}
|
}
|
||||||
|
out = distinct(out)
|
||||||
|
f.memo[n] = out
|
||||||
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// edge records the reference an edge reads, then walks on with every row the read
|
// distinct keeps one of every read gathered by several routes, so a diamond of templates
|
||||||
// pins entered, so a selected row renders only its own cells.
|
// contributes each read once however many routes reach it.
|
||||||
func (s *readSurvey) edge(from node, e renderEdge, at surveyAt) {
|
func distinct(reads []pathRead) []pathRead {
|
||||||
if e.readsRef() {
|
seen := make(map[readKey]bool, len(reads))
|
||||||
a := e.read
|
out := reads[:0:0]
|
||||||
tr := tableReadOf(from.(*template).head(a.head), a, e.to)
|
for _, r := range reads {
|
||||||
if k := (readKey{at.group, a.path, at.pins.key(), at.wholePins.key()}); !s.read[k] {
|
k := readKey{r.at.group, r.a.path, r.at.pins.key(), r.at.wholePins.key(), r.clash != nil}
|
||||||
s.read[k] = true
|
if !seen[k] {
|
||||||
s.reads = append(s.reads, pathRead{at, a, tr})
|
seen[k] = true
|
||||||
}
|
out = append(out, r)
|
||||||
if tr != nil {
|
|
||||||
pins := at.pins.clone()
|
|
||||||
if err := tr.replay(&pins); err != nil {
|
|
||||||
if s.clash == nil {
|
|
||||||
s.clash = &pinClash{at.route, a.spelling, err}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
at.pins = pins
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.walk(e.to, at)
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// viaEdge is what rendering e from n reads: the reference e reads, then what its leaf renders
|
||||||
|
// under the rows that reference pins.
|
||||||
|
func (f *readFold) viaEdge(n node, e renderEdge, route surveyRoute) []pathRead {
|
||||||
|
if !e.readsRef() {
|
||||||
|
return routed(f.reads(e.to), route)
|
||||||
|
}
|
||||||
|
a := e.read
|
||||||
|
tr := tableReadOf(n.(*template).head(a.head), a, e.to)
|
||||||
|
out := []pathRead{{at: surveyAt{route: route}, a: a, tr: tr}}
|
||||||
|
below := f.reads(e.to)
|
||||||
|
if tr != nil {
|
||||||
|
below = pinBelow(below, tr)
|
||||||
|
}
|
||||||
|
return append(out, routed(below, route)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pinBelow is the reads below a table read, once its selected rows are pinned above them: a read
|
||||||
|
// under a cell those rows keep out is dropped, since that cell never renders on this route, and
|
||||||
|
// a read whose own selectors clash with them carries that clash.
|
||||||
|
func pinBelow(reads []pathRead, tr *tableRead) []pathRead {
|
||||||
|
kept := reads[:0:0]
|
||||||
|
for _, r := range reads {
|
||||||
|
pins := r.at.pins.clone()
|
||||||
|
if err := tr.replay(&pins); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if r.tr != nil && r.clash == nil {
|
||||||
|
check := pins.clone()
|
||||||
|
r.clash = r.tr.replay(&check)
|
||||||
|
}
|
||||||
|
r.at.pins = pins
|
||||||
|
kept = append(kept, r)
|
||||||
|
}
|
||||||
|
return kept
|
||||||
|
}
|
||||||
|
|
||||||
|
// rowReads is what the rows of t read through the columns its format renders. 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 {
|
||||||
|
var out []pathRead
|
||||||
|
for _, e := range renderEdges(t.formatTemplate) {
|
||||||
|
if c, isColumn := e.to.(*tableColumn); isColumn {
|
||||||
|
out = append(out, f.cellReads(c, tag)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
var out []pathRead
|
||||||
|
for r := 0; r < c.t.rowCount(); r++ {
|
||||||
|
cell := c.t.cellTemplate(r, c.i)
|
||||||
|
if cell == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, read := range f.reads(cell) {
|
||||||
|
if tag(&read.at, r) {
|
||||||
|
out = append(out, read)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
at.pins = at.pins.entered(t, r)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// routed names the route the render's root reaches reads by, where none is named yet.
|
||||||
|
func routed(reads []pathRead, route surveyRoute) []pathRead {
|
||||||
|
if route == (surveyRoute{}) {
|
||||||
|
return reads
|
||||||
|
}
|
||||||
|
out := make([]pathRead, len(reads))
|
||||||
|
for i, r := range reads {
|
||||||
|
if r.at.route == (surveyRoute{}) {
|
||||||
|
r.at.route = route
|
||||||
|
}
|
||||||
|
out[i] = r
|
||||||
|
}
|
||||||
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// spelled names the route, and the reference it reaches a read by where its root edge is not that
|
// spelled names the route, and the reference it reaches a read by where its root edge is not that
|
||||||
|
|||||||
@@ -36,11 +36,6 @@ on 2026-09-29 a fifth time (Navigation 6.9, Locality 5.2, Shape 6.1, Self-suffic
|
|||||||
|
|
||||||
This round, in order:
|
This round, in order:
|
||||||
|
|
||||||
- Compute what rendering a node reads as a fold memoised per node, `reads(n)`, deduped
|
|
||||||
per node, and have `surveyRender` and `surveyColumns` call it, deleting
|
|
||||||
`readSurvey.walk`, `edge`, `nodeVisit` and `seen`: every run's seats named the walk
|
|
||||||
hardest. Prototyped green with the allocation gate held; without the per-node dedup
|
|
||||||
`TestADeepDiamondChainLoads` doubles its list per level.
|
|
||||||
- Have `checkDrawGroup`, `checkDraws` and `checkRecordDraws` query the fold, deleting
|
- Have `checkDrawGroup`, `checkDraws` and `checkRecordDraws` query the fold, deleting
|
||||||
`drawFence.hasRead`, its helpers and the decision "Each entry point to
|
`drawFence.hasRead`, its helpers and the decision "Each entry point to
|
||||||
`drawFence.hasRead` says what its caller gets".
|
`drawFence.hasRead` says what its caller gets".
|
||||||
|
|||||||
Reference in New Issue
Block a user