Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33
@@ -199,9 +199,9 @@ func inlineScope(n node, label string) nodeScope {
|
||||
// binding is one scope's way through bind, the one load pipeline; its fields are where
|
||||
// scopes differ.
|
||||
type binding struct {
|
||||
scope nodeScope
|
||||
link func() error
|
||||
referenceFence func() error
|
||||
scope nodeScope
|
||||
link func() error
|
||||
scopeFence func() error
|
||||
// Set by a caller that proves the columns against their Go types itself.
|
||||
typedByGo bool
|
||||
}
|
||||
@@ -216,7 +216,7 @@ func treeBinding(root map[string]node) binding {
|
||||
}
|
||||
return linkRefs(root)
|
||||
},
|
||||
referenceFence: func() error { return checkNoCycles(root) },
|
||||
scopeFence: func() error { return checkNoCycles(root) },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ func (b binding) bind() error {
|
||||
if err := b.link(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.referenceFence(); err != nil {
|
||||
if err := b.scopeFence(); err != nil {
|
||||
return err
|
||||
}
|
||||
if !b.typedByGo {
|
||||
@@ -232,13 +232,13 @@ func (b binding) bind() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return checkRenders(b.scope)
|
||||
return checkNodeFences(b.scope)
|
||||
}
|
||||
|
||||
// checkRenders runs the per-node fences over a scope, each over the whole scope
|
||||
// checkNodeFences runs the per-node fences over a scope, each over the whole scope
|
||||
// before the next, so which of several broken nodes is reported does not depend on
|
||||
// the walk. Its walks terminate only where nothing renders itself.
|
||||
func checkRenders(s nodeScope) error {
|
||||
func checkNodeFences(s nodeScope) error {
|
||||
mem := renderCounts{}
|
||||
if err := s(func(path string, n node) error { return repeatCheck(path, n, mem) }); err != nil {
|
||||
return err
|
||||
|
||||
+4
-4
@@ -105,7 +105,7 @@ func checkOwnFamily(t *template) error {
|
||||
return renderEdge{}, nil, false
|
||||
}
|
||||
if e, head, found := find(t); found {
|
||||
return fmt.Errorf("%s reads %s, a table of its own family, which a bare read of %s would draw apart from the row it renders; read the family from a template beside it, or add the value as a column", e.reached(), head.category, own.category)
|
||||
return fmt.Errorf("%s reads %s, a table of its own family, which a bare read of %s would draw apart from the row it renders; read the family from a template beside it, or add the value as a column", e.reached(), head.segment, own.segment)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -252,13 +252,13 @@ func checkFamilyPair(a, b pathRead) error {
|
||||
if s, ok := y.tr.selected(x.tr.headTable); ok && len(x.tr.sels) == 0 {
|
||||
tail := x.a.tail
|
||||
if s.t != x.tr.headTable {
|
||||
tail = append([]string{x.tr.headTable.category}, tail...)
|
||||
tail = append([]string{x.tr.headTable.segment}, tail...)
|
||||
}
|
||||
return fmt.Errorf("%s draws %s, which %s selects a row of; write {%s.%s}, or draw them apart with a drawGroup",
|
||||
x.at.route.spelled(x.a.spelling), drawn.category, y.at.route.spelled(y.a.spelling), s.spelling, joinSegments(tail))
|
||||
x.at.route.spelled(x.a.spelling), drawn.segment, y.at.route.spelled(y.a.spelling), s.spelling, joinSegments(tail))
|
||||
}
|
||||
return fmt.Errorf("%s draws %s, which %s selects a row of; select that row in both, or draw them apart with a drawGroup",
|
||||
x.at.route.spelled(x.a.spelling), drawn.category, y.at.route.spelled(y.a.spelling))
|
||||
x.at.route.spelled(x.a.spelling), drawn.segment, y.at.route.spelled(y.a.spelling))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -64,14 +64,14 @@ type formatToken struct {
|
||||
body string // the braces' content, as written
|
||||
fn string // builtinCall
|
||||
args []string // builtinCall
|
||||
names []string // fieldAlternation: the '|' arms; builtinCall: the operands its builtin reads
|
||||
names []string // nameRead: the '|' arms; builtinCall: the operands its builtin reads
|
||||
}
|
||||
|
||||
type tokenKind uint8
|
||||
|
||||
const (
|
||||
builtinCall tokenKind = iota + 1
|
||||
fieldAlternation
|
||||
nameRead
|
||||
literalRun
|
||||
)
|
||||
|
||||
@@ -93,7 +93,7 @@ func parseFormat(format string) ([]formatToken, error) {
|
||||
}
|
||||
flush()
|
||||
if indexOutside(t.body, '(') < 0 {
|
||||
toks = append(toks, formatToken{kind: fieldAlternation, body: t.body, names: splitOutside(t.body, '|')})
|
||||
toks = append(toks, formatToken{kind: nameRead, body: t.body, names: splitOutside(t.body, '|')})
|
||||
return nil
|
||||
}
|
||||
name, args, ok := funcCall(t.body)
|
||||
@@ -220,7 +220,7 @@ func checkToken(t formatToken, fields map[string]node) error {
|
||||
switch t.kind {
|
||||
case builtinCall:
|
||||
return checkFunc(t, fields)
|
||||
case fieldAlternation:
|
||||
case nameRead:
|
||||
for _, name := range t.names {
|
||||
if isRef(name) {
|
||||
if _, _, err := refShape(name); err != nil {
|
||||
@@ -372,7 +372,7 @@ type callFn func(s *session, emitted string, operands []string) string
|
||||
// re-scans the format.
|
||||
type op struct {
|
||||
formatToken
|
||||
arms []arm // fieldAlternation: the '|' alternatives, split into key and path once
|
||||
arms []arm // nameRead: the '|' alternatives, split into key and path once
|
||||
call callFn
|
||||
// operands are the fields the builtin reads, in the order its operands func
|
||||
// fixed; expand reads them before the call. nil for a builtin that reads none.
|
||||
@@ -446,7 +446,7 @@ func compileOps(toks []formatToken, refs map[string]refBinding) formatOps {
|
||||
c.ops = append(c.ops, op{formatToken: tok})
|
||||
case builtinCall:
|
||||
c.function(tok, refs)
|
||||
case fieldAlternation:
|
||||
case nameRead:
|
||||
c.field(tok, refs)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ func (sc renderScope) at(n node, pins *pinSet) renderScope {
|
||||
// rowOf is the row of t its columns render from.
|
||||
func (sc renderScope) rowOf(t *table) int {
|
||||
if sc.row.t != t {
|
||||
panic(internalError("a column of %s renders in a scope holding no row of it", t.category))
|
||||
panic(internalError("a column of %s renders in a scope holding no row of it", t.segment))
|
||||
}
|
||||
return sc.row.row
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ func inlineBinding(n node, label string, root map[string]node) binding {
|
||||
return binding{
|
||||
scope: scope,
|
||||
link: func() error { return linkNodeRefs(scope, root) },
|
||||
referenceFence: func() error {
|
||||
scopeFence: func() error {
|
||||
if t, isTemplate := n.(*template); isTemplate && t.drawGroup != "" {
|
||||
return fmt.Errorf("%s: drawGroup %q names nothing, since nothing can reference an inline template; drop it", label, t.drawGroup)
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ func (t *table) route(tail []string, descended bool) (tableRoute, error) {
|
||||
case len(tail) == 0 && sel == "" && !descended:
|
||||
r.next = t
|
||||
case len(tail) == 0:
|
||||
r.next = t.pinnedRow
|
||||
r.next = t.rowNode
|
||||
case child != nil:
|
||||
r.next, r.rest, r.descends = child, tail[1:], true
|
||||
default:
|
||||
@@ -415,7 +415,7 @@ func (t *table) selector(tail []string) (sel string, rest []string, err error) {
|
||||
}
|
||||
sel, rest = selectorOf(tail[0]), tail[1:]
|
||||
if len(rest) > 0 && isSelector(rest[0]) {
|
||||
return "", nil, fmt.Errorf("%s[%s] is selected twice; one selector names its row", t.category, sel)
|
||||
return "", nil, fmt.Errorf("%s[%s] is selected twice; one selector names its row", t.segment, sel)
|
||||
}
|
||||
return sel, rest, nil
|
||||
}
|
||||
@@ -429,7 +429,7 @@ func (t *table) step(tail []string) (column node, child *table, err error) {
|
||||
return t.fields[t.header[i]], nil, nil
|
||||
}
|
||||
if child = t.descendant(tail[0]); child == nil {
|
||||
return nil, nil, fmt.Errorf("no column or linked table %q in %s", tail[0], t.category)
|
||||
return nil, nil, fmt.Errorf("no column or linked table %q in %s", tail[0], t.segment)
|
||||
}
|
||||
return nil, child, nil
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ func (p *pinSet) pinned(t *table) (int, bool) {
|
||||
func (p *pinSet) mustRow(t *table) int {
|
||||
r, ok := p.pinned(t)
|
||||
if !ok {
|
||||
panic(internalError("a column of %s is rendered with no row pinned", t.category))
|
||||
panic(internalError("a column of %s is rendered with no row pinned", t.segment))
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ func renderRoot(s *session, t *template, trace renderTrace) {
|
||||
set := holdSet{trace: trace}
|
||||
sc := renderScope{set: &set}
|
||||
sc.groupHold().pins.pin(tbl, r)
|
||||
render(s, tbl.pinnedRow, sc.at(tbl.pinnedRow, &sc.groupHold().pins))
|
||||
render(s, tbl.rowNode, sc.at(tbl.rowNode, &sc.groupHold().pins))
|
||||
}
|
||||
case t.repeat > 1:
|
||||
expand(s, t, sc)
|
||||
|
||||
@@ -264,7 +264,7 @@ func recordOf(n node) (*template, []Column, error) {
|
||||
func renderRecord(s *session, t *template, columns []Column, sc renderScope) *Record {
|
||||
sc = sc.in(t)
|
||||
if t.site.row == formatRow {
|
||||
sc = sc.at(t.site.table.pinnedRow, &sc.groupHold().pins)
|
||||
sc = sc.at(t.site.table.rowNode, &sc.groupHold().pins)
|
||||
}
|
||||
r := &Record{columns: append([]Column(nil), columns...)}
|
||||
for i := range r.columns {
|
||||
|
||||
+1
-1
@@ -155,7 +155,7 @@ func columnReadOf(t *template, link templateLink) *columnRead {
|
||||
|
||||
// loneRef is the reference a format of one reference token and nothing else reads.
|
||||
func loneRef(toks []formatToken) (string, bool) {
|
||||
if len(toks) != 1 || toks[0].kind != fieldAlternation || len(toks[0].names) != 1 || !isRef(toks[0].names[0]) {
|
||||
if len(toks) != 1 || toks[0].kind != nameRead || len(toks[0].names) != 1 || !isRef(toks[0].names[0]) {
|
||||
return "", false
|
||||
}
|
||||
return toks[0].names[0], true
|
||||
|
||||
@@ -121,7 +121,7 @@ func expand(s *session, t *template, sc renderScope) string {
|
||||
switch o.kind {
|
||||
case literalRun:
|
||||
b.WriteString(o.lit)
|
||||
case fieldAlternation:
|
||||
case nameRead:
|
||||
b.WriteString(readField(s, t, held, sc, o.arms[s.IntN(len(o.arms))]).text)
|
||||
case builtinCall:
|
||||
// Read before the call, so the value a calc computes is the value the
|
||||
|
||||
@@ -13,14 +13,14 @@ import (
|
||||
// columns, each row is one draw, and the format renders the drawn row. A column is a
|
||||
// cell of the row a render pinned; a cell carrying tokens compiles to a string node.
|
||||
type table struct {
|
||||
category string
|
||||
segment string // path's last segment
|
||||
path string // the category's path from the data root, which a selector is written at
|
||||
file string
|
||||
formatTemplate *template // fields are the column nodes
|
||||
header []string
|
||||
col map[string]int
|
||||
fields map[string]node // column nodes, the format's fields
|
||||
pinnedRow *tableRow
|
||||
rowNode *tableRow
|
||||
cells []string // rows × columns, flat
|
||||
cellTemplates map[int]*template
|
||||
keyIndex int // keyIndex through parentIndex are column indexes, -1 where the option is absent
|
||||
@@ -84,7 +84,7 @@ func isTableOption(name string) bool {
|
||||
const inSelector = `[]{}"|`
|
||||
|
||||
// compileTable compiles a category object naming a rows file into a table.
|
||||
func compileTable(m map[string]any, category string, files *categoryFiles) (*table, error) {
|
||||
func compileTable(m map[string]any, segment string, files *categoryFiles) (*table, error) {
|
||||
o, err := readTableOptions(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -93,7 +93,7 @@ func compileTable(m map[string]any, category string, files *categoryFiles) (*tab
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t := &table{category: category, file: o.rows, keyIndex: -1, nameIndex: -1, weightIndex: -1, parentIndex: -1}
|
||||
t := &table{segment: segment, file: o.rows, keyIndex: -1, nameIndex: -1, weightIndex: -1, parentIndex: -1}
|
||||
if err := t.parseRows(data); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", o.rows, err)
|
||||
}
|
||||
@@ -328,7 +328,7 @@ func (t *table) compileRowFormat(format string) error {
|
||||
return err
|
||||
}
|
||||
for _, tok := range toks {
|
||||
if tok.kind != fieldAlternation {
|
||||
if tok.kind != nameRead {
|
||||
continue
|
||||
}
|
||||
for _, name := range tok.names {
|
||||
@@ -342,7 +342,7 @@ func (t *table) compileRowFormat(format string) error {
|
||||
return err
|
||||
}
|
||||
t.formatTemplate = &template{format: format, tokens: toks, fields: t.fields, repeat: 1, isRecord: true, site: tableSite{t, formatRow}}
|
||||
t.pinnedRow = &tableRow{t}
|
||||
t.rowNode = &tableRow{t}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -405,7 +405,7 @@ func (t *table) linkParent(path string, siblings map[string]node) error {
|
||||
var ancestors []*table
|
||||
for q, seen := p, map[*table]bool{t: true}; q != nil; q, _ = siblings[q.header[q.parentIndex]].(*table) {
|
||||
if seen[q] {
|
||||
return fmt.Errorf("parent cycle: %s reaches itself through its parents", q.category)
|
||||
return fmt.Errorf("parent cycle: %s reaches itself through its parents", q.segment)
|
||||
}
|
||||
seen[q] = true
|
||||
ancestors = append(ancestors, q)
|
||||
@@ -414,8 +414,8 @@ func (t *table) linkParent(path string, siblings map[string]node) error {
|
||||
}
|
||||
}
|
||||
for _, q := range ancestors {
|
||||
if _, clash := q.col[t.category]; clash {
|
||||
return fmt.Errorf("%q is named like a column of %q, its ancestor, so %s.%s could read either; rename one", t.category, q.category, q.category, t.category)
|
||||
if _, clash := q.col[t.segment]; clash {
|
||||
return fmt.Errorf("%q is named like a column of %q, its ancestor, so %s.%s could read either; rename one", t.segment, q.segment, q.segment, t.segment)
|
||||
}
|
||||
}
|
||||
linked := make(map[string]bool, p.rowCount())
|
||||
@@ -435,7 +435,7 @@ func (t *table) linkParent(path string, siblings map[string]node) error {
|
||||
if p.children == nil {
|
||||
p.children = map[string]*table{}
|
||||
}
|
||||
p.children[t.category] = t
|
||||
p.children[t.segment] = t
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -598,7 +598,7 @@ func (t *table) selectorSpelling(r int) string {
|
||||
case t.keyIndex >= 0:
|
||||
return t.path + "[" + t.cell(r, t.keyIndex) + "]"
|
||||
case t.nameIndex >= 0:
|
||||
return t.parentT.selectorSpelling(t.parentRow(r)) + "." + t.category + "[" + t.cell(r, t.nameIndex) + "]"
|
||||
return t.parentT.selectorSpelling(t.parentRow(r)) + "." + t.segment + "[" + t.cell(r, t.nameIndex) + "]"
|
||||
}
|
||||
return fmt.Sprintf("%s line %d", t.file, r+2)
|
||||
}
|
||||
|
||||
+1
-1
@@ -1088,7 +1088,7 @@ func TestProbeReportsTheTablesADrawPins(t *testing.T) {
|
||||
func categoriesOf(tables map[*table]bool) []string {
|
||||
var out []string
|
||||
for tbl := range tables {
|
||||
out = append(out, tbl.category)
|
||||
out = append(out, tbl.segment)
|
||||
}
|
||||
slices.Sort(out)
|
||||
return out
|
||||
|
||||
@@ -90,7 +90,7 @@ func (p *valueProof) prove(n node) proven {
|
||||
case *tableColumn:
|
||||
v = p.proveCells(n)
|
||||
case *tableRow:
|
||||
v = unproven(fmt.Sprintf("%q renders a row of %s, which is composed text", n.t.formatTemplate.format, n.t.category))
|
||||
v = unproven(fmt.Sprintf("%q renders a row of %s, which is composed text", n.t.formatTemplate.format, n.t.segment))
|
||||
default:
|
||||
v = unproven(`it reads a null, which renders "" outside its own column`)
|
||||
}
|
||||
@@ -157,7 +157,7 @@ func (p *valueProof) proveTemplate(t *template) proven {
|
||||
o := t.compiled.ops[0]
|
||||
body, name, args := o.body, o.fn, o.args
|
||||
switch _, isTransform := transforms[name]; {
|
||||
case o.kind == fieldAlternation:
|
||||
case o.kind == nameRead:
|
||||
var leaves []node
|
||||
for _, a := range o.arms {
|
||||
leaves = append(leaves, pathLeaves(t.head(a.key), a.tail)...)
|
||||
|
||||
Reference in New Issue
Block a user