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 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 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 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 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, and the fence over paths into it 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 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 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 builtins.go the {name()} function registry and its implementations
-131
View File
@@ -6,33 +6,6 @@ import (
"strings" "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, // checkNestedDrawGroup refuses a template beneath one drawing in group that names group again,
// short of a repeat or another draw group. // short of a repeat or another draw group.
func checkNestedDrawGroup(fields map[string]node, group string) error { func checkNestedDrawGroup(fields map[string]node, group string) error {
@@ -104,43 +77,6 @@ func (c *drawCheck) checkDraws(path string, n node) error {
return nil 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 // 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. // 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 { 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 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. // 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 } 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 // 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. // pins entered, so a selected row renders only its own cells.
func (w *drawWalk) edge(from node, e renderEdge, at drawAt) { func (w *drawWalk) edge(from node, e renderEdge, at drawAt) {
+105
View File
@@ -2,6 +2,7 @@ package fejkdata
import ( import (
"fmt" "fmt"
"sort"
"strings" "strings"
) )
@@ -187,3 +188,107 @@ func (r *tableRead) drawnOf(pins *hold) *table {
}) })
return found 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 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 // 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. // template objects carry weight; a present one must be finite and positive.
func weightOf(raw any) (float64, error) { 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: 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` - 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 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 the fence proving the old rule, and `hold` stops being a proof scratchpad whenever its