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 US given names come from a mirror of the SSA file
- `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)},
"uuid": {arity: 0, prep: sample(uuidV7)},
"ulid": {arity: 0, prep: sample(ulid)},
"nanoid": {arity: 1, check: posIntArg, prep: chars(nanoidAlphabet)},
"hex": {arity: 1, check: posIntArg, prep: chars(hexDigits)},
"digits": {arity: 1, check: posIntArg, prep: chars("0123456789"), prints: DataTypeString, number: func(token string, prints DataType, a []string) proven {
"nanoid": {arity: 1, checkArgs: posIntArg, prep: chars(nanoidAlphabet)},
"hex": {arity: 1, checkArgs: posIntArg, prep: chars(hexDigits)},
"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))
}},
"upper": {arity: 1, check: posIntArg, prep: chars("ABCDEFGHIJKLMNOPQRSTUVWXYZ")},
"lower": {arity: 1, check: posIntArg, prep: chars("abcdefghijklmnopqrstuvwxyz")},
"base64": {arity: 1, check: posIntArg, prep: func(a []string) callFn {
"upper": {arity: 1, checkArgs: posIntArg, prep: chars("ABCDEFGHIJKLMNOPQRSTUVWXYZ")},
"lower": {arity: 1, checkArgs: posIntArg, prep: chars("abcdefghijklmnopqrstuvwxyz")},
"base64": {arity: 1, checkArgs: posIntArg, prep: func(a []string) callFn {
n := atoi(a[0])
return func(s *session, _ string, _ []string) string {
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
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))
}},
"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])
return func(s *session, _ string, _ []string) string {
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]))
}},
"iban": {arity: 1, check: ibanArg, prep: func(a []string) callFn {
"iban": {arity: 1, checkArgs: ibanArg, prep: func(a []string) callFn {
cc := a[0]
return func(s *session, _ string, _ []string) string { return iban(s, cc) }
}},
"date": {arity: -1, check: dateArgs, prep: datePrep},
"time": {arity: -1, check: timeArg, prep: timePrep},
"calc": {arity: -1, check: checkCalc, prep: calcPrep, operands: calcOperands},
"date": {arity: -1, checkArgs: dateArgs, prep: datePrep},
"time": {arity: -1, checkArgs: timeArg, prep: timePrep},
"calc": {arity: -1, checkArgs: checkCalc, prep: calcPrep, operands: calcOperands},
// 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
// 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 := ""
if len(a) == 1 {
key = a[0]
@@ -73,7 +73,7 @@ var builtins = withTransforms(map[string]builtin{
return func(s *session, _ string, _ []string) string {
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))
}},
})
+1 -1
View File
@@ -194,7 +194,7 @@ func TestRegistryShapes(t *testing.T) {
if b.prep == nil {
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)
}
}
+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 calcNeg struct{ x calcNode }
type calcBin struct { // a + - * / b
op byte
l, r calcNode
operator byte
l, r calcNode
}
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 {
l, r := n.l.eval(operands), n.r.eval(operands)
switch n.op {
switch n.operator {
case '+':
return l + r
case '-':
@@ -100,7 +100,7 @@ func constantZeroDivisor(n calcNode, fields map[string]node) (string, bool) {
case calcNeg:
return constantZeroDivisor(n.x, fields)
case calcBin:
if n.op == '/' {
if n.operator == '/' {
if v, known := constantValue(n.r, fields); known && v == 0 {
return calcText(n.r), true
}
@@ -134,7 +134,7 @@ func constantValue(n calcNode, fields map[string]node) (float64, bool) {
if !lok || !rok {
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
}
@@ -149,7 +149,7 @@ func calcText(n calcNode) string {
case calcNeg:
return "-" + calcText(n.x)
case calcBin:
return "(" + calcText(n.l) + " " + string(n.op) + " " + calcText(n.r) + ")"
return "(" + calcText(n.l) + " " + string(n.operator) + " " + calcText(n.r) + ")"
}
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.
func neverNumeric(n node) (text string, never bool) {
switch n := n.(type) {
case *null:
case *nullItem:
return "", true
case *template:
if !n.fixed || n.repeat > 1 {
@@ -220,7 +220,7 @@ func indexVars(n calcNode, at map[string]int) calcNode {
case calcNeg:
return calcNeg{indexVars(n.x, at)}
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
}
+12 -12
View File
@@ -17,10 +17,10 @@ type dataSource struct {
label string
onDisk bool
path string
root string
dir string
}
func (s dataSource) name(p string) string {
func (s dataSource) labelled(p string) string {
if s.label == "" {
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)
}
}
dir := src.root
dir := src.dir
if dir == "" {
dir = "."
}
@@ -84,7 +84,7 @@ func loadData(sources []dataSource) (map[string]node, error) {
func loadDir(src dataSource, dir string) (*folder, error) {
entries, err := fs.ReadDir(src.fsys, dir)
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{}}
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 {
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
@@ -127,12 +127,12 @@ type categoryFiles struct {
// readRows reads the rows file a category names beside it.
func (c *categoryFiles) readRows(name string) (string, error) {
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
b, err := fs.ReadFile(c.src.fsys, path.Join(c.dir, name))
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
}
@@ -147,7 +147,7 @@ func loadFolder(src dataSource, g *folder, full, name string) error {
return 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
return nil
@@ -161,19 +161,19 @@ func loadFile(src dataSource, g *folder, full, file string, files *categoryFiles
}
name := strings.TrimSuffix(file, ".json")
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)
if err != nil {
return fmt.Errorf("%s: %w", src.name(full), err)
return fmt.Errorf("%s: %w", src.labelled(full), err)
}
var raw any
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)
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
return nil
+2 -2
View File
@@ -89,7 +89,7 @@ func checkColumns(s nodeScope) error {
}
return s(func(path string, n node) error {
t, ok := n.(*template)
if !ok || !t.record {
if !ok || !t.isRecord {
return nil
}
for _, name := range sortedNames(t.fields) {
@@ -149,7 +149,7 @@ func columnItems(n node) (items []*template, nullable bool) {
}
case *template:
items = append(items, n)
case *null:
case *nullItem:
nullable = true
}
}
+3 -3
View File
@@ -17,7 +17,7 @@ package fejkdata
// Vocabulary
//
// - 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`.
// - render — what owns one `holdSet`, and so what one reference draw spans:
// `Generator.Fake`, `FakeRecord`, `Template.Fake` and `RecordTemplate.Fake`
@@ -32,8 +32,8 @@ package fejkdata
// `pinSet`, `pinSet.pin`, `table.drawIn`.
// - family — a table and every table reaching it through a chain of
// `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
// format: `tableRead.whole` says a read did, `table.whole` is the node.
// - whole — a read that lands on a `tableRow`, so the row renders through its table's
// 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`
// runs them over the data set, `NewTemplate` and `FakeStruct` over what those
// 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 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
`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#the-expansion-hold-and-the-renders-draws-are-two-fences
type drawCheck struct {
memo map[readsMemo]bool
memo map[hasReadMemo]bool
}
type readsMemo struct {
type hasReadMemo struct {
n node
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
func (c *drawCheck) checkRecordDraws(path string, n node) error {
t, ok := n.(*template)
if !ok || !t.record {
if !ok || !t.isRecord {
return nil
}
// 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
// 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
// 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.
func (c *drawCheck) reads(n node, stopAtGroup bool) bool {
k := readsMemo{n, stopAtGroup}
func (c *drawCheck) hasRead(n node, stopAtGroup bool) bool {
k := hasReadMemo{n, stopAtGroup}
if r, done := c.memo[k]; done {
return r
}
r := false
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
break
}
}
if c.memo == nil {
c.memo = map[readsMemo]bool{}
c.memo = map[hasReadMemo]bool{}
}
c.memo[k] = 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
// cells the walk is in.
type drawAt struct {
group string
route drawRoute
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
whole pinSet
group string
route drawRoute
pins pinSet
wholeTable *table // left out of the visit keys: only this table's own cells compare against it, which the own-family fence keeps true
wholePins pinSet
}
// 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
}
if t, isTable := n.(*table); isTable {
at.drawn = t
at.wholeTable = t
}
for _, e := range renderEdges(n) {
cell, isCell := e.to.(*template)
switch {
case !isCell || cell.cellOf == nil:
w.edge(n, e, at)
case cell.cellOf == at.drawn:
case cell.cellOf == at.wholeTable:
in := at
in.whole = at.whole.clone()
in.whole.add(cell.cellOf, cell.cellRow)
in.wholePins = at.wholePins.clone()
in.wholePins.add(cell.cellOf, cell.cellRow)
w.edge(n, e, in)
case at.pins.clash(cell.cellOf, cell.cellRow) == nil:
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.
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
// 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) {
continue
}
if strings.HasPrefix(into.a.path, level.a.path+".") && !(level.tr != nil && level.tr.whole) {
return overlapError(level.at.route, level.a.name, into)
if strings.HasPrefix(into.a.path, level.a.path+".") && !(level.tr != nil && level.tr.landsWhole) {
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 {
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
+19 -17
View File
@@ -10,11 +10,11 @@ import (
// row pinned, and their unpinned ancestors — each selector's spelling, and whether
// it lands on a row rendered whole.
type tableRead struct {
head *table
pins pinSet
drawn map[*table]bool
sels []tableSel
whole bool
headTable *table
pins pinSet
drawn map[*table]bool
sels []tableSel
landsWhole bool
}
// 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 {
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})
written := a.name[:len(a.name)-len(joinSegments(a.tail))]
written := a.spelling[:len(a.spelling)-len(joinSegments(a.tail))]
cur := t
for i, seg := range a.tail {
switch d := cur.descendant(seg); {
@@ -44,7 +44,7 @@ func tableReadOf(head node, a arm, leaf node) *tableRead {
cur = d
}
}
_, tr.whole = leaf.(*row)
_, tr.landsWhole = leaf.(*tableRow)
return tr
}
@@ -91,7 +91,7 @@ func checkFamilies(reads []pathRead) error {
continue
}
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
}
if err := checkFamilyPair(o, r); err != nil {
@@ -127,7 +127,7 @@ func replayPairs(reads []pathRead) 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.
@@ -149,19 +149,19 @@ func checkFamilyPair(a, b pathRead) error {
for _, pair := range [][2]pathRead{{a, b}, {b, a}} {
x, y := pair[0], pair[1]
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 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
if s.t != x.tr.head {
tail = append([]string{x.tr.head.category}, tail...)
if s.t != x.tr.headTable {
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",
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",
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
@@ -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,
// or rows of one whole draw.
// 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
if c.shipped {
sources = append(sources, dataSource{fsys: shippedFS, root: "data"})
sources = append(sources, dataSource{fsys: shippedFS, dir: "data"})
}
sources = append(sources, c.sources...)
if len(sources) == 0 {
@@ -151,11 +151,11 @@ func paths(n node) []string {
}
}
return out
case *null:
case *nullItem:
return []string{""}
case *table:
return tablePaths(n)
case *column, *row:
case *tableColumn, *tableRow:
return []string{""}
case *choice:
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
// direct descents, a step at a time.
func tablePaths(t *table) []string {
out := append([]string{""}, t.columns...)
out := append([]string{""}, t.header...)
sort.Strings(out[1:])
children := make([]string, 0, len(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
// 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
// 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 {
arity int
// prep parses validated args once, at compile time, into the closure expand calls.
prep func(args []string) callFn
check func(fields map[string]node, args []string) error
prep func(args []string) callFn
checkArgs func(fields map[string]node, args []string) error
// operands names the fields the call reads, which expand renders for it; nil
// for a builtin that reads none.
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.
number func(token string, prints DataType, args []string) proven
prints DataType
proveNumber func(token string, prints DataType, args []string) proven
prints DataType
}
// 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 {
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 err := b.check(fields, args); err != nil {
if b.checkArgs != nil {
if err := b.checkArgs(fields, args); err != nil {
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
// compileOps).
type arm struct {
name string // as written, for messages
key string
tail []string
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
spelling string // as written, for messages
key string
tail []string
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
}
// 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 {
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)
}
segs, err := splitPath(name)
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:])
}
@@ -330,7 +330,7 @@ func pathArm(name, key string, segs []string) arm {
for i := 0; i < len(segs)-1; i++ {
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
@@ -400,7 +400,7 @@ func (c *formatOps) holdName(a arm, label string) {
c.bound = map[string]string{}
}
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 {
arms[i] = splitArm(name, refs)
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})
+11 -11
View File
@@ -51,7 +51,7 @@ type namedNode struct {
func contained(n node) []namedNode {
switch n := n.(type) {
case *folder:
return named(n.children)
return namedNodes(n.children)
case *choice:
out := make([]namedNode, len(n.items))
for i, it := range n.items {
@@ -59,10 +59,10 @@ func contained(n node) []namedNode {
}
return out
case *template:
return named(n.fields)
return namedNodes(n.fields)
case *table:
return append([]namedNode{{node: n.format}}, named(n.fields)...)
case *column:
return append([]namedNode{{node: n.formatTemplate}}, namedNodes(n.fields)...)
case *tableColumn:
if len(n.t.cellTemplates) == 0 {
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))
for _, name := range sortedNames(m) {
out = append(out, namedNode{name: name, node: m[name]})
@@ -133,7 +133,7 @@ func renderEdges(n node) []renderEdge {
return
}
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 {
@@ -146,13 +146,13 @@ func renderEdges(n node) []renderEdge {
}
return es
case *table:
return []renderEdge{{to: n.format, label: "format"}}
case *row:
return []renderEdge{{to: n.t.format, label: "format"}}
case *column:
return []renderEdge{{to: n.formatTemplate, label: "format"}}
case *tableRow:
return []renderEdge{{to: n.t.formatTemplate, label: "format"}}
case *tableColumn:
var es []renderEdge
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
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 {
if !t.held[a.key] {
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)}
}
@@ -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
// reference alone whose read drew null.
func renderLeaf(s *session, n node, sc renderScope) draw {
n = drawn(s, n)
if _, isNull := n.(*null); isNull {
n = resolveChoice(s, n)
if _, isNull := n.(*nullItem); isNull {
return draw{null: true}
}
r := draw{text: render(s, n, sc)}
@@ -69,10 +69,10 @@ func renderLeaf(s *session, n node, sc renderScope) draw {
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
// 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) {
n = pick(s, c)
}
+1 -1
View File
@@ -766,6 +766,6 @@ func TestReadFieldPanicsOnAnUnheldPath(t *testing.T) {
t.Fatal("not a template")
}
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
}
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)
}
}
@@ -181,7 +181,7 @@ func checkNoOverlap(ops []op, bound map[string]string) error {
for i, level := range names {
for _, path := range names[i+1:] {
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
// 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
// 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 _, a := range o.operands {
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 {
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
}
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() {}
// null is a column's missing value, rendered ""; sized so two nulls are two map keys.
type null struct{ _ byte }
// nullItem is a column's missing value, rendered ""; sized so two nulls are two map keys.
type nullItem struct{ _ byte }
func (*null) isNode() {}
func (*nullItem) isNode() {}
// 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
@@ -52,7 +52,7 @@ type template struct {
datatype DataType
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
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
// 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 {
return nil, fmt.Errorf(`null is a record column's value; here it only renders "", so write ""`)
}
return &null{}, nil
return &nullItem{}, nil
default:
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 {
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 {
return nil, err
}
@@ -526,7 +526,7 @@ func checkPathNames(path string) error {
if err != nil {
return err
}
for _, seg := range names(segs) {
for _, seg := range nameSegments(segs) {
if err := checkName(seg); err != nil {
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.
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.
func names(segs []string) []string {
// nameSegments is the segments of a path that are names, its selectors left out.
func nameSegments(segs []string) []string {
out := segs[:0:0]
for _, s := range segs {
if !isSelector(s) {
@@ -197,8 +197,8 @@ func walkPath(n node, tail []string, w pathWalk) (node, error) {
return walkPath(child, tail[1:], w)
case *choice:
return walkChoice(n, tail, w)
case *column:
return nil, fmt.Errorf("no field %q: %q is a column, and a cell holds no fields", tail[0], n.t.columns[n.i])
case *tableColumn:
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])
}
@@ -268,7 +268,7 @@ func walkTable(t *table, tail []string, w pathWalk, descended bool) (node, error
case len(tail) == 0 && sel == "" && !descended:
return walkPath(t, nil, w)
case len(tail) == 0:
return walkPath(t.whole, nil, w)
return walkPath(t.wholeRow, nil, w)
case child != nil:
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
}
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 {
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)]
n, drew := d.held.variant[key]
if !drew {
n = drawn(d.s, c)
n = resolveChoice(d.s, c)
if d.held.variant == nil {
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.
type pinSet struct {
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
}
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 {
return q.row, true
}
@@ -39,9 +39,9 @@ func (p *pinSet) mustRow(t *table) int {
}
func (p *pinSet) add(t *table, r int) {
if p.n < len(p.inline) {
p.inline[p.n] = tablePin{t, r}
p.n++
if p.used < len(p.inline) {
p.inline[p.used] = tablePin{t, r}
p.used++
return
}
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.
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)
}
spilled := make([]*table, 0, len(p.spill))
@@ -164,8 +164,8 @@ func (p *pinSet) differs(q *pinSet) bool {
return found
}
// key spells the set for a map, by the tables' identities in path order.
func (p *pinSet) key() string {
// mapKey spells the set for a map, by the tables' identities in path order.
func (p *pinSet) mapKey() string {
var pins []tablePin
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 })
+16 -16
View File
@@ -141,13 +141,13 @@ func (f *Generator) FakeRecord(path string) (*Record, error) {
}
shape := f.recordShapeOf(n)
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)
}
if shape.err != nil {
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,
@@ -161,10 +161,10 @@ func tableRecord(s *session, t *table, tail []string, sc renderScope) (node, err
case *table:
n.drawIn(s, &sc.hold().pins)
return n, nil
case *row:
case *tableRow:
return n.t, nil
case *column:
return nil, fmt.Errorf("descends into %q, a column; a record is a table's row", n.t.columns[n.i])
case *tableColumn:
return nil, fmt.Errorf("descends into %q, a column; a record is a table's row", n.t.header[n.i])
}
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
// columns, or why it is not a record.
type recordShape struct {
t *template
columns []Column
err error
template *template
columns []Column
err error
}
// 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
}
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 {
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,
// ready to render many times with [RecordTemplate.Fake].
type RecordTemplate struct {
g *Generator
t *template
columns []Column
g *Generator
template *template
columns []Column
}
// Fake renders the record with one draw.
@@ -205,7 +205,7 @@ func (t *RecordTemplate) Fake() *Record {
t.g.mu.Lock()
defer t.g.mu.Unlock()
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
@@ -219,7 +219,7 @@ func (f *Generator) NewRecordTemplate(input string) (*RecordTemplate, error) {
if err != nil {
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.
@@ -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.
func recordOf(n node) (*template, []Column, error) {
if tb, isTable := n.(*table); isTable {
n = tb.format
n = tb.formatTemplate
}
t, ok := n.(*template)
if !ok {
@@ -249,7 +249,7 @@ func recordOf(n node) (*template, []Column, error) {
if len(names) == 0 {
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)
}
columns := make([]Column, len(names))
+1 -1
View File
@@ -136,7 +136,7 @@ func columnReadOf(t *template) *columnRead {
}
a := splitArm(name, t.refs)
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 &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) {
case *choice:
return render(s, pick(s, n), sc)
case *null:
case *nullItem:
return ""
case *table:
sc.t, sc.row = n, n.draw(s)
return expand(s, n.format, sc)
case *row:
sc.t, sc.row = n, n.drawRow(s)
return expand(s, n.formatTemplate, sc)
case *tableRow:
sc.t, sc.row = n.t, sc.hold().pins.mustRow(n.t)
return expand(s, n.t.format, sc)
case *column:
return expand(s, n.t.formatTemplate, sc)
case *tableColumn:
if sc.t != 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:
facts[prefix] = readsFact(n)
case *table:
facts[prefix] = "\tformat " + strconv.Quote(n.format.format) + tableFacts(n) + readsFact(n)
for _, name := range n.columns {
facts[prefix] = "\tformat " + strconv.Quote(n.formatTemplate.format) + tableFacts(n) + readsFact(n)
for _, name := range n.header {
facts[join(prefix, name)] = "\tstring"
}
case *template:
@@ -77,9 +77,9 @@ func tableFacts(t *table) string {
for _, o := range []struct {
name string
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 {
b.WriteString("\t" + o.name + " " + t.columns[o.col])
b.WriteString("\t" + o.name + " " + t.header[o.col])
}
}
return b.String()
@@ -96,7 +96,7 @@ func readsFact(n node) string {
collect(it)
}
case *table:
collect(n.format)
collect(n.formatTemplate)
for _, cell := range n.cellTemplates {
collect(cell)
}
+25 -25
View File
@@ -49,8 +49,8 @@ func (f *Generator) structShapeOf(t reflect.Type) (*structShape, error) {
if label == "" {
label = "struct"
}
sc := &structCompile{root: f.categories, visiting: map[reflect.Type]bool{}, structs: maxStructs}
shape, err := sc.record(t, label)
sc := &structCompile{categories: f.categories, visiting: map[reflect.Type]bool{}, structs: maxStructs}
shape, err := sc.compileShape(t, label)
if err == nil && shape.empty() {
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
// index path each column fills, and the struct fields carrying tags of their own.
type structShape struct {
record *template
columns []Column
fields [][]int
nested []nestedStruct
record *template
columns []Column
fieldIndexes [][]int
nested []nestedStruct
}
// nestedStruct is a named struct field, or a pointer to one, filled as a record of its own.
type nestedStruct struct {
index []int
shape *structShape
fieldIndex []int
shape *structShape
}
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
// rather than filled without end, and how many more structs it may walk.
type structCompile struct {
root map[string]node
visiting map[reflect.Type]bool
structs int
categories map[string]node
visiting map[reflect.Type]bool
structs int
}
// structFields gathers what one struct type fills: its tagged fields, those its embedded
@@ -92,24 +92,24 @@ type structCompile struct {
// records.
type structFields struct {
*structCompile
t reflect.Type
typ reflect.Type
label string
tags map[string]any
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 {
return nil, err
}
sc.visiting[t] = true
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 {
return nil, err
}
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
}
}
@@ -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.
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) {
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)
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.typ, sf.Index), sf.Name)
}
v, err := tagValue(sf, tag)
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 {
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
}
c.visiting[elem] = true
@@ -200,13 +200,13 @@ func (c *structFields) embed(sf reflect.StructField, elem reflect.Type) error {
return err
}
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
}
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() {
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)
}
proof := &valueProof{}
s.fields = make([][]int, len(columns))
s.fieldIndexes = make([][]int, len(columns))
for i, c := range columns {
sf, _ := t.FieldByName(c.Name)
if err := proof.checkField(label+"."+c.Name, sf.Type, record.fields[c.Name]); err != nil {
return err
}
s.fields[i] = sf.Index
s.fieldIndexes[i] = sf.Index
}
s.record, s.columns = record, columns
return nil
@@ -327,7 +327,7 @@ func (p *valueProof) checkField(label string, ft reflect.Type, column node) erro
elem := ft
if ft.Kind() == reflect.Pointer {
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)
}
kind := columnKinds[elem.Kind()]
@@ -351,11 +351,11 @@ func (s *structShape) fill(sess *session, v reflect.Value) {
if s.record != nil {
set := eagerHoldSet()
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 {
field := fieldAt(v, n.index)
field := fieldAt(v, n.fieldIndex)
if field.Kind() == reflect.Pointer {
if field.IsNil() {
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
// cell of the row a render pinned; a cell carrying tokens compiles to a string node.
type table struct {
category string
path string // the category's path from the data root, which a selector is written at
file string
format *template // fields are the column nodes
columns []string
col map[string]int
fields map[string]node // column nodes, the format's fields
whole *row // the pinned row rendered by the format
cells []string // rows × columns, flat
cellTemplates map[int]*template
key int // key, name, weight and parent are column indexes, or -1
name int
weight int
parent int
cum []float64 // cumulative weights, nil when uniform
byKey map[string]int
parentT *table
children map[string]*table
once sync.Once
index tableIndex
category string
path string // the category's path from the data root, which a selector is written at
file string
formatTemplate *template // fields are the column nodes
header []string
col map[string]int
fields map[string]node // column nodes, the format's fields
wholeRow *tableRow // the pinned row rendered by the format
cells []string // rows × columns, flat
cellTemplates map[int]*template
keyCol int // keyCol through parentCol are -1 where the option is absent
nameCol int
weightCol int
parentCol int
cum []float64 // cumulative weights, nil when uniform
byKey map[string]int
parentT *table
children map[string]*table
once sync.Once
index tableIndex
}
// tableIndex is what selection and linked draws look up, built on the first draw
// that needs it.
type tableIndex struct {
byName map[string][]int
children map[string][]int // rows by parent key
childCum map[string][]float64 // cumulative weights per parent key, nil when uniform
byName map[string][]int
rowsByParent map[string][]int
childCum map[string][]float64 // cumulative weights per parent key, nil when uniform
}
func (*table) isNode() {}
// column is one column of a table, rendered as the cell of the row the render pinned.
type column struct {
// tableColumn is one column of a table, rendered as the cell of the row the render pinned.
type tableColumn struct {
t *table
i int
}
func (*column) isNode() {}
func (*tableColumn) isNode() {}
// row is the row of a table the render pinned, rendered by the table's format.
type row struct{ t *table }
// tableRow is the row of a table the render pinned, rendered by the table's format.
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,
// 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.
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 {
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 {
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 {
format, key, name, parent, rows, weight string
format, keyColumn, nameColumn, parentColumn, rows, weightColumn string
}
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)
}
}
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]
if !ok {
continue
@@ -140,8 +140,8 @@ func readTableOptions(m map[string]any) (tableOptionValues, error) {
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)
}
if o.key != "" && o.key == o.name {
return o, fmt.Errorf("name names the key column %q, which a selector already reads; drop it", 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.nameColumn)
}
return o, nil
}
@@ -155,10 +155,10 @@ func (t *table) parseRows(data string) error {
if header == "" {
return fmt.Errorf("has no header line naming its columns")
}
t.columns = strings.Split(header, "\t")
t.col = make(map[string]int, len(t.columns))
t.fields = make(map[string]node, len(t.columns))
for i, name := range t.columns {
t.header = strings.Split(header, "\t")
t.col = make(map[string]int, len(t.header))
t.fields = make(map[string]node, len(t.header))
for i, name := range t.header {
if err := checkName(name); err != nil {
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)
}
t.col[name] = i
t.fields[name] = &column{t, i}
t.fields[name] = &tableColumn{t, i}
}
if header == data {
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))
for line := 2; ; line++ {
row, more, found := strings.Cut(rest, "\n")
@@ -195,7 +195,7 @@ func (t *table) appendRow(row string, line int) error {
if row == "" {
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++ {
cell, next, tab := strings.Cut(row, "\t")
if !tab && n < m-1 || tab && n == m-1 {
@@ -213,17 +213,17 @@ func (t *table) bindOptions(o tableOptionValues) error {
for _, opt := range []struct {
name, value string
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 == "" {
continue
}
i, ok := t.col[opt.value]
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
}
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")
}
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.
func (t *table) proveNamesInsideParent() error {
if t.key >= 0 || t.name < 0 {
if t.keyCol >= 0 || t.nameCol < 0 {
return nil
}
inside := make(map[string]int, t.rowCount())
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 {
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
}
@@ -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.
func (t *table) indexKeys() error {
if t.key < 0 {
if t.keyCol < 0 {
return nil
}
t.byKey = make(map[string]int, t.rowCount())
for r := 0; r < t.rowCount(); r++ {
k := t.cell(r, t.key)
k := t.cell(r, t.keyCol)
if k == "" {
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
}
for r := 0; r < t.rowCount() && t.name >= 0; r++ {
if n := t.cell(r, t.name); t.byKey[n] != r {
for r := 0; r < t.rowCount() && t.nameCol >= 0; r++ {
if n := t.cell(r, t.nameCol); t.byKey[n] != r {
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)
}
@@ -279,15 +279,15 @@ func (t *table) indexKeys() error {
// sumWeights proves every weight is a positive number and builds the cumulative table.
func (t *table) sumWeights() error {
if t.weight < 0 {
if t.weightCol < 0 {
return nil
}
t.cum = make([]float64, t.rowCount())
total := 0.0
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 {
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
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.
func (t *table) checkCells() error {
for i, cell := range t.cells {
row, col := i/len(t.columns), i%len(t.columns)
if (col == t.key || col == t.name) && 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])
row, col := i/len(t.header), i%len(t.header)
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.header[col], cell, cell[strings.IndexAny(cell, inSelector):][:1])
}
if strings.IndexByte(cell, '{') < 0 && strings.IndexByte(cell, '}') < 0 {
continue
}
n, err := compileString(cell)
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 {
t.cellTemplates = map[int]*template{}
@@ -335,16 +335,16 @@ func (t *table) compileWhole(format string) error {
for _, name := range tok.names {
a := splitArm(name, nil)
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 {
return err
}
t.format = &template{format: format, tokens: toks, fields: t.fields, repeat: 1, record: true, table: t}
t.whole = &row{t}
return t.format.compileRefFree()
t.formatTemplate = &template{format: format, tokens: toks, fields: t.fields, repeat: 1, isRecord: true, table: t}
t.wholeRow = &tableRow{t}
return t.formatTemplate.compileRefFree()
}
// 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
}
case *table:
if n.parent < 0 {
if n.parentCol < 0 {
continue
}
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 {
name := t.columns[t.parent]
name := t.header[t.parentCol]
p, ok := siblings[name].(*table)
switch {
case siblings[name] == nil:
return fmt.Errorf("parent %q names no table beside it", name)
case !ok:
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)
}
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] {
return fmt.Errorf("parent cycle: %s reaches itself through its parents", q.category)
}
seen[q] = true
ancestors = append(ancestors, q)
if q.parent < 0 {
if q.parentCol < 0 {
break
}
}
@@ -421,14 +421,14 @@ func (t *table) linkParent(path string, siblings map[string]node) error {
}
linked := make(map[string]bool, p.rowCount())
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 {
return fmt.Errorf("%s line %d: %s %q is no key of %s", t.file, r+2, name, k, p.file)
}
linked[k] = true
}
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)
}
}
@@ -442,25 +442,25 @@ func (t *table) linkParent(path string, siblings map[string]node) error {
func (t *table) indexed() *tableIndex {
t.once.Do(func() {
if t.name >= 0 {
if t.nameCol >= 0 {
t.index.byName = make(map[string][]int, t.rowCount())
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)
}
}
if t.parent >= 0 {
t.index.children = map[string][]int{}
if t.parentCol >= 0 {
t.index.rowsByParent = map[string][]int{}
for r := 0; r < t.rowCount(); r++ {
k := t.cell(r, t.parent)
t.index.children[k] = append(t.index.children[k], r)
k := t.cell(r, t.parentCol)
t.index.rowsByParent[k] = append(t.index.rowsByParent[k], r)
}
if t.cum != nil {
t.index.childCum = make(map[string][]float64, len(t.index.children))
for k, rows := range t.index.children {
t.index.childCum = make(map[string][]float64, len(t.index.rowsByParent))
for k, rows := range t.index.rowsByParent {
cum, total := make([]float64, len(rows)), 0.0
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
cum[i] = total
}
@@ -472,9 +472,9 @@ func (t *table) indexed() *tableIndex {
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.
func (t *table) draw(s *session) int {
func (t *table) drawRow(s *session) int {
if t.cum == nil {
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.
func (t *table) drawUnder(s *session, pr int) int {
ix := t.indexed()
k := t.parentT.cell(pr, t.parentT.key)
rows := ix.children[k]
k := t.parentT.cell(pr, t.parentT.keyCol)
rows := ix.rowsByParent[k]
if ix.childCum == nil {
return rows[s.IntN(len(rows))]
}
@@ -516,7 +516,7 @@ func (t *table) drawIn(s *session, p *pinSet) int {
r = t.drawUnder(s, pr)
}
if r < 0 {
r = t.draw(s)
r = t.drawRow(s)
}
p.pin(t, 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.
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.
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
// naming several rows resolves inside the ancestors pinned.
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)
}
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 {
spellings := make([]string, len(rows))
for i, r := range rows {
if t.key < 0 {
if t.keyCol < 0 {
spellings[i] = t.selectorSpelling(r)
} else {
spellings[i] = t.cell(r, t.key)
spellings[i] = t.cell(r, t.keyCol)
}
}
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)
}
inside := ""
@@ -606,10 +606,10 @@ func (t *table) ambiguous(sel string, rows []int) error {
// by name inside its parent's row.
func (t *table) selectorSpelling(r int) string {
switch {
case t.key >= 0:
return t.path + "[" + t.cell(r, t.key) + "]"
case t.name >= 0:
return t.parentT.selectorSpelling(t.parentRow(r)) + "." + t.category + "[" + t.cell(r, t.name) + "]"
case t.keyCol >= 0:
return t.path + "[" + t.cell(r, t.keyCol) + "]"
case t.nameCol >= 0:
return t.parentT.selectorSpelling(t.parentRow(r)) + "." + t.category + "[" + t.cell(r, t.nameCol) + "]"
}
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
for _, entered := range tables {
for row := 0; row < entered.rowCount(); row++ {
whole := drawAt{drawn: entered}
whole.whole.add(entered, row)
whole := drawAt{wholeTable: entered}
whole.wholePins.add(entered, row)
s := none.entered(entered, row)
for _, cand := range tables {
for i := 0; i < 20; i++ {
@@ -1003,8 +1003,8 @@ func TestEnteredRowsAgreeWithPinning(t *testing.T) {
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))
}
other := drawAt{drawn: cand}
other.whole.add(cand, cr)
other := drawAt{wholeTable: cand}
other.wholePins.add(cand, cr)
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)
}
-4
View File
@@ -19,10 +19,6 @@ Self-sufficiency 5.6), every seat capped by Locality or Self-sufficiency.
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.
### Data
+1 -1
View File
@@ -19,7 +19,7 @@ func withTransforms(calls map[string]builtin) map[string]builtin {
if _, clash := calls[name]; clash {
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
}
+17 -17
View File
@@ -17,13 +17,13 @@ type proven struct {
integral bool
notOperand string // why some render reads as no finite number, the way calc reads it
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.
type valueProof struct {
memo map[node]proven
columns map[node]proven
memo map[node]proven
columnMemo map[node]proven
}
// 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
// rendering "".
func (p *valueProof) proveColumn(n node) proven {
if v, done := p.columns[n]; done {
if v, done := p.columnMemo[n]; done {
return v
}
if p.columns == nil {
p.columns = map[node]proven{}
if p.columnMemo == nil {
p.columnMemo = map[node]proven{}
}
items, nullable := columnItems(n)
var v proven
@@ -69,8 +69,8 @@ func (p *valueProof) proveColumn(n node) proven {
v = v.or(w)
}
}
v.null = v.null || nullable
p.columns[n] = v
v.nullable = v.nullable || nullable
p.columnMemo[n] = v
return v
}
@@ -87,10 +87,10 @@ func (p *valueProof) prove(n node) proven {
v = p.proveUnion(n.items)
case *template:
v = p.proveTemplate(n)
case *column:
case *tableColumn:
v = p.proveCells(n)
case *row:
v = unproven(fmt.Sprintf("%q renders a row of %s, which is composed text", n.t.format.format, n.t.category))
case *tableRow:
v = unproven(fmt.Sprintf("%q renders a row of %s, which is composed text", n.t.formatTemplate.format, n.t.category))
default:
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.
func (p *valueProof) proveCells(c *column) proven {
func (p *valueProof) proveCells(c *tableColumn) proven {
var v proven
for r := 0; r < c.t.rowCount(); r++ {
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.
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.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 == "" {
v.notOperand = w.notOperand
}
@@ -164,8 +164,8 @@ func (p *valueProof) proveTemplate(t *template) proven {
return p.proveUnion(leaves)
case name == "calc":
return p.proveCalc(t, body, args)
case builtins[name].number != nil:
return builtins[name].number(body, builtins[name].prints, args)
case builtins[name].proveNumber != nil:
return builtins[name].proveNumber(body, builtins[name].prints, args)
case isTransform:
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) {
var v proven
integral := l.integral && r.integral
switch n.op {
switch n.operator {
case '+':
v = bounded(l.lo+r.lo, l.hi+r.hi, integral)
case '-':
@@ -295,7 +295,7 @@ var typedCalls, operandCalls = numberCalls(false), numberCalls(true)
func numberCalls(text bool) string {
var calls []string
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+"()}")
}
}