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