Keep a template's table in one field, tableSite

This commit is contained in:
2026-09-29 03:30:19 +02:00
committed by Lillem4n
parent 05bc56fe75
commit e9cdfe9edd
9 changed files with 34 additions and 36 deletions
+1 -4
View File
@@ -81,10 +81,7 @@ func (f *drawFence) checkDraws(path string, n node) error {
// it renders, whichever draws the reaching template holds. // it renders, whichever draws the reaching template holds.
// docs/decisions.md#a-table-never-reaches-its-own-family-by-any-route // docs/decisions.md#a-table-never-reaches-its-own-family-by-any-route
func checkOwnFamily(t *template) error { func checkOwnFamily(t *template) error {
own := t.table own := t.site.table
if own == nil {
own = t.cell.table
}
if own == nil { if own == nil {
return nil return nil
} }
+6 -6
View File
@@ -65,12 +65,12 @@ type renderTrace func(group, table string, row int, a arm)
func traceRead(trace renderTrace, t *template, sc renderScope, a arm) { func traceRead(trace renderTrace, t *template, sc renderScope, a arm) {
var table string var table string
var row int row := t.site.row
switch { if t.site.table != nil {
case t.cell.table != nil: table = t.site.table.path
table, row = t.cell.table.path, t.cell.row }
case t.table != nil: if row == formatRow {
table, row = t.table.path, sc.rowOf(t.table) row = sc.rowOf(t.site.table)
} }
trace(strings.Clone(sc.group), strings.Clone(table), row, a) trace(strings.Clone(sc.group), strings.Clone(table), row, a)
} }
+10 -8
View File
@@ -54,20 +54,22 @@ type template struct {
drawGroup string // the draw group it draws in, as written; "" keeps its caller's drawGroup string // the draw group it draws in, as written; "" keeps its caller's
fromString bool // written as a JSON string rather than an object fromString bool // written as a JSON string rather than an object
isRecord bool // compiled at the top without a repeat, so its fields are record columns isRecord bool // compiled at the top without a repeat, so its fields are record columns
table *table // the table whose format this is, whose columns are the fields site tableSite
cell cellSite
// Filled by `linkTemplate`, from the assembled tree: // Filled by `linkTemplate`, from the assembled tree:
link templateLink link templateLink
compiled formatOps compiled formatOps
} }
// cellSite is the table cell a template is compiled from; table is nil for any other template. // tableSite is the table a template belongs to, as its format or one of its cells; table is nil
type cellSite struct { // for any other template.
type tableSite struct {
table *table table *table
row int row int // the cell's row, or formatRow for the format, whose columns are the fields
} }
const formatRow = -1
// templateLink is what a template resolves to in the assembled tree. // templateLink is what a template resolves to in the assembled tree.
type templateLink struct { type templateLink struct {
refs map[string]refBinding // each reference the format reads -> what it resolves to refs map[string]refBinding // each reference the format reads -> what it resolves to
@@ -172,7 +174,7 @@ func compileAt(v any, pos position) (node, error) {
func compileItem(v any, pos position) (node, error) { func compileItem(v any, pos position) (node, error) {
switch v := v.(type) { switch v := v.(type) {
case string: case string:
t, err := compileString(v, cellSite{}) t, err := compileString(v, tableSite{})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -203,12 +205,12 @@ func jsonKind(v any) string {
return fmt.Sprintf("%T", v) return fmt.Sprintf("%T", v)
} }
func compileString(s string, cell cellSite) (*template, error) { func compileString(s string, site tableSite) (*template, error) {
toks, err := parseChecked(s, nil) toks, err := parseChecked(s, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &template{format: s, tokens: toks, repeat: 1, fromString: true, cell: cell}, nil return &template{format: s, tokens: toks, repeat: 1, fromString: true, site: site}, nil
} }
// fixedText is one render's output when the format holds no token; repeat is the caller's. // fixedText is one render's output when the format holds no token; repeat is the caller's.
+3 -3
View File
@@ -114,9 +114,9 @@ func (s *readSurvey) walk(n node, at surveyAt) {
} }
for _, e := range renderEdges(n) { for _, e := range renderEdges(n) {
to, isTemplate := e.to.(*template) to, isTemplate := e.to.(*template)
cell := cellSite{} cell := tableSite{}
if isTemplate { if isTemplate && to.site.row != formatRow {
cell = to.cell cell = to.site
} }
switch { switch {
case cell.table == nil: case cell.table == nil:
+8 -7
View File
@@ -80,13 +80,14 @@ func renderRoot(s *session, t *template, trace renderTrace) {
set := holdSet{trace: trace} set := holdSet{trace: trace}
sc := renderScope{set: &set} sc := renderScope{set: &set}
switch { switch {
case t.table != nil: case t.site.row == formatRow:
render(s, t.table, sc) tbl := t.site.table
for r := 0; r < t.table.rowCount(); r++ { render(s, tbl, sc)
for r := 0; r < tbl.rowCount(); r++ {
set := holdSet{trace: trace} set := holdSet{trace: trace}
sc := renderScope{set: &set} sc := renderScope{set: &set}
sc.groupHold().pins.pin(t.table, r) sc.groupHold().pins.pin(tbl, r)
render(s, t.table.pinnedRow, sc.at(t.table.pinnedRow, &sc.groupHold().pins)) render(s, tbl.pinnedRow, sc.at(tbl.pinnedRow, &sc.groupHold().pins))
} }
case t.repeat > 1: case t.repeat > 1:
expand(s, t, sc) expand(s, t, sc)
@@ -98,8 +99,8 @@ func renderRoot(s *session, t *template, trace renderTrace) {
func renderRecordRoot(s *session, t *template, columns []Column, trace renderTrace) { func renderRecordRoot(s *session, t *template, columns []Column, trace renderTrace) {
set := holdSet{trace: trace} set := holdSet{trace: trace}
sc := renderScope{set: &set} sc := renderScope{set: &set}
if t.table != nil { if t.site.row == formatRow {
t.table.drawIn(s, &sc.groupHold().pins) t.site.table.drawIn(s, &sc.groupHold().pins)
} }
renderRecord(s, t, columns, sc) renderRecord(s, t, columns, sc)
} }
+2 -2
View File
@@ -263,8 +263,8 @@ func recordOf(n node) (*template, []Column, error) {
// over sc's hold; a table's columns read the row pinned there. // over sc's hold; a table's columns read the row pinned there.
func renderRecord(s *session, t *template, columns []Column, sc renderScope) *Record { func renderRecord(s *session, t *template, columns []Column, sc renderScope) *Record {
sc = sc.in(t) sc = sc.in(t)
if t.table != nil { if t.site.row == formatRow {
sc = sc.at(t.table.pinnedRow, &sc.groupHold().pins) sc = sc.at(t.site.table.pinnedRow, &sc.groupHold().pins)
} }
r := &Record{columns: append([]Column(nil), columns...)} r := &Record{columns: append([]Column(nil), columns...)}
for i := range r.columns { for i := range r.columns {
+2 -2
View File
@@ -81,8 +81,8 @@ func linkRefs(root map[string]node) error {
// linkTemplate resolves t's references in category, "" for an inline template, and // linkTemplate resolves t's references in category, "" for an inline template, and
// compiles its format against them. // compiles its format against them.
func linkTemplate(folder []string, path, category string, t *template, root map[string]node) error { func linkTemplate(folder []string, path, category string, t *template, root map[string]node) error {
if t.cell.table != nil { if t.site.table != nil && t.site.row != formatRow {
path = fmt.Sprintf("%s, line %d", path, t.cell.row+2) path = fmt.Sprintf("%s, line %d", path, t.site.row+2)
} }
link, err := t.resolveLink(folder, path, category, root) link, err := t.resolveLink(folder, path, category, root)
if err != nil { if err != nil {
+2 -2
View File
@@ -308,7 +308,7 @@ func (t *table) checkCells() error {
if strings.IndexByte(cell, '{') < 0 && strings.IndexByte(cell, '}') < 0 { if strings.IndexByte(cell, '{') < 0 && strings.IndexByte(cell, '}') < 0 {
continue continue
} }
n, err := compileString(cell, cellSite{t, row}) n, err := compileString(cell, tableSite{t, row})
if err != nil { if err != nil {
return fmt.Errorf("line %d, %s: %w", row+2, t.header[col], err) return fmt.Errorf("line %d, %s: %w", row+2, t.header[col], err)
} }
@@ -341,7 +341,7 @@ func (t *table) compileRowFormat(format string) error {
if err := checkTokens(toks, t.fields); err != nil { if err := checkTokens(toks, t.fields); err != nil {
return err return err
} }
t.formatTemplate = &template{format: format, tokens: toks, fields: t.fields, repeat: 1, isRecord: true, table: t} t.formatTemplate = &template{format: format, tokens: toks, fields: t.fields, repeat: 1, isRecord: true, site: tableSite{t, formatRow}}
t.pinnedRow = &tableRow{t} t.pinnedRow = &tableRow{t}
return nil return nil
} }
-2
View File
@@ -26,8 +26,6 @@ least modify.
This round, in order: This round, in order:
- Keep a template's table in one field: `template.table` and `template.cell.table` both
answer which table it belongs to, and `checkOwnFamily` and `traceRead` branch on both.
- Stop `readField` dereferencing a nil `held`: `renderLeaf` passes nil, safe only - Stop `readField` dereferencing a nil `held`: `renderLeaf` passes nil, safe only
because a `readsColumn` arm is a reference path with a tail, which nothing there says. because a `readsColumn` arm is a reference path with a tail, which nothing there says.
- Test that `disagreements` holds every key `disagreement` can build, and panic through - Test that `disagreements` holds every key `disagreement` can build, and panic through