Add the table node, row selection and linked tables, and ship the misc registers as tables #19

Merged
lilleman merged 22 commits from table-node into main 2026-09-17 23:35:27 +02:00
2 changed files with 45 additions and 13 deletions
Showing only changes of commit f7403f0cfb - Show all commits
+14 -3
View File
@@ -444,9 +444,10 @@ table another path in the group selects a row of, `{/city.name}` beside
`{/country[SE].name}`, which the first token rendered would otherwise decide. `New` `{/country[SE].name}`, which the first token rendered would otherwise decide. `New`
also refuses a link cell that is no key of the parent, a parent row no child links also refuses a link cell that is no key of the parent, a parent row no child links
to, a chain of parents that closes, a table named like a column of any table above to, a chain of parents that closes, a table named like a column of any table above
it, and a cell or format of a table that references a table of its own family, since it, and a cell or format of a table that references a table of its own family, through
a row rendered whole would draw the family apart from itself: read the family from a any template, a `repeat` or a `drawGroup` included, since a row rendered whole would
template beside it, or add the value as a column. draw the family apart from itself: read the family from a template beside it, or add
the value as a column.
### Options and fields ### Options and fields
@@ -969,6 +970,16 @@ renamed or retyped line is a major.
they sit in two rows of one table, or in a row outside a selected ancestor's. A they sit in two rows of one table, or in a row outside a selected ancestor's. A
choice's items get no such treatment yet: two items selecting different rows choice's items get no such treatment yet: two items selecting different rows
are still refused. are still refused.
- **A table never reaches its own family, by any route.** A `repeat` iteration and a
`drawGroup` each draw apart on purpose, but a row that lists three localities from
other regions is the output the family exists to prevent, so the own-family fence
walks through both rather than stopping where the draw fences do.
- **Tables carrying token cells stay small.** The family fence compares the reads of
every pair of rows that can render together, so a table whose every row's cell
selects a row of another table loads in time quadratic in its rows: about a
second at four thousand rows. No shipped table carries such cells, and a register
is a column set rather than a set of references, so the fence is left as it is
until a real data set needs the indexed form.
- **A path is walked once without drawing before it is walked for real.** A path - **A path is walked once without drawing before it is walked for real.** A path
that fails below its first level then moves no seeded stream, at the cost of one that fails below its first level then moves no seeded stream, at the cost of one
draw-free walk per call, which allocates nothing. draw-free walk per call, which allocates nothing.
+31 -10
View File
@@ -137,16 +137,19 @@ func (c *drawCheck) checkDrawGroup(path string, n node) error {
func (c *drawCheck) checkDraws(path string, n node) error { func (c *drawCheck) checkDraws(path string, n node) error {
t, ok := n.(*template) t, ok := n.(*template)
if !ok || !c.readsPath(t) { if !ok {
return nil
}
if err := checkOwnFamily(t); err != nil {
return fmt.Errorf("%s: %w", path, err)
}
if !c.readsPath(t) {
return nil return nil
} }
w := newDrawWalk() w := newDrawWalk()
for _, e := range renderEdges(t) { for _, e := range renderEdges(t) {
w.edge(t, e, drawAt{group: t.drawGroupKey, route: drawRoute{e.reached(), e.label}}) w.edge(t, e, drawAt{group: t.drawGroupKey, route: drawRoute{e.reached(), e.label}})
} }
if err := w.checkOwnFamily(t); err != nil {
return fmt.Errorf("%s: %w", path, err)
}
if err := w.check(); err != nil { if err := w.check(); err != nil {
return fmt.Errorf("%s: %w", path, err) return fmt.Errorf("%s: %w", path, err)
} }
@@ -154,9 +157,10 @@ func (c *drawCheck) checkDraws(path string, n node) error {
} }
// checkOwnFamily refuses a table's format or cell that reads, however many templates // checkOwnFamily refuses a table's format or cell that reads, however many templates
// away, a table of its own family: a row rendered whole draws its row without // away and through a repeat or a draw group too, a table of its own family: a row
// pinning it, so the family would draw apart from the row being rendered. // rendered whole draws its row without pinning it, so the family would draw apart
func (w *drawWalk) checkOwnFamily(t *template) error { // from the row being rendered, whichever draws the reaching template holds.
func checkOwnFamily(t *template) error {
own := t.table own := t.table
if own == nil { if own == nil {
own = t.cellOf own = t.cellOf
@@ -164,10 +168,27 @@ func (w *drawWalk) checkOwnFamily(t *template) error {
if own == nil { if own == nil {
return nil return nil
} }
for _, r := range w.reads { seen := map[node]bool{}
if r.tr != nil && r.tr.head.family() == own.family() { var find func(n node) (renderEdge, *table, bool)
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", r.at.route.spelled(r.a.name), r.tr.head.category, own.category) 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 return nil
} }