Answer architecture round 1: one categoryPath, the walk's label named label, checkAncestors split from proveParent, and file items 115 and 116
This commit is contained in:
@@ -23,13 +23,13 @@ func checkCalcFields(args []string, fields map[string]node) error {
|
||||
}
|
||||
|
||||
// checkCalcNames holds each calc of t reading a name to the checks checkCalcFields makes of a field.
|
||||
func checkCalcNames(path string, t *template) error {
|
||||
func checkCalcNames(label string, t *template) error {
|
||||
for _, o := range t.compiled.ops {
|
||||
if o.Fn != builtinfunc.CalcName || !slices.ContainsFunc(o.operands, func(a arm) bool { return a.kind == namedRead }) {
|
||||
continue
|
||||
}
|
||||
if err := checkOperands(o.Args[0], builtinfunc.ParsedCalc(o.Args[0]), operandNodes(o)); err != nil {
|
||||
return fmt.Errorf("%s: token {%s}: %w", path, o.Body, err)
|
||||
return fmt.Errorf("%s: token {%s}: %w", label, o.Body, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -68,7 +68,7 @@ func mergeChildren(dst, src map[string]node) {
|
||||
|
||||
// nodeScope is the set of nodes one validation pass covers: the categories one load
|
||||
// binds, or a single inline node.
|
||||
type nodeScope func(fn func(path string, n node) error) error
|
||||
type nodeScope func(fn func(label string, n node) error) error
|
||||
|
||||
// categorySite is a loaded category and where it sits: the folder holding it, that
|
||||
// folder's path, and its own dot path.
|
||||
@@ -97,11 +97,14 @@ func categorySites(root *folder) []categorySite {
|
||||
}
|
||||
|
||||
func siteIn(dir []string, in *folder, name string) categorySite {
|
||||
return categorySite{dir: dir, path: join(strings.Join(dir, "."), name), in: in, n: in.children[name]}
|
||||
return categorySite{dir: dir, path: categoryPath(dir, name), in: in, n: in.children[name]}
|
||||
}
|
||||
|
||||
// categoryPath is the dot path of the category name in the folder dir.
|
||||
func categoryPath(dir []string, name string) string { return join(strings.Join(dir, "."), name) }
|
||||
|
||||
func sitesScope(sites []categorySite) nodeScope {
|
||||
return func(fn func(path string, n node) error) error {
|
||||
return func(fn func(label string, n node) error) error {
|
||||
for _, s := range sites {
|
||||
if err := eachNode(s.n, s.path, fn); err != nil {
|
||||
return err
|
||||
@@ -112,7 +115,7 @@ func sitesScope(sites []categorySite) nodeScope {
|
||||
}
|
||||
|
||||
func inlineScope(n node, label string) nodeScope {
|
||||
return func(fn func(path string, m node) error) error { return eachNode(n, label, fn) }
|
||||
return func(fn func(label string, m node) error) error { return eachNode(n, label, fn) }
|
||||
}
|
||||
|
||||
// binding is one scope's way through bind, the one load pipeline; its fields are where
|
||||
@@ -159,7 +162,7 @@ func (b binding) bind() error {
|
||||
// first.
|
||||
func checkNodeFences(s nodeScope) error {
|
||||
mem := renderCounts{}
|
||||
if err := s(func(path string, n node) error { return repeatCheck(path, n, mem) }); err != nil {
|
||||
if err := s(func(label string, n node) error { return repeatCheck(label, n, mem) }); err != nil {
|
||||
return err
|
||||
}
|
||||
return s((&valueProof{}).checkDatatype)
|
||||
|
||||
+5
-5
@@ -59,8 +59,8 @@ func datatypeOf(m map[string]any, pos position) (DataType, error) {
|
||||
// after the columns its items read, so a column read is named before its readers.
|
||||
func checkColumns(s nodeScope) error {
|
||||
checked := map[node]bool{}
|
||||
var check func(path, name string, column node) error
|
||||
check = func(path, name string, column node) error {
|
||||
var check func(label, name string, column node) error
|
||||
check = func(label, name string, column node) error {
|
||||
if checked[column] {
|
||||
return nil
|
||||
}
|
||||
@@ -79,18 +79,18 @@ func checkColumns(s nodeScope) error {
|
||||
first := columnDatatype(column)
|
||||
for _, it := range items[1:] {
|
||||
if d := itemDatatype(it); d != first {
|
||||
return fmt.Errorf("%s: field %q: %w", path, name, disagreement(items[0], first, it, d))
|
||||
return fmt.Errorf("%s: field %q: %w", label, name, disagreement(items[0], first, it, d))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return s(func(path string, n node) error {
|
||||
return s(func(label string, n node) error {
|
||||
t, ok := n.(*template)
|
||||
if !ok || !t.isRecord {
|
||||
return nil
|
||||
}
|
||||
for _, name := range sortedNames(t.fields) {
|
||||
if err := check(path, name, t.fields[name]); err != nil {
|
||||
if err := check(label, name, t.fields[name]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,28 +8,29 @@ import (
|
||||
"github.com/larvit/fejkdata/internal/invariant"
|
||||
)
|
||||
|
||||
// eachNode visits n and every node contained within it once, passing the dot path
|
||||
// that reaches each, followed by a table cell's line. It never crosses a reference edge — a {/path} reference is
|
||||
// skipped — so a single inline node is walked on its own.
|
||||
func eachNode(n node, path string, fn func(path string, n node) error) error {
|
||||
// eachNode visits n and every node contained within it once, passing the label naming
|
||||
// each in an error: the dot path reaching it, and a table cell's line after it. It never
|
||||
// crosses a reference edge — a {/path} reference is skipped — so a single inline node is
|
||||
// walked on its own.
|
||||
func eachNode(n node, label string, fn func(label string, n node) error) error {
|
||||
seen := map[node]bool{}
|
||||
var visit func(string, node) error
|
||||
visit = func(path string, m node) error {
|
||||
visit = func(label string, m node) error {
|
||||
if m == nil || seen[m] {
|
||||
return nil
|
||||
}
|
||||
seen[m] = true
|
||||
if err := fn(path, m); err != nil {
|
||||
if err := fn(label, m); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, c := range contained(m) {
|
||||
if err := visit(c.labelUnder(path), c.node); err != nil {
|
||||
if err := visit(c.labelIn(label), c.node); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return visit(path, n)
|
||||
return visit(label, n)
|
||||
}
|
||||
|
||||
// namedNode is a contained child and the segment reaching it; a choice's items carry
|
||||
@@ -40,12 +41,12 @@ type namedNode struct {
|
||||
line int // a table cell's line in its rows file, else 0
|
||||
}
|
||||
|
||||
// labelUnder is the path naming c in an error, a cell by its line.
|
||||
func (c namedNode) labelUnder(path string) string {
|
||||
// labelIn is c's label inside the node labelled label.
|
||||
func (c namedNode) labelIn(label string) string {
|
||||
if c.line > 0 {
|
||||
return fmt.Sprintf("%s, line %d", path, c.line)
|
||||
return fmt.Sprintf("%s, line %d", label, c.line)
|
||||
}
|
||||
return join(path, c.name)
|
||||
return join(label, c.name)
|
||||
}
|
||||
|
||||
func contained(n node) []namedNode {
|
||||
@@ -164,9 +165,9 @@ func (m renderCounts) renderCount(n node) int {
|
||||
// repeatCheck bounds the renders a repeat multiplies to along any root-to-leaf
|
||||
// path, so nested repeats cannot build what one repeat may not.
|
||||
// docs/decisions.md#the-repeat-cap-bounds-renders-not-bytes
|
||||
func repeatCheck(path string, n node, mem renderCounts) error {
|
||||
func repeatCheck(label string, n node, mem renderCounts) error {
|
||||
if t, ok := n.(*template); ok && t.repeat > 1 && mem.renderCount(n) > MaxRepeat {
|
||||
return fmt.Errorf("%s: repeat %d multiplies to %d renders along one path, above the maximum %d", path, t.repeat, mem.renderCount(n), MaxRepeat)
|
||||
return fmt.Errorf("%s: repeat %d multiplies to %d renders along one path, above the maximum %d", label, t.repeat, mem.renderCount(n), MaxRepeat)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -191,25 +192,25 @@ func checkNoCycles(s nodeScope) error {
|
||||
return nil
|
||||
})
|
||||
color := map[node]int{}
|
||||
var visit func(n node, path string) error
|
||||
visit = func(n node, path string) error {
|
||||
var visit func(n node, label string) error
|
||||
visit = func(n node, label string) error {
|
||||
if !inScope[n] {
|
||||
return nil // loaded before this scope and proven then, so it never reaches back into it
|
||||
}
|
||||
switch color[n] {
|
||||
case grey:
|
||||
return fmt.Errorf("reference cycle: %s", path)
|
||||
return fmt.Errorf("reference cycle: %s", label)
|
||||
case black:
|
||||
return nil
|
||||
}
|
||||
color[n] = grey
|
||||
for _, e := range renderEdges(n) {
|
||||
if err := visit(e.to, path+" -> "+e.label); err != nil {
|
||||
if err := visit(e.to, label+" -> "+e.label); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
color[n] = black
|
||||
return nil
|
||||
}
|
||||
return s(func(path string, n node) error { return visit(n, path) })
|
||||
return s(func(label string, n node) error { return visit(n, label) })
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ func bindInline(root *folder, n node, label string, typedByGo bool) error {
|
||||
// refuseFolderRefs refuses each reference in scope not written from the root, {/x}: an
|
||||
// inline node sits in no folder.
|
||||
func refuseFolderRefs(scope nodeScope) error {
|
||||
return scope(func(path string, m node) error {
|
||||
return scope(func(label string, m node) error {
|
||||
t, ok := m.(*template)
|
||||
if !ok {
|
||||
return nil
|
||||
@@ -112,10 +112,10 @@ func refuseFolderRefs(scope nodeScope) error {
|
||||
for _, name := range refTokens(t.tokens) {
|
||||
sigil, rest, err := grammar.RefShape(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: reference {%s}: %w", path, name, err)
|
||||
return fmt.Errorf("%s: reference {%s}: %w", label, name, err)
|
||||
}
|
||||
if sigil != "/" {
|
||||
return fmt.Errorf("%s: reference {%s}: an inline template has no folder; write {/%s}", path, name, rest)
|
||||
return fmt.Errorf("%s: reference {%s}: an inline template has no folder; write {/%s}", label, name, rest)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -125,18 +125,18 @@ func refuseFolderRefs(scope nodeScope) error {
|
||||
// linkNodeRefs binds the references in an inline node's templates against the
|
||||
// loaded tree.
|
||||
func linkNodeRefs(scope nodeScope, root map[string]node) error {
|
||||
if err := scope(func(path string, m node) error {
|
||||
if err := scope(func(label string, m node) error {
|
||||
if t, ok := m.(*template); ok {
|
||||
return linkTemplate(nil, path, "", t, root)
|
||||
return linkTemplate(nil, label, "", t, root)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, pass := range linkPasses {
|
||||
if err := scope(func(path string, m node) error {
|
||||
if err := scope(func(label string, m node) error {
|
||||
if t, ok := m.(*template); ok {
|
||||
return pass(path, t)
|
||||
return pass(label, t)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
|
||||
@@ -255,14 +255,14 @@ func namedReads(t *template, fn func(o *op, a *arm) error) error {
|
||||
|
||||
// linkPasses run in order once every template is linked, each over every template before the next
|
||||
// starts: a pass reads what the one before filled in.
|
||||
var linkPasses = []func(path string, t *template) error{linkNames, linkColumnRead, checkNameReads, checkCalcNames}
|
||||
var linkPasses = []func(label string, t *template) error{linkNames, linkColumnRead, checkNameReads, checkCalcNames}
|
||||
|
||||
// linkNames compiles t's reads of a name as paths from the head its binding resolved to, once every
|
||||
// template is linked, so each binder has resolved what it binds.
|
||||
func linkNames(path string, t *template) error {
|
||||
func linkNames(label string, t *template) error {
|
||||
return namedReads(t, func(o *op, a *arm) error {
|
||||
if err := linkName(t, a); err != nil {
|
||||
return fmt.Errorf("%s: token {%s}: %w", path, o.Body, err)
|
||||
return fmt.Errorf("%s: token {%s}: %w", label, o.Body, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
+4
-4
@@ -11,22 +11,22 @@ import (
|
||||
// checkNameReads refuses each binding of t that checkUses refuses, a read of a name inside the
|
||||
// field bound to it, and a read {n} beside {n.w} where n's pick renders w twice: the pick keeps one
|
||||
// draw of w.
|
||||
func checkNameReads(path string, t *template) error {
|
||||
func checkNameReads(label string, t *template) error {
|
||||
for _, tok := range t.tokens {
|
||||
if tok.Kind != grammar.NameBind {
|
||||
continue
|
||||
}
|
||||
if err := t.nameScope.bindings[tok.Bound].checkUses(); err != nil {
|
||||
return fmt.Errorf("%s: token {%s}: %w", path, tok.Body, err)
|
||||
return fmt.Errorf("%s: token {%s}: %w", label, tok.Body, err)
|
||||
}
|
||||
}
|
||||
return namedReads(t, func(o *op, a *arm) error {
|
||||
if a.named.bindsField() && rendersInside(compilePath(a.named.head, a.named.tail).leaves, t) {
|
||||
return fmt.Errorf("%s: token {%s}: name %q is read inside %q, the field bound to it; read the name outside that field", path, o.Body, a.named.name, a.named.ref)
|
||||
return fmt.Errorf("%s: token {%s}: name %q is read inside %q, the field bound to it; read the name outside that field", label, o.Body, a.named.name, a.named.ref)
|
||||
}
|
||||
for _, leaf := range a.leaves {
|
||||
if err := a.named.checkOnce(a.spelling, leaf, a.path); err != nil {
|
||||
return fmt.Errorf("%s: token {%s}: %w", path, o.Body, err)
|
||||
return fmt.Errorf("%s: token {%s}: %w", label, o.Body, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -101,7 +101,7 @@ func compile(v any) (node, error) {
|
||||
func compileCategory(c datafiles.Category) (node, error) {
|
||||
if m, ok := c.JSON.(map[string]any); ok {
|
||||
if _, isTable := m["rows"]; isTable {
|
||||
return compileTable(m, c.Name, join(strings.Join(c.Folders, "."), c.Name), c.ReadRows)
|
||||
return compileTable(m, c.Folders, c.Name, c.ReadRows)
|
||||
}
|
||||
}
|
||||
if items, ok := c.JSON.([]any); ok {
|
||||
|
||||
+14
-14
@@ -39,13 +39,13 @@ func refSegments(name string, folder []string) ([]string, error) {
|
||||
// so a reference sees the override-resolved tree. A reference's head binds into
|
||||
// refHeads under its root path and its tail reads like a sibling path.
|
||||
func linkRefs(sites []categorySite, root map[string]node) error {
|
||||
if err := eachTemplate(sites, func(s categorySite, path string, t *template) error {
|
||||
return linkTemplate(s.dir, path, s.path, t, root)
|
||||
if err := eachTemplate(sites, func(s categorySite, label string, t *template) error {
|
||||
return linkTemplate(s.dir, label, s.path, t, root)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, pass := range linkPasses {
|
||||
if err := eachTemplate(sites, func(_ categorySite, path string, t *template) error { return pass(path, t) }); err != nil {
|
||||
if err := eachTemplate(sites, func(_ categorySite, label string, t *template) error { return pass(label, t) }); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -54,8 +54,8 @@ func linkRefs(sites []categorySite, root map[string]node) error {
|
||||
|
||||
// linkTemplate resolves t's references in category, "" for an inline template, and
|
||||
// compiles its format against them.
|
||||
func linkTemplate(folder []string, path, category string, t *template, root map[string]node) error {
|
||||
link, err := t.resolveLink(folder, path, category, root)
|
||||
func linkTemplate(folder []string, label, category string, t *template, root map[string]node) error {
|
||||
link, err := t.resolveLink(folder, label, category, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -92,7 +92,7 @@ func compileArm(t *template, a *arm) {
|
||||
|
||||
// resolveLink binds every reference t reads, refusing one to t's own category:
|
||||
// docs/decisions.md#a-category-never-references-itself-and-a-records-fences-run-at-load
|
||||
func (t *template) resolveLink(folder []string, path, category string, root map[string]node) (templateLink, error) {
|
||||
func (t *template) resolveLink(folder []string, label, category string, root map[string]node) (templateLink, error) {
|
||||
var link templateLink
|
||||
names := refTokens(t.tokens)
|
||||
if len(names) == 0 {
|
||||
@@ -103,18 +103,18 @@ func (t *template) resolveLink(folder []string, path, category string, root map[
|
||||
for _, name := range names {
|
||||
segments, err := refSegments(name, folder)
|
||||
if err != nil {
|
||||
return link, fmt.Errorf("%s: reference {%s}: %w", path, name, err)
|
||||
return link, fmt.Errorf("%s: reference {%s}: %w", label, name, err)
|
||||
}
|
||||
categorySegs, target, tail, err := resolveCategory(root, segments)
|
||||
if err != nil {
|
||||
return link, fmt.Errorf("%s: reference {%s}: %w", path, name, err)
|
||||
return link, fmt.Errorf("%s: reference {%s}: %w", label, name, err)
|
||||
}
|
||||
head := "/" + strings.Join(categorySegs, ".")
|
||||
if category != "" && head == "/"+category {
|
||||
return link, fmt.Errorf("%s: reference {%s}: names the category it sits in; read a sibling field as a path, or move the shared value into its own category and reference that", path, name)
|
||||
return link, fmt.Errorf("%s: reference {%s}: names the category it sits in; read a sibling field as a path, or move the shared value into its own category and reference that", label, name)
|
||||
}
|
||||
if err := checkPathResolves(target, tail, head); err != nil {
|
||||
return link, fmt.Errorf("%s: reference {%s}: %w", path, name, err)
|
||||
return link, fmt.Errorf("%s: reference {%s}: %w", label, name, err)
|
||||
}
|
||||
link.refHeads[head] = target
|
||||
link.refs[name] = refBinding{head, tail}
|
||||
@@ -160,12 +160,12 @@ func linkColumnRead(_ string, t *template) error {
|
||||
}
|
||||
|
||||
// eachTemplate calls fn once per template of the categories, with the category it sits
|
||||
// in and the dot path reaching it.
|
||||
func eachTemplate(sites []categorySite, fn func(s categorySite, path string, t *template) error) error {
|
||||
// in and eachNode's label for it.
|
||||
func eachTemplate(sites []categorySite, fn func(s categorySite, label string, t *template) error) error {
|
||||
for _, s := range sites {
|
||||
if err := eachNode(s.n, s.path, func(path string, n node) error {
|
||||
if err := eachNode(s.n, s.path, func(label string, n node) error {
|
||||
if t, isTemplate := n.(*template); isTemplate {
|
||||
return fn(s, path, t)
|
||||
return fn(s, label, t)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
|
||||
+1
-1
@@ -101,7 +101,7 @@ func loadShipped(root *folder, wanted []unloadedCategory) {
|
||||
}
|
||||
site, err := u.load()
|
||||
if err != nil {
|
||||
panic(invariant.Broken("shipped %s: %v; after a change under data/, regenerate shippedindex.go", join(strings.Join(u.dir, "."), u.name), err))
|
||||
panic(invariant.Broken("shipped %s: %v; after a change under data/, regenerate shippedindex.go", categoryPath(u.dir, u.name), err))
|
||||
}
|
||||
sites = append(sites, site)
|
||||
queue = append(queue, unloadedReads(root, u.dir, sitesScope([]categorySite{site}))...)
|
||||
|
||||
@@ -85,7 +85,7 @@ func isTableOption(name string) bool {
|
||||
// token arm.
|
||||
const inSelector = `[]{}"|`
|
||||
|
||||
func compileTable(m map[string]any, segment, path string, readRows func(file string) (string, error)) (*table, error) {
|
||||
func compileTable(m map[string]any, dir []string, name string, readRows func(file string) (string, error)) (*table, error) {
|
||||
o, err := readTableOptions(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -94,7 +94,7 @@ func compileTable(m map[string]any, segment, path string, readRows func(file str
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t := &table{segment: segment, path: path, file: o.rows, keyIndex: -1, nameIndex: -1, weightIndex: -1, parentIndex: -1}
|
||||
t := &table{segment: name, path: categoryPath(dir, name), 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)
|
||||
}
|
||||
@@ -404,8 +404,7 @@ func linkTables(sites []categorySite) error {
|
||||
}
|
||||
|
||||
// proveParent is the table among siblings that t's link column names, proved: it has a key,
|
||||
// every link cell is one, every row of it is linked to, no chain of parents closes, and no
|
||||
// ancestor has a column named like t.
|
||||
// every link cell is one, every row of it is linked to, and its ancestors pass checkAncestors.
|
||||
// docs/decisions.md#a-parent-row-with-no-child-row-is-a-load-error
|
||||
func (t *table) proveParent(siblings map[string]node) (*table, error) {
|
||||
name := t.header[t.parentIndex]
|
||||
@@ -418,21 +417,8 @@ func (t *table) proveParent(siblings map[string]node) (*table, error) {
|
||||
case p.keyIndex < 0:
|
||||
return nil, fmt.Errorf("parent %q has no key column to link to", name)
|
||||
}
|
||||
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 nil, fmt.Errorf("parent cycle: %s reaches itself through its parents", q.segment)
|
||||
}
|
||||
seen[q] = true
|
||||
ancestors = append(ancestors, q)
|
||||
if q.parentIndex < 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, q := range ancestors {
|
||||
if _, clash := q.col[t.segment]; clash {
|
||||
return nil, 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)
|
||||
}
|
||||
if err := t.checkAncestors(p, siblings); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
linked := make(map[string]bool, p.rowCount())
|
||||
for r := 0; r < t.rowCount(); r++ {
|
||||
@@ -450,6 +436,28 @@ func (t *table) proveParent(siblings map[string]node) (*table, error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// checkAncestors proves the chain of parents from p, t's parent, closes nowhere, and holds no
|
||||
// column named like t.
|
||||
func (t *table) checkAncestors(p *table, 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.segment)
|
||||
}
|
||||
seen[q] = true
|
||||
ancestors = append(ancestors, q)
|
||||
if q.parentIndex < 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, q := range ancestors {
|
||||
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)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *table) builtLookup() *rowLookup {
|
||||
t.once.Do(func() {
|
||||
if t.nameIndex >= 0 {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
`Bar = 9`
|
||||
|
||||
`Next ID = 115`
|
||||
`Next ID = 117`
|
||||
|
||||
| Goal | W |
|
||||
|---|---|
|
||||
@@ -118,6 +118,7 @@
|
||||
| 13 | 0.3.0 | defect | **Place xlsx cells by their `r` reference in `data-import/xlsx.py`.** | 2 | 2 | 3 | 6 | 4 | 13.7 |
|
||||
| 39 | 0.3.0 | | **Stop the `columnKinds` suggestion naming a field's own kind or a narrower one.** | 1 | 2 | 3 | 6 | 6 | 13.5 |
|
||||
| 51 | 0.3.0 | | **Spell a table one way across the errors that name it.** | 1 | 2 | 3 | 6 | 6 | 13.5 |
|
||||
| 115 | 0.3.0 | | **Spell a rows file's line one way in every error, naming the file: today a cell reads `g.t.b, line 3`, a cell check `t.tsv: line 3, b` and a link `t.tsv line 3`.** | 1 | 2 | 3 | 6 | 6 | 13.5 |
|
||||
| 10 | 0.3.0 | | **Test that every node kind reaches each switch over node kinds.** | 1 | 3 | 2 | 6 | 3.2 | 13.1 |
|
||||
| 11 | 0.3.0 | | **Test that every `geo` tree holds the five table names and the `address` columns that port across countries.** | 1 | 2 | 3 | 5 | 4 | 13.0 |
|
||||
| 35 | 0.3.0 | | **Add `{base64url(n)}` for JWT shapes.** | 1 | 2 | 4 | 5 | 8 | 13.0 |
|
||||
@@ -132,6 +133,7 @@
|
||||
| 72 | 0.3.0 | principle | **Fail the merge gate when test coverage falls below the last recorded figure.** | 2 | 3 | 2 | 5 | 3 | 10.2 |
|
||||
| 43 | 0.3.0 | | **Call `templateError` a compile failure in its doc.** | 1 | 1 | 1 | 5 | 3.3 | 9.8 |
|
||||
| 95 | 0.3.0 | principle | **Hold a struct's columns and the field each fills in one slice.** | 1 | 2 | 1 | 5 | 3.2 | 9.5 |
|
||||
| 116 | 0.3.0 | | **Turn a row index into its rows file's line in one function, where eleven sites add 2 today.** | 1 | 2 | 1 | 5 | 3.2 | 9.5 |
|
||||
| 19 | 0.3.0 | | **Fill the 398 Swedish localities weighted 200 from SCB småorter.** | 2 | 4 | 4 | 6 | 14 | 9.2 |
|
||||
| 56 | 0.3.0 | defect | **Build on a manual run of `test.yml`.** | 1 | 1 | 1 | 4 | 1 | 8.8 |
|
||||
| 109 | 0.3.0 | principle | **Indent every script under `data-import/` with hard tabs, as `AGENTS.md` requires and `release-tooling/` does.** | 2 | 4 | 1 | 4 | 3 | 6.2 |
|
||||
@@ -421,6 +423,10 @@ An `int64` or `float64` field unproven in range is told to become itself, and a
|
||||
|
||||
`pinRow` names `t.path` and `mustRow` panics with `t.category`, so one table is `territory` and `misc.territory`, and the short spelling names no file where two folders hold that name.
|
||||
|
||||
### 115. Spell a rows file's line one way in every error, naming the file: today a cell reads `g.t.b, line 3`, a cell check `t.tsv: line 3, b` and a link `t.tsv line 3`.
|
||||
|
||||
The cell's label names the category and column but not the TSV the line is in.
|
||||
|
||||
### 10. Test that every node kind reaches each switch over node kinds.
|
||||
|
||||
Today nine, from `render` to `columnItems`, must agree, and only `render` and `renderEdges` say so.
|
||||
@@ -461,6 +467,10 @@ Its doc says render failure, and it wraps what `NewTemplate` and `NewRecordTempl
|
||||
|
||||
`structShape.columns` and `structShape.fieldIndexes` pair up by index alone, against the technical principle "one owner per value".
|
||||
|
||||
### 116. Turn a row index into its rows file's line in one function, where eleven sites add 2 today.
|
||||
|
||||
`namedNode`'s cell line in `graph.go` and ten sites in `table.go` each add the header line and the one-based count. Item 102 moves the rows into `internal/rows`; the function goes with them.
|
||||
|
||||
### 56. Build on a manual run of `test.yml`.
|
||||
|
||||
`workflow_dispatch` leaves `github.event.before` empty, so the diff compares `HEAD` with itself and skips `docker build`.
|
||||
|
||||
+3
-3
@@ -19,16 +19,16 @@ type valueProof struct {
|
||||
// checkDatatype rejects a typed column item some render of which is not text of its datatype,
|
||||
// and one restating the datatype of the column it is.
|
||||
// docs/decisions.md#a-column-that-only-reads-one-reference-or-name-is-the-column-it-reads
|
||||
func (p *valueProof) checkDatatype(path string, n node) error {
|
||||
func (p *valueProof) checkDatatype(label string, n node) error {
|
||||
t, ok := n.(*template)
|
||||
if !ok || t.datatype == DataTypeString {
|
||||
return nil
|
||||
}
|
||||
if r := t.link.readsColumn; r != nil && columnDatatype(r.column) == t.datatype {
|
||||
return fmt.Errorf(`%s: %s takes datatype %s from the column it reads; drop "datatype"`, path, t.format, t.datatype)
|
||||
return fmt.Errorf(`%s: %s takes datatype %s from the column it reads; drop "datatype"`, label, t.format, t.datatype)
|
||||
}
|
||||
if reason := p.proveColumnItem(t).Not[t.datatype]; reason != "" {
|
||||
return fmt.Errorf("%s: datatype %s: %s", path, t.datatype, reason)
|
||||
return fmt.Errorf("%s: datatype %s: %s", label, t.datatype, reason)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user