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, ".")}
}
// 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
// resolve, so a typo would read as a path that worked.
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
// table's name — becoming a segment of its own, brackets kept. A dot inside a
// selector is part of the key or name.
// table's name — becoming a segment of its own, brackets kept, and so does each "..". A
// dot inside a selector is part of the key or name.
func splitPath(path string) ([]string, error) {
segs := make([]string, 0, strings.Count(path, ".")+2*strings.Count(path, "[")+1)
start, open := 0, -1
@@ -24,11 +24,13 @@ func splitPath(path string) ([]string, error) {
return nil, err
}
segs = append(segs, path[start:i+1])
start, open = i+2, -1
i++
open = -1
segs, i = stepUpAt(segs, path, i+1)
start = i + 1
case '.':
if open < 0 {
segs = append(segs, path[start:i])
segs, i = stepUpAt(segs, path, i)
start = i + 1
}
}
@@ -36,12 +38,21 @@ func splitPath(path string) ([]string, error) {
if open >= 0 {
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:])
}
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
// following no name.
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.
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))
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) {
case *folder:
@@ -169,6 +186,7 @@ type tableRoute struct {
next node
rest []string
descends bool
up bool
}
// 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 {
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)
if err != nil {
return tableRoute{}, err
@@ -200,6 +224,20 @@ func (t *table) route(tail []string, descended bool) (tableRoute, error) {
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
// 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
@@ -220,6 +258,7 @@ const (
stepRow // land on the table's row node
stepColumn
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
@@ -297,7 +336,10 @@ func routeSteps(steps []pathStep, pins *pinSet, t *table, r tableRoute, at int)
}
switch next := r.next.(type) {
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})
}
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;
// 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
// share it.
func drawSteps(s *generatorState, n node, steps []pathStep, pins *pinSet, memo *drawMemo, levels []string) node {
// share it. It returns the leaf and the pins its row is in: a step down after a step up draws
// 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 {
if c, ok := n.(*choice); ok {
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 {
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)
}
return n
return n, pins
}
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]
case stepChild:
return t.descendant(st.name)
case stepParent:
return t.parentT
}
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
}
// 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 {
var b strings.Builder
for _, s := range segs {
if b.Len() > 0 && !isSelector(s) {
for i, s := range segs {
if b.Len() > 0 && !isSelector(s) && s != ".." && segs[i-1] != ".." {
b.WriteByte('.')
}
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 {
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)
r := p.renderAt(s, leaf, a.path, sc)
r := p.renderAt(s, leaf, pins, a.path, sc)
sc.draws.popFrames(mark)
return r
}
@@ -162,7 +162,8 @@ func readUnder(s *generatorState, t *template, sc renderScope, a arm) readValue
for i, l := range a.levels {
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
@@ -174,18 +175,19 @@ func underKey(prefix, path string) string {
return prefix + "." + path
}
// draw draws the path key names under p, its variant at key kept too.
func (p *namedPick) draw(s *generatorState, head node, steps []pathStep, levels []string, key string) node {
leaf := drawSteps(s, head, steps, &p.pins, &p.memo, levels)
// draw draws the path key names under p, its variant at key kept too, returning the leaf and the
// pins its row is in.
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 {
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.
func (p *namedPick) renderAt(s *generatorState, leaf node, key string, sc renderScope) readValue {
sc = sc.at(leaf, &p.pins)
// 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, pins *pinSet, key string, sc renderScope) readValue {
sc = sc.at(leaf, pins)
sc.pick, sc.pickKey = p, key
r := renderLeaf(s, leaf, sc)
if p.memo.value == nil {
+11
View File
@@ -154,6 +154,17 @@ func (p *pinSet) inside(t *table, rows []int) []int {
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.
func (p pinSet) clone() pinSet {
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]
for _, r := range reads {
pins := r.branches.pins.clone()
if err := tr.replay(&pins); err != nil {
if err := tr.replayBelow(&pins); err != nil {
continue
}
if r.tr != nil && r.clash == nil {
@@ -230,6 +230,7 @@ func (r readRoute) spelled(ref string) string {
type tableRead struct {
headTable *table
pins pinSet
leafPins *pinSet // the rows the leaf renders in: pins, short of the rows below a step up
drawn map[*table]bool
sels []tableSel
landsRow bool
@@ -250,9 +251,17 @@ func tableReadOf(head node, a arm, leaf node) *tableRead {
return nil
}
r := &tableRead{headTable: t}
r.leafPins = &r.pins
_, r.landsRow = leaf.(*tableRow)
climbed := false
for _, st := range a.steps {
if climbed && st.kind != stepChild && st.kind != stepParent {
continue
}
switch st.kind {
case stepParent:
t, climbed = t.parentT, true
r.leafPins = r.leafPins.above(t)
case stepSelect:
r.pins.pin(t, st.row)
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.
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
r.pins.each(func(t *table, row int) {
from.each(func(t *table, row int) {
if err == nil {
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 {
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.
@@ -209,10 +214,10 @@ func readMemo(s *generatorState, t *template, memo *drawMemo, pins *pinSet, sc r
if r, done := memo.value[a.path]; done {
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
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)
if mark >= 0 {