From 8f86de2187917e3989b8c0a2614f3f4d2810c153 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Mon, 28 Sep 2026 11:44:06 +0200 Subject: [PATCH] Name pathKeys, pathKeyReaders, proveKeys and readValue's hold for what they hold, and give no token kind to the zero value --- AGENTS.md | 4 ++-- docs/decisions.md | 4 ++-- format.go | 26 +++++++++++++------------- graph.go | 2 +- hold.go | 4 ++-- holdfence.go | 26 +++++++++++++------------- node.go | 10 +++++----- reference.go | 2 +- table.go | 14 +++++++------- 9 files changed, 46 insertions(+), 46 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index cea2259..613c3c7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -63,7 +63,7 @@ In [docs/decisions.md](docs/decisions.md): - A column of one reference alone is the column it reads - A typed column's calc is refused unless proven - `Column` carries text, not a Go value -- `hold` names what a draw is kept in, `draw` the draw itself +- `hold` names what a draw is kept in, `readValue` what a read drew - One name, one meaning - The package stays flat - The performance gate asserts allocations, not wall-clock time @@ -77,7 +77,7 @@ In [docs/decisions.md](docs/decisions.md): - A parent row with no child row is a load error - The choice-of-rows fence guards a data file's root, and requires string fields - A table is a record of string columns -- The key index is built at load, the rest on first draw +- The key map is built at load, the rest on first draw - Two categories may name one TSV - The rows of a table are alternatives - A table never reaches its own family, by any route diff --git a/docs/decisions.md b/docs/decisions.md index 6304093..0dd9e0b 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -302,7 +302,7 @@ in. `Value` is the rendered string beside `DataType` and `Null`, which each serializer writes as the load check proved it; a `Value any` would hand every caller a type switch. -## `hold` names what a draw is kept in, `draw` the draw itself +## `hold` names what a draw is kept in, `readValue` what a read drew Goal 2 wants a name to reach one unit, so a unit takes the stem of what it is, and a file the stem of the units it holds. @@ -439,7 +439,7 @@ Its columns are the CSV header and the `INSERT` column list, fixed by the TSV he a table is a record by construction; every column is a string until a typed column option earns its place. -## The key index is built at load, the rest on first draw +## The key map is built at load, the rest on first draw A link is proved against the parent's keys and a key's uniqueness is a data mistake, so both are load-time; the per-parent child lists serve only a draw, so they wait for the diff --git a/format.go b/format.go index 7fcc43c..16ef085 100644 --- a/format.go +++ b/format.go @@ -70,7 +70,7 @@ type formatToken struct { type tokenKind uint8 const ( - builtinCall tokenKind = iota + builtinCall tokenKind = iota + 1 fieldAlternation literalRun ) @@ -376,18 +376,18 @@ type op struct { } // formatOps is a compiled format: its ops, the size of its literal text (to size -// the render buffer), and the names drawn once per expansion. pathLevels maps each level -// a path reads into to the first such path; held is every such level plus the +// the render buffer), and the names drawn once per expansion. pathKeys maps each key +// a path starts from to the first such path; held is every such key plus the // fields an operand reads; holder maps each held name to the first reader holding // it, for error messages. The maps are nil when the format holds nothing, so data // that holds nothing carries no render-time cost. type formatOps struct { - ops []op - grow int - pathLevels map[string]string - held map[string]bool - holder map[string]string - heldLocal bool // some held name is kept by the expansion itself rather than the render's hold + ops []op + grow int + pathKeys map[string]string + held map[string]bool + holder map[string]string + heldLocal bool // some held name is kept by the expansion itself rather than the render's hold } func (c *formatOps) holdName(a arm, label string) { @@ -403,11 +403,11 @@ func (c *formatOps) holdName(a arm, label string) { c.holder[a.key] = label } if len(a.tail) > 0 { - if c.pathLevels == nil { - c.pathLevels = map[string]string{} + if c.pathKeys == nil { + c.pathKeys = map[string]string{} } - if _, named := c.pathLevels[a.key]; !named { - c.pathLevels[a.key] = a.spelling + if _, named := c.pathKeys[a.key]; !named { + c.pathKeys[a.key] = a.spelling } } } diff --git a/graph.go b/graph.go index fbaee34..252bcba 100644 --- a/graph.go +++ b/graph.go @@ -105,7 +105,7 @@ type renderEdge struct { operand string } -// reached names an edge as the author spelled it, the vocabulary levelReaders uses +// reached names an edge as the author spelled it, the vocabulary pathKeyReaders uses // for the sibling fence. func (e renderEdge) reached() string { if e.operand != "" { diff --git a/hold.go b/hold.go index 18e1236..a09951d 100644 --- a/hold.go +++ b/hold.go @@ -6,8 +6,8 @@ import ( ) // hold is what has already been drawn for held names: the variant each was drawn as, so every -// path under it reads one row; the draw each read made, by its path, so the same read -// written twice reads one draw; and the table rows those reads pinned. An expansion keeps one for +// path under it reads one row; the value each read produced, by its path, so the same read +// written twice reads one value; and the table rows those 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 diff --git a/holdfence.go b/holdfence.go index 1327f7b..59be3f0 100644 --- a/holdfence.go +++ b/holdfence.go @@ -15,9 +15,9 @@ func heldCheck(path string, n node) error { if !ok || len(t.held) == 0 { return nil } - readers := levelReaders(t.ops, t.pathLevels) + readers := pathKeyReaders(t.ops, t.pathKeys) for _, name := range heldNames(t) { - if _, isPath := t.pathLevels[name]; isPath && isRef(name) { + if _, isPath := t.pathKeys[name]; isPath && isRef(name) { continue // held for the render: drawCheck compares every read of it, by path, across the render and its groups } if err := checkNameHeld(t, name, readers); err != nil { @@ -36,8 +36,8 @@ func heldNames(t *template) []string { names = append(names, name) } sort.Slice(names, func(i, j int) bool { - _, pi := t.pathLevels[names[i]] - _, pj := t.pathLevels[names[j]] + _, pi := t.pathKeys[names[i]] + _, pj := t.pathKeys[names[j]] if pi != pj { return !pi } @@ -51,7 +51,7 @@ func heldNames(t *template) []string { // produces. func heldNodes(t *template, name string, readers []reader) map[node]bool { held := map[node]bool{} - if _, isPath := t.pathLevels[name]; !isPath { + if _, isPath := t.pathKeys[name]; !isPath { operandDraw(t.head(name), held) return held } @@ -71,7 +71,7 @@ func checkNameHeld(t *template, name string, readers []reader) error { if len(held) == 0 { return nil // a fixed head holds nothing to reach } - reader, isPath := t.pathLevels[name] + reader, isPath := t.pathKeys[name] seen := map[node]bool{} for _, e := range renderEdges(t) { if splitArm(e.label, t.refs).key == name || !renders(e.to, held, seen) { @@ -177,8 +177,8 @@ func renders(n node, want, seen map[node]bool) bool { // disagree. Reads are compared in sorted order, so which pair is reported does not // depend on where the tokens sit. // docs/decisions.md#a-bare-reference-draws-each-time-a-reference-path-is-held -func checkNoOverlap(ops []op, pathLevels map[string]string) error { - names := levelReaders(ops, pathLevels) +func checkNoOverlap(ops []op, pathKeys map[string]string) error { + names := pathKeyReaders(ops, pathKeys) // Stable over one format-order scan, so two readers of one name (a token and a // calc operand both naming "p") are reported as the format writes them. sort.SliceStable(names, func(i, j int) bool { return names[i].path < names[j].path }) @@ -192,23 +192,23 @@ func checkNoOverlap(ops []op, pathLevels map[string]string) error { return nil } -// reader is one way a format reaches a path level: as written, by its path, +// reader is one way a format reaches a path key: as written, by its path, // and how to name it. type reader struct{ spelling, path, label string } -// levelReaders lists every way a format reaches a sibling path level, in the order the +// pathKeyReaders lists every way a format reaches a sibling path key, in the order the // format writes them. An operand renders its field, so it names a level exactly // as a token does; one scan finds both, which is what puts them in one order. -func levelReaders(ops []op, pathLevels map[string]string) []reader { +func pathKeyReaders(ops []op, pathKeys map[string]string) []reader { var names []reader for _, o := range ops { for _, a := range o.operands { - if _, isLevel := pathLevels[a.key]; isLevel && !isRef(a.key) { + if _, isPathKey := pathKeys[a.key]; isPathKey && !isRef(a.key) { names = append(names, reader{a.spelling, a.path, fmt.Sprintf("%s operand %q", o.fn, a.spelling)}) } } for _, a := range o.arms { - if _, isLevel := pathLevels[a.key]; isLevel && !isRef(a.key) { + if _, isPathKey := pathKeys[a.key]; isPathKey && !isRef(a.key) { names = append(names, reader{a.spelling, a.path, "token {" + a.spelling + "}"}) } } diff --git a/node.go b/node.go index 8e9f1a1..6ff24de 100644 --- a/node.go +++ b/node.go @@ -62,11 +62,11 @@ type template struct { // Filled by `template.compileFormat` at link, from `template.tokens` and `template.refs`: ops []op // what expand walks grow int // minimum output size, to size the render buffer - // pathLevels maps each field the format addresses by dotted path to one path token + // pathKeys maps each key a dotted path in the format starts from to one path token // reading it, which is the half of an overlap the fences name. nil when the // format takes no path. - pathLevels map[string]string - // held is every name drawn once per expansion: the path levels above, plus the + pathKeys map[string]string + // held is every name drawn once per expansion: the path keys above, plus the // siblings a {calc()} reads. nil when the format holds nothing (see expand). held map[string]bool heldLocal bool // some held name is kept by the expansion itself, so expand makes its hold @@ -224,8 +224,8 @@ func (t *template) fixedText() (string, bool) { // compiled reads. func (t *template) compileFormat() error { c := compileOps(t.tokens, t.refs) - t.ops, t.grow, t.pathLevels, t.held, t.heldLocal = c.ops, c.grow, c.pathLevels, c.held, c.heldLocal - if err := checkNoOverlap(t.ops, t.pathLevels); err != nil { + t.ops, t.grow, t.pathKeys, t.held, t.heldLocal = c.ops, c.grow, c.pathKeys, c.held, c.heldLocal + if err := checkNoOverlap(t.ops, t.pathKeys); err != nil { return err } return checkNoRepeatedRead(c) diff --git a/reference.go b/reference.go index 089c8cb..4814a10 100644 --- a/reference.go +++ b/reference.go @@ -188,7 +188,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 category's segments, the node, and the tail left to read into it. -func resolveCategory(root map[string]node, segments []string) (category []string, target node, tail []string, err error) { +func resolveCategory(root map[string]node, segments []string) (categorySegs []string, target node, tail []string, err error) { var n node = &folder{children: root} i := 0 for ; i < len(segments); i++ { diff --git a/table.go b/table.go index 78e8383..7ba2b2b 100644 --- a/table.go +++ b/table.go @@ -226,7 +226,7 @@ func (t *table) bindOptions(o tableOptionValues) error { if t.nameIndex >= 0 && t.keyIndex < 0 && t.parentIndex < 0 { return fmt.Errorf("name selects a row as a key does, and lists the rows it matches by their keys, so it needs a key column, or a parent inside which each name is one row; add key") } - if err := t.mapKeys(); err != nil { + if err := t.proveKeys(); err != nil { return err } if err := t.proveNamesInsideParent(); err != nil { @@ -251,8 +251,8 @@ func (t *table) proveNamesInsideParent() error { return nil } -// mapKeys proves every key names one row, and keeps the map a link is proved by. -func (t *table) mapKeys() error { +// proveKeys proves every key names one row, and keeps the map a link is proved by. +func (t *table) proveKeys() error { if t.keyIndex < 0 { return nil } @@ -488,13 +488,13 @@ func pickCum(s *session, cum []float64) int { // drawUnder picks a row among those linked to parent row pr. func (t *table) drawUnder(s *session, pr int) int { - ix := t.builtLookup() + lookup := t.builtLookup() k := t.parentT.cell(pr, t.parentT.keyIndex) - rows := ix.rowsByParent[k] - if ix.childCum == nil { + rows := lookup.rowsByParent[k] + if lookup.childCum == nil { return rows[s.IntN(len(rows))] } - return rows[pickCum(s, ix.childCum[k])] + return rows[pickCum(s, lookup.childCum[k])] } // drawIn is the render's row of t: the one pinned in p, else one drawn inside the nearest