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.
|
// 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 {
|
for _, o := range t.compiled.ops {
|
||||||
if o.Fn != builtinfunc.CalcName || !slices.ContainsFunc(o.operands, func(a arm) bool { return a.kind == namedRead }) {
|
if o.Fn != builtinfunc.CalcName || !slices.ContainsFunc(o.operands, func(a arm) bool { return a.kind == namedRead }) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := checkOperands(o.Args[0], builtinfunc.ParsedCalc(o.Args[0]), operandNodes(o)); err != nil {
|
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
|
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
|
// nodeScope is the set of nodes one validation pass covers: the categories one load
|
||||||
// binds, or a single inline node.
|
// 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
|
// categorySite is a loaded category and where it sits: the folder holding it, that
|
||||||
// folder's path, and its own dot path.
|
// 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 {
|
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 {
|
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 {
|
for _, s := range sites {
|
||||||
if err := eachNode(s.n, s.path, fn); err != nil {
|
if err := eachNode(s.n, s.path, fn); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -112,7 +115,7 @@ func sitesScope(sites []categorySite) nodeScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func inlineScope(n node, label string) 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
|
// 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.
|
// first.
|
||||||
func checkNodeFences(s nodeScope) error {
|
func checkNodeFences(s nodeScope) error {
|
||||||
mem := renderCounts{}
|
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 err
|
||||||
}
|
}
|
||||||
return s((&valueProof{}).checkDatatype)
|
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.
|
// after the columns its items read, so a column read is named before its readers.
|
||||||
func checkColumns(s nodeScope) error {
|
func checkColumns(s nodeScope) error {
|
||||||
checked := map[node]bool{}
|
checked := map[node]bool{}
|
||||||
var check func(path, name string, column node) error
|
var check func(label, name string, column node) error
|
||||||
check = func(path, name string, column node) error {
|
check = func(label, name string, column node) error {
|
||||||
if checked[column] {
|
if checked[column] {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -79,18 +79,18 @@ func checkColumns(s nodeScope) error {
|
|||||||
first := columnDatatype(column)
|
first := columnDatatype(column)
|
||||||
for _, it := range items[1:] {
|
for _, it := range items[1:] {
|
||||||
if d := itemDatatype(it); d != first {
|
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 nil
|
||||||
}
|
}
|
||||||
return s(func(path string, n node) error {
|
return s(func(label string, n node) error {
|
||||||
t, ok := n.(*template)
|
t, ok := n.(*template)
|
||||||
if !ok || !t.isRecord {
|
if !ok || !t.isRecord {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, name := range sortedNames(t.fields) {
|
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,28 +8,29 @@ import (
|
|||||||
"github.com/larvit/fejkdata/internal/invariant"
|
"github.com/larvit/fejkdata/internal/invariant"
|
||||||
)
|
)
|
||||||
|
|
||||||
// eachNode visits n and every node contained within it once, passing the dot path
|
// eachNode visits n and every node contained within it once, passing the label naming
|
||||||
// that reaches each, followed by a table cell's line. It never crosses a reference edge — a {/path} reference is
|
// each in an error: the dot path reaching it, and a table cell's line after it. It never
|
||||||
// skipped — so a single inline node is walked on its own.
|
// crosses a reference edge — a {/path} reference is skipped — so a single inline node is
|
||||||
func eachNode(n node, path string, fn func(path string, n node) error) error {
|
// walked on its own.
|
||||||
|
func eachNode(n node, label string, fn func(label string, n node) error) error {
|
||||||
seen := map[node]bool{}
|
seen := map[node]bool{}
|
||||||
var visit func(string, node) error
|
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] {
|
if m == nil || seen[m] {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
seen[m] = true
|
seen[m] = true
|
||||||
if err := fn(path, m); err != nil {
|
if err := fn(label, m); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, c := range contained(m) {
|
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 err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
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
|
// 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
|
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.
|
// labelIn is c's label inside the node labelled label.
|
||||||
func (c namedNode) labelUnder(path string) string {
|
func (c namedNode) labelIn(label string) string {
|
||||||
if c.line > 0 {
|
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 {
|
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
|
// repeatCheck bounds the renders a repeat multiplies to along any root-to-leaf
|
||||||
// path, so nested repeats cannot build what one repeat may not.
|
// path, so nested repeats cannot build what one repeat may not.
|
||||||
// docs/decisions.md#the-repeat-cap-bounds-renders-not-bytes
|
// 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 {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
@@ -191,25 +192,25 @@ func checkNoCycles(s nodeScope) error {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
color := map[node]int{}
|
color := map[node]int{}
|
||||||
var visit func(n node, path string) error
|
var visit func(n node, label string) error
|
||||||
visit = func(n node, path string) error {
|
visit = func(n node, label string) error {
|
||||||
if !inScope[n] {
|
if !inScope[n] {
|
||||||
return nil // loaded before this scope and proven then, so it never reaches back into it
|
return nil // loaded before this scope and proven then, so it never reaches back into it
|
||||||
}
|
}
|
||||||
switch color[n] {
|
switch color[n] {
|
||||||
case grey:
|
case grey:
|
||||||
return fmt.Errorf("reference cycle: %s", path)
|
return fmt.Errorf("reference cycle: %s", label)
|
||||||
case black:
|
case black:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
color[n] = grey
|
color[n] = grey
|
||||||
for _, e := range renderEdges(n) {
|
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
color[n] = black
|
color[n] = black
|
||||||
return nil
|
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
|
// refuseFolderRefs refuses each reference in scope not written from the root, {/x}: an
|
||||||
// inline node sits in no folder.
|
// inline node sits in no folder.
|
||||||
func refuseFolderRefs(scope nodeScope) error {
|
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)
|
t, ok := m.(*template)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
@@ -112,10 +112,10 @@ func refuseFolderRefs(scope nodeScope) error {
|
|||||||
for _, name := range refTokens(t.tokens) {
|
for _, name := range refTokens(t.tokens) {
|
||||||
sigil, rest, err := grammar.RefShape(name)
|
sigil, rest, err := grammar.RefShape(name)
|
||||||
if err != nil {
|
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 != "/" {
|
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
|
return nil
|
||||||
@@ -125,18 +125,18 @@ func refuseFolderRefs(scope nodeScope) error {
|
|||||||
// linkNodeRefs binds the references in an inline node's templates against the
|
// linkNodeRefs binds the references in an inline node's templates against the
|
||||||
// loaded tree.
|
// loaded tree.
|
||||||
func linkNodeRefs(scope nodeScope, root map[string]node) error {
|
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 {
|
if t, ok := m.(*template); ok {
|
||||||
return linkTemplate(nil, path, "", t, root)
|
return linkTemplate(nil, label, "", t, root)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, pass := range linkPasses {
|
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 {
|
if t, ok := m.(*template); ok {
|
||||||
return pass(path, t)
|
return pass(label, t)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}); err != 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
|
// 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.
|
// 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
|
// 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.
|
// 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 {
|
return namedReads(t, func(o *op, a *arm) error {
|
||||||
if err := linkName(t, a); err != nil {
|
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
|
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
|
// 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
|
// 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.
|
// draw of w.
|
||||||
func checkNameReads(path string, t *template) error {
|
func checkNameReads(label string, t *template) error {
|
||||||
for _, tok := range t.tokens {
|
for _, tok := range t.tokens {
|
||||||
if tok.Kind != grammar.NameBind {
|
if tok.Kind != grammar.NameBind {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := t.nameScope.bindings[tok.Bound].checkUses(); err != nil {
|
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 {
|
return namedReads(t, func(o *op, a *arm) error {
|
||||||
if a.named.bindsField() && rendersInside(compilePath(a.named.head, a.named.tail).leaves, t) {
|
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 {
|
for _, leaf := range a.leaves {
|
||||||
if err := a.named.checkOnce(a.spelling, leaf, a.path); err != nil {
|
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
|
return nil
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ func compile(v any) (node, error) {
|
|||||||
func compileCategory(c datafiles.Category) (node, error) {
|
func compileCategory(c datafiles.Category) (node, error) {
|
||||||
if m, ok := c.JSON.(map[string]any); ok {
|
if m, ok := c.JSON.(map[string]any); ok {
|
||||||
if _, isTable := m["rows"]; isTable {
|
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 {
|
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
|
// 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.
|
// refHeads under its root path and its tail reads like a sibling path.
|
||||||
func linkRefs(sites []categorySite, root map[string]node) error {
|
func linkRefs(sites []categorySite, root map[string]node) error {
|
||||||
if err := eachTemplate(sites, func(s categorySite, path string, t *template) error {
|
if err := eachTemplate(sites, func(s categorySite, label string, t *template) error {
|
||||||
return linkTemplate(s.dir, path, s.path, t, root)
|
return linkTemplate(s.dir, label, s.path, t, root)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, pass := range linkPasses {
|
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
|
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
|
// linkTemplate resolves t's references in category, "" for an inline template, and
|
||||||
// compiles its format against them.
|
// compiles its format against them.
|
||||||
func linkTemplate(folder []string, path, category string, t *template, root map[string]node) error {
|
func linkTemplate(folder []string, label, category string, t *template, root map[string]node) error {
|
||||||
link, err := t.resolveLink(folder, path, category, root)
|
link, err := t.resolveLink(folder, label, category, root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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:
|
// 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
|
// 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
|
var link templateLink
|
||||||
names := refTokens(t.tokens)
|
names := refTokens(t.tokens)
|
||||||
if len(names) == 0 {
|
if len(names) == 0 {
|
||||||
@@ -103,18 +103,18 @@ func (t *template) resolveLink(folder []string, path, category string, root map[
|
|||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
segments, err := refSegments(name, folder)
|
segments, err := refSegments(name, folder)
|
||||||
if err != nil {
|
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)
|
categorySegs, target, tail, err := resolveCategory(root, segments)
|
||||||
if err != nil {
|
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, ".")
|
head := "/" + strings.Join(categorySegs, ".")
|
||||||
if category != "" && head == "/"+category {
|
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 {
|
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.refHeads[head] = target
|
||||||
link.refs[name] = refBinding{head, tail}
|
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
|
// eachTemplate calls fn once per template of the categories, with the category it sits
|
||||||
// in and the dot path reaching it.
|
// in and eachNode's label for it.
|
||||||
func eachTemplate(sites []categorySite, fn func(s categorySite, path string, t *template) error) error {
|
func eachTemplate(sites []categorySite, fn func(s categorySite, label string, t *template) error) error {
|
||||||
for _, s := range sites {
|
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 {
|
if t, isTemplate := n.(*template); isTemplate {
|
||||||
return fn(s, path, t)
|
return fn(s, label, t)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
|||||||
+1
-1
@@ -101,7 +101,7 @@ func loadShipped(root *folder, wanted []unloadedCategory) {
|
|||||||
}
|
}
|
||||||
site, err := u.load()
|
site, err := u.load()
|
||||||
if err != nil {
|
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)
|
sites = append(sites, site)
|
||||||
queue = append(queue, unloadedReads(root, u.dir, sitesScope([]categorySite{site}))...)
|
queue = append(queue, unloadedReads(root, u.dir, sitesScope([]categorySite{site}))...)
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ func isTableOption(name string) bool {
|
|||||||
// token arm.
|
// token arm.
|
||||||
const inSelector = `[]{}"|`
|
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)
|
o, err := readTableOptions(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -94,7 +94,7 @@ func compileTable(m map[string]any, segment, path string, readRows func(file str
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err := t.parseRows(data); err != nil {
|
||||||
return nil, fmt.Errorf("%s: %w", o.rows, err)
|
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,
|
// 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
|
// every link cell is one, every row of it is linked to, and its ancestors pass checkAncestors.
|
||||||
// ancestor has a column named like t.
|
|
||||||
// docs/decisions.md#a-parent-row-with-no-child-row-is-a-load-error
|
// docs/decisions.md#a-parent-row-with-no-child-row-is-a-load-error
|
||||||
func (t *table) proveParent(siblings map[string]node) (*table, error) {
|
func (t *table) proveParent(siblings map[string]node) (*table, error) {
|
||||||
name := t.header[t.parentIndex]
|
name := t.header[t.parentIndex]
|
||||||
@@ -418,21 +417,8 @@ func (t *table) proveParent(siblings map[string]node) (*table, error) {
|
|||||||
case p.keyIndex < 0:
|
case p.keyIndex < 0:
|
||||||
return nil, fmt.Errorf("parent %q has no key column to link to", name)
|
return nil, fmt.Errorf("parent %q has no key column to link to", name)
|
||||||
}
|
}
|
||||||
var ancestors []*table
|
if err := t.checkAncestors(p, siblings); err != nil {
|
||||||
for q, seen := p, map[*table]bool{t: true}; q != nil; q, _ = siblings[q.header[q.parentIndex]].(*table) {
|
return nil, err
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
linked := make(map[string]bool, p.rowCount())
|
linked := make(map[string]bool, p.rowCount())
|
||||||
for r := 0; r < t.rowCount(); r++ {
|
for r := 0; r < t.rowCount(); r++ {
|
||||||
@@ -450,6 +436,28 @@ func (t *table) proveParent(siblings map[string]node) (*table, error) {
|
|||||||
return p, nil
|
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 {
|
func (t *table) builtLookup() *rowLookup {
|
||||||
t.once.Do(func() {
|
t.once.Do(func() {
|
||||||
if t.nameIndex >= 0 {
|
if t.nameIndex >= 0 {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
`Bar = 9`
|
`Bar = 9`
|
||||||
|
|
||||||
`Next ID = 115`
|
`Next ID = 117`
|
||||||
|
|
||||||
| Goal | W |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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.
|
`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.
|
### 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.
|
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".
|
`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`.
|
### 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`.
|
`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,
|
// 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.
|
// 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
|
// 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)
|
t, ok := n.(*template)
|
||||||
if !ok || t.datatype == DataTypeString {
|
if !ok || t.datatype == DataTypeString {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if r := t.link.readsColumn; r != nil && columnDatatype(r.column) == t.datatype {
|
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 != "" {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user