diff --git a/README.md b/README.md index bbbc200..86eb797 100644 --- a/README.md +++ b/README.md @@ -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 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 shippedindex.go the index of shipped categories, generated from data/ 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 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 -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 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 @@ -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 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 -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 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 diff --git a/arm.go b/arm.go index a17688f..a30ab93 100644 --- a/arm.go +++ b/arm.go @@ -39,13 +39,13 @@ func splitArm(name string, refs map[string]resolvedRef) arm { if resolved { 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) } segs, err := grammar.SplitPath(name) 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:]) } diff --git a/doc.go b/doc.go index 42ad4f7..ffb015b 100644 --- a/doc.go +++ b/doc.go @@ -29,9 +29,9 @@ package fejkdata // `namedPick`, `pickFrame`. // - memo — what a named pick keeps its draws in, the variant drawn at each level // and the value each path read: `drawMemo`. -// - level — a prefix of a read's path, from where it starts to its leaf: `pickKey`. -// - pick key — the key a memo keeps a level's draw under: `pickKey`. -// - addressed key — a pick key some read of a name lands on or passes: `pickKey`. +// - level — `pickKey`. +// - pick key — `pickKey`. +// - addressed key — `pickKey`. // - pin — fixing which row of a table one path, or one named pick, uses: // `pinSet`, `namedPick.pins`, `table.followStep`. // - link — a table's tie to its parent table, each row to its parent row: diff --git a/docs/comprehension-history.md b/docs/comprehension-history.md new file mode 100644 index 0000000..a76056b --- /dev/null +++ b/docs/comprehension-history.md @@ -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 diff --git a/graph.go b/graph.go index 90bead5..ffbfbb8 100644 --- a/graph.go +++ b/graph.go @@ -94,8 +94,7 @@ type renderEdge struct { label string } -// renderEdges lists the children rendering n recurses into, mirroring render: a -// choice's items, and a template's field/reference tokens plus its operands. +// renderEdges lists the children rendering n recurses into, mirroring render. func renderEdges(n node) []renderEdge { switch n := n.(type) { case *choice: diff --git a/list.go b/list.go deleted file mode 100644 index fc9ccdf..0000000 --- a/list.go +++ /dev/null @@ -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 -} diff --git a/node.go b/node.go index 6538b03..9f7406a 100644 --- a/node.go +++ b/node.go @@ -68,7 +68,8 @@ type template struct { refs templateRefs compiled formatOps // 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 // Filled by `settleRecords`, once the load refused every cycle: diff --git a/path.go b/path.go index c4f6ae2..0781fea 100644 --- a/path.go +++ b/path.go @@ -2,6 +2,7 @@ package fejkdata import ( "fmt" + "sort" "strings" "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 // 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 -// 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 { pins pinSet - label string + head string tail []string steps []pathStep leaves []node @@ -88,7 +89,7 @@ func (w *compiledPath) walk(n node, tail []string) (node, error) { return w.walkEvery(c, at.tail) } 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 } } @@ -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 // 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. -func compileStep(at *pathPos, whole []string, label string, steps []pathStep, pins *pinSet) ([]pathStep, error) { +// path; head is the prefix an error puts before it. +func compileStep(at *pathPos, whole []string, head string, steps []pathStep, pins *pinSet) ([]pathStep, error) { i := len(whole) - len(at.tail) switch x := at.n.(type) { case *table: @@ -125,7 +126,7 @@ func compileStep(at *pathPos, whole []string, label string, steps []pathStep, pi return routeSteps(steps, pins, x, r, i) case *template: 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]) @@ -222,6 +223,73 @@ func drawVariant(s *drawstate.State, c *choice, memo *drawMemo, levels []pickKey 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 // through a choice to reach. It intersects, bailing as soon as the set // 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) } -func provePath(n node, tail []string, label string) error { - _, err := (&compiledPath{label: label, tail: tail}).run(n) +func provePath(n node, tail []string, head string) error { + _, err := (&compiledPath{head: head, tail: tail}).run(n) return err } diff --git a/record.go b/record.go index 78ded03..52d9dfb 100644 --- a/record.go +++ b/record.go @@ -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}. -var wholeLevels = []pickKey{""} +var wholeLevels = levelKeys(nil, 0) diff --git a/sorted.go b/sorted.go index bf012d4..8194f20 100644 --- a/sorted.go +++ b/sorted.go @@ -10,13 +10,3 @@ func sortedNames[V any](m map[string]V) []string { sort.Strings(names) return names } - -func join(prefix, name string) string { - switch { - case prefix == "": - return name - case name == "": - return prefix - } - return prefix + "." + name -}