Gather the family fence in family.go

This commit is contained in:
2026-09-23 22:44:47 +02:00
committed by Lillem4n
parent 021a95c114
commit def2a5f25d
5 changed files with 134 additions and 136 deletions
+2 -2
View File
@@ -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
-131
View File
@@ -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) {
+105
View File
@@ -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
}
+27
View File
@@ -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) {
-3
View File
@@ -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