Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33

Open
lilleman wants to merge 966 commits from worktree-todo-relative-dates-exact-counts into main
17 changed files with 279 additions and 284 deletions
Showing only changes of commit 3927dc9fa5 - Show all commits
+3 -2
View File
@@ -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
+2 -1
View File
@@ -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
View File
@@ -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)
+11 -68
View File
@@ -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
}
+1 -1
View File
@@ -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 },
})
+9 -142
View File
@@ -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
View File
@@ -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
View File
@@ -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
+29 -29
View File
@@ -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
View File
@@ -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)
+192
View File
@@ -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()
}
+4 -4
View File
@@ -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 {
+6 -6
View File
@@ -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)
+1 -1
View File
@@ -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)
}
+3 -3
View File
@@ -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
View File
@@ -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)
}
+2 -6
View File
@@ -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