From def2a5f25dfc1154b5244958b952a7b202b77c8e Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Wed, 23 Sep 2026 22:44:47 +0200 Subject: [PATCH] Gather the family fence in family.go --- README.md | 4 +- drawfence.go | 131 --------------------------------------------------- family.go | 105 +++++++++++++++++++++++++++++++++++++++++ node.go | 27 +++++++++++ todo.md | 3 -- 5 files changed, 134 insertions(+), 136 deletions(-) diff --git a/README.md b/README.md index 325c454..b1971de 100644 --- a/README.md +++ b/README.md @@ -1341,8 +1341,8 @@ template.go the {token} grammar: scanning, tokens, operands, validation, com hold.go the hold: one draw per expansion for paths and operands, and the rows it pins holdfence.go the load fences over the hold: the routes and spellings that read one held name two ways holdset.go one reference draw per render and group: the hold set a render reads its paths through -drawfence.go the load fences over reference draws: the drawGroup option, and the walk over a render's reads -family.go a family of linked tables: what a reference path reads of one, and the fence over paths into it +drawfence.go the load fences over reference draws: the walk over a render's reads +family.go a family of linked tables: what a reference path reads of one, the rows a walk enters, and the fences over paths into it reference.go reference sigils, and binding references across the tree graph.go the node graph: tree walks, containment and render edges, and the cycle and repeat fences builtins.go the {name()} function registry and its implementations diff --git a/drawfence.go b/drawfence.go index 64a7e97..7ea756b 100644 --- a/drawfence.go +++ b/drawfence.go @@ -6,33 +6,6 @@ import ( "strings" ) -// drawGroupOf reads a template's "drawGroup" (default ""), which a repeat cannot carry: each -// iteration renders in no draw group. -func drawGroupOf(m map[string]any, repeat int) (string, error) { - v, ok := m["drawGroup"] - if !ok { - return "", nil - } - name, ok := v.(string) - switch { - case !ok: - return "", fmt.Errorf("drawGroup must be a string, got %T", v) - case name == "": - return "", fmt.Errorf(`drawGroup "" is the default, so it has no effect; drop it`) - case repeat > 1: - return "", fmt.Errorf("drawGroup %q on a repeat names nothing, since each iteration is a render of its own; drop it", name) - } - return name, nil -} - -// keyDrawGroup keys t's draw group by the category t sits in, "" for an inline template, so a name -// is local to its category. -func (t *template) keyDrawGroup(category string) { - if t.drawGroup != "" { - t.drawGroupKey = category + "/" + t.drawGroup - } -} - // checkNestedDrawGroup refuses a template beneath one drawing in group that names group again, // short of a repeat or another draw group. func checkNestedDrawGroup(fields map[string]node, group string) error { @@ -104,43 +77,6 @@ func (c *drawCheck) checkDraws(path string, n node) error { return nil } -// checkOwnFamily refuses a table's format or cell that reads, however many templates -// away and through a repeat or a draw group too, a table of its own family: a row -// rendered whole draws its row without pinning it, so the family would draw apart -// from the row being rendered, whichever draws the reaching template holds. -func checkOwnFamily(t *template) error { - own := t.table - if own == nil { - own = t.cellOf - } - if own == nil { - return nil - } - seen := map[node]bool{} - var find func(n node) (renderEdge, *table, bool) - find = func(n node) (renderEdge, *table, bool) { - if seen[n] { - return renderEdge{}, nil, false - } - seen[n] = true - for _, e := range renderEdges(n) { - if a, isRef := refRead(n, e.label); isRef { - if head, isTable := n.(*template).fields[a.key].(*table); isTable && head.family() == own.family() { - return e, head, true - } - } - if e, head, found := find(e.to); found { - return e, head, true - } - } - return renderEdge{}, nil, false - } - if e, head, found := find(t); found { - return fmt.Errorf("%s reads %s, a table of its own family, which a row of %s rendered whole would draw apart from; read the family from a template beside it, or add the value as a column", e.reached(), head.category, own.category) - } - return nil -} - // checkRecordDraws fences the columns of a record — a template compiled at the top without a // repeat — so a load proves the record view of it as well as the string view. func (c *drawCheck) checkRecordDraws(path string, n node) error { @@ -248,59 +184,6 @@ type drawAt struct { drawn *table // left out of the visit keys: only this table's own cells compare against it, which the own-family fence keeps true } -// rowSet stands in at load for the rows (*hold).pin holds at render: a path read enters a row with -// its ancestors, a whole read enters the drawn row alone. -type rowSet []tablePin - -func (s rowSet) rowOf(t *table) (int, bool) { - for _, p := range s { - if p.t == t { - return p.row, true - } - } - return 0, false -} - -// enter is s with row r of t, and with t's ancestors where the render pins that row rather than -// drawing it; the set is copied, since walks branch. -func (s rowSet) enter(t *table, r int, pins bool) rowSet { - if _, in := s.rowOf(t); in { - return s - } - out := append(make(rowSet, 0, len(s)+1), s...) - for { - out = append(out, tablePin{t, r}) - if !pins || t.parentT == nil { - break - } - t, r = t.parentT, t.parentRow(r) - if _, in := out.rowOf(t); in { - break - } - } - sort.Slice(out, func(i, j int) bool { return out[i].t.path < out[j].t.path }) - return out -} - -// key spells the set for a map, by the tables' identities. -func (s rowSet) key() string { - var b strings.Builder - for _, p := range s { - fmt.Fprintf(&b, "%p[%d]", p.t, p.row) - } - return b.String() -} - -// alternatives reports whether two reads sit in different rows of one table. -func alternatives(a, b drawAt) bool { - for _, p := range a.alt { - if r, in := b.alt.rowOf(p.t); in && r != p.row { - return true - } - } - return false -} - // drawRoute is how a render reaches a draw: as its author spells it, and the root edge's label. type drawRoute struct{ spelling, label string } @@ -358,20 +241,6 @@ func (w *drawWalk) walk(n node, at drawAt) { } } -// excludes reports whether a cell's row cannot render with the rows entered: another -// row of its table, or a row outside an entered ancestor's. -func (s rowSet) excludes(cell *template) bool { - for _, p := range s { - if p.t == cell.cellOf { - return p.row != cell.cellRow - } - if cell.cellOf.descends(p.t) && !cell.cellOf.under(cell.cellRow, p.t, p.row) { - return true - } - } - return false -} - // edge records the reference an edge reads, then walks on with every row the read // pins entered, so a selected row renders only its own cells. func (w *drawWalk) edge(from node, e renderEdge, at drawAt) { diff --git a/family.go b/family.go index a495e0c..9403618 100644 --- a/family.go +++ b/family.go @@ -2,6 +2,7 @@ package fejkdata import ( "fmt" + "sort" "strings" ) @@ -187,3 +188,107 @@ func (r *tableRead) drawnOf(pins *hold) *table { }) return found } + +// checkOwnFamily refuses a table's format or cell that reads, however many templates +// away and through a repeat or a draw group too, a table of its own family: a row +// rendered whole draws its row without pinning it, so the family would draw apart +// from the row being rendered, whichever draws the reaching template holds. +func checkOwnFamily(t *template) error { + own := t.table + if own == nil { + own = t.cellOf + } + if own == nil { + return nil + } + seen := map[node]bool{} + var find func(n node) (renderEdge, *table, bool) + find = func(n node) (renderEdge, *table, bool) { + if seen[n] { + return renderEdge{}, nil, false + } + seen[n] = true + for _, e := range renderEdges(n) { + if a, isRef := refRead(n, e.label); isRef { + if head, isTable := n.(*template).fields[a.key].(*table); isTable && head.family() == own.family() { + return e, head, true + } + } + if e, head, found := find(e.to); found { + return e, head, true + } + } + return renderEdge{}, nil, false + } + if e, head, found := find(t); found { + return fmt.Errorf("%s reads %s, a table of its own family, which a row of %s rendered whole would draw apart from; read the family from a template beside it, or add the value as a column", e.reached(), head.category, own.category) + } + return nil +} + +// rowSet stands in at load for the rows (*hold).pin holds at render: a path read enters a row with +// its ancestors, a whole read enters the drawn row alone. +type rowSet []tablePin + +func (s rowSet) rowOf(t *table) (int, bool) { + for _, p := range s { + if p.t == t { + return p.row, true + } + } + return 0, false +} + +// enter is s with row r of t, and with t's ancestors where the render pins that row rather than +// drawing it; the set is copied, since walks branch. +func (s rowSet) enter(t *table, r int, pins bool) rowSet { + if _, in := s.rowOf(t); in { + return s + } + out := append(make(rowSet, 0, len(s)+1), s...) + for { + out = append(out, tablePin{t, r}) + if !pins || t.parentT == nil { + break + } + t, r = t.parentT, t.parentRow(r) + if _, in := out.rowOf(t); in { + break + } + } + sort.Slice(out, func(i, j int) bool { return out[i].t.path < out[j].t.path }) + return out +} + +// key spells the set for a map, by the tables' identities. +func (s rowSet) key() string { + var b strings.Builder + for _, p := range s { + fmt.Fprintf(&b, "%p[%d]", p.t, p.row) + } + return b.String() +} + +// excludes reports whether a cell's row cannot render with the rows entered: another +// row of its table, or a row outside an entered ancestor's. +func (s rowSet) excludes(cell *template) bool { + for _, p := range s { + if p.t == cell.cellOf { + return p.row != cell.cellRow + } + if cell.cellOf.descends(p.t) && !cell.cellOf.under(cell.cellRow, p.t, p.row) { + return true + } + } + return false +} + +// alternatives reports whether two reads sit in different rows of one table. +func alternatives(a, b drawAt) bool { + for _, p := range a.alt { + if r, in := b.alt.rowOf(p.t); in && r != p.row { + return true + } + } + return false +} diff --git a/node.go b/node.go index afac977..cf3134b 100644 --- a/node.go +++ b/node.go @@ -433,6 +433,33 @@ func repeatOf(m map[string]any) (int, error) { return int(r), nil } +// drawGroupOf reads a template's "drawGroup" (default ""), which a repeat cannot carry: each +// iteration renders in no draw group. +func drawGroupOf(m map[string]any, repeat int) (string, error) { + v, ok := m["drawGroup"] + if !ok { + return "", nil + } + name, ok := v.(string) + switch { + case !ok: + return "", fmt.Errorf("drawGroup must be a string, got %T", v) + case name == "": + return "", fmt.Errorf(`drawGroup "" is the default, so it has no effect; drop it`) + case repeat > 1: + return "", fmt.Errorf("drawGroup %q on a repeat names nothing, since each iteration is a render of its own; drop it", name) + } + return name, nil +} + +// keyDrawGroup keys t's draw group by the category t sits in, "" for an inline template, so a name +// is local to its category. +func (t *template) keyDrawGroup(category string) { + if t.drawGroup != "" { + t.drawGroupKey = category + "/" + t.drawGroup + } +} + // weightOf reads a node's "weight" (default 1) from its raw JSON form. Only // template objects carry weight; a present one must be finite and positive. func weightOf(raw any) (float64, error) { diff --git a/todo.md b/todo.md index f4c2a06..4b66f8b 100644 --- a/todo.md +++ b/todo.md @@ -22,9 +22,6 @@ by nothing but tests, whose premise lives only in the README's Decisions. This round, in order: -- Gather the family fence in `family.go`: `alternatives`, `rowSet` and `checkOwnFamily` - beside `checkFamilies`, and the `drawGroup` option parsing out of `drawfence.go`, so - one question about a family opens one file. - Give render and the draw fences one pin model: a pin set `hold.pin`, `hold.rowOf` and the fence's `rowSet` all use, so a change to how a render pins rows cannot leave the fence proving the old rule, and `hold` stops being a proof scratchpad whenever its