Have the probe report the tables a read draws, and hold a row rendered whole in drawAt apart from the pins

This commit is contained in:
2026-09-25 08:25:13 +02:00
committed by Lillem4n
parent bf0f4e9d1f
commit 4a016c119c
6 changed files with 64 additions and 58 deletions
+3 -1
View File
@@ -412,7 +412,9 @@ another never meet, and each may select its own row of another table; the cells
row, and whatever they reach, do meet, and so does the format beside them. The rule the
fence applies is that two reads meet unless they sit in rows that never render together:
rows falling under different rows of a table a path pinned, two rows of that table
itself included. A choice's items get no such treatment yet: two items selecting
itself included. A table read whole draws its row apart from every pin, so its rows
are alternatives only to each other, and a cell of one meets a read pinning a region
the row is not in. A choice's items get no such treatment yet: two items selecting
different rows are still refused.
## A table never reaches its own family, by any route
+26 -13
View File
@@ -179,12 +179,14 @@ type drawWalk struct {
}
// drawAt is where a walk stands: the draw group it draws in, how the render's root reached it, the
// table rows it entered, and the table a whole read draws a row of.
// table rows it pinned, the table a whole read draws a row of, and the row of it whose cells the
// walk is in.
type drawAt struct {
group string
route drawRoute
alt pinSet
drawn *table // left out of the visit keys: only this table's own cells compare against it, which the own-family fence keeps true
whole tablePin
}
// drawRoute is how a render reaches a draw: as its author spells it, and the root edge's label.
@@ -216,7 +218,7 @@ func newDrawWalk() *drawWalk {
// walk follows what rendering n renders. A repeat renders over draws of its own, so the walk stops
// there.
func (w *drawWalk) walk(n node, at drawAt) {
v := nodeVisit{n, at.group, at.alt.key()}
v := nodeVisit{n, at.group, at.rowsKey()}
if w.seen[v] {
return
}
@@ -231,30 +233,41 @@ func (w *drawWalk) walk(n node, at drawAt) {
at.drawn = t
}
for _, e := range renderEdges(n) {
if cell, isCell := e.to.(*template); isCell && cell.cellOf != nil {
if at.alt.clash(cell.cellOf, cell.cellRow) != nil {
continue
}
in := at
in.alt = at.alt.entered(cell.cellOf, cell.cellRow, cell.cellOf != at.drawn)
w.edge(n, e, in)
continue
}
cell, isCell := e.to.(*template)
switch {
case !isCell || cell.cellOf == nil:
w.edge(n, e, at)
case cell.cellOf == at.drawn:
in := at
in.whole = tablePin{cell.cellOf, cell.cellRow}
w.edge(n, e, in)
case at.alt.clash(cell.cellOf, cell.cellRow) == nil:
in := at
in.alt = at.alt.entered(cell.cellOf, cell.cellRow)
w.edge(n, e, in)
}
}
}
// rowsKey spells the rows the walk stands in, pinned and whole, for a map.
func (at drawAt) rowsKey() string {
if at.whole.t == nil {
return at.alt.key()
}
return fmt.Sprintf("%s%p{%d}", at.alt.key(), at.whole.t, at.whole.row)
}
// 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) {
if a, reads := refRead(from, e.label); reads {
tr := tableReadOf(from.(*template).head(a.key), a, e.to)
if k := (readKey{at.group, a.path, at.alt.key()}); !w.read[k] {
if k := (readKey{at.group, a.path, at.rowsKey()}); !w.read[k] {
w.read[k] = true
w.reads = append(w.reads, pathRead{at, a, tr})
}
if tr != nil {
tr.pins.each(func(t *table, r int) { at.alt = at.alt.entered(t, r, true) })
tr.pins.each(func(t *table, r int) { at.alt = at.alt.entered(t, r) })
}
}
w.walk(e.to, at)
+6 -15
View File
@@ -33,33 +33,21 @@ func tableReadOf(head node, a arm, leaf node) *tableRead {
return nil
}
tr := &tableRead{head: t, drawn: map[*table]bool{}}
_, _ = walkPath(t, a.tail, pathWalk{mode: walkProbe, pins: &tr.pins})
_, _ = walkPath(t, a.tail, pathWalk{mode: walkProbe, pins: &tr.pins, drawn: tr.drawn})
written := a.name[:len(a.name)-len(joinSegments(a.tail))]
cur := t
tr.draws(cur)
for i, seg := range a.tail {
switch d := cur.descendant(seg); {
case isSelector(seg):
tr.sels = append(tr.sels, tableSel{cur, written + joinSegments(a.tail[:i+1])})
case d != nil:
cur = d
tr.draws(cur)
}
}
_, tr.whole = leaf.(*row)
return tr
}
// draws marks t and its ancestors drawn, up to the first the read pins.
func (r *tableRead) draws(t *table) {
for ; t != nil; t = t.parentT {
if _, pinned := r.pins.pinned(t); pinned {
return
}
r.drawn[t] = true
}
}
// joinSegments spells segments as a path: a selector attaches to the name before it.
func joinSegments(segs []string) string {
var b strings.Builder
@@ -228,10 +216,13 @@ func checkOwnFamily(t *template) error {
return nil
}
// alternatives reports whether two reads sit in different rows of one table. Not clash: a row drawn
// whole is held without its ancestors, since the render draws it over the whole table.
// alternatives reports whether two reads sit in different rows of one table: rows the walks pinned,
// or rows of one whole draw.
// docs/decisions.md#the-rows-of-a-table-are-alternatives
func alternatives(a, b drawAt) bool {
if a.whole.t != nil && a.whole.t == b.whole.t && a.whole.row != b.whole.row {
return true
}
found := false
a.alt.each(func(t *table, r int) {
if br, in := b.alt.pinned(t); in && br != r {
+13 -6
View File
@@ -136,7 +136,8 @@ const (
// walkCover stops at a choice, handing it whole to leaf, and reads no row.
walkCover
// walkProbe proves as walkEvery does, walking one variant, so a path that
// resolves resolves whichever variants and rows are drawn.
// resolves resolves whichever variants and rows are drawn, and marks in drawn
// the tables a draw would pin.
walkProbe
// walkDraw draws the rows and variants a proved path reads; drawPath is its
// one entry.
@@ -150,6 +151,7 @@ type pathWalk struct {
level func(t *template, rest []string) error
leaf func(n node) error
pins *pinSet
drawn map[*table]bool
draws *pathDraws
}
@@ -299,14 +301,19 @@ func (t *table) step(tail []string) (column node, child *table, err error) {
return nil, child, nil
}
// readRow pins the row a path reads of t: the one its selector names, or, where the
// walk draws, one drawn where the path reads into the table.
// readRow pins the row a path reads of t: the one its selector names, or one drawn
// where the path reads into the table.
func readRow(w pathWalk, t *table, sel string, draw bool) error {
if sel != "" {
switch {
case sel != "":
return w.pins.selectRow(t, sel)
}
if draw && w.mode == walkDraw {
case !draw:
case w.mode == walkDraw:
t.drawIn(w.draws.s, w.pins)
case w.drawn != nil:
for stop := w.pins.nearestPinned(t); t != stop; t = t.parentT {
w.drawn[t] = true
}
}
return nil
}
+17 -18
View File
@@ -12,8 +12,7 @@ type tablePin struct {
row int
}
// pinSet is the table rows fixed so far. A fence walk's set also holds a row rendered whole alone,
// which pin never does.
// pinSet is the table rows fixed so far.
type pinSet struct {
inline [8]tablePin // sized so a render over a country's five-deep geo tree stays off the heap
n int
@@ -39,7 +38,6 @@ func (p *pinSet) mustRow(t *table) int {
return r
}
// add pins row r of t alone.
func (p *pinSet) add(t *table, r int) {
if p.n < len(p.inline) {
p.inline[p.n] = tablePin{t, r}
@@ -54,18 +52,25 @@ func (p *pinSet) add(t *table, r int) {
// pin pins row r of t, and the rows of t's ancestors it links to.
func (p *pinSet) pin(t *table, r int) {
for {
if _, done := p.pinned(t); done {
return
}
for stop := p.nearestPinned(t); t != stop; t = t.parentT {
p.add(t, r)
if t.parentT == nil {
return
if t.parentT != stop {
r = t.parentRow(r)
}
t, r = t.parentT, t.parentRow(r)
}
}
// nearestPinned is the first of t and its ancestors p pins, where pinning a row of t stops; nil
// where none is.
func (p *pinSet) nearestPinned(t *table) *table {
for ; t != nil; t = t.parentT {
if _, ok := p.pinned(t); ok {
return t
}
}
return nil
}
// each calls fn for every pinned row, in pin order, the spilled ones by path.
func (p *pinSet) each(fn func(t *table, r int)) {
for _, q := range p.inline[:p.n] {
@@ -141,16 +146,10 @@ func (p pinSet) clone() pinSet {
return p
}
// entered is p with row r of t: pinned where the render pins it, alone where the render draws it
// to render whole. p is left as it was, since a fence walk branches.
func (p pinSet) entered(t *table, r int, pinsAncestors bool) pinSet {
// entered is p with row r of t pinned. p is left as it was, since a fence walk branches.
func (p pinSet) entered(t *table, r int) pinSet {
p = p.clone()
switch _, in := p.pinned(t); {
case pinsAncestors:
p.pin(t, r)
case !in:
p.add(t, r)
}
return p
}
-6
View File
@@ -19,12 +19,6 @@ Self-sufficiency 5.6), every seat capped by Locality or Self-sufficiency.
This round, in order:
- Have the check mode report the tables a read draws, so `tableRead.drawn` and
`entered`'s `pinsAncestors` flag stop restating `walkTable`'s draw condition and
`table.drawIn`'s ancestor rule: a change to how a render draws rows still leaves the
fence proving the old rule. Hold a row rendered whole in `drawAt` beside `drawn`, so
`pinSet` holds only what `pin` pins and `alternatives` needs no exception. Settle a
cell of a municipality drawn whole beside a path pinning a region it is not in.
- Register a transform once: `transforms` restates the `lowercase`, `uppercase` and
`ascii` entries in `builtins`, and `valueProof.template` classifies by the first while
render calls the second. Derive the numeric builtins `valueProof.template` lists in