From d6ac8ed9fd53296296298b6293b8601bb9810bae Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Fri, 25 Sep 2026 19:10:18 +0200 Subject: [PATCH] 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 --- AGENTS.md | 2 +- builtins.go | 32 ++++---- builtins_test.go | 2 +- calc.go | 16 ++-- data.go | 24 +++--- datatype.go | 4 +- doc.go | 6 +- docs/decisions.md | 4 +- drawfence.go | 46 +++++------ family.go | 36 ++++----- fejkdata.go | 8 +- format.go | 36 ++++----- graph.go | 22 +++--- hold.go | 10 +-- hold_test.go | 2 +- holdfence.go | 12 +-- node.go | 14 ++-- path.go | 14 ++-- pins.go | 16 ++-- record.go | 32 ++++---- reference.go | 2 +- render.go | 12 +-- shape_test.go | 10 +-- struct.go | 50 ++++++------ table.go | 190 +++++++++++++++++++++++----------------------- table_test.go | 8 +- todo.md | 4 - transform.go | 2 +- value.go | 34 ++++----- 29 files changed, 324 insertions(+), 326 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 6f7c33c..cd7bb56 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 diff --git a/builtins.go b/builtins.go index d960701..dd8e72d 100644 --- a/builtins.go +++ b/builtins.go @@ -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)) }}, }) diff --git a/builtins_test.go b/builtins_test.go index 8d419fe..b19c95d 100644 --- a/builtins_test.go +++ b/builtins_test.go @@ -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) } } diff --git a/calc.go b/calc.go index eb0c2c4..a279978 100644 --- a/calc.go +++ b/calc.go @@ -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 } diff --git a/data.go b/data.go index c49c23c..3989539 100644 --- a/data.go +++ b/data.go @@ -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 diff --git a/datatype.go b/datatype.go index 31e1a0a..b8f4610 100644 --- a/datatype.go +++ b/datatype.go @@ -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 } } diff --git a/doc.go b/doc.go index 7b39821..eb4017f 100644 --- a/doc.go +++ b/doc.go @@ -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`. diff --git a/docs/decisions.md b/docs/decisions.md index a50f7d6..2389f2e 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -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. diff --git a/drawfence.go b/drawfence.go index aebaa52..d1ebc31 100644 --- a/drawfence.go +++ b/drawfence.go @@ -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 diff --git a/family.go b/family.go index 4893597..a8003bf 100644 --- a/family.go +++ b/family.go @@ -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) +} diff --git a/fejkdata.go b/fejkdata.go index f476679..ea89e72 100644 --- a/fejkdata.go +++ b/fejkdata.go @@ -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 { diff --git a/format.go b/format.go index 7224bce..fd3635a 100644 --- a/format.go +++ b/format.go @@ -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}) diff --git a/graph.go b/graph.go index 9c017ce..ec34bee 100644 --- a/graph.go +++ b/graph.go @@ -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: diff --git a/hold.go b/hold.go index 7d7ccd7..d8f00f5 100644 --- a/hold.go +++ b/hold.go @@ -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) } diff --git a/hold_test.go b/hold_test.go index 301feca..06c691e 100644 --- a/hold_test.go +++ b/hold_test.go @@ -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"}) }) } diff --git a/holdfence.go b/holdfence.go index bef29f8..c547cde 100644 --- a/holdfence.go +++ b/holdfence.go @@ -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) } } } diff --git a/node.go b/node.go index cc84854..dff687b 100644 --- a/node.go +++ b/node.go @@ -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) } diff --git a/path.go b/path.go index bedd88e..68e9860 100644 --- a/path.go +++ b/path.go @@ -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{} } diff --git a/pins.go b/pins.go index 4b5e668..c8951c9 100644 --- a/pins.go +++ b/pins.go @@ -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 }) diff --git a/record.go b/record.go index 2911f44..6b40b6f 100644 --- a/record.go +++ b/record.go @@ -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)) diff --git a/reference.go b/reference.go index be92a11..3ab410a 100644 --- a/reference.go +++ b/reference.go @@ -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]]} diff --git a/render.go b/render.go index 4aea292..be5a7c5 100644 --- a/render.go +++ b/render.go @@ -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) } diff --git a/shape_test.go b/shape_test.go index 3b6c2c4..aae7dbd 100644 --- a/shape_test.go +++ b/shape_test.go @@ -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) } diff --git a/struct.go b/struct.go index 51f69e6..959fc07 100644 --- a/struct.go +++ b/struct.go @@ -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())) diff --git a/table.go b/table.go index 43ee53c..981de6f 100644 --- a/table.go +++ b/table.go @@ -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) } diff --git a/table_test.go b/table_test.go index 1875906..397c10a 100644 --- a/table_test.go +++ b/table_test.go @@ -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) } diff --git a/todo.md b/todo.md index e8b008c..8ae15fc 100644 --- a/todo.md +++ b/todo.md @@ -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 diff --git a/transform.go b/transform.go index f24be31..d5a3ae6 100644 --- a/transform.go +++ b/transform.go @@ -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 } diff --git a/value.go b/value.go index 2d94b83..1dd8b23 100644 --- a/value.go +++ b/value.go @@ -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+"()}") } }