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 {
return nil
}
for _, name := range recordColumns(t) {
for _, name := range sortedNames(t.fields) {
if err := check(path, name, t.fields[name]); err != nil {
return err
}
+3 -3
View File
@@ -88,7 +88,7 @@ func (c *drawCheck) checkRecordDraws(path string, n node) error {
return nil
}
// A record-only template's format renders nothing, so weigh the columns, not the format.
columns := recordColumns(t)
columns := sortedNames(t.fields)
reads := false
for _, name := range columns {
reads = reads || c.readsPath(t.fields[name])
@@ -148,7 +148,7 @@ func readsTable(n node, a arm) bool {
if !isTemplate {
return false
}
_, isTable := t.fields[a.key].(*table)
_, isTable := t.head(a.key).(*table)
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.
func (w *drawWalk) edge(from node, e renderEdge, at drawAt) {
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] {
w.read[k] = true
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
for _, e := range renderEdges(n) {
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
}
}
-3
View File
@@ -146,9 +146,6 @@ func paths(n node) []string {
case *template:
out := []string{""}
for _, name := range sortedNames(n.fields) {
if isRef(name) { // a binding, not a path segment
continue
}
for _, p := range paths(n.fields[name]) {
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 {
out := make([]namedNode, 0, len(m))
for _, name := range sortedNames(m) {
if isRef(name) {
continue
}
out = append(out, namedNode{name: name, node: m[name]})
}
return out
@@ -134,8 +129,8 @@ func renderEdges(n node) []renderEdge {
var es []renderEdge
add := func(name, operand string) {
a := splitArm(name, n.refs)
c, ok := n.fields[a.key]
if !ok {
c := n.head(a.key)
if c == nil {
return
}
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
// 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
// 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
@@ -32,13 +32,13 @@ func readField(s *session, t *template, held *hold, sc renderScope, a arm) draw
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))
}
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)
if r, done := d.value[a.path]; done {
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 {
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 {
held := map[node]bool{}
if _, isPath := t.bound[head]; !isPath {
operandDraw(t.fields[head], held)
operandDraw(t.head(head), held)
return held
}
for _, r := range readers {
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
+7 -9
View File
@@ -49,7 +49,7 @@ func (*null) isNode() {}
type template struct {
// Filled by `compileString`, `compileTemplate` and `table.compileWhole`:
format string
fields map[string]node // `linkTemplateRefs` adds each bound reference here
fields map[string]node
repeat int
separator string
datatype DataType
@@ -78,19 +78,19 @@ type template struct {
// Filled by `linkTemplateRefs` and `keyDrawGroup`, from the assembled tree:
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
drawGroupKey string // its draw group keyed by its category: what a render reads its reference paths under
}
func (*template) isNode() {}
// field is the node a path segment names; a binding is a render edge, not a field.
func (t *template) field(seg string) (node, bool) {
if isRef(seg) {
return nil, false
// head is the node an arm's key names: a sibling field, or a reference's category.
func (t *template) head(key string) node {
if isRef(key) {
return t.refHeads[key]
}
n, ok := t.fields[seg]
return n, ok
return t.fields[key]
}
// 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
}
// 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)
return c, nil
}
+1 -1
View File
@@ -177,7 +177,7 @@ func walkPath(n node, tail []string, w pathWalk) (node, error) {
return nil, err
}
}
child, ok := n.field(tail[0])
child, ok := n.fields[tail[0]]
if !ok {
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 {
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 {
return nil, nil, ErrNoColumns
}
@@ -274,16 +274,3 @@ func renderRecord(s *session, t *template, columns []Column, sc renderScope) *Re
}
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 —
// 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
// 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
@@ -91,10 +91,8 @@ func linkTemplateRefs(folder []string, path, category string, t *template, root
if len(names) == 0 {
return nil
}
if t.fields == nil {
t.fields = map[string]node{}
}
t.refs = make(map[string]refBinding, len(names))
t.refHeads = make(map[string]node, len(names))
for _, name := range names {
segments, err := refSegments(name, folder)
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 {
return fmt.Errorf("%s: reference {%s}: %w", path, name, err)
}
t.fields[key] = target
t.refHeads[key] = target
t.refs[name] = refBinding{key, tail}
}
if err := t.compileFormat(); err != nil {
@@ -134,7 +132,7 @@ func columnReadOf(t *template) *columnRead {
return nil
}
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 {
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:
- 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`,
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
+1 -1
View File
@@ -158,7 +158,7 @@ func (p *valueProof) template(t *template) proven {
case !isFunc:
var leaves []node
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)
case name == "calc":