diff --git a/family.go b/family.go index 87ddc28..06675ea 100644 --- a/family.go +++ b/family.go @@ -33,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(t, a.tail, pathWalk{mode: walkProbe, pins: &tr.pins}) written := a.name[:len(a.name)-len(joinSegments(a.tail))] cur := t tr.draws(cur) diff --git a/graph.go b/graph.go index b6d8359..fc43333 100644 --- a/graph.go +++ b/graph.go @@ -165,10 +165,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{ - choice: func(c *choice, _ []string) ([]node, error) { return c.items, nil }, - leaf: func(n node) error { out = append(out, n); return nil }, - }) + _, _ = walkPath(n, tail, pathWalk{mode: walkEvery, pins: &pinSet{}, leaf: func(n node) error { out = append(out, n); return nil }}) return out } diff --git a/hold.go b/hold.go index 42c7d0f..2b579d4 100644 --- a/hold.go +++ b/hold.go @@ -37,11 +37,7 @@ func readField(s *session, t *template, held *hold, sc renderScope, a arm) draw if r, done := d.value[a.path]; done { return r } - leaf, err := walkPath(t.head(a.key), a.tail, pathWalk{pins: &d.pins, draws: &pathDraws{s: s, held: d, a: &a}}) - if err != nil { - panic(fmt.Sprintf("fejkdata: %q: %v; a fence should have refused this at New", a.name, err)) - } - r := renderLeaf(s, leaf, sc) + r := renderLeaf(s, drawPath(t.head(a.key), a.tail, &d.pins, &pathDraws{s: s, held: d, a: &a}), sc) if d.value == nil { d.value = map[string]draw{} } diff --git a/holdfence.go b/holdfence.go index 1ab1102..bef29f8 100644 --- a/holdfence.go +++ b/holdfence.go @@ -100,10 +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{ - 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 }, - }) + _, _ = walkPath(n, tail, pathWalk{mode: walkCover, leaf: func(n node) error { cover(n, into, false); return nil }}) } // cover collects a level and everything contained in it. A fixed string outside a diff --git a/path.go b/path.go index e80a60a..ec82450 100644 --- a/path.go +++ b/path.go @@ -125,21 +125,33 @@ func names(segs []string) []string { return out } -// 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 draws, takes the first where draws is nil, or stops where -// pins is nil too; 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; draws is what draws the rows and -// variants the path reads, nil proving instead that the path resolves whichever -// are drawn. A nil action is skipped. A render's walk sets only pins and draws, so -// it allocates nothing. +// walkMode is what a walk does at a choice, and whether it reads the rows a path +// selects and draws. +type walkMode uint8 + +const ( + // walkCover stops at a choice, handing it whole to leaf, and reads no row. + walkCover walkMode = iota + // walkEvery proves every variant carries the rest of the path and walks each, + // pinning the rows it selects in pins. + walkEvery + // walkProbe proves as walkEvery does, walking one variant, so a path that + // resolves resolves whichever variants and rows are drawn. + 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. A render's +// walk sets only pins and draws, 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 *pinSet - draws *pathDraws + mode walkMode + level func(t *template, rest []string) error + leaf func(n node) error + pins *pinSet + draws *pathDraws } // pathDraws is what a walk draws with: the session, and, for a held read, the hold @@ -151,7 +163,7 @@ type pathDraws struct { } // 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 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) { @@ -197,27 +209,32 @@ func (w pathWalk) atLeaf(n node) error { return nil } -// walkChoice continues a walk into the variants w.choice returns, or into one -// w.draws draws where the walk names no choice action. +// 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. +func drawPath(n node, tail []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", joinSegments(tail), err)) + } + return leaf +} + func walkChoice(c *choice, tail []string, w pathWalk) (node, error) { - if w.choice == nil { - if w.pins == nil { - return nil, nil - } - if w.draws == nil { // a probe: every variant carries the tail, so any one proves it - if err := carriedByAll(c, tail); err != nil { - return nil, err - } - return walkPath(c.items[0], tail, w) - } + switch w.mode { + case walkCover: + return c, w.atLeaf(c) + case walkDraw: return walkPath(w.draws.variant(c, tail), tail, w) } - next, err := w.choice(c, tail) - if err != nil { + 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 next { + for _, item := range c.items { + var err error if last, err = walkPath(item, tail, w); err != nil { return nil, err } @@ -240,8 +257,8 @@ 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, w.draws, t, sel, (descended || len(tail) > 0) && !hasSelector(tail)); err != nil { + if w.mode != walkCover { + if err := readRow(w, t, sel, (descended || len(tail) > 0) && !hasSelector(tail)); err != nil { return nil, err } } @@ -284,12 +301,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(p *pinSet, draws *pathDraws, t *table, sel string, draw bool) error { +func readRow(w pathWalk, t *table, sel string, draw bool) error { if sel != "" { - return p.selectRow(t, sel) + return w.pins.selectRow(t, sel) } - if draw && draws != nil { - t.drawIn(draws.s, p) + if draw && w.mode == walkDraw { + t.drawIn(w.draws.s, w.pins) } return nil } @@ -344,12 +361,7 @@ func unreachableInChoice(c *choice, want string) error { func checkPath(n node, tail []string, level string) error { var pins pinSet _, err := walkPath(n, tail, pathWalk{ - choice: func(c *choice, rest []string) ([]node, error) { - if err := carriedByAll(c, rest); err != nil { - return nil, err - } - return c.items, nil - }, + mode: walkEvery, level: func(t *template, rest []string) error { name := join(level, strings.Join(tail[:len(tail)-len(rest)], ".")) switch { diff --git a/render.go b/render.go index b0787ff..ef02a75 100644 --- a/render.go +++ b/render.go @@ -45,10 +45,10 @@ func (f *Generator) Fake(path string) (string, 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. var probe pinSet - if _, err := walkPath(root, segments, pathWalk{pins: &probe}); err != nil { + if _, err := walkPath(root, segments, pathWalk{mode: walkProbe, pins: &probe}); err != nil { return nil, err } - return walkPath(root, segments, pathWalk{pins: &sc.hold().pins, draws: &pathDraws{s: s}}) + return drawPath(root, segments, &sc.hold().pins, &pathDraws{s: s}), nil } // render evaluates a compiled node to a string. compile validates every node up diff --git a/todo.md b/todo.md index 3bb0ae8..608460f 100644 --- a/todo.md +++ b/todo.md @@ -19,17 +19,12 @@ Self-sufficiency 5.6), every seat capped by Locality or Self-sufficiency. This round, in order: -- Name `pathWalk`'s modes: render, probe, check and cover are chosen today by which - callbacks are nil and whether `draws` is, and `walkChoice` returns `nil, nil` where - none is set. Make the render mode reachable only through a proved path: a walk that - draws no longer checks `carriedByAll`, trusting its caller's probe or `New`'s fences. - Have the check mode report the tables a read draws, so `tableRead.drawn` and `entered`'s `pinsAncestors` flag stop restating `walkTable`'s draw condition and `table.drawIn`'s ancestor rule: a change to how a render draws rows still leaves the fence proving the old rule. Hold a row rendered whole in `drawAt` beside `drawn`, so `pinSet` holds only what `pin` pins and `alternatives` needs no exception. Settle a - cell of a municipality drawn whole beside a path pinning a region it is not in. Needs - the item above. + cell of a municipality drawn whole beside a path pinning a region it is not in. - Register a transform once: `transforms` restates the `lowercase`, `uppercase` and `ascii` entries in `builtins`, and `valueProof.template` classifies by the first while render calls the second. Derive the numeric builtins `valueProof.template` lists in