Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33

Open
lilleman wants to merge 966 commits from worktree-todo-relative-dates-exact-counts into main
9 changed files with 46 additions and 46 deletions
Showing only changes of commit 8f86de2187 - Show all commits
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+8 -8
View File
@@ -70,7 +70,7 @@ type formatToken struct {
type tokenKind uint8
const (
builtinCall tokenKind = iota
builtinCall tokenKind = iota + 1
fieldAlternation
literalRun
)
@@ -376,15 +376,15 @@ 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
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
@@ -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
}
}
}
+1 -1
View File
@@ -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 != "" {
+2 -2
View File
@@ -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
+13 -13
View File
@@ -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 + "}"})
}
}
+5 -5
View File
@@ -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)
+1 -1
View File
@@ -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++ {
+7 -7
View File
@@ -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