Files
fejkdata/reads.go
T

285 lines
8.2 KiB
Go

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
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.
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
}
type readKey struct {
group string
path string
pins string
wholePins string
clash bool
}
// readFold gathers, once per node, the reference reads rendering it makes.
type readFold struct {
memo map[node][]pathRead
}
func newReadFold() *readFold { return &readFold{memo: map[node][]pathRead{}} }
// 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
}
// reads is what rendering n reads, short of a repeat, which renders over draws of its own: each
// read with the rows pinned above it inside n, and in its draw group where a template inside n
// names one.
func (f *readFold) reads(n node) []pathRead {
if r, done := f.memo[n]; done {
return r
}
var out []pathRead
switch n := n.(type) {
case *choice:
for _, it := range n.items {
out = append(out, f.reads(it)...)
}
case *template:
if repeats(n) {
break
}
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
}
// distinct keeps one of every read gathered by several routes, so a diamond of templates
// contributes each read once however many routes reach it.
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}
if !seen[k] {
seen[k] = true
out = append(out, r)
}
}
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: 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 {
var out []pathRead
for _, e := range renderEdges(t.formatTemplate) {
if c, isColumn := e.to.(*tableColumn); isColumn && !e.readsRef() {
out = append(out, f.cellReads(c, tag)...)
continue
}
out = append(out, f.viaEdge(t.formatTemplate, e, surveyRoute{})...)
}
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
// reference.
func (r surveyRoute) spelled(ref string) string {
if ref == "" || ref == r.label {
return r.spelling
}
return fmt.Sprintf("%s with {%s}", r.spelling, ref)
}
// tableRead is what a reference path reads of a table family: the table its head
// names, the rows its selectors pin, the tables it draws — those it walks with no
// row pinned, and their unpinned ancestors — each selector's spelling, and whether
// it lands on a `tableRow`.
type tableRead struct {
headTable *table
pins pinSet
drawn map[*table]bool
sels []tableSel
landsRow bool
}
// tableSel is one selector on the way: the table it selects a row of, and the path
// as written up to and including it.
type tableSel struct {
t *table
spelling string
}
// tableReadOf replays a reference path through pathProbe, so two paths pinning one
// row by different routes compare equal. checkPath proved
// each selector names a row.
func tableReadOf(head node, a arm, leaf node) *tableRead {
t, isTable := head.(*table)
if !isTable {
return nil
}
probe := &pathProbe{a: &a}
_, _ = probe.walk(t, a.tail)
_, landsRow := leaf.(*tableRow)
return &tableRead{headTable: t, pins: probe.pins, drawn: probe.drawn, sels: probe.sels, landsRow: landsRow}
}
// selected is the selector in r on t, or on the nearest ancestor of t it selects.
func (r *tableRead) selected(t *table) (tableSel, bool) {
for ; t != nil; t = t.parentT {
for _, s := range r.sels {
if s.t == t {
return s, true
}
}
}
return tableSel{}, false
}
// drawnOf is a table the read draws that pins holds a row of, if any.
func (r *tableRead) drawnOf(pins *pinSet) *table {
var found *table
pins.each(func(t *table, _ int) {
if found == nil && r.drawn[t] {
found = t
}
})
return found
}
// replay pins the read's rows into d, where they agree with the rows pinned before.
func (r *tableRead) replay(d *pinSet) error {
var err error
r.pins.each(func(t *table, row int) {
if err == nil {
err = d.pinRow(t, row)
}
})
return err
}