Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33
@@ -1338,11 +1338,12 @@ 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
|
||||||
template.go the {token} grammar: scanning, tokens, operands, validation, compiling a format
|
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
|
hold.go the hold: one draw per expansion for paths and operands
|
||||||
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
|
||||||
holdset.go one reference draw per render and group: the hold 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: a drawGroup's, a render's and a record's columns, and the walk over what a render reads
|
drawfence.go the load fences over reference draws: a drawGroup's, a render's and a record's columns, and the walk over what a render reads
|
||||||
family.go a family of linked tables: what a reference path reads of one, the rows a walk enters, and the fences over paths into it
|
family.go a family of linked tables: what a reference path reads of one, and the fences over paths into it
|
||||||
|
pins.go the pin set: the table rows a render fixes, which the draw fences replay
|
||||||
reference.go reference sigils, and binding references across the tree
|
reference.go reference sigils, and binding references across the tree
|
||||||
graph.go the node graph: tree walks, containment and render edges, and the cycle and repeat fences
|
graph.go the node graph: tree walks, containment and render edges, and the cycle and repeat fences
|
||||||
builtins.go the {name()} function registry and its implementations
|
builtins.go the {name()} function registry and its implementations
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ package fejkdata
|
|||||||
// - draw group — reference draws held apart inside one render:
|
// - draw group — reference draws held apart inside one render:
|
||||||
// `template.drawGroup` as the data spells it, `template.drawGroupKey` as a
|
// `template.drawGroup` as the data spells it, `template.drawGroupKey` as a
|
||||||
// render reads it, `holdSet`, `renderScope`.
|
// render reads it, `holdSet`, `renderScope`.
|
||||||
// - pin — fixing which row of a table the render uses: `hold.pin`, `hold.rowOf`.
|
// - pin — fixing which row of a table the render uses, which the draw fences replay:
|
||||||
|
// `pinSet`, `pinSet.pin`, `pinSet.rowOf`.
|
||||||
// - 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.family`.
|
// `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
|
// - whole — a read that lands on a `row`, so the row renders through its table's
|
||||||
|
|||||||
+3
-3
@@ -180,7 +180,7 @@ type drawWalk struct {
|
|||||||
type drawAt struct {
|
type drawAt struct {
|
||||||
group string
|
group string
|
||||||
route drawRoute
|
route drawRoute
|
||||||
alt rowSet
|
alt pinSet
|
||||||
drawn *table // left out of the visit keys: only this table's own cells compare against it, which the own-family fence keeps true
|
drawn *table // left out of the visit keys: only this table's own cells compare against it, which the own-family fence keeps true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +233,7 @@ func (w *drawWalk) walk(n node, at drawAt) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
in := at
|
in := at
|
||||||
in.alt = at.alt.enter(cell.cellOf, cell.cellRow, cell.cellOf != at.drawn)
|
in.alt = at.alt.entered(cell.cellOf, cell.cellRow, cell.cellOf != at.drawn)
|
||||||
w.edge(n, e, in)
|
w.edge(n, e, in)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -251,7 +251,7 @@ func (w *drawWalk) edge(from node, e renderEdge, at drawAt) {
|
|||||||
w.reads = append(w.reads, pathRead{at, a, tr})
|
w.reads = append(w.reads, pathRead{at, a, tr})
|
||||||
}
|
}
|
||||||
if tr != nil {
|
if tr != nil {
|
||||||
tr.pins.each(func(t *table, r int) { at.alt = at.alt.enter(t, r, true) })
|
tr.pins.each(func(t *table, r int) { at.alt = at.alt.entered(t, r, true) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.walk(e.to, at)
|
w.walk(e.to, at)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package fejkdata
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -12,7 +11,7 @@ import (
|
|||||||
// it lands on a row rendered whole.
|
// it lands on a row rendered whole.
|
||||||
type tableRead struct {
|
type tableRead struct {
|
||||||
head *table
|
head *table
|
||||||
pins hold
|
pins pinSet
|
||||||
drawn map[*table]bool
|
drawn map[*table]bool
|
||||||
sels []tableSel
|
sels []tableSel
|
||||||
whole bool
|
whole bool
|
||||||
@@ -34,7 +33,7 @@ func tableReadOf(head node, a arm, leaf node) *tableRead {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
tr := &tableRead{head: t, drawn: map[*table]bool{}}
|
tr := &tableRead{head: t, drawn: map[*table]bool{}}
|
||||||
_, _ = walkPath(t, a.tail, pathWalk{pins: &tr.pins})
|
_, _ = walkPath(nil, t, a.tail, pathWalk{pins: &tr.pins})
|
||||||
written := a.name[:len(a.name)-len(joinSegments(a.tail))]
|
written := a.name[:len(a.name)-len(joinSegments(a.tail))]
|
||||||
cur := t
|
cur := t
|
||||||
tr.draws(cur)
|
tr.draws(cur)
|
||||||
@@ -126,7 +125,7 @@ func replayPairs(reads []pathRead) error {
|
|||||||
if o.tr == nil || o.at.group != r.at.group || alternatives(r.at, o.at) {
|
if o.tr == nil || o.at.group != r.at.group || alternatives(r.at, o.at) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var d hold
|
var d pinSet
|
||||||
if err := r.tr.replay(&d); err != nil {
|
if err := r.tr.replay(&d); err != nil {
|
||||||
return conflict(r, err)
|
return conflict(r, err)
|
||||||
}
|
}
|
||||||
@@ -143,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.
|
// replay pins the read's rows into d, where they agree with the rows pinned before.
|
||||||
func (r *tableRead) replay(d *hold) error {
|
func (r *tableRead) replay(d *pinSet) error {
|
||||||
var err error
|
var err error
|
||||||
r.pins.each(func(t *table, row int) {
|
r.pins.each(func(t *table, row int) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -179,7 +178,7 @@ func checkFamilyPair(a, b pathRead) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// drawnOf is a table the read draws that pins holds a row of, if any.
|
// drawnOf is a table the read draws that pins holds a row of, if any.
|
||||||
func (r *tableRead) drawnOf(pins *hold) *table {
|
func (r *tableRead) drawnOf(pins *pinSet) *table {
|
||||||
var found *table
|
var found *table
|
||||||
pins.each(func(t *table, _ int) {
|
pins.each(func(t *table, _ int) {
|
||||||
if found == nil && r.drawn[t] {
|
if found == nil && r.drawn[t] {
|
||||||
@@ -226,69 +225,13 @@ func checkOwnFamily(t *template) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
func (s rowSet) rowOf(t *table) (int, bool) {
|
|
||||||
for _, p := range s {
|
|
||||||
if p.t == t {
|
|
||||||
return p.row, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// enter is s with row r of t, and with t's ancestors where the render pins that row rather than
|
|
||||||
// drawing it; the set is copied, since walks branch.
|
|
||||||
func (s rowSet) enter(t *table, r int, pins bool) rowSet {
|
|
||||||
if _, in := s.rowOf(t); in {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
out := append(make(rowSet, 0, len(s)+1), s...)
|
|
||||||
for {
|
|
||||||
out = append(out, tablePin{t, r})
|
|
||||||
if !pins || t.parentT == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
t, r = t.parentT, t.parentRow(r)
|
|
||||||
if _, in := out.rowOf(t); in {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Slice(out, func(i, j int) bool { return out[i].t.path < out[j].t.path })
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// key spells the set for a map, by the tables' identities.
|
|
||||||
func (s rowSet) key() string {
|
|
||||||
var b strings.Builder
|
|
||||||
for _, p := range s {
|
|
||||||
fmt.Fprintf(&b, "%p[%d]", p.t, p.row)
|
|
||||||
}
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// excludes reports whether a cell's row cannot render with the rows entered: another
|
|
||||||
// row of its table, or a row outside an entered ancestor's.
|
|
||||||
func (s rowSet) excludes(cell *template) bool {
|
|
||||||
for _, p := range s {
|
|
||||||
if p.t == cell.cellOf {
|
|
||||||
return p.row != cell.cellRow
|
|
||||||
}
|
|
||||||
if cell.cellOf.descends(p.t) && !cell.cellOf.under(cell.cellRow, p.t, p.row) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// alternatives reports whether two reads sit in different rows of one table.
|
// alternatives reports whether two reads sit in different rows of one table.
|
||||||
func alternatives(a, b drawAt) bool {
|
func alternatives(a, b drawAt) bool {
|
||||||
for _, p := range a.alt {
|
found := false
|
||||||
if r, in := b.alt.rowOf(p.t); in && r != p.row {
|
a.alt.each(func(t *table, r int) {
|
||||||
return true
|
if br, in := b.alt.pinned(t); in && br != r {
|
||||||
|
found = true
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
return false
|
return found
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ func renderEdges(n node) []renderEdge {
|
|||||||
// already proved the tail resolves in every variant.
|
// already proved the tail resolves in every variant.
|
||||||
func pathLeaves(n node, tail []string) []node {
|
func pathLeaves(n node, tail []string) []node {
|
||||||
var out []node
|
var out []node
|
||||||
_, _ = walkPath(n, tail, pathWalk{
|
_, _ = walkPath(nil, n, tail, pathWalk{
|
||||||
choice: func(c *choice, _ []string) ([]node, error) { return c.items, nil },
|
choice: func(c *choice, _ []string) ([]node, error) { return c.items, nil },
|
||||||
leaf: func(n node) error { out = append(out, n); return nil },
|
leaf: func(n node) error { out = append(out, n); return nil },
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,26 +2,16 @@ package fejkdata
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// tablePin is one table's pinned row.
|
|
||||||
type tablePin struct {
|
|
||||||
t *table
|
|
||||||
row int
|
|
||||||
}
|
|
||||||
|
|
||||||
// hold 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
|
// 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
|
// spelling, so the same read written twice reads one draw, and the table rows its reads pinned. An
|
||||||
// its sibling names, and a render one per group for its reference paths.
|
// expansion keeps one for its sibling names, and a render one per group for its reference paths.
|
||||||
type hold struct {
|
type hold struct {
|
||||||
variant map[string]node
|
variant map[string]node
|
||||||
value map[string]draw
|
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
|
pins pinSet
|
||||||
npins int
|
|
||||||
more map[*table]int // the rows pinned past the inline eight
|
|
||||||
s *session // what draws a row; nil where a walk only proves selectors
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw is what one read drew: its text, and whether it landed on a null.
|
// draw is what one read drew: its text, and whether it landed on a null.
|
||||||
@@ -30,129 +20,6 @@ type draw struct {
|
|||||||
null bool
|
null bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// pinned is the row the render pinned for t, if any.
|
|
||||||
func (d *hold) pinned(t *table) (int, bool) {
|
|
||||||
for _, p := range d.pins[:d.npins] {
|
|
||||||
if p.t == t {
|
|
||||||
return p.row, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r, ok := d.more[t]
|
|
||||||
return r, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// mustRow is the row pinned for t, which the walk reaching a column pinned.
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// pin pins row r of t, and the rows of t's ancestors it links to.
|
|
||||||
func (d *hold) pin(t *table, r int) {
|
|
||||||
for {
|
|
||||||
if _, done := d.pinned(t); done {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if d.npins < len(d.pins) {
|
|
||||||
d.pins[d.npins] = tablePin{t, r}
|
|
||||||
d.npins++
|
|
||||||
} else {
|
|
||||||
if d.more == nil {
|
|
||||||
d.more = map[*table]int{}
|
|
||||||
}
|
|
||||||
d.more[t] = r
|
|
||||||
}
|
|
||||||
if t.parentT == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t, r = t.parentT, t.parentRow(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// each calls fn for every pinned row, in pin order, the spilled ones by table name.
|
|
||||||
func (d *hold) each(fn func(t *table, r int)) {
|
|
||||||
for _, p := range d.pins[:d.npins] {
|
|
||||||
fn(p.t, p.row)
|
|
||||||
}
|
|
||||||
spilled := make([]*table, 0, len(d.more))
|
|
||||||
for t := range d.more {
|
|
||||||
spilled = append(spilled, t)
|
|
||||||
}
|
|
||||||
sort.Slice(spilled, func(i, j int) bool { return spilled[i].category < spilled[j].category })
|
|
||||||
for _, t := range spilled {
|
|
||||||
fn(t, d.more[t])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 *hold) rowOf(t *table) int {
|
|
||||||
if r, ok := d.pinned(t); ok {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
r := -1
|
|
||||||
for a := t.parentT; a != nil && r < 0; a = a.parentT {
|
|
||||||
if _, ok := d.pinned(a); !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if t.parentT != a {
|
|
||||||
d.rowOf(t.parentT)
|
|
||||||
}
|
|
||||||
pr, _ := d.pinned(t.parentT)
|
|
||||||
r = t.drawUnder(d.s, pr)
|
|
||||||
}
|
|
||||||
if r < 0 {
|
|
||||||
r = t.draw(d.s)
|
|
||||||
}
|
|
||||||
d.pin(t, r)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// pinRow pins row r of t where it agrees with the rows pinned before it.
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
for a := t.parentT; a != nil; a = a.parentT {
|
|
||||||
if pa, ok := d.pinned(a); ok && !t.under(r, a, pa) {
|
|
||||||
return fmt.Errorf("%s is not inside %s", t.selectorSpelling(r), a.selectorSpelling(pa))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d.pin(t, r)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// selectRow pins the row a selector names.
|
|
||||||
func (d *hold) selectRow(t *table, sel string) error {
|
|
||||||
r, err := t.find(sel, d)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return d.pinRow(t, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// inside keeps the rows of t that sit inside every pinned ancestor.
|
|
||||||
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 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var kept []int
|
|
||||||
for _, r := range rows {
|
|
||||||
if t.under(r, a, pa) {
|
|
||||||
kept = append(kept, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rows = kept
|
|
||||||
}
|
|
||||||
return rows
|
|
||||||
}
|
|
||||||
|
|
||||||
// readField renders one arm of a token. An arm's key is a sibling field or a
|
// readField renders one arm of a token. An arm's key is a sibling field or a
|
||||||
// reference linkRefs bound into fields. A name the expansion holds — a level some
|
// reference linkRefs bound into fields. A name the expansion holds — a level some
|
||||||
// token addresses by dotted path, or a field an operand reads — is drawn once and
|
// token addresses by dotted path, or a field an operand reads — is drawn once and
|
||||||
@@ -167,11 +34,11 @@ func readField(s *session, t *template, held *hold, sc renderScope, a arm) draw
|
|||||||
}
|
}
|
||||||
return draw{text: render(s, t.fields[a.key], sc)}
|
return draw{text: render(s, t.fields[a.key], sc)}
|
||||||
}
|
}
|
||||||
d := readHold(s, held, sc, a)
|
d := readHold(held, sc, a)
|
||||||
if r, done := d.value[a.path]; done {
|
if r, done := d.value[a.path]; done {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
leaf, err := walkPath(t.fields[a.key], a.tail, pathWalk{
|
leaf, err := walkPath(s, t.fields[a.key], a.tail, pathWalk{
|
||||||
// Hold the draw at every level passed through, so two paths sharing a
|
// Hold the draw at every level passed through, so two paths sharing a
|
||||||
// prefix share it.
|
// prefix share it.
|
||||||
choice: func(c *choice, rest []string) ([]node, error) {
|
choice: func(c *choice, rest []string) ([]node, error) {
|
||||||
@@ -189,7 +56,7 @@ func readField(s *session, t *template, held *hold, sc renderScope, a arm) draw
|
|||||||
}
|
}
|
||||||
return []node{n}, nil
|
return []node{n}, nil
|
||||||
},
|
},
|
||||||
pins: d,
|
pins: &d.pins,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("fejkdata: %q: %v; a fence should have refused this at New", a.name, err))
|
panic(fmt.Sprintf("fejkdata: %q: %v; a fence should have refused this at New", a.name, err))
|
||||||
@@ -205,9 +72,9 @@ func readField(s *session, t *template, held *hold, sc renderScope, a arm) draw
|
|||||||
// readHold is the hold a held read keeps its draw in: for a reference that reads a path,
|
// 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
|
// the render's hold for its group, so its draw spans the render; for a sibling, or a
|
||||||
// reference read whole, held.
|
// reference read whole, held.
|
||||||
func readHold(s *session, held *hold, sc renderScope, a arm) *hold {
|
func readHold(held *hold, sc renderScope, a arm) *hold {
|
||||||
if isRef(a.key) && len(a.tail) > 0 {
|
if isRef(a.key) && len(a.tail) > 0 {
|
||||||
return sc.hold(s)
|
return sc.hold()
|
||||||
}
|
}
|
||||||
return held
|
return held
|
||||||
}
|
}
|
||||||
@@ -221,7 +88,7 @@ func renderLeaf(s *session, n node, sc renderScope) draw {
|
|||||||
}
|
}
|
||||||
r := draw{text: render(s, n, sc)}
|
r := draw{text: render(s, n, sc)}
|
||||||
if t, _ := n.(*template); t != nil && t.readsColumn != nil {
|
if t, _ := n.(*template); t != nil && t.readsColumn != nil {
|
||||||
r.null = sc.in(t).hold(s).value[t.readsColumn.a.path].null
|
r.null = sc.in(t).hold().value[t.readsColumn.a.path].null
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -106,7 +106,7 @@ func operandReader(t *template, head string) string {
|
|||||||
// coverPath collects what holding one path pins: every choice level the path
|
// coverPath collects what holding one path pins: every choice level the path
|
||||||
// passes through, whole, and the leaf it renders.
|
// passes through, whole, and the leaf it renders.
|
||||||
func coverPath(n node, tail []string, into map[node]bool) {
|
func coverPath(n node, tail []string, into map[node]bool) {
|
||||||
_, _ = walkPath(n, tail, pathWalk{
|
_, _ = walkPath(nil, n, tail, pathWalk{
|
||||||
choice: func(c *choice, _ []string) ([]node, error) { cover(c, into, false); return nil, nil },
|
choice: func(c *choice, _ []string) ([]node, error) { cover(c, into, false); return nil, nil },
|
||||||
leaf: func(n node) error { cover(n, into, false); return nil },
|
leaf: func(n node) error { cover(n, into, false); return nil },
|
||||||
})
|
})
|
||||||
|
|||||||
+8
-13
@@ -20,18 +20,14 @@ type renderScope struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// eagerHoldSet makes the unnamed group's maps where the set is declared, keeping them on that frame's
|
// eagerHoldSet 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; lazyHoldSet makes them on its first read instead.
|
// stack for a render that reads through them; a zero holdSet makes them on its first read instead.
|
||||||
func eagerHoldSet(s *session) holdSet {
|
func eagerHoldSet() holdSet {
|
||||||
return holdSet{unnamed: hold{variant: map[string]node{}, value: map[string]draw{}, s: s}}
|
return holdSet{unnamed: hold{variant: map[string]node{}, value: map[string]draw{}}}
|
||||||
}
|
|
||||||
|
|
||||||
func lazyHoldSet(s *session) holdSet {
|
|
||||||
return holdSet{unnamed: hold{s: s}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderOnce renders n as one render, over a hold set of its own.
|
// renderOnce renders n as one render, over a hold set of its own.
|
||||||
func renderOnce(s *session, n node) string {
|
func renderOnce(s *session, n node) string {
|
||||||
set := lazyHoldSet(s)
|
var set holdSet
|
||||||
return render(s, n, renderScope{set: &set})
|
return render(s, n, renderScope{set: &set})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +36,7 @@ func renderOnce(s *session, n node) string {
|
|||||||
//
|
//
|
||||||
//go:noinline
|
//go:noinline
|
||||||
func expandAnew(s *session, t *template) string {
|
func expandAnew(s *session, t *template) string {
|
||||||
set := lazyHoldSet(s)
|
var set holdSet
|
||||||
return expand(s, t, renderScope{set: &set})
|
return expand(s, t, renderScope{set: &set})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,9 +48,8 @@ func (sc renderScope) in(t *template) renderScope {
|
|||||||
return sc
|
return sc
|
||||||
}
|
}
|
||||||
|
|
||||||
// hold is the set's hold for sc's draw group. The session is passed in rather than
|
// hold is the set's hold for sc's draw group.
|
||||||
// read out of the set: copying it from there would leak the set's maps to the heap.
|
func (sc renderScope) hold() *hold {
|
||||||
func (sc renderScope) hold(s *session) *hold {
|
|
||||||
if sc.group == "" {
|
if sc.group == "" {
|
||||||
return &sc.set.unnamed
|
return &sc.set.unnamed
|
||||||
}
|
}
|
||||||
@@ -63,7 +58,7 @@ func (sc renderScope) hold(s *session) *hold {
|
|||||||
if sc.set.named == nil {
|
if sc.set.named == nil {
|
||||||
sc.set.named = map[string]*hold{}
|
sc.set.named = map[string]*hold{}
|
||||||
}
|
}
|
||||||
d = &hold{variant: map[string]node{}, value: map[string]draw{}, s: s}
|
d = &hold{variant: map[string]node{}, value: map[string]draw{}}
|
||||||
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
|
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
|
return d
|
||||||
|
|||||||
@@ -127,27 +127,27 @@ func names(segs []string) []string {
|
|||||||
|
|
||||||
// pathWalk is what one walk of a dotted path does at each kind of level: choice
|
// pathWalk is what one walk of a dotted path does at each kind of level: choice
|
||||||
// returns the variants to continue into (none stops the walk), and where it is nil
|
// returns the variants to continue into (none stops the walk), and where it is nil
|
||||||
// the walk draws one with the pins' session, or stops; level runs at each template
|
// the walk draws one, or stops; level runs at each template a segment descends
|
||||||
// a segment descends into; leaf runs where the tail ends; pins is where the walk
|
// into; leaf runs where the tail ends; pins is where the walk pins the table rows
|
||||||
// pins the table rows it selects and draws, nil skipping tables. A nil action is
|
// it selects and draws, nil skipping tables. A nil action is skipped. A render's
|
||||||
// skipped. A render's walk sets only pins, so it allocates nothing.
|
// walk sets only pins, so it allocates nothing.
|
||||||
type pathWalk struct {
|
type pathWalk struct {
|
||||||
choice func(c *choice, rest []string) ([]node, error)
|
choice func(c *choice, rest []string) ([]node, error)
|
||||||
level func(t *template, rest []string) error
|
level func(t *template, rest []string) error
|
||||||
leaf func(n node) error
|
leaf func(n node) error
|
||||||
pins *hold
|
pins *pinSet
|
||||||
}
|
}
|
||||||
|
|
||||||
// walkPath descends tail from n and returns the node it ends at: a folder or
|
// walkPath descends tail from n and returns the node it ends at: a folder or
|
||||||
// template by its next segment, a choice by w.choice, which consumes no segment, a
|
// template by its next segment, a choice by w.choice, which consumes no segment, a
|
||||||
// table by walkTable. A missing segment is an error, so no walk reaches past what
|
// table by walkTable. A missing segment is an error, so no walk reaches past what
|
||||||
// the data holds.
|
// the data holds. s draws rows and variants; nil, the walk only proves the path.
|
||||||
func walkPath(n node, tail []string, w pathWalk) (node, error) {
|
func walkPath(s *session, n node, tail []string, w pathWalk) (node, error) {
|
||||||
if len(tail) == 0 {
|
if len(tail) == 0 {
|
||||||
return n, w.atLeaf(n)
|
return n, w.atLeaf(n)
|
||||||
}
|
}
|
||||||
if t, isTable := n.(*table); isTable {
|
if t, isTable := n.(*table); isTable {
|
||||||
return walkTable(t, tail, w, false)
|
return walkTable(s, t, tail, w, false)
|
||||||
}
|
}
|
||||||
if isSelector(tail[0]) {
|
if isSelector(tail[0]) {
|
||||||
return nil, fmt.Errorf("%s is not a table, so it has no row to select", selectorOf(tail[0]))
|
return nil, fmt.Errorf("%s is not a table, so it has no row to select", selectorOf(tail[0]))
|
||||||
@@ -158,7 +158,7 @@ func walkPath(n node, tail []string, w pathWalk) (node, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("no entry %q", tail[0])
|
return nil, fmt.Errorf("no entry %q", tail[0])
|
||||||
}
|
}
|
||||||
return walkPath(child, tail[1:], w)
|
return walkPath(s, child, tail[1:], w)
|
||||||
case *template:
|
case *template:
|
||||||
if w.level != nil {
|
if w.level != nil {
|
||||||
if err := w.level(n, tail); err != nil {
|
if err := w.level(n, tail); err != nil {
|
||||||
@@ -169,9 +169,9 @@ func walkPath(n node, tail []string, w pathWalk) (node, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("no field %q", tail[0])
|
return nil, fmt.Errorf("no field %q", tail[0])
|
||||||
}
|
}
|
||||||
return walkPath(child, tail[1:], w)
|
return walkPath(s, child, tail[1:], w)
|
||||||
case *choice:
|
case *choice:
|
||||||
return walkChoice(n, tail, w)
|
return walkChoice(s, n, tail, w)
|
||||||
case *column:
|
case *column:
|
||||||
return nil, fmt.Errorf("no field %q: %q is a column, and a cell holds no fields", tail[0], n.t.columns[n.i])
|
return nil, fmt.Errorf("no field %q: %q is a column, and a cell holds no fields", tail[0], n.t.columns[n.i])
|
||||||
}
|
}
|
||||||
@@ -186,8 +186,8 @@ func (w pathWalk) atLeaf(n node) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// walkChoice continues a walk into the variants w.choice returns, or into one drawn
|
// walkChoice continues a walk into the variants w.choice returns, or into one drawn
|
||||||
// by the pins' session where the walk names no choice action.
|
// by s where the walk names no choice action.
|
||||||
func walkChoice(c *choice, tail []string, w pathWalk) (node, error) {
|
func walkChoice(s *session, c *choice, tail []string, w pathWalk) (node, error) {
|
||||||
if w.choice == nil {
|
if w.choice == nil {
|
||||||
if w.pins == nil {
|
if w.pins == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -195,10 +195,10 @@ func walkChoice(c *choice, tail []string, w pathWalk) (node, error) {
|
|||||||
if err := carriedByAll(c, tail); err != nil {
|
if err := carriedByAll(c, tail); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if w.pins.s == nil { // a probe: every variant carries the tail, so any one proves it
|
if s == nil { // a probe: every variant carries the tail, so any one proves it
|
||||||
return walkPath(c.items[0], tail, w)
|
return walkPath(s, c.items[0], tail, w)
|
||||||
}
|
}
|
||||||
return walkPath(pick(w.pins.s, c), tail, w)
|
return walkPath(s, pick(s, c), tail, w)
|
||||||
}
|
}
|
||||||
next, err := w.choice(c, tail)
|
next, err := w.choice(c, tail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -206,7 +206,7 @@ func walkChoice(c *choice, tail []string, w pathWalk) (node, error) {
|
|||||||
}
|
}
|
||||||
var last node
|
var last node
|
||||||
for _, item := range next {
|
for _, item := range next {
|
||||||
if last, err = walkPath(item, tail, w); err != nil {
|
if last, err = walkPath(s, item, tail, w); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,7 +217,7 @@ func walkChoice(c *choice, tail []string, w pathWalk) (node, error) {
|
|||||||
// linked table by name. The walk reads a row wherever a segment follows or the
|
// linked table by name. The walk reads a row wherever a segment follows or the
|
||||||
// table was reached from another; a table reached whole, with no selector, is left
|
// table was reached from another; a table reached whole, with no selector, is left
|
||||||
// to a render's own draw.
|
// to a render's own draw.
|
||||||
func walkTable(t *table, tail []string, w pathWalk, descended bool) (node, error) {
|
func walkTable(s *session, t *table, tail []string, w pathWalk, descended bool) (node, error) {
|
||||||
sel, tail, err := t.selector(tail)
|
sel, tail, err := t.selector(tail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -229,19 +229,19 @@ func walkTable(t *table, tail []string, w pathWalk, descended bool) (node, error
|
|||||||
// A selector further down pins this table by ancestry, so the walk draws only
|
// A selector further down pins this table by ancestry, so the walk draws only
|
||||||
// where none follows; drawing first could pick a row the selector is not inside.
|
// where none follows; drawing first could pick a row the selector is not inside.
|
||||||
if w.pins != nil {
|
if w.pins != nil {
|
||||||
if err := readRow(w.pins, t, sel, (descended || len(tail) > 0) && !hasSelector(tail)); err != nil {
|
if err := readRow(s, w.pins, t, sel, (descended || len(tail) > 0) && !hasSelector(tail)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case len(tail) == 0 && sel == "" && !descended:
|
case len(tail) == 0 && sel == "" && !descended:
|
||||||
return walkPath(t, nil, w)
|
return walkPath(s, t, nil, w)
|
||||||
case len(tail) == 0:
|
case len(tail) == 0:
|
||||||
return walkPath(t.whole, nil, w)
|
return walkPath(s, t.whole, nil, w)
|
||||||
case child != nil:
|
case child != nil:
|
||||||
return walkTable(child, tail[1:], w, true)
|
return walkTable(s, child, tail[1:], w, true)
|
||||||
}
|
}
|
||||||
return walkPath(column, tail[1:], w)
|
return walkPath(s, column, tail[1:], w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// selector splits the selector a tail starts with from the rest of it.
|
// selector splits the selector a tail starts with from the rest of it.
|
||||||
@@ -272,12 +272,12 @@ 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
|
// 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.
|
// walk draws, one drawn where the path reads into the table.
|
||||||
func readRow(d *hold, t *table, sel string, draw bool) error {
|
func readRow(s *session, pins *pinSet, t *table, sel string, draw bool) error {
|
||||||
if sel != "" {
|
if sel != "" {
|
||||||
return d.selectRow(t, sel)
|
return pins.selectRow(t, sel)
|
||||||
}
|
}
|
||||||
if draw && d.s != nil {
|
if draw && s != nil {
|
||||||
d.rowOf(t)
|
pins.rowOf(s, t)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -312,8 +312,8 @@ func unreachableInChoice(c *choice, want string) error {
|
|||||||
// carries a repeat or a drawGroup, which one draw of it could not apply. So a path
|
// 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.
|
// that validates here resolves on every render, and a typo is a New-time error.
|
||||||
func checkPath(n node, tail []string, level string) error {
|
func checkPath(n node, tail []string, level string) error {
|
||||||
var pins hold
|
var pins pinSet
|
||||||
_, err := walkPath(n, tail, pathWalk{
|
_, err := walkPath(nil, n, tail, pathWalk{
|
||||||
choice: func(c *choice, rest []string) ([]node, error) {
|
choice: func(c *choice, rest []string) ([]node, error) {
|
||||||
if err := carriedByAll(c, rest); err != nil {
|
if err := carriedByAll(c, rest); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
+3
-3
@@ -13,14 +13,14 @@ func TestWalkPathStopsAtAMissingSegment(t *testing.T) {
|
|||||||
level: func(tm *template, rest []string) error { seen = append(seen, "level:"+rest[0]); return nil },
|
level: func(tm *template, rest []string) error { seen = append(seen, "level:"+rest[0]); return nil },
|
||||||
leaf: func(n node) error { seen = append(seen, "leaf"); return nil },
|
leaf: func(n node) error { seen = append(seen, "leaf"); return nil },
|
||||||
}
|
}
|
||||||
if _, err := walkPath(n, []string{"a", "b"}, walk); err != nil {
|
if _, err := walkPath(nil, n, []string{"a", "b"}, walk); err != nil {
|
||||||
t.Fatalf("walkPath(a.b) = %v", err)
|
t.Fatalf("walkPath(a.b) = %v", err)
|
||||||
}
|
}
|
||||||
if want := []string{"level:a", "level:b", "leaf"}; !slices.Equal(seen, want) {
|
if want := []string{"level:a", "level:b", "leaf"}; !slices.Equal(seen, want) {
|
||||||
t.Errorf("walk visited %v, want %v", seen, want)
|
t.Errorf("walk visited %v, want %v", seen, want)
|
||||||
}
|
}
|
||||||
seen = nil
|
seen = nil
|
||||||
_, err := walkPath(n, []string{"a", "nope", "deeper"}, walk)
|
_, err := walkPath(nil, n, []string{"a", "nope", "deeper"}, walk)
|
||||||
if err == nil || !strings.Contains(err.Error(), `no field "nope"`) {
|
if err == nil || !strings.Contains(err.Error(), `no field "nope"`) {
|
||||||
t.Errorf("walkPath(a.nope.deeper) = %v, want the missing segment named", err)
|
t.Errorf("walkPath(a.nope.deeper) = %v, want the missing segment named", err)
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ func TestWalkPathStopsAtAMissingSegment(t *testing.T) {
|
|||||||
func TestWalkPathChoiceConsumesNoSegment(t *testing.T) {
|
func TestWalkPathChoiceConsumesNoSegment(t *testing.T) {
|
||||||
n := compiled(t, `[{"format":"{f}","f":"1"},{"format":"{f}","f":"2"}]`)
|
n := compiled(t, `[{"format":"{f}","f":"1"},{"format":"{f}","f":"2"}]`)
|
||||||
var leaves []node
|
var leaves []node
|
||||||
_, err := walkPath(n, []string{"f"}, pathWalk{
|
_, err := walkPath(nil, n, []string{"f"}, pathWalk{
|
||||||
choice: func(c *choice, rest []string) ([]node, error) {
|
choice: func(c *choice, rest []string) ([]node, error) {
|
||||||
if len(rest) != 1 || rest[0] != "f" {
|
if len(rest) != 1 || rest[0] != "f" {
|
||||||
t.Errorf("choice saw rest %v, want [f]", rest)
|
t.Errorf("choice saw rest %v, want [f]", rest)
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
package fejkdata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// tablePin is one table's pinned row.
|
||||||
|
type tablePin struct {
|
||||||
|
t *table
|
||||||
|
row int
|
||||||
|
}
|
||||||
|
|
||||||
|
// pinSet is the rows of tables fixed so far: by a render, by a path read replayed at load, and by
|
||||||
|
// a fence walk over the rows a render can enter.
|
||||||
|
type pinSet struct {
|
||||||
|
pins [8]tablePin // inline, so a render over a country's five-deep geo tree stays off the heap
|
||||||
|
n int
|
||||||
|
spill map[*table]int
|
||||||
|
}
|
||||||
|
|
||||||
|
// pinned is the row pinned for t, if any.
|
||||||
|
func (p *pinSet) pinned(t *table) (int, bool) {
|
||||||
|
for _, q := range p.pins[:p.n] {
|
||||||
|
if q.t == t {
|
||||||
|
return q.row, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r, ok := p.spill[t]
|
||||||
|
return r, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// mustRow is the row pinned for t, which the walk reaching a column pinned.
|
||||||
|
func (p *pinSet) mustRow(t *table) int {
|
||||||
|
r, ok := p.pinned(t)
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Sprintf("fejkdata: a column of %s is rendered with no row pinned", t.category))
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// add pins row r of t alone.
|
||||||
|
func (p *pinSet) add(t *table, r int) {
|
||||||
|
if p.n < len(p.pins) {
|
||||||
|
p.pins[p.n] = tablePin{t, r}
|
||||||
|
p.n++
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if p.spill == nil {
|
||||||
|
p.spill = map[*table]int{}
|
||||||
|
}
|
||||||
|
p.spill[t] = r
|
||||||
|
}
|
||||||
|
|
||||||
|
// pin pins row r of t, and the rows of t's ancestors it links to.
|
||||||
|
func (p *pinSet) pin(t *table, r int) {
|
||||||
|
for {
|
||||||
|
if _, done := p.pinned(t); done {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.add(t, r)
|
||||||
|
if t.parentT == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t, r = t.parentT, t.parentRow(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// each calls fn for every pinned row, in pin order, the spilled ones by table name.
|
||||||
|
func (p *pinSet) each(fn func(t *table, r int)) {
|
||||||
|
for _, q := range p.pins[:p.n] {
|
||||||
|
fn(q.t, q.row)
|
||||||
|
}
|
||||||
|
spilled := make([]*table, 0, len(p.spill))
|
||||||
|
for t := range p.spill {
|
||||||
|
spilled = append(spilled, t)
|
||||||
|
}
|
||||||
|
sort.Slice(spilled, func(i, j int) bool { return spilled[i].category < spilled[j].category })
|
||||||
|
for _, t := range spilled {
|
||||||
|
fn(t, p.spill[t])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rowOf is the row of t: the one pinned, else one s draws 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 (p *pinSet) rowOf(s *session, t *table) int {
|
||||||
|
if r, ok := p.pinned(t); ok {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
r := -1
|
||||||
|
for a := t.parentT; a != nil && r < 0; a = a.parentT {
|
||||||
|
if _, ok := p.pinned(a); !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if t.parentT != a {
|
||||||
|
p.rowOf(s, t.parentT)
|
||||||
|
}
|
||||||
|
pr, _ := p.pinned(t.parentT)
|
||||||
|
r = t.drawUnder(s, pr)
|
||||||
|
}
|
||||||
|
if r < 0 {
|
||||||
|
r = t.draw(s)
|
||||||
|
}
|
||||||
|
p.pin(t, r)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// clash is the table whose pinned row keeps row r of t out: t itself pinned to another row, or the
|
||||||
|
// nearest ancestor pinned to a row r is not inside; nil where none does.
|
||||||
|
func (p *pinSet) clash(t *table, r int) *table {
|
||||||
|
if pr, ok := p.pinned(t); ok && pr != r {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
for a := t.parentT; a != nil; a = a.parentT {
|
||||||
|
if pa, ok := p.pinned(a); ok && !t.under(r, a, pa) {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pinRow pins row r of t where it agrees with the rows pinned before it.
|
||||||
|
func (p *pinSet) pinRow(t *table, r int) error {
|
||||||
|
switch a := p.clash(t, r); {
|
||||||
|
case a == t:
|
||||||
|
pr, _ := p.pinned(t)
|
||||||
|
return fmt.Errorf("%s and %s are two rows of %s", t.selectorSpelling(pr), t.selectorSpelling(r), t.path)
|
||||||
|
case a != nil:
|
||||||
|
pa, _ := p.pinned(a)
|
||||||
|
return fmt.Errorf("%s is not inside %s", t.selectorSpelling(r), a.selectorSpelling(pa))
|
||||||
|
}
|
||||||
|
p.pin(t, r)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// selectRow pins the row a selector names.
|
||||||
|
func (p *pinSet) selectRow(t *table, sel string) error {
|
||||||
|
r, err := t.find(sel, p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return p.pinRow(t, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// inside keeps the rows of t that sit inside every pinned ancestor.
|
||||||
|
func (p *pinSet) inside(t *table, rows []int) []int {
|
||||||
|
for a := t.parentT; a != nil; a = a.parentT {
|
||||||
|
pa, ok := p.pinned(a)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var kept []int
|
||||||
|
for _, r := range rows {
|
||||||
|
if t.under(r, a, pa) {
|
||||||
|
kept = append(kept, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rows = kept
|
||||||
|
}
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
|
// entered is p with row r of t: pinned where the render pins it, alone where the render draws it
|
||||||
|
// to render whole. p is left as it was, since a fence walk branches.
|
||||||
|
func (p pinSet) entered(t *table, r int, pins bool) pinSet {
|
||||||
|
p.spill = maps.Clone(p.spill)
|
||||||
|
switch _, in := p.pinned(t); {
|
||||||
|
case pins:
|
||||||
|
p.pin(t, r)
|
||||||
|
case !in:
|
||||||
|
p.add(t, r)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// excludes reports whether a cell's row cannot render with the rows pinned.
|
||||||
|
func (p *pinSet) excludes(cell *template) bool { return p.clash(cell.cellOf, cell.cellRow) != nil }
|
||||||
|
|
||||||
|
// key spells the set for a map, by the tables' identities in path order.
|
||||||
|
func (p *pinSet) key() string {
|
||||||
|
var pins []tablePin
|
||||||
|
p.each(func(t *table, r int) { pins = append(pins, tablePin{t, r}) })
|
||||||
|
sort.Slice(pins, func(i, j int) bool { return pins[i].t.path < pins[j].t.path })
|
||||||
|
var b strings.Builder
|
||||||
|
for _, q := range pins {
|
||||||
|
fmt.Fprintf(&b, "%p[%d]", q.t, q.row)
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
@@ -130,7 +130,7 @@ 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)
|
||||||
}
|
}
|
||||||
set := eagerHoldSet(f.rand)
|
set := eagerHoldSet()
|
||||||
sc := renderScope{set: &set}
|
sc := renderScope{set: &set}
|
||||||
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 {
|
||||||
@@ -159,7 +159,7 @@ func tableRecord(s *session, t *table, tail []string, sc renderScope) (node, err
|
|||||||
}
|
}
|
||||||
switch n := n.(type) {
|
switch n := n.(type) {
|
||||||
case *table:
|
case *table:
|
||||||
sc.hold(s).rowOf(n)
|
sc.hold().pins.rowOf(s, n)
|
||||||
return n, nil
|
return n, nil
|
||||||
case *row:
|
case *row:
|
||||||
return n.t, nil
|
return n.t, nil
|
||||||
@@ -204,7 +204,7 @@ 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()
|
||||||
set := eagerHoldSet(t.g.rand)
|
set := eagerHoldSet()
|
||||||
return renderRecord(t.g.rand, t.t, t.columns, renderScope{set: &set})
|
return renderRecord(t.g.rand, t.t, t.columns, renderScope{set: &set})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,7 +265,7 @@ func recordOf(n node) (*template, []Column, error) {
|
|||||||
func renderRecord(s *session, t *template, columns []Column, sc renderScope) *Record {
|
func renderRecord(s *session, t *template, columns []Column, sc renderScope) *Record {
|
||||||
sc = sc.in(t)
|
sc = sc.in(t)
|
||||||
if t.table != nil {
|
if t.table != nil {
|
||||||
sc.t, sc.row = t.table, sc.hold(s).mustRow(t.table)
|
sc.t, sc.row = t.table, sc.hold().pins.mustRow(t.table)
|
||||||
}
|
}
|
||||||
r := &Record{columns: append([]Column(nil), columns...)}
|
r := &Record{columns: append([]Column(nil), columns...)}
|
||||||
for i := range r.columns {
|
for i := range r.columns {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ 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)
|
||||||
}
|
}
|
||||||
f.set = lazyHoldSet(f.rand)
|
f.set = holdSet{}
|
||||||
sc := renderScope{set: &f.set}
|
sc := renderScope{set: &f.set}
|
||||||
f.root.children = f.categories
|
f.root.children = f.categories
|
||||||
n, err := descend(f.rand, &f.root, segments, sc)
|
n, err := descend(f.rand, &f.root, segments, sc)
|
||||||
@@ -44,11 +44,11 @@ func (f *Generator) Fake(path string) (string, error) {
|
|||||||
// every call.
|
// every call.
|
||||||
func descend(s *session, root node, segments []string, sc renderScope) (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.
|
// Walked once without drawing first, so a path that fails moves no seeded stream.
|
||||||
var probe hold
|
var probe pinSet
|
||||||
if _, err := walkPath(root, segments, pathWalk{pins: &probe}); err != nil {
|
if _, err := walkPath(nil, root, segments, pathWalk{pins: &probe}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return walkPath(root, segments, pathWalk{pins: sc.hold(s)})
|
return walkPath(s, root, segments, pathWalk{pins: &sc.hold().pins})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
@@ -64,11 +64,11 @@ func render(s *session, n node, sc renderScope) string {
|
|||||||
sc.t, sc.row = n, n.draw(s)
|
sc.t, sc.row = n, n.draw(s)
|
||||||
return expand(s, n.format, sc)
|
return expand(s, n.format, sc)
|
||||||
case *row:
|
case *row:
|
||||||
sc.t, sc.row = n.t, sc.hold(s).mustRow(n.t)
|
sc.t, sc.row = n.t, sc.hold().pins.mustRow(n.t)
|
||||||
return expand(s, n.t.format, sc)
|
return expand(s, n.t.format, sc)
|
||||||
case *column:
|
case *column:
|
||||||
if sc.t != n.t {
|
if sc.t != n.t {
|
||||||
sc.t, sc.row = n.t, sc.hold(s).mustRow(n.t)
|
sc.t, sc.row = n.t, sc.hold().pins.mustRow(n.t)
|
||||||
}
|
}
|
||||||
if cell := n.t.cellNode(sc.row, n.i); cell != nil {
|
if cell := n.t.cellNode(sc.row, n.i); cell != nil {
|
||||||
return render(s, cell, sc)
|
return render(s, cell, sc)
|
||||||
|
|||||||
@@ -349,7 +349,7 @@ 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(sess *session, v reflect.Value) {
|
func (s *structShape) fill(sess *session, v reflect.Value) {
|
||||||
if s.record != nil {
|
if s.record != nil {
|
||||||
set := eagerHoldSet(sess)
|
set := eagerHoldSet()
|
||||||
for i, c := range renderRecord(sess, s.record, s.columns, renderScope{set: &set}).columns {
|
for i, c := range renderRecord(sess, s.record, s.columns, renderScope{set: &set}).columns {
|
||||||
setColumn(fieldAt(v, s.fields[i]), c)
|
setColumn(fieldAt(v, s.fields[i]), c)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -524,8 +524,8 @@ 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
|
// 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.
|
// naming several rows resolves inside the ancestors pinned.
|
||||||
func (t *table) find(sel string, d *hold) (int, error) {
|
func (t *table) find(sel string, pins *pinSet) (int, error) {
|
||||||
if t.key < 0 && t.name < 0 {
|
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)
|
return 0, fmt.Errorf("%s has no key or name column to select a row by", t.path)
|
||||||
}
|
}
|
||||||
@@ -534,7 +534,7 @@ func (t *table) find(sel string, d *hold) (int, error) {
|
|||||||
}
|
}
|
||||||
rows := t.indexed().byName[sel]
|
rows := t.indexed().byName[sel]
|
||||||
if len(rows) > 1 {
|
if len(rows) > 1 {
|
||||||
rows = d.inside(t, rows)
|
rows = pins.inside(t, rows)
|
||||||
}
|
}
|
||||||
switch len(rows) {
|
switch len(rows) {
|
||||||
case 1:
|
case 1:
|
||||||
|
|||||||
+1
-1
@@ -316,7 +316,7 @@ 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++ {
|
||||||
set := lazyHoldSet(f.rand)
|
var set holdSet
|
||||||
if got := len(expand(f.rand, tmpl, renderScope{set: &set})); got < tmpl.grow {
|
if got := len(expand(f.rand, tmpl, renderScope{set: &set})); got < tmpl.grow {
|
||||||
t.Errorf("format %q: expand emitted %d bytes, below grow %d", format, got, tmpl.grow)
|
t.Errorf("format %q: expand emitted %d bytes, below grow %d", format, got, tmpl.grow)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,10 +22,6 @@ by nothing but tests, whose premise lives only in the README's Decisions.
|
|||||||
|
|
||||||
This round, in order:
|
This round, in order:
|
||||||
|
|
||||||
- Give render and the draw fences one pin model: a pin set `hold.pin`, `hold.rowOf`
|
|
||||||
and the fence's `rowSet` all use, so a change to how a render pins rows cannot leave
|
|
||||||
the fence proving the old rule, and `hold` stops being a proof scratchpad whenever its
|
|
||||||
session is nil.
|
|
||||||
- Move the README's Decisions section to `docs/decisions.md`, leaving a one-line index
|
- Move the README's Decisions section to `docs/decisions.md`, leaving a one-line index
|
||||||
of the titles in `AGENTS.md`, and drop the `AGENTS.md` line pointing decisions at the
|
of the titles in `AGENTS.md`, and drop the `AGENTS.md` line pointing decisions at the
|
||||||
README. Give each fence a bare link to the entry it enforces: every seat needed
|
README. Give each fence a bare link to the entry it enforces: every seat needed
|
||||||
@@ -46,7 +42,7 @@ This round, in order:
|
|||||||
`operandReader`, `checkNoRepeatedRead` and `compileOps`, and function-versus-field is
|
`operandReader`, `checkNoRepeatedRead` and `compileOps`, and function-versus-field is
|
||||||
decided by `indexOutside(body, '(')` in two of them and by `funcCall` in the rest.
|
decided by `indexOutside(body, '(')` in two of them and by `funcCall` in the rest.
|
||||||
- Name `pathWalk`'s modes: render, probe, check and cover are chosen today by which
|
- Name `pathWalk`'s modes: render, probe, check and cover are chosen today by which
|
||||||
callbacks are nil and whether `pins.s` is, and `walkChoice` returns `nil, nil` where
|
callbacks are nil and whether its session is, and `walkChoice` returns `nil, nil` where
|
||||||
none is set.
|
none is set.
|
||||||
- Register a transform once: `transforms` restates the `lowercase`, `uppercase` and
|
- Register a transform once: `transforms` restates the `lowercase`, `uppercase` and
|
||||||
`ascii` entries in `builtins`, and `valueProof.template` classifies by the first while
|
`ascii` entries in `builtins`, and `valueProof.template` classifies by the first while
|
||||||
@@ -287,7 +283,7 @@ because pairing a street with its exact postnummer rewrites shipped rows.
|
|||||||
- Hold every change the charter says owes a `CHANGELOG.md` entry to one in CI: the
|
- Hold every change the charter says owes a `CHANGELOG.md` entry to one in CI: the
|
||||||
check covers `data` and `testdata/shipped_shape.txt` alone, where `AGENTS.md` adds a
|
check covers `data` and `testdata/shipped_shape.txt` alone, where `AGENTS.md` adds a
|
||||||
flag, an exit code, an exported name, a fence, a builtin and the lowest Go.
|
flag, an exit code, an exported name, a fence, a builtin and the lowest Go.
|
||||||
- Measure the allocation gate over the shipped `geo` trees too: `hold.pins [8]` is sized
|
- Measure the allocation gate over the shipped `geo` trees too: `pinSet.pins [8]` is sized
|
||||||
for them, and `perf_test.go` checks a synthetic five-deep tree only.
|
for them, and `perf_test.go` checks a synthetic five-deep tree only.
|
||||||
- Make a named draw group's hold lazy, `&hold{s: s}` in `renderScope.hold`: it escapes
|
- Make a named draw group's hold lazy, `&hold{s: s}` in `renderScope.hold`: it escapes
|
||||||
to the heap anyway, so its two eager maps buy no stack and cost two allocations per
|
to the heap anyway, so its two eager maps buy no stack and cost two allocations per
|
||||||
|
|||||||
Reference in New Issue
Block a user