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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
@@ -28,7 +28,8 @@ package fejkdata
|
||||
// - draw group — reference draws held apart inside one render:
|
||||
// `template.drawGroup` as the data spells it, `template.drawGroupKey` as a
|
||||
// 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
|
||||
// `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
|
||||
|
||||
+3
-3
@@ -180,7 +180,7 @@ type drawWalk struct {
|
||||
type drawAt struct {
|
||||
group string
|
||||
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
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ func (w *drawWalk) walk(n node, at drawAt) {
|
||||
continue
|
||||
}
|
||||
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)
|
||||
continue
|
||||
}
|
||||
@@ -251,7 +251,7 @@ func (w *drawWalk) edge(from node, e renderEdge, at drawAt) {
|
||||
w.reads = append(w.reads, pathRead{at, a, tr})
|
||||
}
|
||||
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)
|
||||
|
||||
@@ -2,7 +2,6 @@ package fejkdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -12,7 +11,7 @@ import (
|
||||
// it lands on a row rendered whole.
|
||||
type tableRead struct {
|
||||
head *table
|
||||
pins hold
|
||||
pins pinSet
|
||||
drawn map[*table]bool
|
||||
sels []tableSel
|
||||
whole bool
|
||||
@@ -34,7 +33,7 @@ func tableReadOf(head node, a arm, leaf node) *tableRead {
|
||||
return nil
|
||||
}
|
||||
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))]
|
||||
cur := t
|
||||
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) {
|
||||
continue
|
||||
}
|
||||
var d hold
|
||||
var d pinSet
|
||||
if err := r.tr.replay(&d); err != nil {
|
||||
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.
|
||||
func (r *tableRead) replay(d *hold) error {
|
||||
func (r *tableRead) replay(d *pinSet) error {
|
||||
var err error
|
||||
r.pins.each(func(t *table, row int) {
|
||||
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.
|
||||
func (r *tableRead) drawnOf(pins *hold) *table {
|
||||
func (r *tableRead) drawnOf(pins *pinSet) *table {
|
||||
var found *table
|
||||
pins.each(func(t *table, _ int) {
|
||||
if found == nil && r.drawn[t] {
|
||||
@@ -226,69 +225,13 @@ func checkOwnFamily(t *template) error {
|
||||
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.
|
||||
func alternatives(a, b drawAt) bool {
|
||||
for _, p := range a.alt {
|
||||
if r, in := b.alt.rowOf(p.t); in && r != p.row {
|
||||
return true
|
||||
found := false
|
||||
a.alt.each(func(t *table, r int) {
|
||||
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.
|
||||
func pathLeaves(n node, tail []string) []node {
|
||||
var out []node
|
||||
_, _ = walkPath(n, tail, pathWalk{
|
||||
_, _ = walkPath(nil, n, tail, pathWalk{
|
||||
choice: func(c *choice, _ []string) ([]node, error) { return c.items, nil },
|
||||
leaf: func(n node) error { out = append(out, n); return nil },
|
||||
})
|
||||
|
||||
@@ -2,26 +2,16 @@ package fejkdata
|
||||
|
||||
import (
|
||||
"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,
|
||||
// so every path under it reads one row, and the draw each read made, by its one
|
||||
// spelling, so the same read written twice reads one draw. An expansion keeps one for
|
||||
// its sibling names, and a render one per group for its reference paths.
|
||||
// spelling, so the same read written twice reads one draw, and the table rows its reads pinned. An
|
||||
// expansion keeps one for its sibling names, and a render one per group for its reference paths.
|
||||
type hold struct {
|
||||
variant map[string]node
|
||||
value map[string]draw
|
||||
pins [8]tablePin // the rows pinned, inline so a render over a country's five-deep geo tree stays off the heap
|
||||
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
|
||||
pins pinSet
|
||||
}
|
||||
|
||||
// draw is what one read drew: its text, and whether it landed on a null.
|
||||
@@ -30,129 +20,6 @@ type draw struct {
|
||||
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
|
||||
// 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
|
||||
@@ -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)}
|
||||
}
|
||||
d := readHold(s, held, sc, a)
|
||||
d := readHold(held, sc, a)
|
||||
if r, done := d.value[a.path]; done {
|
||||
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
|
||||
// prefix share it.
|
||||
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
|
||||
},
|
||||
pins: d,
|
||||
pins: &d.pins,
|
||||
})
|
||||
if err != nil {
|
||||
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,
|
||||
// the render's hold for its group, so its draw spans the render; for a sibling, or a
|
||||
// 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 {
|
||||
return sc.hold(s)
|
||||
return sc.hold()
|
||||
}
|
||||
return held
|
||||
}
|
||||
@@ -221,7 +88,7 @@ func renderLeaf(s *session, n node, sc renderScope) draw {
|
||||
}
|
||||
r := draw{text: render(s, n, sc)}
|
||||
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
|
||||
}
|
||||
|
||||
+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
|
||||
// passes through, whole, and the leaf it renders.
|
||||
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 },
|
||||
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
|
||||
// stack for a render that reads through them; lazyHoldSet makes them on its first read instead.
|
||||
func eagerHoldSet(s *session) holdSet {
|
||||
return holdSet{unnamed: hold{variant: map[string]node{}, value: map[string]draw{}, s: s}}
|
||||
}
|
||||
|
||||
func lazyHoldSet(s *session) holdSet {
|
||||
return holdSet{unnamed: hold{s: s}}
|
||||
// stack for a render that reads through them; a zero holdSet makes them on its first read instead.
|
||||
func eagerHoldSet() holdSet {
|
||||
return holdSet{unnamed: hold{variant: map[string]node{}, value: map[string]draw{}}}
|
||||
}
|
||||
|
||||
// renderOnce renders n as one render, over a hold set of its own.
|
||||
func renderOnce(s *session, n node) string {
|
||||
set := lazyHoldSet(s)
|
||||
var set holdSet
|
||||
return render(s, n, renderScope{set: &set})
|
||||
}
|
||||
|
||||
@@ -40,7 +36,7 @@ func renderOnce(s *session, n node) string {
|
||||
//
|
||||
//go:noinline
|
||||
func expandAnew(s *session, t *template) string {
|
||||
set := lazyHoldSet(s)
|
||||
var set holdSet
|
||||
return expand(s, t, renderScope{set: &set})
|
||||
}
|
||||
|
||||
@@ -52,9 +48,8 @@ func (sc renderScope) in(t *template) renderScope {
|
||||
return sc
|
||||
}
|
||||
|
||||
// hold is the set's hold for sc's draw group. The session is passed in rather than
|
||||
// read out of the set: copying it from there would leak the set's maps to the heap.
|
||||
func (sc renderScope) hold(s *session) *hold {
|
||||
// hold is the set's hold for sc's draw group.
|
||||
func (sc renderScope) hold() *hold {
|
||||
if sc.group == "" {
|
||||
return &sc.set.unnamed
|
||||
}
|
||||
@@ -63,7 +58,7 @@ func (sc renderScope) hold(s *session) *hold {
|
||||
if sc.set.named == nil {
|
||||
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
|
||||
}
|
||||
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
|
||||
// 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
|
||||
// a segment descends into; leaf runs where the tail ends; pins is where the walk
|
||||
// pins the table rows it selects and draws, nil skipping tables. A nil action is
|
||||
// skipped. A render's walk sets only pins, so it allocates nothing.
|
||||
// the walk draws one, or stops; level runs at each template a segment descends
|
||||
// into; leaf runs where the tail ends; pins is where the walk pins the table rows
|
||||
// it selects and draws, nil skipping tables. A nil action is skipped. A render's
|
||||
// walk sets only pins, so it allocates nothing.
|
||||
type pathWalk struct {
|
||||
choice func(c *choice, rest []string) ([]node, error)
|
||||
level func(t *template, rest []string) 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
|
||||
// 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
|
||||
// the data holds.
|
||||
func walkPath(n node, tail []string, w pathWalk) (node, error) {
|
||||
// the data holds. s draws rows and variants; nil, the walk only proves the path.
|
||||
func walkPath(s *session, n node, tail []string, w pathWalk) (node, error) {
|
||||
if len(tail) == 0 {
|
||||
return n, w.atLeaf(n)
|
||||
}
|
||||
if t, isTable := n.(*table); isTable {
|
||||
return walkTable(t, tail, w, false)
|
||||
return walkTable(s, t, tail, w, false)
|
||||
}
|
||||
if isSelector(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 {
|
||||
return nil, fmt.Errorf("no entry %q", tail[0])
|
||||
}
|
||||
return walkPath(child, tail[1:], w)
|
||||
return walkPath(s, child, tail[1:], w)
|
||||
case *template:
|
||||
if w.level != 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 {
|
||||
return nil, fmt.Errorf("no field %q", tail[0])
|
||||
}
|
||||
return walkPath(child, tail[1:], w)
|
||||
return walkPath(s, child, tail[1:], w)
|
||||
case *choice:
|
||||
return walkChoice(n, tail, w)
|
||||
return walkChoice(s, n, tail, w)
|
||||
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])
|
||||
}
|
||||
@@ -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
|
||||
// by the pins' session where the walk names no choice action.
|
||||
func walkChoice(c *choice, tail []string, w pathWalk) (node, error) {
|
||||
// by s where the walk names no choice action.
|
||||
func walkChoice(s *session, c *choice, tail []string, w pathWalk) (node, error) {
|
||||
if w.choice == nil {
|
||||
if w.pins == 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 {
|
||||
return nil, err
|
||||
}
|
||||
if w.pins.s == nil { // a probe: every variant carries the tail, so any one proves it
|
||||
return walkPath(c.items[0], tail, w)
|
||||
if s == nil { // a probe: every variant carries the tail, so any one proves it
|
||||
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)
|
||||
if err != nil {
|
||||
@@ -206,7 +206,7 @@ func walkChoice(c *choice, tail []string, w pathWalk) (node, error) {
|
||||
}
|
||||
var last node
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
// table was reached from another; a table reached whole, with no selector, is left
|
||||
// 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)
|
||||
if err != nil {
|
||||
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
|
||||
// where none follows; drawing first could pick a row the selector is not inside.
|
||||
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
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case len(tail) == 0 && sel == "" && !descended:
|
||||
return walkPath(t, nil, w)
|
||||
return walkPath(s, t, nil, w)
|
||||
case len(tail) == 0:
|
||||
return walkPath(t.whole, nil, w)
|
||||
return walkPath(s, t.whole, nil, w)
|
||||
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.
|
||||
@@ -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
|
||||
// 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 != "" {
|
||||
return d.selectRow(t, sel)
|
||||
return pins.selectRow(t, sel)
|
||||
}
|
||||
if draw && d.s != nil {
|
||||
d.rowOf(t)
|
||||
if draw && s != nil {
|
||||
pins.rowOf(s, t)
|
||||
}
|
||||
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
|
||||
// that validates here resolves on every render, and a typo is a New-time error.
|
||||
func checkPath(n node, tail []string, level string) error {
|
||||
var pins hold
|
||||
_, err := walkPath(n, tail, pathWalk{
|
||||
var pins pinSet
|
||||
_, err := walkPath(nil, n, tail, pathWalk{
|
||||
choice: func(c *choice, rest []string) ([]node, error) {
|
||||
if err := carriedByAll(c, rest); err != nil {
|
||||
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 },
|
||||
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)
|
||||
}
|
||||
if want := []string{"level:a", "level:b", "leaf"}; !slices.Equal(seen, want) {
|
||||
t.Errorf("walk visited %v, want %v", seen, want)
|
||||
}
|
||||
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"`) {
|
||||
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) {
|
||||
n := compiled(t, `[{"format":"{f}","f":"1"},{"format":"{f}","f":"2"}]`)
|
||||
var leaves []node
|
||||
_, err := walkPath(n, []string{"f"}, pathWalk{
|
||||
_, err := walkPath(nil, n, []string{"f"}, pathWalk{
|
||||
choice: func(c *choice, rest []string) ([]node, error) {
|
||||
if len(rest) != 1 || rest[0] != "f" {
|
||||
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 {
|
||||
return nil, fmt.Errorf("fejkdata: %s: %w", path, err)
|
||||
}
|
||||
set := eagerHoldSet(f.rand)
|
||||
set := eagerHoldSet()
|
||||
sc := renderScope{set: &set}
|
||||
if t, isTable := n.(*table); isTable {
|
||||
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) {
|
||||
case *table:
|
||||
sc.hold(s).rowOf(n)
|
||||
sc.hold().pins.rowOf(s, n)
|
||||
return n, nil
|
||||
case *row:
|
||||
return n.t, nil
|
||||
@@ -204,7 +204,7 @@ type RecordTemplate struct {
|
||||
func (t *RecordTemplate) Fake() *Record {
|
||||
t.g.mu.Lock()
|
||||
defer t.g.mu.Unlock()
|
||||
set := eagerHoldSet(t.g.rand)
|
||||
set := eagerHoldSet()
|
||||
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 {
|
||||
sc = sc.in(t)
|
||||
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...)}
|
||||
for i := range r.columns {
|
||||
|
||||
@@ -23,7 +23,7 @@ func (f *Generator) Fake(path string) (string, error) {
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("fejkdata: %w", err)
|
||||
}
|
||||
f.set = lazyHoldSet(f.rand)
|
||||
f.set = holdSet{}
|
||||
sc := renderScope{set: &f.set}
|
||||
f.root.children = f.categories
|
||||
n, err := descend(f.rand, &f.root, segments, sc)
|
||||
@@ -44,11 +44,11 @@ func (f *Generator) Fake(path string) (string, error) {
|
||||
// every call.
|
||||
func descend(s *session, root node, segments []string, sc renderScope) (node, error) {
|
||||
// Walked once without drawing first, so a path that fails moves no seeded stream.
|
||||
var probe hold
|
||||
if _, err := walkPath(root, segments, pathWalk{pins: &probe}); err != nil {
|
||||
var probe pinSet
|
||||
if _, err := walkPath(nil, root, segments, pathWalk{pins: &probe}); err != nil {
|
||||
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
|
||||
@@ -64,11 +64,11 @@ func render(s *session, n node, sc renderScope) string {
|
||||
sc.t, sc.row = n, n.draw(s)
|
||||
return expand(s, n.format, sc)
|
||||
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)
|
||||
case *column:
|
||||
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 {
|
||||
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.
|
||||
func (s *structShape) fill(sess *session, v reflect.Value) {
|
||||
if s.record != nil {
|
||||
set := eagerHoldSet(sess)
|
||||
set := eagerHoldSet()
|
||||
for i, c := range renderRecord(sess, s.record, s.columns, renderScope{set: &set}).columns {
|
||||
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
|
||||
// naming several rows resolves inside the ancestors pinned in d.
|
||||
func (t *table) find(sel string, d *hold) (int, error) {
|
||||
// naming several rows resolves inside the ancestors pinned.
|
||||
func (t *table) find(sel string, pins *pinSet) (int, error) {
|
||||
if t.key < 0 && t.name < 0 {
|
||||
return 0, fmt.Errorf("%s has no key or name column to select a row by", t.path)
|
||||
}
|
||||
@@ -534,7 +534,7 @@ func (t *table) find(sel string, d *hold) (int, error) {
|
||||
}
|
||||
rows := t.indexed().byName[sel]
|
||||
if len(rows) > 1 {
|
||||
rows = d.inside(t, rows)
|
||||
rows = pins.inside(t, rows)
|
||||
}
|
||||
switch len(rows) {
|
||||
case 1:
|
||||
|
||||
+1
-1
@@ -316,7 +316,7 @@ func TestGrowIsALowerBound(t *testing.T) {
|
||||
t.Fatalf("format %q did not compile to a template", format)
|
||||
}
|
||||
for i := 0; i < 50; i++ {
|
||||
set := lazyHoldSet(f.rand)
|
||||
var set holdSet
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -22,10 +22,6 @@ by nothing but tests, whose premise lives only in the README's Decisions.
|
||||
|
||||
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
|
||||
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
|
||||
@@ -46,7 +42,7 @@ This round, in order:
|
||||
`operandReader`, `checkNoRepeatedRead` and `compileOps`, and function-versus-field is
|
||||
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
|
||||
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.
|
||||
- Register a transform once: `transforms` restates the `lowercase`, `uppercase` and
|
||||
`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
|
||||
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.
|
||||
- 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.
|
||||
- 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
|
||||
|
||||
Reference in New Issue
Block a user