Give check, children, column, columns, draw, drawn, fields, format, head, index, key, n, name, named, names, null, number, op, reads, record, root, row, t and whole one meaning each

This commit is contained in:
2026-09-25 19:10:18 +02:00
committed by Lillem4n
parent cfe8124cc3
commit d6ac8ed9fd
29 changed files with 324 additions and 326 deletions
+1 -1
View File
@@ -110,4 +110,4 @@ In [docs/decisions.md](docs/decisions.md):
- The Swedish ids draw Skatteverket's test series - The Swedish ids draw Skatteverket's test series
- The US given names come from a mirror of the SSA file - The US given names come from a mirror of the SSA file
- `List` advertises direct descents only - `List` advertises direct descents only
- Each entry point to `drawCheck.reads` says what its caller gets - Each entry point to `drawCheck.hasRead` says what its caller gets
+16 -16
View File
@@ -28,44 +28,44 @@ var builtins = withTransforms(map[string]builtin{
"ean": {arity: 0, prep: derive(eanCheck)}, "ean": {arity: 0, prep: derive(eanCheck)},
"uuid": {arity: 0, prep: sample(uuidV7)}, "uuid": {arity: 0, prep: sample(uuidV7)},
"ulid": {arity: 0, prep: sample(ulid)}, "ulid": {arity: 0, prep: sample(ulid)},
"nanoid": {arity: 1, check: posIntArg, prep: chars(nanoidAlphabet)}, "nanoid": {arity: 1, checkArgs: posIntArg, prep: chars(nanoidAlphabet)},
"hex": {arity: 1, check: posIntArg, prep: chars(hexDigits)}, "hex": {arity: 1, checkArgs: posIntArg, prep: chars(hexDigits)},
"digits": {arity: 1, check: posIntArg, prep: chars("0123456789"), prints: DataTypeString, number: func(token string, prints DataType, a []string) proven { "digits": {arity: 1, checkArgs: posIntArg, prep: chars("0123456789"), prints: DataTypeString, proveNumber: func(token string, prints DataType, a []string) proven {
return printing(token, prints, bounded(0, math.Pow(10, float64(atoi(a[0])))-1, true)) return printing(token, prints, bounded(0, math.Pow(10, float64(atoi(a[0])))-1, true))
}}, }},
"upper": {arity: 1, check: posIntArg, prep: chars("ABCDEFGHIJKLMNOPQRSTUVWXYZ")}, "upper": {arity: 1, checkArgs: posIntArg, prep: chars("ABCDEFGHIJKLMNOPQRSTUVWXYZ")},
"lower": {arity: 1, check: posIntArg, prep: chars("abcdefghijklmnopqrstuvwxyz")}, "lower": {arity: 1, checkArgs: posIntArg, prep: chars("abcdefghijklmnopqrstuvwxyz")},
"base64": {arity: 1, check: posIntArg, prep: func(a []string) callFn { "base64": {arity: 1, checkArgs: posIntArg, prep: func(a []string) callFn {
n := atoi(a[0]) n := atoi(a[0])
return func(s *session, _ string, _ []string) string { return func(s *session, _ string, _ []string) string {
return base64.StdEncoding.EncodeToString(randBytes(s, n)) return base64.StdEncoding.EncodeToString(randBytes(s, n))
} }
}}, }},
"int": {arity: 2, check: intRangeArgs, prep: func(a []string) callFn { "int": {arity: 2, checkArgs: intRangeArgs, prep: func(a []string) callFn {
lo, span := atoi(a[0]), atoi(a[1])-atoi(a[0])+1 lo, span := atoi(a[0]), atoi(a[1])-atoi(a[0])+1
return func(s *session, _ string, _ []string) string { return strconv.Itoa(lo + s.IntN(span)) } return func(s *session, _ string, _ []string) string { return strconv.Itoa(lo + s.IntN(span)) }
}, prints: DataTypeInteger, number: func(token string, prints DataType, a []string) proven { }, prints: DataTypeInteger, proveNumber: func(token string, prints DataType, a []string) proven {
return printing(token, prints, bounded(float64(atoi(a[0])), float64(atoi(a[1])), true)) return printing(token, prints, bounded(float64(atoi(a[0])), float64(atoi(a[1])), true))
}}, }},
"float": {arity: 3, check: floatArgs, prep: func(a []string) callFn { "float": {arity: 3, checkArgs: floatArgs, prep: func(a []string) callFn {
lo, hi, dp := atof(a[0]), atof(a[1]), atoi(a[2]) lo, hi, dp := atof(a[0]), atof(a[1]), atoi(a[2])
return func(s *session, _ string, _ []string) string { return func(s *session, _ string, _ []string) string {
return formatFloat(lo+s.Float64()*(hi-lo), dp) return formatFloat(lo+s.Float64()*(hi-lo), dp)
} }
}, prints: DataTypeNumber, number: func(token string, _ DataType, a []string) proven { }, prints: DataTypeNumber, proveNumber: func(token string, _ DataType, a []string) proven {
return printedNumber(token, bounded(atof(a[0]), atof(a[1]), false), atoi(a[2])) return printedNumber(token, bounded(atof(a[0]), atof(a[1]), false), atoi(a[2]))
}}, }},
"iban": {arity: 1, check: ibanArg, prep: func(a []string) callFn { "iban": {arity: 1, checkArgs: ibanArg, prep: func(a []string) callFn {
cc := a[0] cc := a[0]
return func(s *session, _ string, _ []string) string { return iban(s, cc) } return func(s *session, _ string, _ []string) string { return iban(s, cc) }
}}, }},
"date": {arity: -1, check: dateArgs, prep: datePrep}, "date": {arity: -1, checkArgs: dateArgs, prep: datePrep},
"time": {arity: -1, check: timeArg, prep: timePrep}, "time": {arity: -1, checkArgs: timeArg, prep: timePrep},
"calc": {arity: -1, check: checkCalc, prep: calcPrep, operands: calcOperands}, "calc": {arity: -1, checkArgs: checkCalc, prep: calcPrep, operands: calcOperands},
// seq is the one stateful builtin: a per-session counter from 1, advancing on // seq is the one stateful builtin: a per-session counter from 1, advancing on
// each call. An optional name selects an independent counter; no name uses the // each call. An optional name selects an independent counter; no name uses the
// default one. Deterministic by construction, so seeded output stays stable. // default one. Deterministic by construction, so seeded output stays stable.
"seq": {arity: -1, check: seqArg, prep: func(a []string) callFn { "seq": {arity: -1, checkArgs: seqArg, prep: func(a []string) callFn {
key := "" key := ""
if len(a) == 1 { if len(a) == 1 {
key = a[0] key = a[0]
@@ -73,7 +73,7 @@ var builtins = withTransforms(map[string]builtin{
return func(s *session, _ string, _ []string) string { return func(s *session, _ string, _ []string) string {
return strconv.FormatUint(s.next(key), 10) return strconv.FormatUint(s.next(key), 10)
} }
}, prints: DataTypeInteger, number: func(token string, prints DataType, _ []string) proven { }, prints: DataTypeInteger, proveNumber: func(token string, prints DataType, _ []string) proven {
return printing(token, prints, bounded(1, math.MaxInt64, true)) return printing(token, prints, bounded(1, math.MaxInt64, true))
}}, }},
}) })
+1 -1
View File
@@ -194,7 +194,7 @@ func TestRegistryShapes(t *testing.T) {
if b.prep == nil { if b.prep == nil {
t.Errorf("builtin %q has no prep: compileOps would call a nil func", name) t.Errorf("builtin %q has no prep: compileOps would call a nil func", name)
} }
if b.arity != 0 && b.check == nil { if b.arity != 0 && b.checkArgs == nil {
t.Errorf("builtin %q parses args in prep with no check", name) t.Errorf("builtin %q parses args in prep with no check", name)
} }
} }
+8 -8
View File
@@ -19,8 +19,8 @@ type calcVar string // a sibling-field name, before indexVars places it
type calcIdx int // an operand, by its position in the values expand read type calcIdx int // an operand, by its position in the values expand read
type calcNeg struct{ x calcNode } type calcNeg struct{ x calcNode }
type calcBin struct { // a + - * / b type calcBin struct { // a + - * / b
op byte operator byte
l, r calcNode l, r calcNode
} }
func (n calcNum) eval([]string) float64 { return float64(n) } func (n calcNum) eval([]string) float64 { return float64(n) }
@@ -45,7 +45,7 @@ func (n calcNeg) eval(operands []string) float64 { return -n.x.eval(operands) }
func (n calcBin) eval(operands []string) float64 { func (n calcBin) eval(operands []string) float64 {
l, r := n.l.eval(operands), n.r.eval(operands) l, r := n.l.eval(operands), n.r.eval(operands)
switch n.op { switch n.operator {
case '+': case '+':
return l + r return l + r
case '-': case '-':
@@ -100,7 +100,7 @@ func constantZeroDivisor(n calcNode, fields map[string]node) (string, bool) {
case calcNeg: case calcNeg:
return constantZeroDivisor(n.x, fields) return constantZeroDivisor(n.x, fields)
case calcBin: case calcBin:
if n.op == '/' { if n.operator == '/' {
if v, known := constantValue(n.r, fields); known && v == 0 { if v, known := constantValue(n.r, fields); known && v == 0 {
return calcText(n.r), true return calcText(n.r), true
} }
@@ -134,7 +134,7 @@ func constantValue(n calcNode, fields map[string]node) (float64, bool) {
if !lok || !rok { if !lok || !rok {
return 0, false return 0, false
} }
return calcBin{n.op, calcNum(l), calcNum(r)}.eval(nil), true return calcBin{n.operator, calcNum(l), calcNum(r)}.eval(nil), true
} }
return 0, false return 0, false
} }
@@ -149,7 +149,7 @@ func calcText(n calcNode) string {
case calcNeg: case calcNeg:
return "-" + calcText(n.x) return "-" + calcText(n.x)
case calcBin: case calcBin:
return "(" + calcText(n.l) + " " + string(n.op) + " " + calcText(n.r) + ")" return "(" + calcText(n.l) + " " + string(n.operator) + " " + calcText(n.r) + ")"
} }
return "?" return "?"
} }
@@ -158,7 +158,7 @@ func calcText(n calcNode) string {
// does not parse, or a choice of only such items. text is one such render. // does not parse, or a choice of only such items. text is one such render.
func neverNumeric(n node) (text string, never bool) { func neverNumeric(n node) (text string, never bool) {
switch n := n.(type) { switch n := n.(type) {
case *null: case *nullItem:
return "", true return "", true
case *template: case *template:
if !n.fixed || n.repeat > 1 { if !n.fixed || n.repeat > 1 {
@@ -220,7 +220,7 @@ func indexVars(n calcNode, at map[string]int) calcNode {
case calcNeg: case calcNeg:
return calcNeg{indexVars(n.x, at)} return calcNeg{indexVars(n.x, at)}
case calcBin: case calcBin:
return calcBin{n.op, indexVars(n.l, at), indexVars(n.r, at)} return calcBin{n.operator, indexVars(n.l, at), indexVars(n.r, at)}
} }
return n return n
} }
+12 -12
View File
@@ -17,10 +17,10 @@ type dataSource struct {
label string label string
onDisk bool onDisk bool
path string path string
root string dir string
} }
func (s dataSource) name(p string) string { func (s dataSource) labelled(p string) string {
if s.label == "" { if s.label == "" {
return p return p
} }
@@ -49,7 +49,7 @@ func loadData(sources []dataSource) (map[string]node, error) {
return nil, fmt.Errorf("%s is not a directory", src.path) return nil, fmt.Errorf("%s is not a directory", src.path)
} }
} }
dir := src.root dir := src.dir
if dir == "" { if dir == "" {
dir = "." dir = "."
} }
@@ -84,7 +84,7 @@ func loadData(sources []dataSource) (map[string]node, error) {
func loadDir(src dataSource, dir string) (*folder, error) { func loadDir(src dataSource, dir string) (*folder, error) {
entries, err := fs.ReadDir(src.fsys, dir) entries, err := fs.ReadDir(src.fsys, dir)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s: %w", src.name(dir), err) return nil, fmt.Errorf("%s: %w", src.labelled(dir), err)
} }
g := &folder{children: map[string]node{}} g := &folder{children: map[string]node{}}
files := &categoryFiles{src: src, dir: dir, tsv: map[string]bool{}} files := &categoryFiles{src: src, dir: dir, tsv: map[string]bool{}}
@@ -110,7 +110,7 @@ func loadDir(src dataSource, dir string) (*folder, error) {
} }
for name, named := range files.tsv { for name, named := range files.tsv {
if !named && !strings.HasPrefix(name, ".") { if !named && !strings.HasPrefix(name, ".") {
return nil, fmt.Errorf("%s: no category names it in its rows; a table's rows file sits beside a category file naming it", src.name(path.Join(dir, name))) return nil, fmt.Errorf("%s: no category names it in its rows; a table's rows file sits beside a category file naming it", src.labelled(path.Join(dir, name)))
} }
} }
return g, nil return g, nil
@@ -127,12 +127,12 @@ type categoryFiles struct {
// readRows reads the rows file a category names beside it. // readRows reads the rows file a category names beside it.
func (c *categoryFiles) readRows(name string) (string, error) { func (c *categoryFiles) readRows(name string) (string, error) {
if _, present := c.tsv[name]; !present { if _, present := c.tsv[name]; !present {
return "", fmt.Errorf("rows names %s, which is not beside it in %s", name, c.src.name(c.dir)) return "", fmt.Errorf("rows names %s, which is not beside it in %s", name, c.src.labelled(c.dir))
} }
c.tsv[name] = true c.tsv[name] = true
b, err := fs.ReadFile(c.src.fsys, path.Join(c.dir, name)) b, err := fs.ReadFile(c.src.fsys, path.Join(c.dir, name))
if err != nil { if err != nil {
return "", fmt.Errorf("%s: %w", c.src.name(path.Join(c.dir, name)), err) return "", fmt.Errorf("%s: %w", c.src.labelled(path.Join(c.dir, name)), err)
} }
return string(b), nil return string(b), nil
} }
@@ -147,7 +147,7 @@ func loadFolder(src dataSource, g *folder, full, name string) error {
return nil return nil
} }
if err := checkName(name); err != nil { if err := checkName(name); err != nil {
return fmt.Errorf("%s: folder %w", src.name(full), err) return fmt.Errorf("%s: folder %w", src.labelled(full), err)
} }
g.children[name] = child g.children[name] = child
return nil return nil
@@ -161,19 +161,19 @@ func loadFile(src dataSource, g *folder, full, file string, files *categoryFiles
} }
name := strings.TrimSuffix(file, ".json") name := strings.TrimSuffix(file, ".json")
if err := checkName(name); err != nil { if err := checkName(name); err != nil {
return fmt.Errorf("%s: category %w", src.name(full), err) return fmt.Errorf("%s: category %w", src.labelled(full), err)
} }
b, err := fs.ReadFile(src.fsys, full) b, err := fs.ReadFile(src.fsys, full)
if err != nil { if err != nil {
return fmt.Errorf("%s: %w", src.name(full), err) return fmt.Errorf("%s: %w", src.labelled(full), err)
} }
var raw any var raw any
if err := json.Unmarshal(b, &raw); err != nil { if err := json.Unmarshal(b, &raw); err != nil {
return fmt.Errorf("%s: %w", src.name(full), err) return fmt.Errorf("%s: %w", src.labelled(full), err)
} }
n, err := compileCategory(raw, name, files) n, err := compileCategory(raw, name, files)
if err != nil { if err != nil {
return fmt.Errorf("%s: %w", src.name(full), err) return fmt.Errorf("%s: %w", src.labelled(full), err)
} }
g.children[name] = n g.children[name] = n
return nil return nil
+2 -2
View File
@@ -89,7 +89,7 @@ func checkColumns(s nodeScope) error {
} }
return s(func(path string, n node) error { return s(func(path string, n node) error {
t, ok := n.(*template) t, ok := n.(*template)
if !ok || !t.record { if !ok || !t.isRecord {
return nil return nil
} }
for _, name := range sortedNames(t.fields) { for _, name := range sortedNames(t.fields) {
@@ -149,7 +149,7 @@ func columnItems(n node) (items []*template, nullable bool) {
} }
case *template: case *template:
items = append(items, n) items = append(items, n)
case *null: case *nullItem:
nullable = true nullable = true
} }
} }
+3 -3
View File
@@ -17,7 +17,7 @@ package fejkdata
// Vocabulary // Vocabulary
// //
// - draw — one pick from a choice, or one row taken from a table, and the value a // - draw — one pick from a choice, or one row taken from a table, and the value a
// read produced: `pick`, `drawn`, `table.draw`, `draw`. // read produced: `pick`, `resolveChoice`, `table.drawRow`, `draw`.
// - expansion — one render of one format: `expand`. // - expansion — one render of one format: `expand`.
// - render — what owns one `holdSet`, and so what one reference draw spans: // - render — what owns one `holdSet`, and so what one reference draw spans:
// `Generator.Fake`, `FakeRecord`, `Template.Fake` and `RecordTemplate.Fake` // `Generator.Fake`, `FakeRecord`, `Template.Fake` and `RecordTemplate.Fake`
@@ -32,8 +32,8 @@ package fejkdata
// `pinSet`, `pinSet.pin`, `table.drawIn`. // `pinSet`, `pinSet.pin`, `table.drawIn`.
// - family — a table and every table reaching it through a chain of // - family — a table and every table reaching it through a chain of
// `table.parentT`, named by the root that chain ends at: `table.family`. // `table.parentT`, named by the root that chain ends at: `table.family`.
// - whole — a read that lands on a `row`, so the row renders through its table's // - whole — a read that lands on a `tableRow`, so the row renders through its table's
// format: `tableRead.whole` says a read did, `table.whole` is the node. // format: `tableRead.landsWhole` says a read did, `table.wholeRow` is the node.
// - fence — a load-time check, so rendering a compiled tree cannot fail: `New` // - fence — a load-time check, so rendering a compiled tree cannot fail: `New`
// runs them over the data set, `NewTemplate` and `FakeStruct` over what those // runs them over the data set, `NewTemplate` and `FakeStruct` over what those
// compile. The draw fences are `heldCheck`, `drawCheck` and `checkFamilies`. // compile. The draw fences are `heldCheck`, `drawCheck` and `checkFamilies`.
+2 -2
View File
@@ -650,8 +650,8 @@ import drops them by name rather than by a rank a regeneration would move.
the set of every descent through a chain of five tables is every subsequence of it, and the set of every descent through a chain of five tables is every subsequence of it, and
the direct chain is the one a reader can predict from the tables' parents. the direct chain is the one a reader can predict from the tables' parents.
## Each entry point to `drawCheck.reads` says what its caller gets ## Each entry point to `drawCheck.hasRead` says what its caller gets
2026-09-25, larv-review; approved 2026-09-25 by lilleman. Goal 2: a reader lands on 2026-09-25, larv-review; approved 2026-09-25 by lilleman. Goal 2: a reader lands on
`readsPath` or `splitsDraws` from its caller, so each doc states where that walk stops, `readsPath` or `splitsDraws` from its caller, so each doc states where that walk stops,
though `reads` states the same stop rule. Valid while the two differ in where they stop. though `hasRead` states the same stop rule. Valid while the two differ in where they stop.
+23 -23
View File
@@ -41,10 +41,10 @@ func checkNestedDrawGroup(fields map[string]node, group string) error {
// docs/decisions.md#a-render-shares-one-reference-draw-per-category-per-group // docs/decisions.md#a-render-shares-one-reference-draw-per-category-per-group
// docs/decisions.md#the-expansion-hold-and-the-renders-draws-are-two-fences // docs/decisions.md#the-expansion-hold-and-the-renders-draws-are-two-fences
type drawCheck struct { type drawCheck struct {
memo map[readsMemo]bool memo map[hasReadMemo]bool
} }
type readsMemo struct { type hasReadMemo struct {
n node n node
stopAtGroup bool stopAtGroup bool
} }
@@ -84,7 +84,7 @@ func (c *drawCheck) checkDraws(path string, n node) error {
// docs/decisions.md#a-category-never-references-itself-and-a-records-fences-run-at-load // docs/decisions.md#a-category-never-references-itself-and-a-records-fences-run-at-load
func (c *drawCheck) checkRecordDraws(path string, n node) error { func (c *drawCheck) checkRecordDraws(path string, n node) error {
t, ok := n.(*template) t, ok := n.(*template)
if !ok || !t.record { if !ok || !t.isRecord {
return nil return nil
} }
// A record-only template's format renders nothing, so weigh the columns, not the format. // A record-only template's format renders nothing, so weigh the columns, not the format.
@@ -104,28 +104,28 @@ func (c *drawCheck) checkRecordDraws(path string, n node) error {
// readsPath reports whether rendering n reads a reference path, short of a repeat, which renders // readsPath reports whether rendering n reads a reference path, short of a repeat, which renders
// over draws of its own. // over draws of its own.
func (c *drawCheck) readsPath(n node) bool { return c.reads(n, false) } func (c *drawCheck) readsPath(n node) bool { return c.hasRead(n, false) }
// splitsDraws reports whether rendering n reads a reference path that n's own draw group answers // splitsDraws reports whether rendering n reads a reference path that n's own draw group answers
// for: one outside a repeat and outside a nested draw group, which hold their own draws. // for: one outside a repeat and outside a nested draw group, which hold their own draws.
func (c *drawCheck) splitsDraws(n node) bool { return c.reads(n, true) } func (c *drawCheck) splitsDraws(n node) bool { return c.hasRead(n, true) }
// reads walks what rendering n renders for a reference path, stopping at a repeat — and at a nested // hasRead walks what rendering n renders for a reference path, stopping at a repeat — and at a nested
// draw group when stopAtGroup — since each holds draws of its own. // draw group when stopAtGroup — since each holds draws of its own.
func (c *drawCheck) reads(n node, stopAtGroup bool) bool { func (c *drawCheck) hasRead(n node, stopAtGroup bool) bool {
k := readsMemo{n, stopAtGroup} k := hasReadMemo{n, stopAtGroup}
if r, done := c.memo[k]; done { if r, done := c.memo[k]; done {
return r return r
} }
r := false r := false
for _, e := range renderEdges(n) { for _, e := range renderEdges(n) {
if readsOnEdge(n, e.label) || (walksInto(e.to, stopAtGroup) && c.reads(e.to, stopAtGroup)) { if readsOnEdge(n, e.label) || (walksInto(e.to, stopAtGroup) && c.hasRead(e.to, stopAtGroup)) {
r = true r = true
break break
} }
} }
if c.memo == nil { if c.memo == nil {
c.memo = map[readsMemo]bool{} c.memo = map[hasReadMemo]bool{}
} }
c.memo[k] = r c.memo[k] = r
return r return r
@@ -191,11 +191,11 @@ type drawWalk struct {
// table rows it pinned, the table a whole read draws a row of, and the rows of whole draws whose // table rows it pinned, the table a whole read draws a row of, and the rows of whole draws whose
// cells the walk is in. // cells the walk is in.
type drawAt struct { type drawAt struct {
group string group string
route drawRoute route drawRoute
pins pinSet pins 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 wholeTable *table // left out of the visit keys: only this table's own cells compare against it, which the own-family fence keeps true
whole pinSet wholePins pinSet
} }
// 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.
@@ -239,17 +239,17 @@ func (w *drawWalk) walk(n node, at drawAt) {
at.group = t.drawGroupKey at.group = t.drawGroupKey
} }
if t, isTable := n.(*table); isTable { if t, isTable := n.(*table); isTable {
at.drawn = t at.wholeTable = t
} }
for _, e := range renderEdges(n) { for _, e := range renderEdges(n) {
cell, isCell := e.to.(*template) cell, isCell := e.to.(*template)
switch { switch {
case !isCell || cell.cellOf == nil: case !isCell || cell.cellOf == nil:
w.edge(n, e, at) w.edge(n, e, at)
case cell.cellOf == at.drawn: case cell.cellOf == at.wholeTable:
in := at in := at
in.whole = at.whole.clone() in.wholePins = at.wholePins.clone()
in.whole.add(cell.cellOf, cell.cellRow) in.wholePins.add(cell.cellOf, cell.cellRow)
w.edge(n, e, in) w.edge(n, e, in)
case at.pins.clash(cell.cellOf, cell.cellRow) == nil: case at.pins.clash(cell.cellOf, cell.cellRow) == nil:
in := at in := at
@@ -260,7 +260,7 @@ func (w *drawWalk) walk(n node, at drawAt) {
} }
// rowsKey spells the rows the walk stands in, pinned and whole, for a map. // rowsKey spells the rows the walk stands in, pinned and whole, for a map.
func (at drawAt) rowsKey() string { return at.pins.key() + "|" + at.whole.key() } func (at drawAt) rowsKey() string { return at.pins.mapKey() + "|" + at.wholePins.mapKey() }
// 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.
@@ -293,8 +293,8 @@ func (w *drawWalk) check() error {
if into.at.group != level.at.group || alternatives(level.at, into.at) { if into.at.group != level.at.group || alternatives(level.at, into.at) {
continue continue
} }
if strings.HasPrefix(into.a.path, level.a.path+".") && !(level.tr != nil && level.tr.whole) { if strings.HasPrefix(into.a.path, level.a.path+".") && !(level.tr != nil && level.tr.landsWhole) {
return overlapError(level.at.route, level.a.name, into) return overlapError(level.at.route, level.a.spelling, into)
} }
} }
} }
@@ -302,7 +302,7 @@ func (w *drawWalk) check() error {
} }
func overlapError(route drawRoute, ref string, into pathRead) error { func overlapError(route drawRoute, ref string, into pathRead) error {
return fmt.Errorf("%s renders a level that %s reads a path into; name the fields you want instead, or draw them apart with a drawGroup", route.spelled(ref), into.at.route.spelled(into.a.name)) return fmt.Errorf("%s renders a level that %s reads a path into; name the fields you want instead, or draw them apart with a drawGroup", route.spelled(ref), into.at.route.spelled(into.a.spelling))
} }
// spelled names the route, and the reference it reaches a draw by where its root edge is not that // spelled names the route, and the reference it reaches a draw by where its root edge is not that
+19 -17
View File
@@ -10,11 +10,11 @@ import (
// row pinned, and their unpinned ancestors — each selector's spelling, and whether // row pinned, and their unpinned ancestors — each selector's spelling, and whether
// it lands on a row rendered whole. // it lands on a row rendered whole.
type tableRead struct { type tableRead struct {
head *table headTable *table
pins pinSet pins pinSet
drawn map[*table]bool drawn map[*table]bool
sels []tableSel sels []tableSel
whole bool landsWhole bool
} }
// tableSel is one selector on the way: the table it selects a row of, and the path // tableSel is one selector on the way: the table it selects a row of, and the path
@@ -32,9 +32,9 @@ func tableReadOf(head node, a arm, leaf node) *tableRead {
if !isTable { if !isTable {
return nil return nil
} }
tr := &tableRead{head: t, drawn: map[*table]bool{}} tr := &tableRead{headTable: t, drawn: map[*table]bool{}}
_, _ = walkPath(t, a.tail, pathWalk{mode: walkProbe, pins: &tr.pins, drawn: tr.drawn}) _, _ = walkPath(t, a.tail, pathWalk{mode: walkProbe, pins: &tr.pins, drawn: tr.drawn})
written := a.name[:len(a.name)-len(joinSegments(a.tail))] written := a.spelling[:len(a.spelling)-len(joinSegments(a.tail))]
cur := t cur := t
for i, seg := range a.tail { for i, seg := range a.tail {
switch d := cur.descendant(seg); { switch d := cur.descendant(seg); {
@@ -44,7 +44,7 @@ func tableReadOf(head node, a arm, leaf node) *tableRead {
cur = d cur = d
} }
} }
_, tr.whole = leaf.(*row) _, tr.landsWhole = leaf.(*tableRow)
return tr return tr
} }
@@ -91,7 +91,7 @@ func checkFamilies(reads []pathRead) error {
continue continue
} }
for _, o := range reads[:i] { for _, o := range reads[:i] {
if o.tr == nil || o.at.group != r.at.group || alternatives(o.at, r.at) || o.tr.head.family() != r.tr.head.family() { if o.tr == nil || o.at.group != r.at.group || alternatives(o.at, r.at) || o.tr.headTable.family() != r.tr.headTable.family() {
continue continue
} }
if err := checkFamilyPair(o, r); err != nil { if err := checkFamilyPair(o, r); err != nil {
@@ -127,7 +127,7 @@ func replayPairs(reads []pathRead) error {
} }
func conflict(r pathRead, err error) error { func conflict(r pathRead, err error) error {
return fmt.Errorf("%s: %w; select the same rows in every path into the family, or draw them apart with a drawGroup", r.at.route.spelled(r.a.name), err) return fmt.Errorf("%s: %w; select the same rows in every path into the family, or draw them apart with a drawGroup", r.at.route.spelled(r.a.spelling), err)
} }
// replay pins the read's rows into d, where they agree with the rows pinned before. // replay pins the read's rows into d, where they agree with the rows pinned before.
@@ -149,19 +149,19 @@ func checkFamilyPair(a, b pathRead) error {
for _, pair := range [][2]pathRead{{a, b}, {b, a}} { for _, pair := range [][2]pathRead{{a, b}, {b, a}} {
x, y := pair[0], pair[1] x, y := pair[0], pair[1]
if len(x.a.tail) == 0 && len(y.a.tail) > 0 { if len(x.a.tail) == 0 && len(y.a.tail) > 0 {
return overlapError(x.at.route, x.a.name, y) return overlapError(x.at.route, x.a.spelling, y)
} }
if drawn := x.tr.drawnOf(&y.tr.pins); drawn != nil { if drawn := x.tr.drawnOf(&y.tr.pins); drawn != nil {
if s, ok := y.tr.selected(x.tr.head); ok && len(x.tr.sels) == 0 { if s, ok := y.tr.selected(x.tr.headTable); ok && len(x.tr.sels) == 0 {
tail := x.a.tail tail := x.a.tail
if s.t != x.tr.head { if s.t != x.tr.headTable {
tail = append([]string{x.tr.head.category}, tail...) tail = append([]string{x.tr.headTable.category}, tail...)
} }
return fmt.Errorf("%s draws %s, which %s selects a row of; write {%s.%s}, or draw them apart with a drawGroup", return fmt.Errorf("%s draws %s, which %s selects a row of; write {%s.%s}, or draw them apart with a drawGroup",
x.at.route.spelled(x.a.name), drawn.category, y.at.route.spelled(y.a.name), s.spelling, joinSegments(tail)) x.at.route.spelled(x.a.spelling), drawn.category, y.at.route.spelled(y.a.spelling), s.spelling, joinSegments(tail))
} }
return fmt.Errorf("%s draws %s, which %s selects a row of; select that row in both, or draw them apart with a drawGroup", return fmt.Errorf("%s draws %s, which %s selects a row of; select that row in both, or draw them apart with a drawGroup",
x.at.route.spelled(x.a.name), drawn.category, y.at.route.spelled(y.a.name)) x.at.route.spelled(x.a.spelling), drawn.category, y.at.route.spelled(y.a.spelling))
} }
} }
return nil return nil
@@ -219,4 +219,6 @@ func checkOwnFamily(t *template) error {
// alternatives reports whether two reads sit in different rows of one table: rows the walks pinned, // alternatives reports whether two reads sit in different rows of one table: rows the walks pinned,
// or rows of one whole draw. // or rows of one whole draw.
// docs/decisions.md#the-rows-of-a-table-are-alternatives // docs/decisions.md#the-rows-of-a-table-are-alternatives
func alternatives(a, b drawAt) bool { return a.pins.differs(&b.pins) || a.whole.differs(&b.whole) } func alternatives(a, b drawAt) bool {
return a.pins.differs(&b.pins) || a.wholePins.differs(&b.wholePins)
}
+4 -4
View File
@@ -99,7 +99,7 @@ func New(opts ...Option) (*Generator, error) {
} }
var sources []dataSource var sources []dataSource
if c.shipped { if c.shipped {
sources = append(sources, dataSource{fsys: shippedFS, root: "data"}) sources = append(sources, dataSource{fsys: shippedFS, dir: "data"})
} }
sources = append(sources, c.sources...) sources = append(sources, c.sources...)
if len(sources) == 0 { if len(sources) == 0 {
@@ -151,11 +151,11 @@ func paths(n node) []string {
} }
} }
return out return out
case *null: case *nullItem:
return []string{""} return []string{""}
case *table: case *table:
return tablePaths(n) return tablePaths(n)
case *column, *row: case *tableColumn, *tableRow:
return []string{""} return []string{""}
case *choice: case *choice:
out := []string{""} out := []string{""}
@@ -170,7 +170,7 @@ func paths(n node) []string {
// tablePaths is a table's columns, then each table linked to it under its name: the // tablePaths is a table's columns, then each table linked to it under its name: the
// direct descents, a step at a time. // direct descents, a step at a time.
func tablePaths(t *table) []string { func tablePaths(t *table) []string {
out := append([]string{""}, t.columns...) out := append([]string{""}, t.header...)
sort.Strings(out[1:]) sort.Strings(out[1:])
children := make([]string, 0, len(t.children)) children := make([]string, 0, len(t.children))
for name := range t.children { for name := range t.children {
+18 -18
View File
@@ -109,19 +109,19 @@ func parseFormat(format string) ([]formatToken, error) {
// the values of the operands it named (calc and the transforms name them). All must // the values of the operands it named (calc and the transforms name them). All must
// stay pure over (rng, emitted, args) so seeded output is reproducible; seq advances // stay pure over (rng, emitted, args) so seeded output is reproducible; seq advances
// per-session counter state, which is itself deterministic. arity is the exact arg // per-session counter state, which is itself deterministic. arity is the exact arg
// count, or -1 for variadic (then check does all the validation). // count, or -1 for variadic (then checkArgs does all the validation).
type builtin struct { type builtin struct {
arity int arity int
// prep parses validated args once, at compile time, into the closure expand calls. // prep parses validated args once, at compile time, into the closure expand calls.
prep func(args []string) callFn prep func(args []string) callFn
check func(fields map[string]node, args []string) error checkArgs func(fields map[string]node, args []string) error
// operands names the fields the call reads, which expand renders for it; nil // operands names the fields the call reads, which expand renders for it; nil
// for a builtin that reads none. // for a builtin that reads none.
operands func(args []string) []string operands func(args []string) []string
// number proves what a call prints, token its body: the bounds of its number and the // proveNumber proves what a call prints, token its body: the bounds of its number and the
// datatype of its text, prints or one inside it; nil for a builtin whose text is no number. // datatype of its text, prints or one inside it; nil for a builtin whose text is no number.
number func(token string, prints DataType, args []string) proven proveNumber func(token string, prints DataType, args []string) proven
prints DataType prints DataType
} }
// funcCall splits a "{token}" body shaped name(args) into its parts; ok is false // funcCall splits a "{token}" body shaped name(args) into its parts; ok is false
@@ -178,8 +178,8 @@ func checkFunc(tok formatToken, fields map[string]node) error {
if b.arity >= 0 && len(args) != b.arity { if b.arity >= 0 && len(args) != b.arity {
return fmt.Errorf("token {%s}: %s takes %d argument%s, got %d", body, name, b.arity, plural(b.arity), len(args)) return fmt.Errorf("token {%s}: %s takes %d argument%s, got %d", body, name, b.arity, plural(b.arity), len(args))
} }
if b.check != nil { if b.checkArgs != nil {
if err := b.check(fields, args); err != nil { if err := b.checkArgs(fields, args); err != nil {
return fmt.Errorf("token {%s}: %w", body, err) return fmt.Errorf("token {%s}: %w", body, err)
} }
} }
@@ -297,11 +297,11 @@ func checkNoRepeatedArm(body string, names []string) error {
// is what makes the arm a bound draw: its head is drawn once per expansion (see // is what makes the arm a bound draw: its head is drawn once per expansion (see
// compileOps). // compileOps).
type arm struct { type arm struct {
name string // as written, for messages spelling string // as written, for messages
key string key string
tail []string tail []string
levels []string // the key at each level the tail passes through, the head's first levels []string // the key at each level the tail passes through, the head's first
path string // key and tail, the one spelling every way of writing this read shares path string // key and tail, the one spelling every way of writing this read shares
} }
// splitArm splits one name into key and tail. refs maps a reference to what // splitArm splits one name into key and tail. refs maps a reference to what
@@ -314,13 +314,13 @@ func splitArm(name string, refs map[string]refBinding) arm {
if bound { if bound {
key = b.key key = b.key
} }
return arm{name: name, key: key, path: key} return arm{spelling: name, key: key, path: key}
} }
return pathArm(name, b.key, b.tail) return pathArm(name, b.key, b.tail)
} }
segs, err := splitPath(name) segs, err := splitPath(name)
if err != nil || len(segs) == 1 { if err != nil || len(segs) == 1 {
return arm{name: name, key: name, path: name} return arm{spelling: name, key: name, path: name}
} }
return pathArm(name, segs[0], segs[1:]) return pathArm(name, segs[0], segs[1:])
} }
@@ -330,7 +330,7 @@ func pathArm(name, key string, segs []string) arm {
for i := 0; i < len(segs)-1; i++ { for i := 0; i < len(segs)-1; i++ {
levels = append(levels, key+"."+strings.Join(segs[:i+1], ".")) levels = append(levels, key+"."+strings.Join(segs[:i+1], "."))
} }
return arm{name: name, key: key, tail: segs, levels: levels, path: key + "." + strings.Join(segs, ".")} return arm{spelling: name, key: key, tail: segs, levels: levels, path: key + "." + strings.Join(segs, ".")}
} }
// checkSegments rejects an unfinished path: "{a.}" and "{a..b}" each have a // checkSegments rejects an unfinished path: "{a.}" and "{a..b}" each have a
@@ -400,7 +400,7 @@ func (c *formatOps) holdName(a arm, label string) {
c.bound = map[string]string{} c.bound = map[string]string{}
} }
if _, named := c.bound[a.key]; !named { if _, named := c.bound[a.key]; !named {
c.bound[a.key] = a.name c.bound[a.key] = a.spelling
} }
} }
} }
@@ -420,7 +420,7 @@ func (c *formatOps) field(tok formatToken, refs map[string]refBinding) {
for i, name := range tok.names { for i, name := range tok.names {
arms[i] = splitArm(name, refs) arms[i] = splitArm(name, refs)
if len(arms[i].tail) > 0 { if len(arms[i].tail) > 0 {
c.holdName(arms[i], "token {"+arms[i].name+"}") c.holdName(arms[i], "token {"+arms[i].spelling+"}")
} }
} }
c.ops = append(c.ops, op{formatToken: tok, arms: arms}) c.ops = append(c.ops, op{formatToken: tok, arms: arms})
+11 -11
View File
@@ -51,7 +51,7 @@ type namedNode struct {
func contained(n node) []namedNode { func contained(n node) []namedNode {
switch n := n.(type) { switch n := n.(type) {
case *folder: case *folder:
return named(n.children) return namedNodes(n.children)
case *choice: case *choice:
out := make([]namedNode, len(n.items)) out := make([]namedNode, len(n.items))
for i, it := range n.items { for i, it := range n.items {
@@ -59,10 +59,10 @@ func contained(n node) []namedNode {
} }
return out return out
case *template: case *template:
return named(n.fields) return namedNodes(n.fields)
case *table: case *table:
return append([]namedNode{{node: n.format}}, named(n.fields)...) return append([]namedNode{{node: n.formatTemplate}}, namedNodes(n.fields)...)
case *column: case *tableColumn:
if len(n.t.cellTemplates) == 0 { if len(n.t.cellTemplates) == 0 {
return nil return nil
} }
@@ -78,7 +78,7 @@ func contained(n node) []namedNode {
} }
} }
func named(m map[string]node) []namedNode { func namedNodes(m map[string]node) []namedNode {
out := make([]namedNode, 0, len(m)) out := make([]namedNode, 0, len(m))
for _, name := range sortedNames(m) { for _, name := range sortedNames(m) {
out = append(out, namedNode{name: name, node: m[name]}) out = append(out, namedNode{name: name, node: m[name]})
@@ -133,7 +133,7 @@ func renderEdges(n node) []renderEdge {
return return
} }
for _, leaf := range pathLeaves(c, a.tail) { for _, leaf := range pathLeaves(c, a.tail) {
es = append(es, renderEdge{leaf, a.name, operand}) es = append(es, renderEdge{leaf, a.spelling, operand})
} }
} }
for _, o := range n.ops { for _, o := range n.ops {
@@ -146,13 +146,13 @@ func renderEdges(n node) []renderEdge {
} }
return es return es
case *table: case *table:
return []renderEdge{{to: n.format, label: "format"}} return []renderEdge{{to: n.formatTemplate, label: "format"}}
case *row: case *tableRow:
return []renderEdge{{to: n.t.format, label: "format"}} return []renderEdge{{to: n.t.formatTemplate, label: "format"}}
case *column: case *tableColumn:
var es []renderEdge var es []renderEdge
for _, c := range contained(n) { for _, c := range contained(n) {
es = append(es, renderEdge{to: c.node, label: n.t.columns[n.i]}) es = append(es, renderEdge{to: c.node, label: n.t.header[n.i]})
} }
return es return es
default: default:
+5 -5
View File
@@ -29,7 +29,7 @@ type draw struct {
func readField(s *session, t *template, held *hold, sc renderScope, a arm) draw { func readField(s *session, t *template, held *hold, sc renderScope, a arm) draw {
if !t.held[a.key] { if !t.held[a.key] {
if len(a.tail) > 0 { if len(a.tail) > 0 {
panic(fmt.Sprintf("fejkdata: %q reads a path into %q, which the expansion does not hold", a.name, a.key)) panic(fmt.Sprintf("fejkdata: %q reads a path into %q, which the expansion does not hold", a.spelling, a.key))
} }
return draw{text: render(s, t.head(a.key), sc)} return draw{text: render(s, t.head(a.key), sc)}
} }
@@ -58,8 +58,8 @@ func readHold(held *hold, sc renderScope, a arm) *hold {
// renderLeaf draws and renders what a read lands on: null on a null item, or on a column of one // renderLeaf draws and renders what a read lands on: null on a null item, or on a column of one
// reference alone whose read drew null. // reference alone whose read drew null.
func renderLeaf(s *session, n node, sc renderScope) draw { func renderLeaf(s *session, n node, sc renderScope) draw {
n = drawn(s, n) n = resolveChoice(s, n)
if _, isNull := n.(*null); isNull { if _, isNull := n.(*nullItem); isNull {
return draw{null: true} return draw{null: true}
} }
r := draw{text: render(s, n, sc)} r := draw{text: render(s, n, sc)}
@@ -69,10 +69,10 @@ func renderLeaf(s *session, n node, sc renderScope) draw {
return r return r
} }
// drawn resolves a choice to one variant, so a bound head is a concrete node the // resolveChoice resolves a choice to one variant, so a bound head is a concrete node the
// rest of the expansion shares. Nested choices unwrap too: a draw is one value, not // rest of the expansion shares. Nested choices unwrap too: a draw is one value, not
// another set to pick from. // another set to pick from.
func drawn(s *session, n node) node { func resolveChoice(s *session, n node) node {
for c, ok := n.(*choice); ok; c, ok = n.(*choice) { for c, ok := n.(*choice); ok; c, ok = n.(*choice) {
n = pick(s, c) n = pick(s, c)
} }
+1 -1
View File
@@ -766,6 +766,6 @@ func TestReadFieldPanicsOnAnUnheldPath(t *testing.T) {
t.Fatal("not a template") t.Fatal("not a template")
} }
mustPanic(t, "unheld arm with a path", func() { mustPanic(t, "unheld arm with a path", func() {
readField(engine(1).rand, tm, nil, renderScope{}, arm{name: "w.x", key: "w", tail: []string{"x"}, path: "w.x"}) readField(engine(1).rand, tm, nil, renderScope{}, arm{spelling: "w.x", key: "w", tail: []string{"x"}, path: "w.x"})
}) })
} }
+6 -6
View File
@@ -56,7 +56,7 @@ func heldNodes(t *template, head string, readers []reader) map[node]bool {
return held return held
} }
for _, r := range readers { for _, r := range readers {
if a := splitArm(r.name, t.refs); a.key == head { if a := splitArm(r.spelling, t.refs); a.key == head {
coverPath(t.head(head), a.tail, held) coverPath(t.head(head), a.tail, held)
} }
} }
@@ -181,7 +181,7 @@ func checkNoOverlap(ops []op, bound map[string]string) error {
for i, level := range names { for i, level := range names {
for _, path := range names[i+1:] { for _, path := range names[i+1:] {
if strings.HasPrefix(path.path, level.path+".") { if strings.HasPrefix(path.path, level.path+".") {
return fmt.Errorf("%s renders a level that {%s} reads a path into; name the fields you want instead", level.label, path.name) return fmt.Errorf("%s renders a level that {%s} reads a path into; name the fields you want instead", level.label, path.spelling)
} }
} }
} }
@@ -190,7 +190,7 @@ func checkNoOverlap(ops []op, bound map[string]string) error {
// reader is one way a format reaches a bound field: as written, by its one // reader is one way a format reaches a bound field: as written, by its one
// spelling, and how to name it. // spelling, and how to name it.
type reader struct{ name, path, label string } type reader struct{ spelling, path, label string }
// boundReaders lists every way a format reaches a bound sibling field, in the order the // boundReaders lists every way a format reaches a bound sibling field, in the order the
// format writes them. An operand renders its field, so it names a level exactly // format writes them. An operand renders its field, so it names a level exactly
@@ -200,12 +200,12 @@ func boundReaders(ops []op, bound map[string]string) []reader {
for _, o := range ops { for _, o := range ops {
for _, a := range o.operands { for _, a := range o.operands {
if _, isBound := bound[a.key]; isBound && !isRef(a.key) { if _, isBound := bound[a.key]; isBound && !isRef(a.key) {
names = append(names, reader{a.name, a.path, fmt.Sprintf("%s operand %q", o.fn, a.name)}) names = append(names, reader{a.spelling, a.path, fmt.Sprintf("%s operand %q", o.fn, a.spelling)})
} }
} }
for _, a := range o.arms { for _, a := range o.arms {
if _, isBound := bound[a.key]; isBound && !isRef(a.key) { if _, isBound := bound[a.key]; isBound && !isRef(a.key) {
names = append(names, reader{a.name, a.path, "token {" + a.name + "}"}) names = append(names, reader{a.spelling, a.path, "token {" + a.spelling + "}"})
} }
} }
} }
@@ -224,7 +224,7 @@ func checkNoRepeatedRead(c formatOps) error {
continue continue
} }
if count[a.key]++; count[a.key] > 1 { if count[a.key]++; count[a.key] > 1 {
return fmt.Errorf("token {%s} is repeated, and %s holds %q to one draw per expansion; write {%s} once", a.name, c.holder[a.key], a.key, a.name) return fmt.Errorf("token {%s} is repeated, and %s holds %q to one draw per expansion; write {%s} once", a.spelling, c.holder[a.key], a.key, a.spelling)
} }
} }
} }
+7 -7
View File
@@ -33,10 +33,10 @@ type choice struct {
func (*choice) isNode() {} func (*choice) isNode() {}
// null is a column's missing value, rendered ""; sized so two nulls are two map keys. // nullItem is a column's missing value, rendered ""; sized so two nulls are two map keys.
type null struct{ _ byte } type nullItem struct{ _ byte }
func (*null) isNode() {} func (*nullItem) isNode() {}
// template renders a format string, substituting {tokens} from fields. A bare // template renders a format string, substituting {tokens} from fields. A bare
// JSON string is a template with no fields. repeat (default 1) renders that format // JSON string is a template with no fields. repeat (default 1) renders that format
@@ -52,7 +52,7 @@ type template struct {
datatype DataType datatype DataType
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
record 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 table *table // the table whose format this is, whose columns are the fields
// Filled by `checkCells`, after the cell's own format has compiled: // Filled by `checkCells`, after the cell's own format has compiled:
@@ -185,7 +185,7 @@ func compileItem(v any, pos position) (node, error) {
if pos != inColumn { if pos != inColumn {
return nil, fmt.Errorf(`null is a record column's value; here it only renders "", so write ""`) return nil, fmt.Errorf(`null is a record column's value; here it only renders "", so write ""`)
} }
return &null{}, nil return &nullItem{}, nil
default: default:
return nil, fmt.Errorf("a template value must be a string, a list or an object, not %s", jsonKind(v)) return nil, fmt.Errorf("a template value must be a string, a list or an object, not %s", jsonKind(v))
} }
@@ -340,7 +340,7 @@ func compileTemplate(m map[string]any, pos position) (node, error) {
if err := checkNestedDrawGroup(fields, o.group); err != nil { if err := checkNestedDrawGroup(fields, o.group); err != nil {
return nil, err return nil, err
} }
t := &template{format: o.format, tokens: toks, fields: fields, repeat: o.repeat, separator: o.separator, datatype: o.datatype, drawGroup: o.group, record: fieldPos == inColumn} t := &template{format: o.format, tokens: toks, fields: fields, repeat: o.repeat, separator: o.separator, datatype: o.datatype, drawGroup: o.group, isRecord: fieldPos == inColumn}
if err := t.compileRefFree(); err != nil { if err := t.compileRefFree(); err != nil {
return nil, err return nil, err
} }
@@ -526,7 +526,7 @@ func checkPathNames(path string) error {
if err != nil { if err != nil {
return err return err
} }
for _, seg := range names(segs) { for _, seg := range nameSegments(segs) {
if err := checkName(seg); err != nil { if err := checkName(seg); err != nil {
return fmt.Errorf("path %w", err) return fmt.Errorf("path %w", err)
} }
+7 -7
View File
@@ -114,8 +114,8 @@ func hasSelector(segs []string) bool {
// selectorOf is the key or name a selector segment holds. // selectorOf is the key or name a selector segment holds.
func selectorOf(seg string) string { return seg[1 : len(seg)-1] } func selectorOf(seg string) string { return seg[1 : len(seg)-1] }
// names is the segments of a path that are names, its selectors left out. // nameSegments is the segments of a path that are names, its selectors left out.
func names(segs []string) []string { func nameSegments(segs []string) []string {
out := segs[:0:0] out := segs[:0:0]
for _, s := range segs { for _, s := range segs {
if !isSelector(s) { if !isSelector(s) {
@@ -197,8 +197,8 @@ func walkPath(n node, tail []string, w pathWalk) (node, error) {
return walkPath(child, tail[1:], w) return walkPath(child, tail[1:], w)
case *choice: case *choice:
return walkChoice(n, tail, w) return walkChoice(n, tail, w)
case *column: case *tableColumn:
return nil, fmt.Errorf("no field %q: %q is a column, and a cell holds no fields", tail[0], n.t.columns[n.i]) return nil, fmt.Errorf("no field %q: %q is a column, and a cell holds no fields", tail[0], n.t.header[n.i])
} }
return nil, fmt.Errorf("no field %q", tail[0]) return nil, fmt.Errorf("no field %q", tail[0])
} }
@@ -268,7 +268,7 @@ func walkTable(t *table, tail []string, w pathWalk, descended bool) (node, error
case len(tail) == 0 && sel == "" && !descended: case len(tail) == 0 && sel == "" && !descended:
return walkPath(t, nil, w) return walkPath(t, nil, w)
case len(tail) == 0: case len(tail) == 0:
return walkPath(t.whole, nil, w) return walkPath(t.wholeRow, nil, w)
case child != nil: case child != nil:
return walkTable(child, tail[1:], w, true) return walkTable(child, tail[1:], w, true)
} }
@@ -293,7 +293,7 @@ func (t *table) step(tail []string) (column node, child *table, err error) {
return nil, nil, nil return nil, nil, nil
} }
if i, ok := t.col[tail[0]]; ok { if i, ok := t.col[tail[0]]; ok {
return t.fields[t.columns[i]], nil, nil return t.fields[t.header[i]], nil, nil
} }
if child = t.descendant(tail[0]); child == nil { if child = t.descendant(tail[0]); child == nil {
return nil, nil, fmt.Errorf("no column or linked table %q in %s", tail[0], t.category) return nil, nil, fmt.Errorf("no column or linked table %q in %s", tail[0], t.category)
@@ -327,7 +327,7 @@ func (d *pathDraws) variant(c *choice, rest []string) node {
key := d.a.levels[len(d.a.tail)-len(rest)] key := d.a.levels[len(d.a.tail)-len(rest)]
n, drew := d.held.variant[key] n, drew := d.held.variant[key]
if !drew { if !drew {
n = drawn(d.s, c) n = resolveChoice(d.s, c)
if d.held.variant == nil { if d.held.variant == nil {
d.held.variant = map[string]node{} d.held.variant = map[string]node{}
} }
+8 -8
View File
@@ -15,12 +15,12 @@ type tablePin struct {
// pinSet is the table rows fixed so far. // pinSet is the table rows fixed so far.
type pinSet struct { type pinSet struct {
inline [8]tablePin // sized so a render over a country's five-deep geo tree stays off the heap inline [8]tablePin // sized so a render over a country's five-deep geo tree stays off the heap
n int used int
spill map[*table]int spill map[*table]int
} }
func (p *pinSet) pinned(t *table) (int, bool) { func (p *pinSet) pinned(t *table) (int, bool) {
for _, q := range p.inline[:p.n] { for _, q := range p.inline[:p.used] {
if q.t == t { if q.t == t {
return q.row, true return q.row, true
} }
@@ -39,9 +39,9 @@ func (p *pinSet) mustRow(t *table) int {
} }
func (p *pinSet) add(t *table, r int) { func (p *pinSet) add(t *table, r int) {
if p.n < len(p.inline) { if p.used < len(p.inline) {
p.inline[p.n] = tablePin{t, r} p.inline[p.used] = tablePin{t, r}
p.n++ p.used++
return return
} }
if p.spill == nil { if p.spill == nil {
@@ -73,7 +73,7 @@ func (p *pinSet) nearestPinned(t *table) *table {
// each calls fn for every pinned row, in pin order, the spilled ones by path. // 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)) { func (p *pinSet) each(fn func(t *table, r int)) {
for _, q := range p.inline[:p.n] { for _, q := range p.inline[:p.used] {
fn(q.t, q.row) fn(q.t, q.row)
} }
spilled := make([]*table, 0, len(p.spill)) spilled := make([]*table, 0, len(p.spill))
@@ -164,8 +164,8 @@ func (p *pinSet) differs(q *pinSet) bool {
return found return found
} }
// key spells the set for a map, by the tables' identities in path order. // mapKey spells the set for a map, by the tables' identities in path order.
func (p *pinSet) key() string { func (p *pinSet) mapKey() string {
var pins []tablePin var pins []tablePin
p.each(func(t *table, r int) { pins = append(pins, tablePin{t, r}) }) p.each(func(t *table, r int) { pins = append(pins, tablePin{t, r}) })
sort.Slice(pins, func(i, j int) bool { return pins[i].t.path < pins[j].t.path }) sort.Slice(pins, func(i, j int) bool { return pins[i].t.path < pins[j].t.path })
+16 -16
View File
@@ -141,13 +141,13 @@ func (f *Generator) FakeRecord(path string) (*Record, error) {
} }
shape := f.recordShapeOf(n) shape := f.recordShapeOf(n)
if errors.Is(shape.err, ErrNoColumns) { if errors.Is(shape.err, ErrNoColumns) {
ns := names(segments) ns := nameSegments(segments)
return nil, fmt.Errorf(`fejkdata: %s %w; render it as a column of one: {"format":"","%s":"{/%s}"}`, path, shape.err, ns[len(ns)-1], path) return nil, fmt.Errorf(`fejkdata: %s %w; render it as a column of one: {"format":"","%s":"{/%s}"}`, path, shape.err, ns[len(ns)-1], path)
} }
if shape.err != nil { if shape.err != nil {
return nil, fmt.Errorf("fejkdata: %s %w", path, shape.err) return nil, fmt.Errorf("fejkdata: %s %w", path, shape.err)
} }
return renderRecord(f.rand, shape.t, shape.columns, sc), nil return renderRecord(f.rand, shape.template, shape.columns, sc), nil
} }
// tableRecord walks a path's tail from a table to the table whose row is the record, // tableRecord walks a path's tail from a table to the table whose row is the record,
@@ -161,10 +161,10 @@ func tableRecord(s *session, t *table, tail []string, sc renderScope) (node, err
case *table: case *table:
n.drawIn(s, &sc.hold().pins) n.drawIn(s, &sc.hold().pins)
return n, nil return n, nil
case *row: case *tableRow:
return n.t, nil return n.t, nil
case *column: case *tableColumn:
return nil, fmt.Errorf("descends into %q, a column; a record is a table's row", n.t.columns[n.i]) return nil, fmt.Errorf("descends into %q, a column; a record is a table's row", n.t.header[n.i])
} }
return n, nil return n, nil
} }
@@ -172,9 +172,9 @@ func tableRecord(s *session, t *table, tail []string, sc renderScope) (node, err
// recordShape is what recordOf settled about a node: the template to project, its // recordShape is what recordOf settled about a node: the template to project, its
// columns, or why it is not a record. // columns, or why it is not a record.
type recordShape struct { type recordShape struct {
t *template template *template
columns []Column columns []Column
err error err error
} }
// recordShapeOf fences a node once and remembers the answer. Callers hold the // recordShapeOf fences a node once and remembers the answer. Callers hold the
@@ -184,7 +184,7 @@ func (f *Generator) recordShapeOf(n node) recordShape {
return shape return shape
} }
t, columns, err := recordOf(n) t, columns, err := recordOf(n)
shape := recordShape{t: t, columns: columns, err: err} shape := recordShape{template: t, columns: columns, err: err}
if f.records == nil { if f.records == nil {
f.records = map[node]recordShape{} f.records = map[node]recordShape{}
} }
@@ -195,9 +195,9 @@ func (f *Generator) recordShapeOf(n node) recordShape {
// RecordTemplate is an inline record compiled, referenced and validated once, // RecordTemplate is an inline record compiled, referenced and validated once,
// ready to render many times with [RecordTemplate.Fake]. // ready to render many times with [RecordTemplate.Fake].
type RecordTemplate struct { type RecordTemplate struct {
g *Generator g *Generator
t *template template *template
columns []Column columns []Column
} }
// Fake renders the record with one draw. // Fake renders the record with one draw.
@@ -205,7 +205,7 @@ func (t *RecordTemplate) Fake() *Record {
t.g.mu.Lock() t.g.mu.Lock()
defer t.g.mu.Unlock() defer t.g.mu.Unlock()
set := eagerHoldSet() set := eagerHoldSet()
return renderRecord(t.g.rand, t.t, t.columns, renderScope{set: &set}) return renderRecord(t.g.rand, t.template, t.columns, renderScope{set: &set})
} }
// NewRecordTemplate compiles an inline record — a JSON object with a format and // NewRecordTemplate compiles an inline record — a JSON object with a format and
@@ -219,7 +219,7 @@ func (f *Generator) NewRecordTemplate(input string) (*RecordTemplate, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("fejkdata: an inline record %w", err) return nil, fmt.Errorf("fejkdata: an inline record %w", err)
} }
return &RecordTemplate{g: f, t: tm, columns: columns}, nil return &RecordTemplate{g: f, template: tm, columns: columns}, nil
} }
// FakeRecordTemplate compiles and renders an inline record in one call. // FakeRecordTemplate compiles and renders an inline record in one call.
@@ -239,7 +239,7 @@ var ErrNoColumns = errors.New("has no fields, so no columns")
// the template, fixed for every draw the caller goes on to make. // the template, fixed for every draw the caller goes on to make.
func recordOf(n node) (*template, []Column, error) { func recordOf(n node) (*template, []Column, error) {
if tb, isTable := n.(*table); isTable { if tb, isTable := n.(*table); isTable {
n = tb.format n = tb.formatTemplate
} }
t, ok := n.(*template) t, ok := n.(*template)
if !ok { if !ok {
@@ -249,7 +249,7 @@ func recordOf(n node) (*template, []Column, error) {
if len(names) == 0 { if len(names) == 0 {
return nil, nil, ErrNoColumns return nil, nil, ErrNoColumns
} }
if !t.record { if !t.isRecord {
return nil, nil, fmt.Errorf("carries repeat %d, which composes its format into one string; a record projects columns instead — drop the repeat and render the record again for more rows", t.repeat) return nil, nil, fmt.Errorf("carries repeat %d, which composes its format into one string; a record projects columns instead — drop the repeat and render the record again for more rows", t.repeat)
} }
columns := make([]Column, len(names)) columns := make([]Column, len(names))
+1 -1
View File
@@ -136,7 +136,7 @@ func columnReadOf(t *template) *columnRead {
} }
a := splitArm(name, t.refs) a := splitArm(name, t.refs)
target, isTemplate := t.head(a.key).(*template) target, isTemplate := t.head(a.key).(*template)
if !isTemplate || !target.record || len(a.tail) != 1 { if !isTemplate || !target.isRecord || len(a.tail) != 1 {
return nil return nil
} }
return &columnRead{a: a, column: target.fields[a.tail[0]]} return &columnRead{a: a, column: target.fields[a.tail[0]]}
+6 -6
View File
@@ -58,15 +58,15 @@ func render(s *session, n node, sc renderScope) string {
switch n := n.(type) { switch n := n.(type) {
case *choice: case *choice:
return render(s, pick(s, n), sc) return render(s, pick(s, n), sc)
case *null: case *nullItem:
return "" return ""
case *table: case *table:
sc.t, sc.row = n, n.draw(s) sc.t, sc.row = n, n.drawRow(s)
return expand(s, n.format, sc) return expand(s, n.formatTemplate, sc)
case *row: case *tableRow:
sc.t, sc.row = n.t, sc.hold().pins.mustRow(n.t) sc.t, sc.row = n.t, sc.hold().pins.mustRow(n.t)
return expand(s, n.t.format, sc) return expand(s, n.t.formatTemplate, sc)
case *column: case *tableColumn:
if sc.t != n.t { if sc.t != n.t {
sc.t, sc.row = n.t, sc.hold().pins.mustRow(n.t) sc.t, sc.row = n.t, sc.hold().pins.mustRow(n.t)
} }
+5 -5
View File
@@ -44,8 +44,8 @@ func shippedShape(f *Generator) string {
case *choice: case *choice:
facts[prefix] = readsFact(n) facts[prefix] = readsFact(n)
case *table: case *table:
facts[prefix] = "\tformat " + strconv.Quote(n.format.format) + tableFacts(n) + readsFact(n) facts[prefix] = "\tformat " + strconv.Quote(n.formatTemplate.format) + tableFacts(n) + readsFact(n)
for _, name := range n.columns { for _, name := range n.header {
facts[join(prefix, name)] = "\tstring" facts[join(prefix, name)] = "\tstring"
} }
case *template: case *template:
@@ -77,9 +77,9 @@ func tableFacts(t *table) string {
for _, o := range []struct { for _, o := range []struct {
name string name string
col int col int
}{{"key", t.key}, {"name", t.name}, {"weight", t.weight}, {"parent", t.parent}} { }{{"key", t.keyCol}, {"name", t.nameCol}, {"weight", t.weightCol}, {"parent", t.parentCol}} {
if o.col >= 0 { if o.col >= 0 {
b.WriteString("\t" + o.name + " " + t.columns[o.col]) b.WriteString("\t" + o.name + " " + t.header[o.col])
} }
} }
return b.String() return b.String()
@@ -96,7 +96,7 @@ func readsFact(n node) string {
collect(it) collect(it)
} }
case *table: case *table:
collect(n.format) collect(n.formatTemplate)
for _, cell := range n.cellTemplates { for _, cell := range n.cellTemplates {
collect(cell) collect(cell)
} }
+25 -25
View File
@@ -49,8 +49,8 @@ func (f *Generator) structShapeOf(t reflect.Type) (*structShape, error) {
if label == "" { if label == "" {
label = "struct" label = "struct"
} }
sc := &structCompile{root: f.categories, visiting: map[reflect.Type]bool{}, structs: maxStructs} sc := &structCompile{categories: f.categories, visiting: map[reflect.Type]bool{}, structs: maxStructs}
shape, err := sc.record(t, label) shape, err := sc.compileShape(t, label)
if err == nil && shape.empty() { if err == nil && shape.empty() {
err = fmt.Errorf("%s has no fake tags, so nothing to fill", t) err = fmt.Errorf("%s has no fake tags, so nothing to fill", t)
} }
@@ -64,16 +64,16 @@ func (f *Generator) structShapeOf(t reflect.Type) (*structShape, error) {
// structShape is a struct type compiled to fill: its tagged fields as one record, the field // structShape is a struct type compiled to fill: its tagged fields as one record, the field
// index path each column fills, and the struct fields carrying tags of their own. // index path each column fills, and the struct fields carrying tags of their own.
type structShape struct { type structShape struct {
record *template record *template
columns []Column columns []Column
fields [][]int fieldIndexes [][]int
nested []nestedStruct nested []nestedStruct
} }
// nestedStruct is a named struct field, or a pointer to one, filled as a record of its own. // nestedStruct is a named struct field, or a pointer to one, filled as a record of its own.
type nestedStruct struct { type nestedStruct struct {
index []int fieldIndex []int
shape *structShape shape *structShape
} }
func (s *structShape) empty() bool { return s.record == nil && len(s.nested) == 0 } func (s *structShape) empty() bool { return s.record == nil && len(s.nested) == 0 }
@@ -82,9 +82,9 @@ func (s *structShape) empty() bool { return s.record == nil && len(s.nested) ==
// loaded tree, the types compiling or embedded above, so a pointer back to one is left alone // loaded tree, the types compiling or embedded above, so a pointer back to one is left alone
// rather than filled without end, and how many more structs it may walk. // rather than filled without end, and how many more structs it may walk.
type structCompile struct { type structCompile struct {
root map[string]node categories map[string]node
visiting map[reflect.Type]bool visiting map[reflect.Type]bool
structs int structs int
} }
// structFields gathers what one struct type fills: its tagged fields, those its embedded // structFields gathers what one struct type fills: its tagged fields, those its embedded
@@ -92,24 +92,24 @@ type structCompile struct {
// records. // records.
type structFields struct { type structFields struct {
*structCompile *structCompile
t reflect.Type typ reflect.Type
label string label string
tags map[string]any tags map[string]any
shape *structShape shape *structShape
} }
func (sc *structCompile) record(t reflect.Type, label string) (*structShape, error) { func (sc *structCompile) compileShape(t reflect.Type, label string) (*structShape, error) {
if err := sc.spend(label); err != nil { if err := sc.spend(label); err != nil {
return nil, err return nil, err
} }
sc.visiting[t] = true sc.visiting[t] = true
defer delete(sc.visiting, t) defer delete(sc.visiting, t)
c := &structFields{structCompile: sc, t: t, label: label, tags: map[string]any{}, shape: &structShape{}} c := &structFields{structCompile: sc, typ: t, label: label, tags: map[string]any{}, shape: &structShape{}}
if err := c.gatherFields(t, nil); err != nil { if err := c.gatherFields(t, nil); err != nil {
return nil, err return nil, err
} }
if len(c.tags) > 0 { if len(c.tags) > 0 {
if err := c.shape.compileRecord(sc.root, t, label, c.tags); err != nil { if err := c.shape.compileRecord(sc.categories, t, label, c.tags); err != nil {
return nil, err return nil, err
} }
} }
@@ -169,8 +169,8 @@ func structOf(t reflect.Type) reflect.Type {
// addTag adds a tagged field's tag to the record, refusing one another field hides. // addTag adds a tagged field's tag to the record, refusing one another field hides.
func (c *structFields) addTag(sf reflect.StructField, tag string) error { func (c *structFields) addTag(sf reflect.StructField, tag string) error {
if visible, ok := c.t.FieldByName(sf.Name); !ok || !slices.Equal(visible.Index, sf.Index) { if visible, ok := c.typ.FieldByName(sf.Name); !ok || !slices.Equal(visible.Index, sf.Index) {
return fmt.Errorf("%s.%s: hidden by another field named %s, so its fake tag cannot fill it; rename one", c.label, fieldPath(c.t, sf.Index), sf.Name) return fmt.Errorf("%s.%s: hidden by another field named %s, so its fake tag cannot fill it; rename one", c.label, fieldPath(c.typ, sf.Index), sf.Name)
} }
v, err := tagValue(sf, tag) v, err := tagValue(sf, tag)
if err != nil { if err != nil {
@@ -190,7 +190,7 @@ func fieldPath(t reflect.Type, index []int) string {
} }
func (c *structFields) embed(sf reflect.StructField, elem reflect.Type) error { func (c *structFields) embed(sf reflect.StructField, elem reflect.Type) error {
if err := c.spend(c.label + "." + fieldPath(c.t, sf.Index)); err != nil { if err := c.spend(c.label + "." + fieldPath(c.typ, sf.Index)); err != nil {
return err return err
} }
c.visiting[elem] = true c.visiting[elem] = true
@@ -200,13 +200,13 @@ func (c *structFields) embed(sf reflect.StructField, elem reflect.Type) error {
return err return err
} }
if sf.Type.Kind() == reflect.Pointer && !sf.IsExported() && (len(c.tags) > tags || len(c.shape.nested) > nested) { if sf.Type.Kind() == reflect.Pointer && !sf.IsExported() && (len(c.tags) > tags || len(c.shape.nested) > nested) {
return fmt.Errorf("%s.%s: an unexported embedded pointer field cannot be set, so the tags beneath it cannot fill; embed %s by value", c.label, fieldPath(c.t, sf.Index), elem) return fmt.Errorf("%s.%s: an unexported embedded pointer field cannot be set, so the tags beneath it cannot fill; embed %s by value", c.label, fieldPath(c.typ, sf.Index), elem)
} }
return nil return nil
} }
func (c *structFields) nest(sf reflect.StructField, elem reflect.Type) error { func (c *structFields) nest(sf reflect.StructField, elem reflect.Type) error {
nested, err := c.record(elem, c.label+"."+sf.Name) nested, err := c.compileShape(elem, c.label+"."+sf.Name)
if err != nil || nested.empty() { if err != nil || nested.empty() {
return err return err
} }
@@ -266,13 +266,13 @@ func (s *structShape) compileRecord(root map[string]node, t reflect.Type, label
return fmt.Errorf("%s: %w", label, err) return fmt.Errorf("%s: %w", label, err)
} }
proof := &valueProof{} proof := &valueProof{}
s.fields = make([][]int, len(columns)) s.fieldIndexes = make([][]int, len(columns))
for i, c := range columns { for i, c := range columns {
sf, _ := t.FieldByName(c.Name) sf, _ := t.FieldByName(c.Name)
if err := proof.checkField(label+"."+c.Name, sf.Type, record.fields[c.Name]); err != nil { if err := proof.checkField(label+"."+c.Name, sf.Type, record.fields[c.Name]); err != nil {
return err return err
} }
s.fields[i] = sf.Index s.fieldIndexes[i] = sf.Index
} }
s.record, s.columns = record, columns s.record, s.columns = record, columns
return nil return nil
@@ -327,7 +327,7 @@ func (p *valueProof) checkField(label string, ft reflect.Type, column node) erro
elem := ft elem := ft
if ft.Kind() == reflect.Pointer { if ft.Kind() == reflect.Pointer {
elem = ft.Elem() elem = ft.Elem()
} else if p.proveColumn(column).null { } else if p.proveColumn(column).nullable {
return fmt.Errorf("%s: its tag can draw null, which %s cannot hold; make it *%s", label, ft, ft) return fmt.Errorf("%s: its tag can draw null, which %s cannot hold; make it *%s", label, ft, ft)
} }
kind := columnKinds[elem.Kind()] kind := columnKinds[elem.Kind()]
@@ -351,11 +351,11 @@ func (s *structShape) fill(sess *session, v reflect.Value) {
if s.record != nil { if s.record != nil {
set := eagerHoldSet() set := eagerHoldSet()
for i, c := range renderRecord(sess, s.record, s.columns, renderScope{set: &set}).columns { for i, c := range renderRecord(sess, s.record, s.columns, renderScope{set: &set}).columns {
setColumn(fieldAt(v, s.fields[i]), c) setColumn(fieldAt(v, s.fieldIndexes[i]), c)
} }
} }
for _, n := range s.nested { for _, n := range s.nested {
field := fieldAt(v, n.index) field := fieldAt(v, n.fieldIndex)
if field.Kind() == reflect.Pointer { if field.Kind() == reflect.Pointer {
if field.IsNil() { if field.IsNil() {
field.Set(reflect.New(field.Type().Elem())) field.Set(reflect.New(field.Type().Elem()))
+95 -95
View File
@@ -13,58 +13,58 @@ import (
// columns, each row is one draw, and the format renders the drawn row. A column is a // columns, each row is one draw, and the format renders the drawn row. A column is a
// cell of the row a render pinned; a cell carrying tokens compiles to a string node. // cell of the row a render pinned; a cell carrying tokens compiles to a string node.
type table struct { type table struct {
category string category string
path string // the category's path from the data root, which a selector is written at path string // the category's path from the data root, which a selector is written at
file string file string
format *template // fields are the column nodes formatTemplate *template // fields are the column nodes
columns []string header []string
col map[string]int col map[string]int
fields map[string]node // column nodes, the format's fields fields map[string]node // column nodes, the format's fields
whole *row // the pinned row rendered by the format wholeRow *tableRow // the pinned row rendered by the format
cells []string // rows × columns, flat cells []string // rows × columns, flat
cellTemplates map[int]*template cellTemplates map[int]*template
key int // key, name, weight and parent are column indexes, or -1 keyCol int // keyCol through parentCol are -1 where the option is absent
name int nameCol int
weight int weightCol int
parent int parentCol int
cum []float64 // cumulative weights, nil when uniform cum []float64 // cumulative weights, nil when uniform
byKey map[string]int byKey map[string]int
parentT *table parentT *table
children map[string]*table children map[string]*table
once sync.Once once sync.Once
index tableIndex index tableIndex
} }
// tableIndex is what selection and linked draws look up, built on the first draw // tableIndex is what selection and linked draws look up, built on the first draw
// that needs it. // that needs it.
type tableIndex struct { type tableIndex struct {
byName map[string][]int byName map[string][]int
children map[string][]int // rows by parent key rowsByParent map[string][]int
childCum map[string][]float64 // cumulative weights per parent key, nil when uniform childCum map[string][]float64 // cumulative weights per parent key, nil when uniform
} }
func (*table) isNode() {} func (*table) isNode() {}
// column is one column of a table, rendered as the cell of the row the render pinned. // tableColumn is one column of a table, rendered as the cell of the row the render pinned.
type column struct { type tableColumn struct {
t *table t *table
i int i int
} }
func (*column) isNode() {} func (*tableColumn) isNode() {}
// row is the row of a table the render pinned, rendered by the table's format. // tableRow is the row of a table the render pinned, rendered by the table's format.
type row struct{ t *table } type tableRow struct{ t *table }
func (*row) isNode() {} func (*tableRow) isNode() {}
func (t *table) rowCount() int { return len(t.cells) / len(t.columns) } func (t *table) rowCount() int { return len(t.cells) / len(t.header) }
func (t *table) cell(row, col int) string { return t.cells[row*len(t.columns)+col] } func (t *table) cell(row, col int) string { return t.cells[row*len(t.header)+col] }
// cellTemplate is what a cell renders: its compiled template where it carries tokens, // cellTemplate is what a cell renders: its compiled template where it carries tokens,
// else nil for its text. // else nil for its text.
func (t *table) cellTemplate(row, col int) *template { return t.cellTemplates[row*len(t.columns)+col] } func (t *table) cellTemplate(row, col int) *template { return t.cellTemplates[row*len(t.header)+col] }
// tableOptions are the keys a table object takes; every other key is refused. // tableOptions are the keys a table object takes; every other key is refused.
var tableOptions = []string{"format", "key", "name", "parent", "rows", "weight"} var tableOptions = []string{"format", "key", "name", "parent", "rows", "weight"}
@@ -93,7 +93,7 @@ func compileTable(m map[string]any, category string, files *categoryFiles) (*tab
if err != nil { if err != nil {
return nil, err return nil, err
} }
t := &table{category: category, file: o.rows, key: -1, name: -1, weight: -1, parent: -1} t := &table{category: category, file: o.rows, keyCol: -1, nameCol: -1, weightCol: -1, parentCol: -1}
if err := t.parseRows(data); err != nil { if err := t.parseRows(data); err != nil {
return nil, fmt.Errorf("%s: %w", o.rows, err) return nil, fmt.Errorf("%s: %w", o.rows, err)
} }
@@ -110,7 +110,7 @@ func compileTable(m map[string]any, category string, files *categoryFiles) (*tab
} }
type tableOptionValues struct { type tableOptionValues struct {
format, key, name, parent, rows, weight string format, keyColumn, nameColumn, parentColumn, rows, weightColumn string
} }
func readTableOptions(m map[string]any) (tableOptionValues, error) { func readTableOptions(m map[string]any) (tableOptionValues, error) {
@@ -120,7 +120,7 @@ func readTableOptions(m map[string]any) (tableOptionValues, error) {
return o, fmt.Errorf("a table takes %s; %q is none of them", strings.Join(tableOptions, ", "), k) return o, fmt.Errorf("a table takes %s; %q is none of them", strings.Join(tableOptions, ", "), k)
} }
} }
for k, into := range map[string]*string{"format": &o.format, "key": &o.key, "name": &o.name, "parent": &o.parent, "rows": &o.rows, "weight": &o.weight} { for k, into := range map[string]*string{"format": &o.format, "key": &o.keyColumn, "name": &o.nameColumn, "parent": &o.parentColumn, "rows": &o.rows, "weight": &o.weightColumn} {
v, ok := m[k] v, ok := m[k]
if !ok { if !ok {
continue continue
@@ -140,8 +140,8 @@ func readTableOptions(m map[string]any) (tableOptionValues, error) {
if !strings.HasSuffix(o.rows, ".tsv") || strings.Contains(o.rows, "/") { if !strings.HasSuffix(o.rows, ".tsv") || strings.Contains(o.rows, "/") {
return o, fmt.Errorf("rows names a .tsv file beside the category, not %q", o.rows) return o, fmt.Errorf("rows names a .tsv file beside the category, not %q", o.rows)
} }
if o.key != "" && o.key == o.name { if o.keyColumn != "" && o.keyColumn == o.nameColumn {
return o, fmt.Errorf("name names the key column %q, which a selector already reads; drop it", o.name) return o, fmt.Errorf("name names the key column %q, which a selector already reads; drop it", o.nameColumn)
} }
return o, nil return o, nil
} }
@@ -155,10 +155,10 @@ func (t *table) parseRows(data string) error {
if header == "" { if header == "" {
return fmt.Errorf("has no header line naming its columns") return fmt.Errorf("has no header line naming its columns")
} }
t.columns = strings.Split(header, "\t") t.header = strings.Split(header, "\t")
t.col = make(map[string]int, len(t.columns)) t.col = make(map[string]int, len(t.header))
t.fields = make(map[string]node, len(t.columns)) t.fields = make(map[string]node, len(t.header))
for i, name := range t.columns { for i, name := range t.header {
if err := checkName(name); err != nil { if err := checkName(name); err != nil {
return fmt.Errorf("column %w", err) return fmt.Errorf("column %w", err)
} }
@@ -166,12 +166,12 @@ func (t *table) parseRows(data string) error {
return fmt.Errorf("column %q is named twice", name) return fmt.Errorf("column %q is named twice", name)
} }
t.col[name] = i t.col[name] = i
t.fields[name] = &column{t, i} t.fields[name] = &tableColumn{t, i}
} }
if header == data { if header == data {
return fmt.Errorf("has no rows below its header; a table is at least two rows") return fmt.Errorf("has no rows below its header; a table is at least two rows")
} }
m := len(t.columns) m := len(t.header)
t.cells = make([]string, 0, m*(strings.Count(rest, "\n")+1)) t.cells = make([]string, 0, m*(strings.Count(rest, "\n")+1))
for line := 2; ; line++ { for line := 2; ; line++ {
row, more, found := strings.Cut(rest, "\n") row, more, found := strings.Cut(rest, "\n")
@@ -195,7 +195,7 @@ func (t *table) appendRow(row string, line int) error {
if row == "" { if row == "" {
return fmt.Errorf("line %d is empty; every line below the header is a row", line) return fmt.Errorf("line %d is empty; every line below the header is a row", line)
} }
m := len(t.columns) m := len(t.header)
for n := 0; n < m; n++ { for n := 0; n < m; n++ {
cell, next, tab := strings.Cut(row, "\t") cell, next, tab := strings.Cut(row, "\t")
if !tab && n < m-1 || tab && n == m-1 { if !tab && n < m-1 || tab && n == m-1 {
@@ -213,17 +213,17 @@ func (t *table) bindOptions(o tableOptionValues) error {
for _, opt := range []struct { for _, opt := range []struct {
name, value string name, value string
into *int into *int
}{{"key", o.key, &t.key}, {"name", o.name, &t.name}, {"parent", o.parent, &t.parent}, {"weight", o.weight, &t.weight}} { }{{"key", o.keyColumn, &t.keyCol}, {"name", o.nameColumn, &t.nameCol}, {"parent", o.parentColumn, &t.parentCol}, {"weight", o.weightColumn, &t.weightCol}} {
if opt.value == "" { if opt.value == "" {
continue continue
} }
i, ok := t.col[opt.value] i, ok := t.col[opt.value]
if !ok { if !ok {
return fmt.Errorf("%s names no column %q of %s; the columns are %v", opt.name, opt.value, o.rows, t.columns) return fmt.Errorf("%s names no column %q of %s; the columns are %v", opt.name, opt.value, o.rows, t.header)
} }
*opt.into = i *opt.into = i
} }
if t.name >= 0 && t.key < 0 && t.parent < 0 { if t.nameCol >= 0 && t.keyCol < 0 && t.parentCol < 0 {
return fmt.Errorf("name selects a row as a key does, and lists the rows it matches by their keys, so it needs a key column, or a parent inside which each name is one row; add key") return fmt.Errorf("name selects a row as a key does, and lists the rows it matches by their keys, so it needs a key column, or a parent inside which each name is one row; add key")
} }
if err := t.indexKeys(); err != nil { if err := t.indexKeys(); err != nil {
@@ -237,14 +237,14 @@ func (t *table) bindOptions(o tableOptionValues) error {
// proveNamesInsideParent proves a name without a key names one row inside its parent. // proveNamesInsideParent proves a name without a key names one row inside its parent.
func (t *table) proveNamesInsideParent() error { func (t *table) proveNamesInsideParent() error {
if t.key >= 0 || t.name < 0 { if t.keyCol >= 0 || t.nameCol < 0 {
return nil return nil
} }
inside := make(map[string]int, t.rowCount()) inside := make(map[string]int, t.rowCount())
for r := 0; r < t.rowCount(); r++ { for r := 0; r < t.rowCount(); r++ {
k := t.cell(r, t.parent) + "\t" + t.cell(r, t.name) k := t.cell(r, t.parentCol) + "\t" + t.cell(r, t.nameCol)
if first, dup := inside[k]; dup { if first, dup := inside[k]; dup {
return fmt.Errorf("%s line %d: name %q repeats line %d inside %s %q; a name selects one row inside its parent; drop one, or add a key column", t.file, r+2, t.cell(r, t.name), first+2, t.columns[t.parent], t.cell(r, t.parent)) return fmt.Errorf("%s line %d: name %q repeats line %d inside %s %q; a name selects one row inside its parent; drop one, or add a key column", t.file, r+2, t.cell(r, t.nameCol), first+2, t.header[t.parentCol], t.cell(r, t.parentCol))
} }
inside[k] = r inside[k] = r
} }
@@ -253,12 +253,12 @@ func (t *table) proveNamesInsideParent() error {
// indexKeys proves every key names one row, and keeps the index a link is proved by. // indexKeys proves every key names one row, and keeps the index a link is proved by.
func (t *table) indexKeys() error { func (t *table) indexKeys() error {
if t.key < 0 { if t.keyCol < 0 {
return nil return nil
} }
t.byKey = make(map[string]int, t.rowCount()) t.byKey = make(map[string]int, t.rowCount())
for r := 0; r < t.rowCount(); r++ { for r := 0; r < t.rowCount(); r++ {
k := t.cell(r, t.key) k := t.cell(r, t.keyCol)
if k == "" { if k == "" {
return fmt.Errorf("%s line %d: the key is empty", t.file, r+2) return fmt.Errorf("%s line %d: the key is empty", t.file, r+2)
} }
@@ -267,8 +267,8 @@ func (t *table) indexKeys() error {
} }
t.byKey[k] = r t.byKey[k] = r
} }
for r := 0; r < t.rowCount() && t.name >= 0; r++ { for r := 0; r < t.rowCount() && t.nameCol >= 0; r++ {
if n := t.cell(r, t.name); t.byKey[n] != r { if n := t.cell(r, t.nameCol); t.byKey[n] != r {
if other, isKey := t.byKey[n]; isKey { if other, isKey := t.byKey[n]; isKey {
return fmt.Errorf("%s line %d: name %q is the key of line %d, which a selector reads first, so the name could never select this row", t.file, r+2, n, other+2) return fmt.Errorf("%s line %d: name %q is the key of line %d, which a selector reads first, so the name could never select this row", t.file, r+2, n, other+2)
} }
@@ -279,15 +279,15 @@ func (t *table) indexKeys() error {
// sumWeights proves every weight is a positive number and builds the cumulative table. // sumWeights proves every weight is a positive number and builds the cumulative table.
func (t *table) sumWeights() error { func (t *table) sumWeights() error {
if t.weight < 0 { if t.weightCol < 0 {
return nil return nil
} }
t.cum = make([]float64, t.rowCount()) t.cum = make([]float64, t.rowCount())
total := 0.0 total := 0.0
for r := range t.cum { for r := range t.cum {
w, err := strconv.ParseFloat(t.cell(r, t.weight), 64) w, err := strconv.ParseFloat(t.cell(r, t.weightCol), 64)
if err != nil || math.IsInf(w, 0) || math.IsNaN(w) || w <= 0 { if err != nil || math.IsInf(w, 0) || math.IsNaN(w) || w <= 0 {
return fmt.Errorf("%s line %d: weight %q is not a positive number", t.file, r+2, t.cell(r, t.weight)) return fmt.Errorf("%s line %d: weight %q is not a positive number", t.file, r+2, t.cell(r, t.weightCol))
} }
total += w total += w
t.cum[r] = total t.cum[r] = total
@@ -301,16 +301,16 @@ func (t *table) sumWeights() error {
// checkCells compiles every cell carrying a token, and fences what a selector reads. // checkCells compiles every cell carrying a token, and fences what a selector reads.
func (t *table) checkCells() error { func (t *table) checkCells() error {
for i, cell := range t.cells { for i, cell := range t.cells {
row, col := i/len(t.columns), i%len(t.columns) row, col := i/len(t.header), i%len(t.header)
if (col == t.key || col == t.name) && strings.ContainsAny(cell, inSelector) { if (col == t.keyCol || col == t.nameCol) && strings.ContainsAny(cell, inSelector) {
return fmt.Errorf("line %d: %s %q contains %q, which a selector cannot spell", row+2, t.columns[col], cell, cell[strings.IndexAny(cell, inSelector):][:1]) return fmt.Errorf("line %d: %s %q contains %q, which a selector cannot spell", row+2, t.header[col], cell, cell[strings.IndexAny(cell, inSelector):][:1])
} }
if strings.IndexByte(cell, '{') < 0 && strings.IndexByte(cell, '}') < 0 { if strings.IndexByte(cell, '{') < 0 && strings.IndexByte(cell, '}') < 0 {
continue continue
} }
n, err := compileString(cell) n, err := compileString(cell)
if err != nil { if err != nil {
return fmt.Errorf("line %d, %s: %w", row+2, t.columns[col], err) return fmt.Errorf("line %d, %s: %w", row+2, t.header[col], err)
} }
if t.cellTemplates == nil { if t.cellTemplates == nil {
t.cellTemplates = map[int]*template{} t.cellTemplates = map[int]*template{}
@@ -335,16 +335,16 @@ func (t *table) compileWhole(format string) error {
for _, name := range tok.names { for _, name := range tok.names {
a := splitArm(name, nil) a := splitArm(name, nil)
if _, ok := t.col[a.key]; !ok && !isRef(a.key) && a.key != "" { if _, ok := t.col[a.key]; !ok && !isRef(a.key) && a.key != "" {
return fmt.Errorf("format names no column %q of %s; the columns are %v", a.key, t.file, t.columns) return fmt.Errorf("format names no column %q of %s; the columns are %v", a.key, t.file, t.header)
} }
} }
} }
if err := checkTokens(toks, t.fields); err != nil { if err := checkTokens(toks, t.fields); err != nil {
return err return err
} }
t.format = &template{format: format, tokens: toks, fields: t.fields, repeat: 1, record: true, table: t} t.formatTemplate = &template{format: format, tokens: toks, fields: t.fields, repeat: 1, isRecord: true, table: t}
t.whole = &row{t} t.wholeRow = &tableRow{t}
return t.format.compileRefFree() return t.formatTemplate.compileRefFree()
} }
// setTablePaths gives every table the path a selector on it is written at, before a // setTablePaths gives every table the path a selector on it is written at, before a
@@ -379,7 +379,7 @@ func linkTables(root map[string]node) error {
return err return err
} }
case *table: case *table:
if n.parent < 0 { if n.parentCol < 0 {
continue continue
} }
if err := n.linkParent(path, children); err != nil { if err := n.linkParent(path, children); err != nil {
@@ -393,24 +393,24 @@ func linkTables(root map[string]node) error {
} }
func (t *table) linkParent(path string, siblings map[string]node) error { func (t *table) linkParent(path string, siblings map[string]node) error {
name := t.columns[t.parent] name := t.header[t.parentCol]
p, ok := siblings[name].(*table) p, ok := siblings[name].(*table)
switch { switch {
case siblings[name] == nil: case siblings[name] == nil:
return fmt.Errorf("parent %q names no table beside it", name) return fmt.Errorf("parent %q names no table beside it", name)
case !ok: case !ok:
return fmt.Errorf("parent %q is not a table; a link column reads a table's key", name) return fmt.Errorf("parent %q is not a table; a link column reads a table's key", name)
case p.key < 0: case p.keyCol < 0:
return fmt.Errorf("parent %q has no key column to link to", name) return fmt.Errorf("parent %q has no key column to link to", name)
} }
var ancestors []*table var ancestors []*table
for q, seen := p, map[*table]bool{t: true}; q != nil; q, _ = siblings[q.columns[q.parent]].(*table) { for q, seen := p, map[*table]bool{t: true}; q != nil; q, _ = siblings[q.header[q.parentCol]].(*table) {
if seen[q] { if seen[q] {
return fmt.Errorf("parent cycle: %s reaches itself through its parents", q.category) return fmt.Errorf("parent cycle: %s reaches itself through its parents", q.category)
} }
seen[q] = true seen[q] = true
ancestors = append(ancestors, q) ancestors = append(ancestors, q)
if q.parent < 0 { if q.parentCol < 0 {
break break
} }
} }
@@ -421,14 +421,14 @@ func (t *table) linkParent(path string, siblings map[string]node) error {
} }
linked := make(map[string]bool, p.rowCount()) linked := make(map[string]bool, p.rowCount())
for r := 0; r < t.rowCount(); r++ { for r := 0; r < t.rowCount(); r++ {
k := t.cell(r, t.parent) k := t.cell(r, t.parentCol)
if _, ok := p.byKey[k]; !ok { if _, ok := p.byKey[k]; !ok {
return fmt.Errorf("%s line %d: %s %q is no key of %s", t.file, r+2, name, k, p.file) return fmt.Errorf("%s line %d: %s %q is no key of %s", t.file, r+2, name, k, p.file)
} }
linked[k] = true linked[k] = true
} }
for r := 0; r < p.rowCount(); r++ { for r := 0; r < p.rowCount(); r++ {
if k := p.cell(r, p.key); !linked[k] { if k := p.cell(r, p.keyCol); !linked[k] {
return fmt.Errorf("%s links no row to %s %q; every %s row needs one, or drop line %d of %s", t.file, name, k, name, r+2, p.file) return fmt.Errorf("%s links no row to %s %q; every %s row needs one, or drop line %d of %s", t.file, name, k, name, r+2, p.file)
} }
} }
@@ -442,25 +442,25 @@ func (t *table) linkParent(path string, siblings map[string]node) error {
func (t *table) indexed() *tableIndex { func (t *table) indexed() *tableIndex {
t.once.Do(func() { t.once.Do(func() {
if t.name >= 0 { if t.nameCol >= 0 {
t.index.byName = make(map[string][]int, t.rowCount()) t.index.byName = make(map[string][]int, t.rowCount())
for r := 0; r < t.rowCount(); r++ { for r := 0; r < t.rowCount(); r++ {
n := t.cell(r, t.name) n := t.cell(r, t.nameCol)
t.index.byName[n] = append(t.index.byName[n], r) t.index.byName[n] = append(t.index.byName[n], r)
} }
} }
if t.parent >= 0 { if t.parentCol >= 0 {
t.index.children = map[string][]int{} t.index.rowsByParent = map[string][]int{}
for r := 0; r < t.rowCount(); r++ { for r := 0; r < t.rowCount(); r++ {
k := t.cell(r, t.parent) k := t.cell(r, t.parentCol)
t.index.children[k] = append(t.index.children[k], r) t.index.rowsByParent[k] = append(t.index.rowsByParent[k], r)
} }
if t.cum != nil { if t.cum != nil {
t.index.childCum = make(map[string][]float64, len(t.index.children)) t.index.childCum = make(map[string][]float64, len(t.index.rowsByParent))
for k, rows := range t.index.children { for k, rows := range t.index.rowsByParent {
cum, total := make([]float64, len(rows)), 0.0 cum, total := make([]float64, len(rows)), 0.0
for i, r := range rows { for i, r := range rows {
w, _ := strconv.ParseFloat(t.cell(r, t.weight), 64) // sumWeights proved it w, _ := strconv.ParseFloat(t.cell(r, t.weightCol), 64) // sumWeights proved it
total += w total += w
cum[i] = total cum[i] = total
} }
@@ -472,9 +472,9 @@ func (t *table) indexed() *tableIndex {
return &t.index return &t.index
} }
// draw picks a row over the whole table. The session is concrete rather than the rng // drawRow picks a row over the whole table. The session is concrete rather than the rng
// interface so that the walk that draws through it keeps its hold set off the heap. // interface so that the walk that draws through it keeps its hold set off the heap.
func (t *table) draw(s *session) int { func (t *table) drawRow(s *session) int {
if t.cum == nil { if t.cum == nil {
return s.IntN(t.rowCount()) return s.IntN(t.rowCount())
} }
@@ -489,8 +489,8 @@ func pickCum(s *session, cum []float64) int {
// drawUnder picks a row among those linked to parent row pr. // drawUnder picks a row among those linked to parent row pr.
func (t *table) drawUnder(s *session, pr int) int { func (t *table) drawUnder(s *session, pr int) int {
ix := t.indexed() ix := t.indexed()
k := t.parentT.cell(pr, t.parentT.key) k := t.parentT.cell(pr, t.parentT.keyCol)
rows := ix.children[k] rows := ix.rowsByParent[k]
if ix.childCum == nil { if ix.childCum == nil {
return rows[s.IntN(len(rows))] return rows[s.IntN(len(rows))]
} }
@@ -516,7 +516,7 @@ func (t *table) drawIn(s *session, p *pinSet) int {
r = t.drawUnder(s, pr) r = t.drawUnder(s, pr)
} }
if r < 0 { if r < 0 {
r = t.draw(s) r = t.drawRow(s)
} }
p.pin(t, r) p.pin(t, r)
return r return r
@@ -536,7 +536,7 @@ func (t *table) descendant(name string) *table {
} }
// parentRow is the row of t's parent that row r links to. // parentRow is the row of t's parent that row r links to.
func (t *table) parentRow(r int) int { return t.parentT.byKey[t.cell(r, t.parent)] } func (t *table) parentRow(r int) int { return t.parentT.byKey[t.cell(r, t.parentCol)] }
// descends reports whether a is an ancestor of t. // descends reports whether a is an ancestor of t.
func (t *table) descends(a *table) bool { func (t *table) descends(a *table) bool {
@@ -561,7 +561,7 @@ func (t *table) under(r int, a *table, pr int) bool {
// find is the row a selector names: by key first, then by name, where a name // find is the row a selector names: by key first, then by name, where a name
// naming several rows resolves inside the ancestors pinned. // naming several rows resolves inside the ancestors pinned.
func (t *table) find(sel string, pins *pinSet) (int, error) { func (t *table) find(sel string, pins *pinSet) (int, error) {
if t.key < 0 && t.name < 0 { if t.keyCol < 0 && t.nameCol < 0 {
return 0, fmt.Errorf("%s has no key or name column to select a row by", t.path) return 0, fmt.Errorf("%s has no key or name column to select a row by", t.path)
} }
if r, ok := t.byKey[sel]; ok { if r, ok := t.byKey[sel]; ok {
@@ -585,14 +585,14 @@ func (t *table) find(sel string, pins *pinSet) (int, error) {
func (t *table) ambiguous(sel string, rows []int) error { func (t *table) ambiguous(sel string, rows []int) error {
spellings := make([]string, len(rows)) spellings := make([]string, len(rows))
for i, r := range rows { for i, r := range rows {
if t.key < 0 { if t.keyCol < 0 {
spellings[i] = t.selectorSpelling(r) spellings[i] = t.selectorSpelling(r)
} else { } else {
spellings[i] = t.cell(r, t.key) spellings[i] = t.cell(r, t.keyCol)
} }
} }
listed := strings.Join(spellings, ", ") listed := strings.Join(spellings, ", ")
if t.key < 0 { if t.keyCol < 0 {
return fmt.Errorf("%q names %d rows of %s; select it inside its %s, one of %s", sel, len(rows), t.path, t.parentT.path, listed) return fmt.Errorf("%q names %d rows of %s; select it inside its %s, one of %s", sel, len(rows), t.path, t.parentT.path, listed)
} }
inside := "" inside := ""
@@ -606,10 +606,10 @@ func (t *table) ambiguous(sel string, rows []int) error {
// by name inside its parent's row. // by name inside its parent's row.
func (t *table) selectorSpelling(r int) string { func (t *table) selectorSpelling(r int) string {
switch { switch {
case t.key >= 0: case t.keyCol >= 0:
return t.path + "[" + t.cell(r, t.key) + "]" return t.path + "[" + t.cell(r, t.keyCol) + "]"
case t.name >= 0: case t.nameCol >= 0:
return t.parentT.selectorSpelling(t.parentRow(r)) + "." + t.category + "[" + t.cell(r, t.name) + "]" return t.parentT.selectorSpelling(t.parentRow(r)) + "." + t.category + "[" + t.cell(r, t.nameCol) + "]"
} }
return fmt.Sprintf("%s line %d", t.file, r+2) return fmt.Sprintf("%s line %d", t.file, r+2)
} }
+4 -4
View File
@@ -983,8 +983,8 @@ func TestEnteredRowsAgreeWithPinning(t *testing.T) {
var none pinSet var none pinSet
for _, entered := range tables { for _, entered := range tables {
for row := 0; row < entered.rowCount(); row++ { for row := 0; row < entered.rowCount(); row++ {
whole := drawAt{drawn: entered} whole := drawAt{wholeTable: entered}
whole.whole.add(entered, row) whole.wholePins.add(entered, row)
s := none.entered(entered, row) s := none.entered(entered, row)
for _, cand := range tables { for _, cand := range tables {
for i := 0; i < 20; i++ { for i := 0; i < 20; i++ {
@@ -1003,8 +1003,8 @@ func TestEnteredRowsAgreeWithPinning(t *testing.T) {
if alternatives(whole, candAt) || alternatives(candAt, whole) { if alternatives(whole, candAt) || alternatives(candAt, whole) {
t.Errorf("%s rendered whole is an alternative to %s pinned, though the whole draw ignores the pin", entered.selectorSpelling(row), cand.selectorSpelling(cr)) t.Errorf("%s rendered whole is an alternative to %s pinned, though the whole draw ignores the pin", entered.selectorSpelling(row), cand.selectorSpelling(cr))
} }
other := drawAt{drawn: cand} other := drawAt{wholeTable: cand}
other.whole.add(cand, cr) other.wholePins.add(cand, cr)
if want := cand == entered && cr != row; alternatives(whole, other) != want { if want := cand == entered && cr != row; alternatives(whole, other) != want {
t.Errorf("alternatives(%s, %s), both rendered whole, = %v, want %v", entered.selectorSpelling(row), cand.selectorSpelling(cr), !want, want) t.Errorf("alternatives(%s, %s), both rendered whole, = %v, want %v", entered.selectorSpelling(row), cand.selectorSpelling(cr), !want, want)
} }
-4
View File
@@ -19,10 +19,6 @@ Self-sufficiency 5.6), every seat capped by Locality or Self-sufficiency.
This round, in order: This round, in order:
- Rename the shared names left that name two things, per the decision "One name, one
meaning": judge each of `check`, `children`, `column`, `columns`, `draw`, `drawn`,
`fields`, `format`, `head`, `index`, `key`, `n`, `name`, `named`, `names`, `null`,
`number`, `op`, `reads`, `record`, `root`, `row`, `t`, `variant` and `whole`.
- Run the nine-seat panel again and file what it names here. - Run the nine-seat panel again and file what it names here.
### Data ### Data
+1 -1
View File
@@ -19,7 +19,7 @@ func withTransforms(calls map[string]builtin) map[string]builtin {
if _, clash := calls[name]; clash { if _, clash := calls[name]; clash {
panic("fejkdata: " + name + " is registered as a builtin and a transform") panic("fejkdata: " + name + " is registered as a builtin and a transform")
} }
calls[name] = builtin{arity: 1, check: transformArg, prep: transformPrep(fn), operands: transformOperand} calls[name] = builtin{arity: 1, checkArgs: transformArg, prep: transformPrep(fn), operands: transformOperand}
} }
return calls return calls
} }
+17 -17
View File
@@ -17,13 +17,13 @@ type proven struct {
integral bool integral bool
notOperand string // why some render reads as no finite number, the way calc reads it notOperand string // why some render reads as no finite number, the way calc reads it
not [len(dataTypeNames)]string not [len(dataTypeNames)]string
null bool // some draw of a column is null nullable bool // some draw of a column is null
} }
// valueProof proves what typed columns and their calc operands hold, each node once per scope. // valueProof proves what typed columns and their calc operands hold, each node once per scope.
type valueProof struct { type valueProof struct {
memo map[node]proven memo map[node]proven
columns map[node]proven columnMemo map[node]proven
} }
// checkDatatype rejects a typed column item some render of which is not text of its datatype, // checkDatatype rejects a typed column item some render of which is not text of its datatype,
@@ -54,11 +54,11 @@ func (p *valueProof) proveColumnItem(t *template) proven {
// proveColumn proves a column over what its items draw, a null item marking it null rather than // proveColumn proves a column over what its items draw, a null item marking it null rather than
// rendering "". // rendering "".
func (p *valueProof) proveColumn(n node) proven { func (p *valueProof) proveColumn(n node) proven {
if v, done := p.columns[n]; done { if v, done := p.columnMemo[n]; done {
return v return v
} }
if p.columns == nil { if p.columnMemo == nil {
p.columns = map[node]proven{} p.columnMemo = map[node]proven{}
} }
items, nullable := columnItems(n) items, nullable := columnItems(n)
var v proven var v proven
@@ -69,8 +69,8 @@ func (p *valueProof) proveColumn(n node) proven {
v = v.or(w) v = v.or(w)
} }
} }
v.null = v.null || nullable v.nullable = v.nullable || nullable
p.columns[n] = v p.columnMemo[n] = v
return v return v
} }
@@ -87,10 +87,10 @@ func (p *valueProof) prove(n node) proven {
v = p.proveUnion(n.items) v = p.proveUnion(n.items)
case *template: case *template:
v = p.proveTemplate(n) v = p.proveTemplate(n)
case *column: case *tableColumn:
v = p.proveCells(n) v = p.proveCells(n)
case *row: case *tableRow:
v = unproven(fmt.Sprintf("%q renders a row of %s, which is composed text", n.t.format.format, n.t.category)) v = unproven(fmt.Sprintf("%q renders a row of %s, which is composed text", n.t.formatTemplate.format, n.t.category))
default: default:
v = unproven(`it reads a null, which renders "" outside its own column`) v = unproven(`it reads a null, which renders "" outside its own column`)
} }
@@ -99,7 +99,7 @@ func (p *valueProof) prove(n node) proven {
} }
// proveCells proves a table column over every cell it may render. // proveCells proves a table column over every cell it may render.
func (p *valueProof) proveCells(c *column) proven { func (p *valueProof) proveCells(c *tableColumn) proven {
var v proven var v proven
for r := 0; r < c.t.rowCount(); r++ { for r := 0; r < c.t.rowCount(); r++ {
var w proven var w proven
@@ -128,7 +128,7 @@ func (p *valueProof) proveUnion(nodes []node) proven {
// or is what a proof knows of a render that is either v or w. // or is what a proof knows of a render that is either v or w.
func (v proven) or(w proven) proven { func (v proven) or(w proven) proven {
v.lo, v.hi, v.nonZero = min(v.lo, w.lo), max(v.hi, w.hi), min(v.nonZero, w.nonZero) v.lo, v.hi, v.nonZero = min(v.lo, w.lo), max(v.hi, w.hi), min(v.nonZero, w.nonZero)
v.integral, v.null = v.integral && w.integral, v.null || w.null v.integral, v.nullable = v.integral && w.integral, v.nullable || w.nullable
if v.notOperand == "" { if v.notOperand == "" {
v.notOperand = w.notOperand v.notOperand = w.notOperand
} }
@@ -164,8 +164,8 @@ func (p *valueProof) proveTemplate(t *template) proven {
return p.proveUnion(leaves) return p.proveUnion(leaves)
case name == "calc": case name == "calc":
return p.proveCalc(t, body, args) return p.proveCalc(t, body, args)
case builtins[name].number != nil: case builtins[name].proveNumber != nil:
return builtins[name].number(body, builtins[name].prints, args) return builtins[name].proveNumber(body, builtins[name].prints, args)
case isTransform: case isTransform:
return unproven(fmt.Sprintf("{%s} rewrites text rather than printing a value; write the values it would print", body)) return unproven(fmt.Sprintf("{%s} rewrites text rather than printing a value; write the values it would print", body))
} }
@@ -225,7 +225,7 @@ func (p *valueProof) proveExpr(n calcNode, fields map[string]node) (proven, stri
func combine(n calcBin, l, r proven) (proven, string) { func combine(n calcBin, l, r proven) (proven, string) {
var v proven var v proven
integral := l.integral && r.integral integral := l.integral && r.integral
switch n.op { switch n.operator {
case '+': case '+':
v = bounded(l.lo+r.lo, l.hi+r.hi, integral) v = bounded(l.lo+r.lo, l.hi+r.hi, integral)
case '-': case '-':
@@ -295,7 +295,7 @@ var typedCalls, operandCalls = numberCalls(false), numberCalls(true)
func numberCalls(text bool) string { func numberCalls(text bool) string {
var calls []string var calls []string
for name, b := range builtins { for name, b := range builtins {
if b.number != nil && (text || b.prints != DataTypeString) { if b.proveNumber != nil && (text || b.prints != DataTypeString) {
calls = append(calls, "{"+name+"()}") calls = append(calls, "{"+name+"()}")
} }
} }