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
13 changed files with 26 additions and 53 deletions
Showing only changes of commit 5aae86638b - Show all commits
+1 -1
View File
@@ -92,7 +92,7 @@ func checkColumns(s nodeScope) error {
if !ok || !t.record { if !ok || !t.record {
return nil return nil
} }
for _, name := range recordColumns(t) { for _, name := range sortedNames(t.fields) {
if err := check(path, name, t.fields[name]); err != nil { if err := check(path, name, t.fields[name]); err != nil {
return err return err
} }
+3 -3
View File
@@ -88,7 +88,7 @@ func (c *drawCheck) checkRecordDraws(path string, n node) error {
return nil return nil
} }
// A record-only template's format renders nothing, so weigh the columns, not the format. // A record-only template's format renders nothing, so weigh the columns, not the format.
columns := recordColumns(t) columns := sortedNames(t.fields)
reads := false reads := false
for _, name := range columns { for _, name := range columns {
reads = reads || c.readsPath(t.fields[name]) reads = reads || c.readsPath(t.fields[name])
@@ -148,7 +148,7 @@ func readsTable(n node, a arm) bool {
if !isTemplate { if !isTemplate {
return false return false
} }
_, isTable := t.fields[a.key].(*table) _, isTable := t.head(a.key).(*table)
return isTable return isTable
} }
@@ -248,7 +248,7 @@ func (w *drawWalk) walk(n node, at drawAt) {
// pins entered, so a selected row renders only its own cells. // pins entered, so a selected row renders only its own cells.
func (w *drawWalk) edge(from node, e renderEdge, at drawAt) { func (w *drawWalk) edge(from node, e renderEdge, at drawAt) {
if a, reads := refRead(from, e.label); reads { if a, reads := refRead(from, e.label); reads {
tr := tableReadOf(from.(*template).fields[a.key], a, e.to) tr := tableReadOf(from.(*template).head(a.key), a, e.to)
if k := (readKey{at.group, a.path, at.alt.key()}); !w.read[k] { if k := (readKey{at.group, a.path, at.alt.key()}); !w.read[k] {
w.read[k] = true w.read[k] = true
w.reads = append(w.reads, pathRead{at, a, tr}) w.reads = append(w.reads, pathRead{at, a, tr})
+1 -1
View File
@@ -212,7 +212,7 @@ func checkOwnFamily(t *template) error {
seen[n] = true seen[n] = true
for _, e := range renderEdges(n) { for _, e := range renderEdges(n) {
if a, isRef := refRead(n, e.label); isRef { if a, isRef := refRead(n, e.label); isRef {
if head, isTable := n.(*template).fields[a.key].(*table); isTable && head.family() == own.family() { if head, isTable := n.(*template).head(a.key).(*table); isTable && head.family() == own.family() {
return e, head, true return e, head, true
} }
} }
-3
View File
@@ -146,9 +146,6 @@ func paths(n node) []string {
case *template: case *template:
out := []string{""} out := []string{""}
for _, name := range sortedNames(n.fields) { for _, name := range sortedNames(n.fields) {
if isRef(name) { // a binding, not a path segment
continue
}
for _, p := range paths(n.fields[name]) { for _, p := range paths(n.fields[name]) {
out = append(out, join(name, p)) out = append(out, join(name, p))
} }
+2 -7
View File
@@ -78,14 +78,9 @@ func contained(n node) []namedNode {
} }
} }
// named skips a bound {/path} key: it is a render edge, not containment, so using
// it as a path segment would report a node under a path that does not reach it.
func named(m map[string]node) []namedNode { func named(m map[string]node) []namedNode {
out := make([]namedNode, 0, len(m)) out := make([]namedNode, 0, len(m))
for _, name := range sortedNames(m) { for _, name := range sortedNames(m) {
if isRef(name) {
continue
}
out = append(out, namedNode{name: name, node: m[name]}) out = append(out, namedNode{name: name, node: m[name]})
} }
return out return out
@@ -134,8 +129,8 @@ func renderEdges(n node) []renderEdge {
var es []renderEdge var es []renderEdge
add := func(name, operand string) { add := func(name, operand string) {
a := splitArm(name, n.refs) a := splitArm(name, n.refs)
c, ok := n.fields[a.key] c := n.head(a.key)
if !ok { if c == nil {
return return
} }
for _, leaf := range pathLeaves(c, a.tail) { for _, leaf := range pathLeaves(c, a.tail) {
+3 -3
View File
@@ -21,7 +21,7 @@ type draw struct {
} }
// readField renders one arm of a token. An arm's key is a sibling field or a // readField renders one arm of a token. An arm's key is a sibling field or a
// reference linkRefs bound into fields. A name the expansion holds — a level some // reference linkRefs bound into refHeads. A name the expansion holds — a level some
// token addresses by dotted path, or a field an operand reads — is drawn once and // token addresses by dotted path, or a field an operand reads — is drawn once and
// kept, so {place.postal-code} and {place.locality} read one row, either read twice // kept, so {place.postal-code} and {place.locality} read one row, either read twice
// gives one value, and a shown operand is the operand computed. Every other name is // gives one value, and a shown operand is the operand computed. Every other name is
@@ -32,13 +32,13 @@ func readField(s *session, t *template, held *hold, sc renderScope, a arm) draw
if len(a.tail) > 0 { if len(a.tail) > 0 {
panic(fmt.Sprintf("fejkdata: %q reads a path into %q, which the expansion does not hold", a.name, a.key)) panic(fmt.Sprintf("fejkdata: %q reads a path into %q, which the expansion does not hold", a.name, a.key))
} }
return draw{text: render(s, t.fields[a.key], sc)} return draw{text: render(s, t.head(a.key), sc)}
} }
d := readHold(held, sc, a) d := readHold(held, sc, a)
if r, done := d.value[a.path]; done { if r, done := d.value[a.path]; done {
return r return r
} }
leaf, err := walkPath(t.fields[a.key], a.tail, pathWalk{pins: &d.pins, draws: &pathDraws{s: s, held: d, a: &a}}) leaf, err := walkPath(t.head(a.key), a.tail, pathWalk{pins: &d.pins, draws: &pathDraws{s: s, held: d, a: &a}})
if err != nil { if err != nil {
panic(fmt.Sprintf("fejkdata: %q: %v; a fence should have refused this at New", a.name, err)) panic(fmt.Sprintf("fejkdata: %q: %v; a fence should have refused this at New", a.name, err))
} }
+2 -2
View File
@@ -52,12 +52,12 @@ func heldHeads(t *template) []string {
func heldNodes(t *template, head string, readers []reader) map[node]bool { func heldNodes(t *template, head string, readers []reader) map[node]bool {
held := map[node]bool{} held := map[node]bool{}
if _, isPath := t.bound[head]; !isPath { if _, isPath := t.bound[head]; !isPath {
operandDraw(t.fields[head], held) operandDraw(t.head(head), held)
return held return held
} }
for _, r := range readers { for _, r := range readers {
if a := splitArm(r.name, t.refs); a.key == head { if a := splitArm(r.name, t.refs); a.key == head {
coverPath(t.fields[head], a.tail, held) coverPath(t.head(head), a.tail, held)
} }
} }
return held return held
+7 -9
View File
@@ -49,7 +49,7 @@ func (*null) isNode() {}
type template struct { type template struct {
// Filled by `compileString`, `compileTemplate` and `table.compileWhole`: // Filled by `compileString`, `compileTemplate` and `table.compileWhole`:
format string format string
fields map[string]node // `linkTemplateRefs` adds each bound reference here fields map[string]node
repeat int repeat int
separator string separator string
datatype DataType datatype DataType
@@ -78,19 +78,19 @@ type template struct {
// Filled by `linkTemplateRefs` and `keyDrawGroup`, from the assembled tree: // Filled by `linkTemplateRefs` and `keyDrawGroup`, from the assembled tree:
refs map[string]refBinding // each reference the format reads -> what it is bound to refs map[string]refBinding // each reference the format reads -> what it is bound to
refHeads map[string]node // each refBinding.key -> the category it names
readsColumn *columnRead // set when the format is one reference alone reading a record's column readsColumn *columnRead // set when the format is one reference alone reading a record's column
drawGroupKey string // its draw group keyed by its category: what a render reads its reference paths under drawGroupKey string // its draw group keyed by its category: what a render reads its reference paths under
} }
func (*template) isNode() {} func (*template) isNode() {}
// field is the node a path segment names; a binding is a render edge, not a field. // head is the node an arm's key names: a sibling field, or a reference's category.
func (t *template) field(seg string) (node, bool) { func (t *template) head(key string) node {
if isRef(seg) { if isRef(key) {
return nil, false return t.refHeads[key]
} }
n, ok := t.fields[seg] return t.fields[key]
return n, ok
} }
// compile converts parsed JSON — a category or an inline template — into a node tree, // compile converts parsed JSON — a category or an inline template — into a node tree,
@@ -277,8 +277,6 @@ func compileChoice(items []any, pos position) (node, error) {
} }
c.cum = cum c.cum = cum
} }
// Computed before linkRefs binds references into the items: a binding is keyed
// by a reference sigil, which paths skips, so the set is the same after.
c.shared = sharedPaths(c.items) c.shared = sharedPaths(c.items)
return c, nil return c, nil
} }
+1 -1
View File
@@ -177,7 +177,7 @@ func walkPath(n node, tail []string, w pathWalk) (node, error) {
return nil, err return nil, err
} }
} }
child, ok := n.field(tail[0]) child, ok := n.fields[tail[0]]
if !ok { if !ok {
return nil, fmt.Errorf("no field %q", tail[0]) return nil, fmt.Errorf("no field %q", tail[0])
} }
+1 -14
View File
@@ -245,7 +245,7 @@ func recordOf(n node) (*template, []Column, error) {
if !ok { if !ok {
return nil, nil, errors.New("names a choice, not a template; a record is a template whose fields are its columns") return nil, nil, errors.New("names a choice, not a template; a record is a template whose fields are its columns")
} }
names := recordColumns(t) names := sortedNames(t.fields)
if len(names) == 0 { if len(names) == 0 {
return nil, nil, ErrNoColumns return nil, nil, ErrNoColumns
} }
@@ -274,16 +274,3 @@ func renderRecord(s *session, t *template, columns []Column, sc renderScope) *Re
} }
return r return r
} }
// recordColumns is the sorted non-reference field names — the columns a record
// projects. A {/path} binding is carried in fields under its root path, so only a
// name that is not a reference is a column.
func recordColumns(t *template) []string {
var names []string
for _, name := range sortedNames(t.fields) {
if !isRef(name) {
names = append(names, name)
}
}
return names
}
+4 -6
View File
@@ -68,7 +68,7 @@ func refSegments(name string, folder []string) ([]string, error) {
} }
// linkRefs resolves every reference in the assembled tree. The head of the path — // linkRefs resolves every reference in the assembled tree. The head of the path —
// up to the category it names — is bound into the referring template's fields // up to the category it names — is bound into the referring template's refHeads
// under its root path, and the rest reads into it the way a sibling path does, so // under its root path, and the rest reads into it the way a sibling path does, so
// a reference is held like a sibling and two spellings of one target are one // a reference is held like a sibling and two spellings of one target are one
// draw. It runs once, after all data is merged, so a reference sees the final // draw. It runs once, after all data is merged, so a reference sees the final
@@ -91,10 +91,8 @@ func linkTemplateRefs(folder []string, path, category string, t *template, root
if len(names) == 0 { if len(names) == 0 {
return nil return nil
} }
if t.fields == nil {
t.fields = map[string]node{}
}
t.refs = make(map[string]refBinding, len(names)) t.refs = make(map[string]refBinding, len(names))
t.refHeads = make(map[string]node, len(names))
for _, name := range names { for _, name := range names {
segments, err := refSegments(name, folder) segments, err := refSegments(name, folder)
if err != nil { if err != nil {
@@ -111,7 +109,7 @@ func linkTemplateRefs(folder []string, path, category string, t *template, root
if err := checkPath(target, tail, key); err != nil { if err := checkPath(target, tail, key); err != nil {
return fmt.Errorf("%s: reference {%s}: %w", path, name, err) return fmt.Errorf("%s: reference {%s}: %w", path, name, err)
} }
t.fields[key] = target t.refHeads[key] = target
t.refs[name] = refBinding{key, tail} t.refs[name] = refBinding{key, tail}
} }
if err := t.compileFormat(); err != nil { if err := t.compileFormat(); err != nil {
@@ -134,7 +132,7 @@ func columnReadOf(t *template) *columnRead {
return nil return nil
} }
a := splitArm(name, t.refs) a := splitArm(name, t.refs)
target, isTemplate := t.fields[a.key].(*template) target, isTemplate := t.head(a.key).(*template)
if !isTemplate || !target.record || len(a.tail) != 1 { if !isTemplate || !target.record || len(a.tail) != 1 {
return nil return nil
} }
-2
View File
@@ -19,8 +19,6 @@ Self-sufficiency 5.6), every seat capped by Locality or Self-sufficiency.
This round, in order: This round, in order:
- Keep reference bindings out of `template.fields`, so no walk over a template's fields
needs the `isRef` filter `named`, `paths`, `recordColumns` and `template.field` carry.
- Compile a template's format once: `compileFormat` runs again from `linkTemplateRefs`, - Compile a template's format once: `compileFormat` runs again from `linkTemplateRefs`,
so `ops`, `bound` and `held` are provisional until linking and its fences run twice. so `ops`, `bound` and `held` are provisional until linking and its fences run twice.
- Parse a format once and have the fences read `t.ops`: `eachToken` rescans the format - Parse a format once and have the fences read `t.ops`: `eachToken` rescans the format
+1 -1
View File
@@ -158,7 +158,7 @@ func (p *valueProof) template(t *template) proven {
case !isFunc: case !isFunc:
var leaves []node var leaves []node
for _, a := range splitArms(body, t.refs) { for _, a := range splitArms(body, t.refs) {
leaves = append(leaves, pathLeaves(t.fields[a.key], a.tail)...) leaves = append(leaves, pathLeaves(t.head(a.key), a.tail)...)
} }
return p.unite(leaves) return p.unite(leaves)
case name == "calc": case name == "calc":