Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user