From cfe653de4d13a841f212f6767cc6edf23d7488f7 Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Mon, 28 Sep 2026 10:32:28 +0200 Subject: [PATCH] Run New, NewTemplate and FakeStruct through one bind pipeline naming where they differ --- README.md | 2 +- data.go | 57 +++++++++++++++++++++++++++++++++++------------ docs/decisions.md | 2 +- inline.go | 18 +++++++-------- node_test.go | 2 +- struct.go | 4 +++- todo.md | 2 -- 7 files changed, 58 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index e9e5301..5eb803b 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/data.go b/data.go index ed0542b..46c9cda 100644 --- a/data.go +++ b/data.go @@ -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 diff --git a/docs/decisions.md b/docs/decisions.md index 5cc3b00..1c697e6 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -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 diff --git a/inline.go b/inline.go index b44127e..29ecf11 100644 --- a/inline.go +++ b/inline.go @@ -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 diff --git a/node_test.go b/node_test.go index 83f1634..b8e1759 100644 --- a/node_test.go +++ b/node_test.go @@ -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() { diff --git a/struct.go b/struct.go index 66bfd61..251f199 100644 --- a/struct.go +++ b/struct.go @@ -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) diff --git a/todo.md b/todo.md index 312f48e..e9e2783 100644 --- a/todo.md +++ b/todo.md @@ -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