Replay each path's selectors through the walk for the family fence, require a key for a name, refuse a name shadowing a key, a table reading its own family and an ancestor's column clash, add the row node and family.go, and pin eight rows inline
Tests / vet + fmt + tests (pull_request) Successful in 1m8s
Tests / Gitea release from CHANGELOG.md (pull_request) Has been skipped

This commit is contained in:
2026-09-17 12:54:24 +02:00
parent fb83aa6178
commit 047237f361
14 changed files with 419 additions and 277 deletions
+1 -213
View File
@@ -21,110 +21,6 @@ type drawScope struct {
row int
}
// pinned is the row the render pinned for t, if any.
func (d *draws) pinned(t *table) (int, bool) {
for _, p := range d.pins[:d.npins] {
if p.t == t {
return p.row, true
}
}
r, ok := d.more[t]
return r, ok
}
// mustRow is the row pinned for t, which the walk reaching a column pinned.
func (d *draws) mustRow(t *table) int {
r, ok := d.pinned(t)
if !ok {
panic(fmt.Sprintf("fejkdata: a column of %s is rendered with no row pinned", t.category))
}
return r
}
// pin pins row r of t, and the rows of t's ancestors it links to.
func (d *draws) pin(t *table, r int) {
for {
if _, done := d.pinned(t); done {
return
}
if d.npins < len(d.pins) {
d.pins[d.npins] = tablePin{t, r}
d.npins++
} else {
if d.more == nil {
d.more = map[*table]int{}
}
d.more[t] = r
}
if t.parentT == nil {
return
}
t, r = t.parentT, t.parentRow(r)
}
}
// rowOf is the render's row of t: the one pinned, else one drawn inside the
// nearest pinned ancestor — its parent drawn inside that first where the ancestor
// is further up — or over the whole table, and pinned with its ancestors.
func (d *draws) rowOf(t *table) int {
if r, ok := d.pinned(t); ok {
return r
}
r := -1
for a := t.parentT; a != nil && r < 0; a = a.parentT {
if _, ok := d.pinned(a); !ok {
continue
}
if t.parentT != a {
d.rowOf(t.parentT)
}
pr, _ := d.pinned(t.parentT)
r = t.drawUnder(d.s, pr)
}
if r < 0 {
r = t.draw(d.s)
}
d.pin(t, r)
return r
}
// selectRow pins the row a selector names, refusing one outside the rows pinned
// before it.
func (d *draws) selectRow(t *table, sel string) error {
r, err := t.find(sel, d)
if err != nil {
return err
}
if pr, ok := d.pinned(t); ok && pr != r {
return fmt.Errorf("%s is not %s, the row already drawn", t.selectorSpelling(r), t.selectorSpelling(pr))
}
for a := t.parentT; a != nil; a = a.parentT {
if pa, ok := d.pinned(a); ok && !t.under(r, a, pa) {
return fmt.Errorf("%s is not inside %s", t.selectorSpelling(r), a.selectorSpelling(pa))
}
}
d.pin(t, r)
return nil
}
// inside keeps the rows of t that sit inside every pinned ancestor.
func (d *draws) inside(t *table, rows []int) []int {
for a := t.parentT; a != nil; a = a.parentT {
pa, ok := d.pinned(a)
if !ok {
continue
}
var kept []int
for _, r := range rows {
if t.under(r, a, pa) {
kept = append(kept, r)
}
}
rows = kept
}
return rows
}
// newDrawSet makes the unnamed group's maps where the set is declared, keeping them on that frame's
// stack for a render that reads through them; a zero drawSet makes them on its first read instead.
func newDrawSet(s *session) drawSet {
@@ -368,78 +264,6 @@ type pathRead struct {
tr *tableRead // set where the reference names a table
}
// tableRead is what a reference reads of a table family: the table named, the rows
// its selectors pin along the way, and whether it lands on a row rendered whole.
type tableRead struct {
head *table
sels []tableSel
whole bool
}
// tableSel is one selector on the way: the table it selects a row of, and the
// selector's spelling up to its closing bracket.
type tableSel struct {
t *table
row int
spelling string
}
// tableReadOf reads what a reference path does of a table, replaying its selectors
// over a walk of its own — checkPath proved each names a row — so two paths naming
// one row by key and by name compare equal.
func tableReadOf(head node, a arm, leaf node) *tableRead {
t, isTable := head.(*table)
if !isTable {
return nil
}
tr := &tableRead{head: t}
var pins draws
cur, seen := t, a.name
for _, seg := range a.tail {
switch {
case isSelector(seg):
end := strings.Index(seen, "]") + 1
_ = pins.selectRow(cur, selectorOf(seg))
row, _ := pins.pinned(cur)
tr.sels = append(tr.sels, tableSel{cur, row, a.name[:len(a.name)-len(seen)+end]})
seen = seen[end:]
case cur.children[seg] != nil:
cur = cur.children[seg]
}
}
c, isColumn := leaf.(*column)
tr.whole = isColumn && c.i < 0
return tr
}
// family is the table a chain of parents ends at.
func (t *table) family() *table {
for t.parentT != nil {
t = t.parentT
}
return t
}
// selected is the selector in r that pins t, or the nearest ancestor of t it pins.
func (r *tableRead) selected(t *table) (tableSel, bool) {
for ; t != nil; t = t.parentT {
for _, s := range r.sels {
if s.t == t {
return s, true
}
}
}
return tableSel{}, false
}
func (r *tableRead) selection() string {
parts := make([]string, len(r.sels))
for i, s := range r.sels {
parts[i] = fmt.Sprintf("%s[%d]", s.t.category, s.row)
}
return strings.Join(parts, " ")
}
type drawVisit struct {
n node
group string
@@ -501,45 +325,9 @@ func (w *drawWalk) check() error {
if strings.HasPrefix(into.a.path, level.a.path+".") && !(level.tr != nil && level.tr.whole) {
return overlapError(level.at.route, level.a.name, into)
}
if err := checkFamily(level, into); err != nil {
return err
}
}
}
return nil
}
// checkFamily refuses two reads of one table family in one group that cannot read one consistent
// draw: a table rendered whole beside a path into the family, and two paths selecting different rows.
func checkFamily(a, b pathRead) error {
if a.tr == nil || b.tr == nil || a.tr.head.family() != b.tr.head.family() {
return nil
}
for _, pair := range [][2]pathRead{{a, b}, {b, a}} {
bare, path := pair[0], pair[1]
if len(bare.a.tail) == 0 && len(path.a.tail) > 0 {
return overlapError(bare.at.route, bare.a.name, path)
}
}
if len(a.a.tail) == 0 || len(b.a.tail) == 0 || a.tr.selection() == b.tr.selection() {
return nil
}
for _, pair := range [][2]pathRead{{a, b}, {b, a}} {
selected, plain := pair[0], pair[1]
if len(plain.tr.sels) > 0 {
continue
}
if s, ok := selected.tr.selected(plain.tr.head); ok {
tail := plain.a.tail
if s.t != plain.tr.head {
tail = append([]string{plain.tr.head.category}, tail...)
}
return fmt.Errorf("%s reads %s without the row %s selects; write {%s.%s}, or draw them apart with a drawGroup",
plain.at.route.spelled(plain.a.name), plain.tr.head.category, selected.at.route.spelled(selected.a.name), s.spelling, strings.Join(tail, "."))
}
}
return fmt.Errorf("%s and %s select different rows of one table family; select the same rows in both, or draw them apart with a drawGroup",
a.at.route.spelled(a.a.name), b.at.route.spelled(b.a.name))
return checkFamilies(w.reads)
}
func overlapError(route drawRoute, ref string, into pathRead) error {