Split walkPath's four modes into walkers of their own, sharing how a path steps

This commit is contained in:
2026-09-28 00:19:43 +02:00
committed by Lillem4n
parent 9126e0e07d
commit 94eb7aa81f
7 changed files with 261 additions and 187 deletions
+3 -2
View File
@@ -32,8 +32,9 @@ func tableReadOf(head node, a arm, leaf node) *tableRead {
if !isTable {
return nil
}
tr := &tableRead{headTable: t, drawn: map[*table]bool{}}
_, _ = walkPath(t, a.tail, pathWalk{mode: walkProbe, pins: &tr.pins, drawn: tr.drawn})
probe := &probeWalk{}
_, _ = probe.walk(t, a.tail)
tr := &tableRead{headTable: t, pins: probe.pins, drawn: probe.drawn}
written := a.spelling[:len(a.spelling)-len(joinSegments(a.tail))]
cur := t
for i, seg := range a.tail {
+3 -3
View File
@@ -164,9 +164,9 @@ func renderEdges(n node) []renderEdge {
// contributes every variant, since any of them may be the one drawn. checkPath has
// already proved the tail resolves in every variant.
func pathLeaves(n node, tail []string) []node {
var out []node
_, _ = walkPath(n, tail, pathWalk{mode: walkEvery, pins: &pinSet{}, leaf: func(n node) error { out = append(out, n); return nil }})
return out
w := &proveWalk{tail: tail}
_, _ = w.walk(n, tail)
return w.leaves
}
type renderCounts map[node]int
+1 -1
View File
@@ -44,7 +44,7 @@ func readField(s *session, t *template, held *hold, sc renderScope, a arm) draw
if len(a.tail) > 0 {
sc.pins = &d.pins
}
r := renderLeaf(s, drawPath(t.head(a.key), a.tail, a.key, &d.pins, &pathDraws{s: s, held: d, a: &a}), sc)
r := renderLeaf(s, drawPath(t.head(a.key), a.tail, a.key, &renderWalk{s: s, pins: &d.pins, held: d, a: &a}), sc)
if d.value == nil {
d.value = map[string]draw{}
}
+1 -1
View File
@@ -100,7 +100,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{mode: walkCover, leaf: func(n node) error { cover(n, into, false); return nil }})
_, _ = (&coverWalk{into: into}).walk(n, tail)
}
// cover collects a level and everything contained in it. A fixed string outside a
+248 -172
View File
@@ -125,154 +125,282 @@ func nameSegments(segs []string) []string {
return out
}
// walkMode is what a walk does at a choice, and whether it reads the rows a path
// selects and draws.
type walkMode uint8
const (
// walkEvery proves every variant carries the rest of the path and walks each,
// pinning the rows it selects in pins.
walkEvery walkMode = iota
// walkCover stops at a choice, handing it whole to leaf, and reads no row.
walkCover
// walkProbe proves as walkEvery does, walking one variant, so a path that
// resolves resolves whichever variants and rows are drawn, and marks in drawn
// the tables a draw would pin.
walkProbe
// walkDraw draws the rows and variants a proved path reads; drawPath is its
// one entry.
walkDraw
)
// pathWalk is one walk of a dotted path: level runs at each template a segment
// descends into, leaf where the walk ends, and a nil action is skipped.
type pathWalk struct {
mode walkMode
level func(t *template, rest []string) error
leaf func(n node) error
pins *pinSet
drawn map[*table]bool
draws *pathDraws
}
// pathDraws is what a walk draws with: the session, and, for a held read, the hold
// keeping the variant drawn at each level of a, so paths sharing a prefix share it.
type pathDraws struct {
s *session
held *hold
a *arm
}
// walkPath descends tail from n and returns the node it ends at: a folder or
// template by its next segment, a choice by its mode, 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) {
if len(tail) == 0 {
return n, w.atLeaf(n)
}
if t, isTable := n.(*table); isTable {
return walkTable(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]))
// stepInto is what seg names under n: a folder's entry or a template's field. A
// missing segment is an error, so no walk reaches past what the data holds.
func stepInto(n node, seg string) (node, error) {
if isSelector(seg) {
return nil, fmt.Errorf("%s is not a table, so it has no row to select", selectorOf(seg))
}
switch n := n.(type) {
case *folder:
child, ok := n.children[tail[0]]
if !ok {
return nil, fmt.Errorf("no entry %q", tail[0])
if child, ok := n.children[seg]; ok {
return child, nil
}
return walkPath(child, tail[1:], w)
return nil, fmt.Errorf("no entry %q", seg)
case *template:
if w.level != nil {
if err := w.level(n, tail); err != nil {
if child, ok := n.fields[seg]; ok {
return child, nil
}
case *tableColumn:
return nil, fmt.Errorf("no field %q: %q is a column, and a cell holds no fields", seg, n.t.header[n.i])
}
return nil, fmt.Errorf("no field %q", seg)
}
// tableRoute is how a path passes one table: the row it reads, by selector or
// drawn, and what it goes on into, a linked table it descends to among them.
type tableRoute struct {
sel string
draw bool
next node
rest []string
descends bool
}
// route is how tail passes t, reached from another table where descended. The path
// 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 (t *table) route(tail []string, descended bool) (tableRoute, error) {
sel, tail, err := t.selector(tail)
if err != nil {
return tableRoute{}, err
}
column, child, err := t.step(tail)
if err != nil {
return tableRoute{}, err
}
// 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.
r := tableRoute{sel: sel, draw: sel == "" && (descended || len(tail) > 0) && !hasSelector(tail)}
switch {
case len(tail) == 0 && sel == "" && !descended:
r.next = t
case len(tail) == 0:
r.next = t.pinnedRow
case child != nil:
r.next, r.rest, r.descends = child, tail[1:], true
default:
r.next, r.rest = column, tail[1:]
}
return r, nil
}
// proveWalk proves a path resolves whichever way the draws go: every variant of a
// choice carries the rest of it, and is walked, a selector names a row inside the
// rows selected before it, and no level read carries a repeat or a drawGroup. It
// collects every leaf the path may render; level and tail name a refused level.
type proveWalk struct {
pins pinSet
level string
tail []string
leaves []node
}
func (w *proveWalk) walk(n node, tail []string) (node, error) {
for descended := false; len(tail) > 0 || descended; {
var err error
switch x := n.(type) {
case *choice:
return w.walkEvery(x, tail)
case *table:
var r tableRoute
if r, err = x.route(tail, descended); err != nil {
return nil, err
}
if r.sel != "" {
if err := w.pins.selectRow(x, r.sel); err != nil {
return nil, err
}
}
child, ok := n.fields[tail[0]]
if !ok {
return nil, fmt.Errorf("no field %q", tail[0])
n, tail, descended = r.next, r.rest, r.descends
continue
case *template:
if err := w.enter(x, tail); err != nil {
return nil, err
}
return walkPath(child, tail[1:], w)
case *choice:
return walkChoice(n, tail, w)
case *tableColumn:
return nil, fmt.Errorf("no field %q: %q is a column, and a cell holds no fields", tail[0], n.t.header[n.i])
}
return nil, fmt.Errorf("no field %q", tail[0])
if n, err = stepInto(n, tail[0]); err != nil {
return nil, err
}
tail = tail[1:]
}
w.leaves = append(w.leaves, n)
return n, nil
}
func (w pathWalk) atLeaf(n node) error {
if w.leaf != nil {
return w.leaf(n)
}
return nil
}
// drawPath draws the rows and variants a path reads, pinning them in pins. The path
// must be proved first, by a probe or by New's fences, so the walk cannot fail.
// head is what n is reached by, for the panic.
func drawPath(n node, tail []string, head string, pins *pinSet, draws *pathDraws) node {
leaf, err := walkPath(n, tail, pathWalk{mode: walkDraw, pins: pins, draws: draws})
if err != nil {
panic(fmt.Sprintf("fejkdata: %s: %v; the path should have been proved before it was drawn", join(head, joinSegments(tail)), err))
}
return leaf
}
func walkChoice(c *choice, tail []string, w pathWalk) (node, error) {
switch w.mode {
case walkCover:
return c, w.atLeaf(c)
case walkDraw:
return walkPath(w.draws.variant(c, tail), tail, w)
}
func (w *proveWalk) walkEvery(c *choice, tail []string) (node, error) {
if err := carriedByAll(c, tail); err != nil {
return nil, err
}
if w.mode == walkProbe {
return walkPath(c.items[0], tail, w)
}
var last node
for _, item := range c.items {
var err error
if last, err = walkPath(item, tail, w); err != nil {
if last, err = w.walk(item, tail); err != nil {
return nil, err
}
}
return last, nil
}
// walkTable descends tail from a table: a selector first, then a column or a
// 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) {
sel, tail, err := t.selector(tail)
func (w *proveWalk) enter(t *template, rest []string) error {
name := join(w.level, strings.Join(w.tail[:len(w.tail)-len(rest)], "."))
switch {
case t.repeat > 1:
return fmt.Errorf("the level %q carries a repeat, which a path reading one draw of it cannot apply", name)
case t.drawGroup != "":
return fmt.Errorf("the level %q carries a drawGroup, which a path reading into it cannot apply", name)
}
return nil
}
// coverWalk collects into what holding a path pins: every choice level it passes
// through, whole, and the leaf it renders. It stops at a choice and reads no row.
type coverWalk struct{ into map[node]bool }
func (w *coverWalk) walk(n node, tail []string) (node, error) {
for descended := false; len(tail) > 0 || descended; {
var err error
switch x := n.(type) {
case *choice:
cover(x, w.into, false)
return x, nil
case *table:
var r tableRoute
if r, err = x.route(tail, descended); err != nil {
return nil, err
}
n, tail, descended = r.next, r.rest, r.descends
continue
}
if n, err = stepInto(n, tail[0]); err != nil {
return nil, err
}
tail = tail[1:]
}
cover(n, w.into, false)
return n, nil
}
// probeWalk proves a path as proveWalk does, walking one variant of a choice, so a
// path that resolves resolves whichever variants and rows are drawn. It draws
// nothing, marking in drawn the tables a draw would pin.
type probeWalk struct {
pins pinSet
drawn map[*table]bool
}
func (w *probeWalk) walk(n node, tail []string) (node, error) {
for descended := false; len(tail) > 0 || descended; {
var err error
switch x := n.(type) {
case *choice:
if err := carriedByAll(x, tail); err != nil {
return nil, err
}
n = x.items[0]
continue
case *table:
var r tableRoute
if r, err = x.route(tail, descended); err != nil {
return nil, err
}
if err := w.readRow(x, r); err != nil {
return nil, err
}
n, tail, descended = r.next, r.rest, r.descends
continue
}
if n, err = stepInto(n, tail[0]); err != nil {
return nil, err
}
tail = tail[1:]
}
return n, nil
}
func (w *probeWalk) readRow(t *table, r tableRoute) error {
if r.sel != "" {
return w.pins.selectRow(t, r.sel)
}
if !r.draw {
return nil
}
if w.drawn == nil {
w.drawn = map[*table]bool{}
}
for stop := w.pins.nearestPinned(t); t != stop; t = t.parentT {
w.drawn[t] = true
}
return nil
}
// renderWalk draws the rows and variants a proved path reads, pinning them in pins.
// For a held read, held keeps the variant drawn at each level of a, so paths
// sharing a prefix share it.
type renderWalk struct {
s *session
pins *pinSet
held *hold
a *arm
}
// drawPath walks w over a path proved first, by a probe or by New's fences, so the
// walk cannot fail. head is what n is reached by, for the panic.
func drawPath(n node, tail []string, head string, w *renderWalk) node {
leaf, err := w.walk(n, tail)
if err != nil {
return nil, err
panic(fmt.Sprintf("fejkdata: %s: %v; the path should have been proved before it was drawn", join(head, joinSegments(tail)), err))
}
column, child, err := t.step(tail)
if err != nil {
return nil, err
return leaf
}
// 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.mode != walkCover {
if err := readRow(w, t, sel, (descended || len(tail) > 0) && !hasSelector(tail)); err != nil {
func (w *renderWalk) walk(n node, tail []string) (node, error) {
for descended := false; len(tail) > 0 || descended; {
var err error
switch x := n.(type) {
case *choice:
n = w.variant(x, tail)
continue
case *table:
var r tableRoute
if r, err = x.route(tail, descended); err != nil {
return nil, err
}
}
switch {
case len(tail) == 0 && sel == "" && !descended:
return walkPath(t, nil, w)
case len(tail) == 0:
return walkPath(t.pinnedRow, nil, w)
case child != nil:
return walkTable(child, tail[1:], w, true)
case r.sel != "":
if err := w.pins.selectRow(x, r.sel); err != nil {
return nil, err
}
return walkPath(column, tail[1:], w)
case r.draw:
x.drawIn(w.s, w.pins)
}
n, tail, descended = r.next, r.rest, r.descends
continue
}
if n, err = stepInto(n, tail[0]); err != nil {
return nil, err
}
tail = tail[1:]
}
return n, nil
}
// variant is the variant of c the walk continues into: the one held for this level
// of the read, drawn once, where the walk holds its draws; else one drawn afresh.
func (w *renderWalk) variant(c *choice, rest []string) node {
if w.held == nil {
return pick(w.s, c)
}
key := w.a.levels[len(w.a.tail)-len(rest)]
n, drew := w.held.variant[key]
if !drew {
n = resolveChoice(w.s, c)
if w.held.variant == nil {
w.held.variant = map[string]node{}
}
w.held.variant[key] = n
}
return n
}
// selector splits the selector a tail starts with from the rest of it.
@@ -301,41 +429,6 @@ func (t *table) step(tail []string) (column node, child *table, err error) {
return nil, child, nil
}
// readRow pins the row a path reads of t: the one its selector names, or one drawn
// where the path reads into the table.
func readRow(w pathWalk, t *table, sel string, draw bool) error {
switch {
case sel != "":
return w.pins.selectRow(t, sel)
case !draw:
case w.mode == walkDraw:
t.drawIn(w.draws.s, w.pins)
case w.drawn != nil:
for stop := w.pins.nearestPinned(t); t != stop; t = t.parentT {
w.drawn[t] = true
}
}
return nil
}
// variant is the variant of c the walk continues into: the one held for this level
// of the read, drawn once, where the walk holds its draws; else one drawn afresh.
func (d *pathDraws) variant(c *choice, rest []string) node {
if d.held == nil {
return pick(d.s, c)
}
key := d.a.levels[len(d.a.tail)-len(rest)]
n, drew := d.held.variant[key]
if !drew {
n = resolveChoice(d.s, c)
if d.held.variant == nil {
d.held.variant = map[string]node{}
}
d.held.variant[key] = n
}
return n
}
// carriedByAll is the choice rule a path that must resolve on every call obeys:
// the rest of the tail must be one every variant carries.
func carriedByAll(c *choice, rest []string) error {
@@ -360,26 +453,9 @@ func unreachableInChoice(c *choice, want string) error {
return fmt.Errorf("not every variant of this %d-way choice carries %q; all carry %v", len(c.items), want, offered)
}
// checkPath proves a dotted tail resolves whichever way the draws go — a choice
// must carry the rest of the path in the set every variant shares, a selector must
// name one row inside the rows selected before it — and that no level a path reads
// 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.
// checkPath proves a dotted tail as proveWalk does, 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 pinSet
_, err := walkPath(n, tail, pathWalk{
mode: walkEvery,
level: func(t *template, rest []string) error {
name := join(level, strings.Join(tail[:len(tail)-len(rest)], "."))
switch {
case t.repeat > 1:
return fmt.Errorf("the level %q carries a repeat, which a path reading one draw of it cannot apply", name)
case t.drawGroup != "":
return fmt.Errorf("the level %q carries a drawGroup, which a path reading into it cannot apply", name)
}
return nil
},
pins: &pins,
})
_, err := (&proveWalk{level: level, tail: tail}).walk(n, tail)
return err
}
+1 -3
View File
@@ -187,9 +187,7 @@ func eachTemplate(root map[string]node, fn func(folder []string, path string, t
}
// resolveCategory walks a dotted path through the folders to the category it
// names, returning that head, the node, and the tail left to read into it. A
// descent of its own rather than a walkPath: it walks folders only and returns
// where they end, not a leaf.
// names, returning that head, the node, and the tail left to read into it.
func resolveCategory(root map[string]node, segments []string) (head []string, target node, tail []string, err error) {
var n node = &folder{children: root}
i := 0
+2 -3
View File
@@ -44,11 +44,10 @@ 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 pinSet
if _, err := walkPath(root, segments, pathWalk{mode: walkProbe, pins: &probe}); err != nil {
if _, err := (&probeWalk{}).walk(root, segments); err != nil {
return nil, err
}
return drawPath(root, segments, "", &sc.hold().pins, &pathDraws{s: s}), nil
return drawPath(root, segments, "", &renderWalk{s: s, pins: &sc.hold().pins}), nil
}
// render evaluates a compiled node to a string. compile validates every node up