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
6 changed files with 115 additions and 30 deletions
Showing only changes of commit 8b76d51f2c - Show all commits
+1 -1
View File
@@ -435,7 +435,7 @@ func pathArm(name, head, writtenHead string, segs []string) arm {
return arm{spelling: name, head: head, writtenHead: writtenHead, tail: segs, levels: levels, path: head + "." + strings.Join(segs, ".")} return arm{spelling: name, head: head, writtenHead: writtenHead, tail: segs, levels: levels, path: head + "." + strings.Join(segs, ".")}
} }
// checkSegments rejects an unfinished path: "{a.}" and "{a..b}" each have a // checkSegments rejects an unfinished path: "{a.}" and "{a...b}" each have a
// segment naming nothing. A field really named "" would otherwise make them // segment naming nothing. A field really named "" would otherwise make them
// resolve, so a typo would read as a path that worked. // resolve, so a typo would read as a path that worked.
func checkSegments(a arm) error { func checkSegments(a arm) error {
+66 -13
View File
@@ -6,8 +6,8 @@ import (
) )
// splitPath splits a dotted path into segments, a selector — [key or name] after a // splitPath splits a dotted path into segments, a selector — [key or name] after a
// table's name — becoming a segment of its own, brackets kept. A dot inside a // table's name — becoming a segment of its own, brackets kept, and so does each "..". A
// selector is part of the key or name. // dot inside a selector is part of the key or name.
func splitPath(path string) ([]string, error) { func splitPath(path string) ([]string, error) {
segs := make([]string, 0, strings.Count(path, ".")+2*strings.Count(path, "[")+1) segs := make([]string, 0, strings.Count(path, ".")+2*strings.Count(path, "[")+1)
start, open := 0, -1 start, open := 0, -1
@@ -24,11 +24,13 @@ func splitPath(path string) ([]string, error) {
return nil, err return nil, err
} }
segs = append(segs, path[start:i+1]) segs = append(segs, path[start:i+1])
start, open = i+2, -1 open = -1
i++ segs, i = stepUpAt(segs, path, i+1)
start = i + 1
case '.': case '.':
if open < 0 { if open < 0 {
segs = append(segs, path[start:i]) segs = append(segs, path[start:i])
segs, i = stepUpAt(segs, path, i)
start = i + 1 start = i + 1
} }
} }
@@ -36,12 +38,21 @@ func splitPath(path string) ([]string, error) {
if open >= 0 { if open >= 0 {
return nil, fmt.Errorf(`%q opens a selector with "[" and never closes it with "]"`, path) return nil, fmt.Errorf(`%q opens a selector with "[" and never closes it with "]"`, path)
} }
if start <= len(path) { if start < len(path) || start == len(path) && (len(segs) == 0 || segs[len(segs)-1] != "..") {
segs = append(segs, path[start:]) segs = append(segs, path[start:])
} }
return segs, nil return segs, nil
} }
// stepUpAt appends a ".." segment where the dot at i is the first of two, returning the
// index of the last dot it consumed.
func stepUpAt(segs []string, path string, i int) ([]string, int) {
if i+1 < len(path) && path[i+1] == '.' {
return append(segs, ".."), i + 1
}
return segs, i
}
// checkOpen refuses a "[" at i that opens no selector: one inside a selector, or one // checkOpen refuses a "[" at i that opens no selector: one inside a selector, or one
// following no name. // following no name.
func checkOpen(path string, i, start, open int) error { func checkOpen(path string, i, start, open int) error {
@@ -139,8 +150,14 @@ func nameSegments(segs []string) []string {
// stepInto is what seg names under n: a folder's entry or a template's field. // stepInto is what seg names under n: a folder's entry or a template's field.
func stepInto(n node, seg string) (node, error) { func stepInto(n node, seg string) (node, error) {
if isSelector(seg) { switch {
case isSelector(seg):
return nil, fmt.Errorf("%s is not a table, so it has no row to select", selectorOf(seg)) return nil, fmt.Errorf("%s is not a table, so it has no row to select", selectorOf(seg))
case seg == "..":
if c, ok := n.(*tableColumn); ok {
return nil, fmt.Errorf(`".." steps up from a table's row, and %q is a column`, c.t.header[c.i])
}
return nil, fmt.Errorf(`".." steps up from a table's row to the row it links to, and what it follows is not a table`)
} }
switch n := n.(type) { switch n := n.(type) {
case *folder: case *folder:
@@ -169,6 +186,7 @@ type tableRoute struct {
next node next node
rest []string rest []string
descends bool descends bool
up bool
} }
// route is how tail passes t. descended means the previous route stepped into t // route is how tail passes t. descended means the previous route stepped into t
@@ -180,6 +198,12 @@ func (t *table) route(tail []string, descended bool) (tableRoute, error) {
if err != nil { if err != nil {
return tableRoute{}, err return tableRoute{}, err
} }
if len(tail) > 0 && tail[0] == ".." {
if err := t.stepUp(tail[1:]); err != nil {
return tableRoute{}, err
}
return tableRoute{sel: sel, draw: sel == "", next: t.parentT, rest: tail[2:], descends: true, up: true}, nil
}
column, child, err := t.step(tail) column, child, err := t.step(tail)
if err != nil { if err != nil {
return tableRoute{}, err return tableRoute{}, err
@@ -200,6 +224,20 @@ func (t *table) route(tail []string, descended bool) (tableRoute, error) {
return r, nil return r, nil
} }
// stepUp proves a ".." after a row of t can step up: t has a parent, rest names it, and no
// selector follows, since a row selected below the parent row could lie outside it.
func (t *table) stepUp(rest []string) error {
switch {
case t.parentT == nil:
return fmt.Errorf(`%s has no parent table for ".." to step up to`, t.segment)
case len(rest) == 0 || rest[0] != t.parentT.segment:
return fmt.Errorf(`".." steps up from %s to its parent table, so name that next: ..%s`, t.segment, t.parentT.segment)
case hasSelector(rest):
return fmt.Errorf(`a path selects its rows before a "..", since a row selected after it could lie outside the row it steps up to; start a path at the table to select from it`)
}
return nil
}
// pathStep is one step of a compiled path, taken at the node the steps before it // pathStep is one step of a compiled path, taken at the node the steps before it
// reached, a choice there resolved first; at indexes the tail where it is taken, and // reached, a choice there resolved first; at indexes the tail where it is taken, and
// only a table's step sits past the tail's end. A step holds no node, so a // only a table's step sits past the tail's end. A step holds no node, so a
@@ -220,6 +258,7 @@ const (
stepRow // land on the table's row node stepRow // land on the table's row node
stepColumn stepColumn
stepChild stepChild
stepParent // up to the table's parent, at the row its own row links to
) )
// pathCheck proves a path resolves whichever way the draws go: every variant of a // pathCheck proves a path resolves whichever way the draws go: every variant of a
@@ -297,7 +336,10 @@ func routeSteps(steps []pathStep, pins *pinSet, t *table, r tableRoute, at int)
} }
switch next := r.next.(type) { switch next := r.next.(type) {
case *table: case *table:
if next != t { switch {
case r.up:
steps = append(steps, pathStep{kind: stepParent, at: at})
case next != t:
steps = append(steps, pathStep{kind: stepChild, at: at, name: next.segment}) steps = append(steps, pathStep{kind: stepChild, at: at, name: next.segment})
} }
case *tableRow: case *tableRow:
@@ -380,8 +422,10 @@ func probePath(n node, tail []string, steps []pathStep) ([]pathStep, error) {
// drawSteps draws the rows and variants a path's steps read from n, pinning the rows in pins; // drawSteps draws the rows and variants a path's steps read from n, pinning the rows in pins;
// pins is nil for a sibling path, which never crosses a table, since a table is only a category. // pins is nil for a sibling path, which never crosses a table, since a table is only a category.
// For a memoized read, memo keeps the variant drawn at each of levels, so paths sharing a prefix // For a memoized read, memo keeps the variant drawn at each of levels, so paths sharing a prefix
// share it. // share it. It returns the leaf and the pins its row is in: a step down after a step up draws
func drawSteps(s *generatorState, n node, steps []pathStep, pins *pinSet, memo *drawMemo, levels []string) node { // afresh, into pins of its own.
func drawSteps(s *generatorState, n node, steps []pathStep, pins *pinSet, memo *drawMemo, levels []string) (node, *pinSet) {
var climbed *table
for _, st := range steps { for _, st := range steps {
if c, ok := n.(*choice); ok { if c, ok := n.(*choice); ok {
n = drawVariant(s, c, memo, levels, st.at) n = drawVariant(s, c, memo, levels, st.at)
@@ -397,9 +441,15 @@ func drawSteps(s *generatorState, n node, steps []pathStep, pins *pinSet, memo *
if !ok { if !ok {
panic(internalError("step %d: a table's step of kind %d on %T", st.at, st.kind, n)) panic(internalError("step %d: a table's step of kind %d on %T", st.at, st.kind, n))
} }
switch {
case st.kind == stepParent:
climbed = t.parentT
case st.kind == stepChild && climbed != nil:
pins, climbed = pins.above(climbed), nil
}
n = t.drawStep(s, st, pins) n = t.drawStep(s, st, pins)
} }
return n return n, pins
} }
func (t *table) drawStep(s *generatorState, st pathStep, pins *pinSet) node { func (t *table) drawStep(s *generatorState, st pathStep, pins *pinSet) node {
@@ -418,6 +468,8 @@ func (t *table) drawStep(s *generatorState, st pathStep, pins *pinSet) node {
return t.formatTemplate.fields[st.name] return t.formatTemplate.fields[st.name]
case stepChild: case stepChild:
return t.descendant(st.name) return t.descendant(st.name)
case stepParent:
return t.parentT
} }
panic(internalError("drawStep has no case for step kind %d", st.kind)) panic(internalError("drawStep has no case for step kind %d", st.kind))
} }
@@ -487,11 +539,12 @@ func compilePath(n node, tail []string) pathCheck {
return w return w
} }
// joinSegments spells segments as a path: a selector attaches to the name before it. // joinSegments spells segments as a path: a selector attaches to the name before it, and
// so do a ".." and the name after it.
func joinSegments(segs []string) string { func joinSegments(segs []string) string {
var b strings.Builder var b strings.Builder
for _, s := range segs { for i, s := range segs {
if b.Len() > 0 && !isSelector(s) { if b.Len() > 0 && !isSelector(s) && s != ".." && segs[i-1] != ".." {
b.WriteByte('.') b.WriteByte('.')
} }
b.WriteString(s) b.WriteString(s)
+12 -10
View File
@@ -130,9 +130,9 @@ func readName(s *generatorState, sc renderScope, a arm) readValue {
if r, done := p.memo.value[a.path]; done { if r, done := p.memo.value[a.path]; done {
return r return r
} }
leaf := p.draw(s, a.named.head, a.steps, a.levels, a.path) leaf, pins := p.draw(s, a.named.head, a.steps, a.levels, a.path)
sc, mark := sc.enter(leaf, &p.memo) sc, mark := sc.enter(leaf, &p.memo)
r := p.renderAt(s, leaf, a.path, sc) r := p.renderAt(s, leaf, pins, a.path, sc)
sc.draws.popFrames(mark) sc.draws.popFrames(mark)
return r return r
} }
@@ -162,7 +162,8 @@ func readUnder(s *generatorState, t *template, sc renderScope, a arm) readValue
for i, l := range a.levels { for i, l := range a.levels {
levels[i] = underKey(sc.pickKey, l) levels[i] = underKey(sc.pickKey, l)
} }
return p.renderAt(s, p.draw(s, t.head(a.head), a.steps, levels, key), key, sc) leaf, pins := p.draw(s, t.head(a.head), a.steps, levels, key)
return p.renderAt(s, leaf, pins, key, sc)
} }
// underKey is the key of path under the pick key prefix. It never returns prefix itself, which a // underKey is the key of path under the pick key prefix. It never returns prefix itself, which a
@@ -174,18 +175,19 @@ func underKey(prefix, path string) string {
return prefix + "." + path return prefix + "." + path
} }
// draw draws the path key names under p, its variant at key kept too. // draw draws the path key names under p, its variant at key kept too, returning the leaf and the
func (p *namedPick) draw(s *generatorState, head node, steps []pathStep, levels []string, key string) node { // pins its row is in.
leaf := drawSteps(s, head, steps, &p.pins, &p.memo, levels) func (p *namedPick) draw(s *generatorState, head node, steps []pathStep, levels []string, key string) (node, *pinSet) {
leaf, pins := drawSteps(s, head, steps, &p.pins, &p.memo, levels)
if c, isChoice := leaf.(*choice); isChoice { if c, isChoice := leaf.(*choice); isChoice {
leaf = p.memo.variantOf(s, c, key) leaf = p.memo.variantOf(s, c, key)
} }
return leaf return leaf, pins
} }
// renderAt renders leaf, drawn at key, as part of p, and keeps what it rendered. // renderAt renders leaf, drawn at key into pins, as part of p, and keeps what it rendered.
func (p *namedPick) renderAt(s *generatorState, leaf node, key string, sc renderScope) readValue { func (p *namedPick) renderAt(s *generatorState, leaf node, pins *pinSet, key string, sc renderScope) readValue {
sc = sc.at(leaf, &p.pins) sc = sc.at(leaf, pins)
sc.pick, sc.pickKey = p, key sc.pick, sc.pickKey = p, key
r := renderLeaf(s, leaf, sc) r := renderLeaf(s, leaf, sc)
if p.memo.value == nil { if p.memo.value == nil {
+11
View File
@@ -154,6 +154,17 @@ func (p *pinSet) inside(t *table, rows []int) []int {
return rows return rows
} }
// above is a pin set holding the rows p pins of t and its ancestors, and none below them.
func (p *pinSet) above(t *table) *pinSet {
q := new(pinSet)
for ; t != nil; t = t.parentT {
if r, ok := p.pinned(t); ok {
q.add(t, r)
}
}
return q
}
// clone is a copy of p that pins apart from it; a plain copy shares the spill map. // clone is a copy of p that pins apart from it; a plain copy shares the spill map.
func (p pinSet) clone() pinSet { func (p pinSet) clone() pinSet {
p.spill = maps.Clone(p.spill) p.spill = maps.Clone(p.spill)
+17 -3
View File
@@ -144,7 +144,7 @@ func pinBelow(reads []pathRead, tr *tableRead) []pathRead {
kept := reads[:0:0] kept := reads[:0:0]
for _, r := range reads { for _, r := range reads {
pins := r.branches.pins.clone() pins := r.branches.pins.clone()
if err := tr.replay(&pins); err != nil { if err := tr.replayBelow(&pins); err != nil {
continue continue
} }
if r.tr != nil && r.clash == nil { if r.tr != nil && r.clash == nil {
@@ -230,6 +230,7 @@ func (r readRoute) spelled(ref string) string {
type tableRead struct { type tableRead struct {
headTable *table headTable *table
pins pinSet pins pinSet
leafPins *pinSet // the rows the leaf renders in: pins, short of the rows below a step up
drawn map[*table]bool drawn map[*table]bool
sels []tableSel sels []tableSel
landsRow bool landsRow bool
@@ -250,9 +251,17 @@ func tableReadOf(head node, a arm, leaf node) *tableRead {
return nil return nil
} }
r := &tableRead{headTable: t} r := &tableRead{headTable: t}
r.leafPins = &r.pins
_, r.landsRow = leaf.(*tableRow) _, r.landsRow = leaf.(*tableRow)
climbed := false
for _, st := range a.steps { for _, st := range a.steps {
if climbed && st.kind != stepChild && st.kind != stepParent {
continue
}
switch st.kind { switch st.kind {
case stepParent:
t, climbed = t.parentT, true
r.leafPins = r.leafPins.above(t)
case stepSelect: case stepSelect:
r.pins.pin(t, st.row) r.pins.pin(t, st.row)
r.sels = append(r.sels, tableSel{t, joinSegments(append([]string{a.writtenHead}, a.tail[:st.at+1]...))}) r.sels = append(r.sels, tableSel{t, joinSegments(append([]string{a.writtenHead}, a.tail[:st.at+1]...))})
@@ -294,9 +303,14 @@ func (r *tableRead) drawnOf(pins *pinSet) *table {
} }
// replay pins the read's rows into d, where they agree with the rows pinned before. // replay pins the read's rows into d, where they agree with the rows pinned before.
func (r *tableRead) replay(d *pinSet) error { func (r *tableRead) replay(d *pinSet) error { return replayPins(&r.pins, d) }
// replayBelow pins into d the rows the read's leaf renders in.
func (r *tableRead) replayBelow(d *pinSet) error { return replayPins(r.leafPins, d) }
func replayPins(from, d *pinSet) error {
var err error var err error
r.pins.each(func(t *table, row int) { from.each(func(t *table, row int) {
if err == nil { if err == nil {
err = d.pinRow(t, row) err = d.pinRow(t, row)
} }
+8 -3
View File
@@ -48,7 +48,12 @@ func descend(s *generatorState, root node, segments []string, sc renderScope) (n
if err != nil { if err != nil {
return nil, err return nil, err
} }
return drawSteps(s, root, steps, &sc.groupDraws().pins, nil, nil), nil group := sc.groupDraws()
n, pins := drawSteps(s, root, steps, &group.pins, nil, nil)
// A caller's path is its render's first draw, so the rows a step down after a step up drew
// are all the render pins.
group.pins = *pins
return n, nil
} }
// renderOnce renders n as one render, over draws of its own. // renderOnce renders n as one render, over draws of its own.
@@ -209,10 +214,10 @@ func readMemo(s *generatorState, t *template, memo *drawMemo, pins *pinSet, sc r
if r, done := memo.value[a.path]; done { if r, done := memo.value[a.path]; done {
return r return r
} }
leaf := drawSteps(s, t.head(a.head), a.steps, pins, memo, a.levels) leaf, leafPins := drawSteps(s, t.head(a.head), a.steps, pins, memo, a.levels)
mark := -1 mark := -1
if pins != nil { if pins != nil {
sc, mark = sc.at(leaf, pins).enter(leaf, memo) sc, mark = sc.at(leaf, leafPins).enter(leaf, memo)
} }
r := renderLeaf(s, leaf, sc) r := renderLeaf(s, leaf, sc)
if mark >= 0 { if mark >= 0 {