Answer architecture round 1: List's walk and join sit in path.go, a compiled path's head is named head, every pick key comes from levelKeys, and readsColumn names all four readers

This commit is contained in:
2026-10-09 11:00:40 +02:00
committed by Lillem4n
parent dc03021a5d
commit 5c9b85e362
10 changed files with 109 additions and 94 deletions
+2 -3
View File
@@ -982,7 +982,6 @@ Where the project is heading; the sections before Audience document what ships t
``` ```
doc.go the package doc, and the vocabulary the package is written in doc.go the package doc, and the vocabulary the package is written in
fejkdata.go Generator, New, options, List fejkdata.go Generator, New, options, List
list.go the paths List advertises: direct descents only
shipped.go the embedded data set, and loading a shipped category on the first call reaching it shipped.go the embedded data set, and loading a shipped category on the first call reaching it
shippedindex.go the index of shipped categories, generated from data/ shippedindex.go the index of shipped categories, generated from data/
node.go the node model and JSON -> node compilation node.go the node model and JSON -> node compilation
@@ -991,7 +990,7 @@ name.go names: {x as n} bindings, the scopes they live in, the reads the
namefence.go the load fences over names namefence.go the load fences over names
pick.go a render's named picks: the key of each level a pick keeps a draw at, built at load and read at render, the frame of each name scope rendering, the memo a pick keeps, and the reads kept under it pick.go a render's named picks: the key of each level a pick keeps a draw at, built at load and read at render, the frame of each name scope rendering, the memo a pick keeps, and the reads kept under it
env.go renderEnv: what a render reads names and rows through, the stack of frames it renders in, and the frames a read opens and finds env.go renderEnv: what a render reads names and rows through, the stack of frames it renders in, and the frames a read opens and finds
path.go the dotted-path walks, proving a path reaches a node, and the sub-paths every variant of a choice carries path.go the dotted-path walks: the paths List advertises, the sub-paths every variant of a choice carries, proving a path reaches a node, and drawing it
render.go Fake, the recursive renderer (choices, format strings, expansions) and the reads its tokens make render.go Fake, the recursive renderer (choices, format strings, expansions) and the reads its tokens make
record.go records: Record, the JSON/CSV/SQL serializers, and their entry points record.go records: Record, the JSON/CSV/SQL serializers, and their entry points
struct.go structs: FakeStruct, fake tags as one record's columns, and a field's Go type as its column's datatype struct.go structs: FakeStruct, fake tags as one record's columns, and a field's Go type as its column's datatype
@@ -1001,7 +1000,7 @@ arm.go the compiled read of one {a|b} alternative or operand: its head,
reference.go one reference's resolution: its sigil, its category, its tail reference.go one reference's resolution: its sigil, its category, its tail
resolve.go resolving templates against the assembled tree in steps, each over every template before the next resolve.go resolving templates against the assembled tree in steps, each over every template before the next
graph.go the node graph: tree walks, containment and render edges, and the cycle and repeat fences graph.go the node graph: tree walks, containment and render edges, and the cycle and repeat fences
sorted.go sortedNames, a map's keys sorted, and join, two dotted paths joined sorted.go sortedNames: a map's keys, sorted
calc.go the {calc()} operand checks: never a number, and a constant zero divisor calc.go the {calc()} operand checks: never a number, and a constant zero divisor
datatype.go column datatypes: DataType, a template's declared datatype, a column's datatype, and a format that only reads a column datatype.go column datatypes: DataType, a template's declared datatype, a column's datatype, and a format that only reads a column
valueproof.go the value proof: what a typed column, calc operand or struct field holds, checked at load valueproof.go the value proof: what a typed column, calc operand or struct field holds, checked at load
+2 -2
View File
@@ -39,13 +39,13 @@ func splitArm(name string, refs map[string]resolvedRef) arm {
if resolved { if resolved {
head = r.head head = r.head
} }
return arm{spelling: name, head: head, levels: []pickKey{pickKey(head)}} return arm{spelling: name, head: head, levels: levelKeys([]string{head}, 1)}
} }
return pathArm(name, r.head, r.tail) return pathArm(name, r.head, r.tail)
} }
segs, err := grammar.SplitPath(name) segs, err := grammar.SplitPath(name)
if err != nil || len(segs) == 1 { if err != nil || len(segs) == 1 {
return arm{spelling: name, head: name, levels: []pickKey{pickKey(name)}} return arm{spelling: name, head: name, levels: levelKeys([]string{name}, 1)}
} }
return pathArm(name, segs[0], segs[1:]) return pathArm(name, segs[0], segs[1:])
} }
+3 -3
View File
@@ -29,9 +29,9 @@ package fejkdata
// `namedPick`, `pickFrame`. // `namedPick`, `pickFrame`.
// - memo — what a named pick keeps its draws in, the variant drawn at each level // - memo — what a named pick keeps its draws in, the variant drawn at each level
// and the value each path read: `drawMemo`. // and the value each path read: `drawMemo`.
// - level — a prefix of a read's path, from where it starts to its leaf: `pickKey`. // - level — `pickKey`.
// - pick key — the key a memo keeps a level's draw under: `pickKey`. // - pick key — `pickKey`.
// - addressed key — a pick key some read of a name lands on or passes: `pickKey`. // - addressed key — `pickKey`.
// - pin — fixing which row of a table one path, or one named pick, uses: // - pin — fixing which row of a table one path, or one named pick, uses:
// `pinSet`, `namedPick.pins`, `table.followStep`. // `pinSet`, `namedPick.pins`, `table.followStep`.
// - link — a table's tie to its parent table, each row to its parent row: // - link — a table's tie to its parent table, each row to its parent row:
+22
View File
@@ -0,0 +1,22 @@
# Comprehension history
## 2026-10-09T08:59:19Z, PR #190 at 17f732a, against ee4b40b
Ruling: better
| Seat | Vote |
|---|---|
| Mid A | better |
| Inherited architect | better |
Mid A, decided by:
- `pick.go:9` `pickKey`: the definition sits on the type, beside `levelKeys`, `addressedKeys`, `keptInPick` and `readUnder`, where four files held them
- `render.go:155` `readField`: one named predicate, `keptInPick`, states the whole keep rule
- `datatype.go:217` `columnRead`: beside the code that reads it
Inherited architect, decided by:
- `pick.go:9` `pickKey`: the 3am path for a named pick now ends in one file, with its worked example
- `render.go:155` `readField`: `keptInPick` holds its own nil and reference checks
- `path.go:72` `compiledPath`: the field no longer reuses the vocabulary word level
+1 -2
View File
@@ -94,8 +94,7 @@ type renderEdge struct {
label string label string
} }
// renderEdges lists the children rendering n recurses into, mirroring render: a // renderEdges lists the children rendering n recurses into, mirroring render.
// choice's items, and a template's field/reference tokens plus its operands.
func renderEdges(n node) []renderEdge { func renderEdges(n node) []renderEdge {
switch n := n.(type) { switch n := n.(type) {
case *choice: case *choice:
-64
View File
@@ -1,64 +0,0 @@
package fejkdata
import (
"sort"
"github.com/larvit/fejkdata/internal/invariant"
)
// paths lists the dot paths addressable from n, relative to it, where "" is n
// itself. A folder has no value of its own, so it contributes only its children's.
// With intoRepeats, it also lists the paths below a level carrying a repeat, so a path
// there meets the repeat's refusal.
func paths(n node, intoRepeats bool) []string {
switch n := n.(type) {
case *folder:
var out []string
for _, name := range sortedNames(n.children) {
out = appendUnder(out, name, paths(n.children[name], intoRepeats))
}
for _, name := range sortedNames(n.unloaded) {
out = appendUnder(out, name, n.unloaded[name].paths)
}
return out
case *template:
out := []string{""}
if n.repeat > 1 && !intoRepeats {
return out
}
for _, name := range sortedNames(n.fields) {
out = appendUnder(out, name, paths(n.fields[name], intoRepeats))
}
return out
case *table:
return tablePaths(n, intoRepeats)
case *tableColumn, *tableRow, *nullItem:
return []string{""}
case *choice:
shared := n.shared
if !intoRepeats {
shared = sharedPaths(n.items, false)
}
return append([]string{""}, sortedNames(shared)...)
default:
panic(invariant.Broken("paths has no case for node %T", n))
}
}
func appendUnder(out []string, name string, ps []string) []string {
for _, p := range ps {
out = append(out, join(name, p))
}
return out
}
// tablePaths is a table's columns, then each table linked to it under its name: the
// direct descents, a step at a time.
func tablePaths(t *table, intoRepeats bool) []string {
out := append([]string{""}, t.rows.Header()...)
sort.Strings(out[1:])
for _, c := range t.rows.Children() {
out = appendUnder(out, c.Segment(), paths(c.Owner(), intoRepeats))
}
return out
}
+2 -1
View File
@@ -68,7 +68,8 @@ type template struct {
refs templateRefs refs templateRefs
compiled formatOps compiled formatOps
// readsColumn is set when the format only reads one reference or name, and that read is a // readsColumn is set when the format only reads one reference or name, and that read is a
// record's column. renderLeaf, proveColumnItem and itemDatatype each read it: change them together. // record's column. checkColumns, itemDatatype, proveColumnItem and renderLeaf each read it: change
// them together.
readsColumn *columnRead readsColumn *columnRead
// Filled by `settleRecords`, once the load refused every cycle: // Filled by `settleRecords`, once the load refused every cycle:
+76 -8
View File
@@ -2,6 +2,7 @@ package fejkdata
import ( import (
"fmt" "fmt"
"sort"
"strings" "strings"
"github.com/larvit/fejkdata/internal/drawstate" "github.com/larvit/fejkdata/internal/drawstate"
@@ -68,10 +69,10 @@ const (
// compiledPath walks a path and proves it reaches a node whichever way the draws go: every // compiledPath walks a path and proves it reaches a node 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 // 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. Once run, it holds the steps a // rows selected before it, and no level read carries a repeat. Once run, it holds the steps a
// draw takes, and every leaf the path may render; label names the head in its errors. // draw takes, and every leaf the path may render; head names where it starts in its errors.
type compiledPath struct { type compiledPath struct {
pins pinSet pins pinSet
label string head string
tail []string tail []string
steps []pathStep steps []pathStep
leaves []node leaves []node
@@ -88,7 +89,7 @@ func (w *compiledPath) walk(n node, tail []string) (node, error) {
return w.walkEvery(c, at.tail) return w.walkEvery(c, at.tail)
} }
var err error var err error
if w.steps, err = compileStep(&at, w.tail, w.label, w.steps, &w.pins); err != nil { if w.steps, err = compileStep(&at, w.tail, w.head, w.steps, &w.pins); err != nil {
return nil, err return nil, err
} }
} }
@@ -112,8 +113,8 @@ func (p pathPos) more() bool { return len(p.tail) > 0 || p.fromRow }
// compileStep takes the first step of at.tail from at.n, which must not be a choice, moves at past // compileStep takes the first step of at.tail from at.n, which must not be a choice, moves at past
// it, and appends the step to steps: a field, or the route through a table. whole is the full // it, and appends the step to steps: a field, or the route through a table. whole is the full
// path; label is the prefix an error puts before it. // path; head is the prefix an error puts before it.
func compileStep(at *pathPos, whole []string, label string, steps []pathStep, pins *pinSet) ([]pathStep, error) { func compileStep(at *pathPos, whole []string, head string, steps []pathStep, pins *pinSet) ([]pathStep, error) {
i := len(whole) - len(at.tail) i := len(whole) - len(at.tail)
switch x := at.n.(type) { switch x := at.n.(type) {
case *table: case *table:
@@ -125,7 +126,7 @@ func compileStep(at *pathPos, whole []string, label string, steps []pathStep, pi
return routeSteps(steps, pins, x, r, i) return routeSteps(steps, pins, x, r, i)
case *template: case *template:
if x.repeat > 1 && !grammar.IsSelector(at.tail[0]) { if x.repeat > 1 && !grammar.IsSelector(at.tail[0]) {
return steps, repeatLevelError(join(label, strings.Join(whole[:i], "."))) return steps, repeatLevelError(join(head, grammar.JoinSegments(whole[:i])))
} }
} }
next, err := childNamed(at.n, at.tail[0]) next, err := childNamed(at.n, at.tail[0])
@@ -222,6 +223,73 @@ func drawVariant(s *drawstate.State, c *choice, memo *drawMemo, levels []pickKey
return memo.variantOf(s, c, levels[at]) return memo.variantOf(s, c, levels[at])
} }
// paths lists the dot paths addressable from n, relative to it, where "" is n
// itself. A folder has no value of its own, so it contributes only its children's.
// With intoRepeats, it also lists the paths below a level carrying a repeat, so a path
// there meets the repeat's refusal.
func paths(n node, intoRepeats bool) []string {
switch n := n.(type) {
case *folder:
var out []string
for _, name := range sortedNames(n.children) {
out = appendUnder(out, name, paths(n.children[name], intoRepeats))
}
for _, name := range sortedNames(n.unloaded) {
out = appendUnder(out, name, n.unloaded[name].paths)
}
return out
case *template:
out := []string{""}
if n.repeat > 1 && !intoRepeats {
return out
}
for _, name := range sortedNames(n.fields) {
out = appendUnder(out, name, paths(n.fields[name], intoRepeats))
}
return out
case *table:
return tablePaths(n, intoRepeats)
case *tableColumn, *tableRow, *nullItem:
return []string{""}
case *choice:
shared := n.shared
if !intoRepeats {
shared = sharedPaths(n.items, false)
}
return append([]string{""}, sortedNames(shared)...)
default:
panic(invariant.Broken("paths has no case for node %T", n))
}
}
func appendUnder(out []string, name string, ps []string) []string {
for _, p := range ps {
out = append(out, join(name, p))
}
return out
}
// tablePaths is a table's columns, then each table linked to it under its name: the
// direct descents, a step at a time.
func tablePaths(t *table, intoRepeats bool) []string {
out := append([]string{""}, t.rows.Header()...)
sort.Strings(out[1:])
for _, c := range t.rows.Children() {
out = appendUnder(out, c.Segment(), paths(c.Owner(), intoRepeats))
}
return out
}
func join(prefix, name string) string {
switch {
case prefix == "":
return name
case name == "":
return prefix
}
return prefix + "." + name
}
// sharedPaths is the sub-paths every item carries — the only ones a path may step // sharedPaths is the sub-paths every item carries — the only ones a path may step
// through a choice to reach. It intersects, bailing as soon as the set // through a choice to reach. It intersects, bailing as soon as the set
// is empty, which is immediate for a choice of plain strings. // is empty, which is immediate for a choice of plain strings.
@@ -271,8 +339,8 @@ 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) return fmt.Errorf("not every variant of this %d-way choice carries %q; all carry %v", len(c.items), want, offered)
} }
func provePath(n node, tail []string, label string) error { func provePath(n node, tail []string, head string) error {
_, err := (&compiledPath{label: label, tail: tail}).run(n) _, err := (&compiledPath{head: head, tail: tail}).run(n)
return err return err
} }
+1 -1
View File
@@ -303,4 +303,4 @@ func renderRecord(s *drawstate.State, t *template, env renderEnv) *Record {
} }
// wholeLevels is the levels of a read of a whole name, as compileArm compiles {n}. // wholeLevels is the levels of a read of a whole name, as compileArm compiles {n}.
var wholeLevels = []pickKey{""} var wholeLevels = levelKeys(nil, 0)
-10
View File
@@ -10,13 +10,3 @@ func sortedNames[V any](m map[string]V) []string {
sort.Strings(names) sort.Strings(names)
return names return names
} }
func join(prefix, name string) string {
switch {
case prefix == "":
return name
case name == "":
return prefix
}
return prefix + "." + name
}