Run New, NewTemplate and FakeStruct through one bind pipeline naming where they differ

This commit is contained in:
2026-09-28 10:32:28 +02:00
committed by Lillem4n
parent 9ea8f84421
commit cfe653de4d
7 changed files with 58 additions and 29 deletions
+1 -1
View File
@@ -890,7 +890,7 @@ transform.go the builtins that rewrite an operand's value, and the ASCII fold
calc.go the {calc()} arithmetic evaluator: parser, eval, validation
datatype.go column datatypes: DataType, where datatype and null may sit, a column's datatype
value.go the value proof: what a typed column or calc operand holds, checked at load
data.go the load path: fs.FS folders/files -> merged namespace tree, and the fence order a scope runs
data.go the load path: fs.FS folders/files -> merged namespace tree, and the bind pipeline every entry point runs
cmd/fejkdata/ the fejkdata CLI
data/ shipped data (JSON, and a TSV per table), embedded at build: locale folders, geo, misc
data-import/ the scripts that rebuild each sourced table (see DATA-LICENSES.md)
+43 -14
View File
@@ -62,17 +62,7 @@ func loadData(sources []dataSource) (map[string]node, error) {
if len(root) == 0 {
return nil, fmt.Errorf("no .json data found")
}
setTablePaths(root)
if err := linkTables(root); err != nil {
return nil, err
}
if err := linkRefs(root); err != nil {
return nil, err
}
if err := checkNoCycles(root); err != nil {
return nil, err
}
if err := checkScope(treeScope(root)); err != nil {
if err := treeBinding(root).bind(); err != nil {
return nil, err
}
return root, nil
@@ -206,11 +196,50 @@ func inlineScope(n node, label string) nodeScope {
return func(fn func(path string, m node) error) error { return eachNode(n, label, fn) }
}
func checkScope(s nodeScope) error {
if err := checkColumns(s); err != nil {
// binding is one entry point's nodes on their way through bind, the load pipeline New,
// NewTemplate and FakeStruct share; its fields are where they differ.
type binding struct {
scope nodeScope
// link binds the references: a loaded tree's relative to each category's folder, an
// inline node's from the root alone.
link func() error
// noCycles refuses a reference cycle, which only a loaded tree can close: nothing
// references an inline node.
noCycles func() error
// typedByGo marks a struct's columns, whose datatypes its Go types set and checkField
// proves, so checkColumns skips them.
typedByGo bool
}
func treeBinding(root map[string]node) binding {
return binding{
scope: treeScope(root),
link: func() error {
setTablePaths(root)
if err := linkTables(root); err != nil {
return err
}
return linkRefs(root)
},
noCycles: func() error { return checkNoCycles(root) },
}
}
func (b binding) bind() error {
if err := b.link(); err != nil {
return err
}
return checkRenders(s)
if b.noCycles != nil {
if err := b.noCycles(); err != nil {
return err
}
}
if !b.typedByGo {
if err := checkColumns(b.scope); err != nil {
return err
}
}
return checkRenders(b.scope)
}
// checkRenders runs the per-node fences over a scope, each over the whole scope
+1 -1
View File
@@ -57,7 +57,7 @@ tags and any other caller read one.
`New` proves the loaded tree acyclic, an inline node is a finite tree of its own, and
nothing in the tree can reference it, so no render of it reaches itself. Every other
fence runs over both, from one `checkScope`, except that struct tags leave column
fence runs over both, from one `binding.bind`, except that struct tags leave column
agreement to their Go types.
## An inline template that does not compile is misuse (exit 2), including a reference that resolves to nothing
+9 -9
View File
@@ -32,7 +32,7 @@ func (f *Generator) NewTemplate(input string) (*Template, error) {
if err != nil {
return nil, fmt.Errorf("fejkdata: %w", err)
}
if err := bindInline(n, "template", f.categories, checkScope); err != nil {
if err := inlineBinding(n, "template", f.categories).bind(); err != nil {
return nil, fmt.Errorf("fejkdata: %w", err)
}
return &Template{g: f, n: n}, nil
@@ -146,17 +146,17 @@ func inputValue(input string) (any, error) {
return raw, nil
}
// bindInline links an inline node's references against root and runs check over it, naming its
// nodes from label.
func bindInline(n node, label string, root map[string]node, check func(nodeScope) error) error {
// inlineBinding binds an inline node against root, naming its nodes from label.
func inlineBinding(n node, label string, root map[string]node) binding {
scope := inlineScope(n, label)
return binding{scope: scope, link: func() error { return linkInline(n, label, scope, root) }}
}
func linkInline(n node, label string, scope nodeScope, root map[string]node) 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)
}
scope := inlineScope(n, label)
if err := linkNodeRefs(scope, root); err != nil {
return err
}
return check(scope)
return linkNodeRefs(scope, root)
}
// linkNodeRefs binds the references in an inline node's templates against the
+1 -1
View File
@@ -107,7 +107,7 @@ func TestEveryFormatCompilesAtLink(t *testing.T) {
t.Errorf("%s compiled before link", format)
}
}
if err := bindInline(n, "t", map[string]node{"w": w}, func(nodeScope) error { return nil }); err != nil {
if err := linkInline(n, "t", inlineScope(n, "t"), map[string]node{"w": w}); err != nil {
t.Fatal(err)
}
for format, has := range compiledByPath() {
+3 -1
View File
@@ -258,7 +258,9 @@ func (s *structShape) compileRecord(categories map[string]node, t reflect.Type,
if err != nil {
return fmt.Errorf("%s: %w", label, err)
}
if err := bindInline(n, label, categories, checkRenders); err != nil {
b := inlineBinding(n, label, categories)
b.typedByGo = true
if err := b.bind(); err != nil {
return err
}
record, columns, err := recordOf(n)
-2
View File
@@ -21,8 +21,6 @@ Self-sufficiency 5.6), every seat capped by Locality, and all nine naming
This round, in order:
- Run `New`, `NewTemplate` and `FakeStruct` through one bind pipeline that names
where they differ: each picks its own fences today.
- Make `disagreement` and `bothTyped` a table of cases: each swaps its operands and
runs a proof to pick an error's wording.
- Derive a column's datatype in one place: `columnItems` yields nothing for a