Build the walk root in New, drop the categories copy, and correct the node and renders comments

This commit is contained in:
2026-09-29 04:34:50 +02:00
committed by Lillem4n
parent d519a61f58
commit c422c9e436
12 changed files with 24 additions and 31 deletions
+3 -3
View File
@@ -28,7 +28,7 @@ func fenceRoots(t *testing.T, f *Generator) ([]fenceRoot, map[string]*table) {
t.Helper()
var roots []fenceRoot
tables := map[string]*table{}
_ = walkNodes(f.categories, func(path string, n node) error {
_ = walkNodes(f.root.children, func(path string, n node) error {
switch n := n.(type) {
case *template:
roots = append(roots, fenceRoot{path, n})
@@ -61,7 +61,7 @@ func TestReplayPairsFindsWhatAWholeReplayFinds(t *testing.T) {
if err != nil {
t.Fatalf("New() = %v", err)
}
_ = walkNodes(shipped.categories, func(path string, n node) error {
_ = walkNodes(shipped.root.children, func(path string, n node) error {
if tm, ok := n.(*template); ok {
roots = append(roots, fenceRoot{path, tm})
}
@@ -85,7 +85,7 @@ func TestReplayPairsFindsWhatAWholeReplayOfSelectedRowsFinds(t *testing.T) {
t.Fatalf("New = %v", err)
}
var reads []pathRead
_ = walkNodes(f.categories, func(_ string, n node) error {
_ = walkNodes(f.root.children, func(_ string, n node) error {
tb, isTable := n.(*table)
if !isTable || tb.keyIndex < 0 || tb.familyRoot().path != "region" {
return nil
+8 -9
View File
@@ -31,12 +31,11 @@ var ErrNoData = errors.New("no data: WithoutShippedData needs at least one WithD
// from one goroutine. The compiled tree is immutable after [New], so List, NewTemplate
// and NewRecordTemplate read it without a lock; mu guards the rest, which every render takes.
type Generator struct {
mu sync.Mutex
rand *session
categories map[string]node
root folder // the categories as the node a path walks from, owned here so a walk allocates none
records map[node]recordShape
structs map[reflect.Type]structResult
mu sync.Mutex
rand *session
root folder // the categories as the node a path walks from, owned here so a walk allocates none
records map[node]recordShape
structs map[reflect.Type]structResult
}
// session is one generator's mutable render state: the seeded rng plus the {seq()}
@@ -112,7 +111,7 @@ func New(opts ...Option) (*Generator, error) {
if err != nil {
return nil, fmt.Errorf("fejkdata: %w", err)
}
return &Generator{rand: rng, categories: cats}, nil
return &Generator{rand: rng, root: folder{children: cats}}, nil
}
// List returns the sorted dotted paths Fake renders: each category and every field,
@@ -120,8 +119,8 @@ func New(opts ...Option) (*Generator, error) {
// segment, so a path continues through one only where every variant carries it.
func (f *Generator) List() []string {
var out []string
for _, name := range sortedNames(f.categories) {
for _, p := range paths(f.categories[name]) {
for _, name := range sortedNames(f.root.children) {
for _, p := range paths(f.root.children[name]) {
out = append(out, join(name, p))
}
}
+2 -3
View File
@@ -152,9 +152,8 @@ func isFixed(n node) bool {
}
// renders reports whether rendering n can reach anything in want, following the
// same edges expand does. seen keeps a node shared by several routes from being
// walked twice; checkNoCycles has already proved the graph is a DAG, so the walk
// ends.
// same edges expand does. seen keeps a node from being walked twice, so the walk
// ends across a cycle too.
func renders(n node, want, seen map[node]bool) bool {
if want[n] {
return true
+1 -1
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 := inlineBinding(n, "template", f.categories).bind(); err != nil {
if err := inlineBinding(n, "template", f.root.children).bind(); err != nil {
return nil, fmt.Errorf("fejkdata: %w", err)
}
return &Template{g: f, n: n}, nil
+1 -1
View File
@@ -11,7 +11,7 @@ import (
// node is a compiled element of the namespace tree: a folder, choice, null,
// template, table, column or row. Compiling JSON into these once (see compile)
// means rendering never re-inspects the raw JSON or re-sums weights. A new kind needs a
// means rendering never re-inspects the raw JSON. A new kind needs a
// case in contained, paths, render and renderEdges; each panics on a kind it does not list.
type node interface{ isNode() }
+3 -3
View File
@@ -50,7 +50,7 @@ func TestDeepDottedPath(t *testing.T) {
// A 5-segment path descends through alternating object/array nodes; choices
// on the path are single-variant, so it resolves deterministically.
f := engine(1)
f.categories = map[string]node{
f.root.children = map[string]node{
"deep": compiled(t, `{"format":"{a}","a":{"format":"{b}","b":{"format":"{c}","c":{"format":"{d}","d":"leaf"}}}}`),
}
if got, err := f.Fake("deep.a.b.c.d"); err != nil || got != "leaf" {
@@ -64,7 +64,7 @@ func TestDeepDottedPath(t *testing.T) {
func TestDescendIntoStringErrors(t *testing.T) {
f := engine(1)
f.categories = map[string]node{"greeting": compiled(t, `"hej"`)}
f.root.children = map[string]node{"greeting": compiled(t, `"hej"`)}
if _, err := f.Fake("greeting.extra"); err == nil || !strings.Contains(err.Error(), `no field "extra"`) {
t.Fatalf("Fake(greeting.extra) = %v, want a no-field error", err)
}
@@ -153,7 +153,7 @@ func TestBindingKeyIsNotAPathSegment(t *testing.T) {
func TestFakePathNavigation(t *testing.T) {
f := engine(1)
f.categories = map[string]node{
f.root.children = map[string]node{
"addr": compiled(t, `{"format":"{street}","street":"Main"}`),
}
if got, err := f.Fake("addr"); err != nil || !strings.Contains(got, "Main") {
+1 -1
View File
@@ -126,7 +126,7 @@ func (f *Generator) FakeRecord(path string) (*Record, error) {
if err != nil {
return nil, fmt.Errorf("fejkdata: %w", err)
}
_, n, tail, err := resolveCategory(f.categories, segments)
_, n, tail, err := resolveCategory(f.root.children, segments)
if err != nil {
return nil, fmt.Errorf("fejkdata: %s: %w", path, err)
}
-1
View File
@@ -25,7 +25,6 @@ func (f *Generator) Fake(path string) (string, error) {
}
var set holdSet
sc := renderScope{set: &set}
f.root.children = f.categories
n, err := descend(f.rand, &f.root, segments, sc)
if err != nil {
return "", fmt.Errorf("fejkdata: %s: %w", path, err)
+2 -2
View File
@@ -61,8 +61,8 @@ func shippedShape(f *Generator) string {
}
}
}
for _, name := range sortedNames(f.categories) {
walk(name, f.categories[name])
for _, name := range sortedNames(f.root.children) {
walk(name, f.root.children[name])
}
var b strings.Builder
for _, p := range f.List() {
+1 -1
View File
@@ -49,7 +49,7 @@ func (f *Generator) structShapeOf(t reflect.Type) (*structShape, error) {
if label == "" {
label = "struct"
}
sc := &structCompile{categories: f.categories, visiting: map[reflect.Type]bool{}, structsLeft: maxStructs}
sc := &structCompile{categories: f.root.children, visiting: map[reflect.Type]bool{}, structsLeft: maxStructs}
shape, err := sc.compileShape(t, label)
if err == nil && shape.empty() {
err = fmt.Errorf("%s has no fake tags, so nothing to fill", t)
+2 -2
View File
@@ -991,7 +991,7 @@ func TestEnteredRowsAgreeWithPinning(t *testing.T) {
f := newGenerator(t, writeFiles(t, geo()), WithSeed(1))
var tables []*table
for _, category := range []string{"region", "municipality", "locality"} {
tbl, isTable := f.categories[category].(*table)
tbl, isTable := f.root.children[category].(*table)
if !isTable {
t.Fatalf("%s is not a table", category)
}
@@ -1063,7 +1063,7 @@ func TestProbeReportsTheTablesADrawPins(t *testing.T) {
if err != nil {
t.Fatal(err)
}
head := f.categories[segs[0]].(*table)
head := f.root.children[segs[0]].(*table)
probe := &pathProbe{drawn: map[*table]bool{}}
if _, err := probe.walk(head, segs[1:]); err != nil {
t.Fatalf("%s: %v", path, err)
-4
View File
@@ -26,10 +26,6 @@ least modify.
This round, in order:
- Correct the false comments: `node` says rendering never re-sums weights, which
`builtLookup` does on first draw; `Generator` claims immutable after `New`, while
`Fake` sets `root.children` each call, which belongs in `New`; `renders` credits
`checkNoCycles`, which an inline binding never runs.
- Run the nine-seat panel again and file what it names here.
### Data