Move a table's rows, links and pins into internal/rows, and walk every path through one per-step function, so Fake refuses a level carrying a repeat
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
package rows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/larvit/fejkdata/internal/invariant"
|
||||
)
|
||||
|
||||
type pin[P any] struct {
|
||||
t *Table[P]
|
||||
row int
|
||||
}
|
||||
|
||||
// Pins is the table rows fixed so far.
|
||||
type Pins[P any] struct {
|
||||
inline [8]pin[P] // sized so a render over a country's five-deep geo tree stays off the heap
|
||||
used int
|
||||
spill map[*Table[P]]int
|
||||
}
|
||||
|
||||
func (p *Pins[P]) Pinned(t *Table[P]) (int, bool) {
|
||||
for _, q := range p.inline[:p.used] {
|
||||
if q.t == t {
|
||||
return q.row, true
|
||||
}
|
||||
}
|
||||
r, ok := p.spill[t]
|
||||
return r, ok
|
||||
}
|
||||
|
||||
// MustRow is the row pinned for t, which the walk reaching a column pinned.
|
||||
func (p *Pins[P]) MustRow(t *Table[P]) int {
|
||||
r, ok := p.Pinned(t)
|
||||
if !ok {
|
||||
panic(invariant.Broken("a column of %s is rendered with no row pinned", t.segment))
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (p *Pins[P]) add(t *Table[P], r int) {
|
||||
if p.used < len(p.inline) {
|
||||
p.inline[p.used] = pin[P]{t, r}
|
||||
p.used++
|
||||
return
|
||||
}
|
||||
if p.spill == nil {
|
||||
p.spill = map[*Table[P]]int{}
|
||||
}
|
||||
p.spill[t] = r
|
||||
}
|
||||
|
||||
// pin pins row r of t, and the rows of t's ancestors it links to. It checks nothing, so r must
|
||||
// agree with the row p pins of nearestPinned(t): ask clash first, as PinRow does, or draw r inside
|
||||
// it, as DrawIn does.
|
||||
func (p *Pins[P]) pin(t *Table[P], r int) {
|
||||
for stop := p.nearestPinned(t); t != stop; t = t.parent {
|
||||
p.add(t, r)
|
||||
if t.parent != stop {
|
||||
r = t.parentRow(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nearestPinned is the first of t and its ancestors p pins, where pinning a row of t stops; nil
|
||||
// where none is.
|
||||
func (p *Pins[P]) nearestPinned(t *Table[P]) *Table[P] {
|
||||
for ; t != nil; t = t.parent {
|
||||
if _, ok := p.Pinned(t); ok {
|
||||
return t
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// clash is the table whose pinned row keeps row r of t out: t itself pinned to another row, or the
|
||||
// nearest ancestor pinned to a row r is not inside; nil where none does.
|
||||
func (p *Pins[P]) clash(t *Table[P], r int) *Table[P] {
|
||||
if pr, ok := p.Pinned(t); ok && pr != r {
|
||||
return t
|
||||
}
|
||||
for a := t.parent; a != nil; a = a.parent {
|
||||
if pa, ok := p.Pinned(a); ok && !t.under(r, a, pa) {
|
||||
return a
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PinRow pins row r of t where it agrees with the rows pinned before it.
|
||||
func (p *Pins[P]) PinRow(t *Table[P], r int) error {
|
||||
switch a := p.clash(t, r); {
|
||||
case a == t:
|
||||
pr, _ := p.Pinned(t)
|
||||
return fmt.Errorf("%s and %s are two rows of %s", t.selectorSpelling(pr), t.selectorSpelling(r), t.path)
|
||||
case a != nil:
|
||||
pa, _ := p.Pinned(a)
|
||||
return fmt.Errorf("%s is not inside %s", t.selectorSpelling(r), a.selectorSpelling(pa))
|
||||
}
|
||||
p.pin(t, r)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Select pins the row of t that sel names, and returns it.
|
||||
func (p *Pins[P]) Select(t *Table[P], sel string) (int, error) {
|
||||
r, err := t.find(sel, p)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return r, p.PinRow(t, r)
|
||||
}
|
||||
|
||||
// inside keeps the rows of t that sit inside every pinned ancestor.
|
||||
func (p *Pins[P]) inside(t *Table[P], rows []int) []int {
|
||||
for a := t.parent; a != nil; a = a.parent {
|
||||
pa, ok := p.Pinned(a)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
var kept []int
|
||||
for _, r := range rows {
|
||||
if t.under(r, a, pa) {
|
||||
kept = append(kept, r)
|
||||
}
|
||||
}
|
||||
rows = kept
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// Above is a pin set holding the rows p pins of t and its ancestors, and none below them.
|
||||
func (p *Pins[P]) Above(t *Table[P]) *Pins[P] {
|
||||
q := new(Pins[P])
|
||||
for ; t != nil; t = t.parent {
|
||||
if r, ok := p.Pinned(t); ok {
|
||||
q.add(t, r)
|
||||
}
|
||||
}
|
||||
return q
|
||||
}
|
||||
@@ -0,0 +1,508 @@
|
||||
// Package rows holds a table's rows: the TSV they are read from, the options proved over
|
||||
// them, the links between tables, and the rows a draw takes or a selector names.
|
||||
package rows
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/larvit/fejkdata/internal/drawstate"
|
||||
"github.com/larvit/fejkdata/internal/grammar"
|
||||
)
|
||||
|
||||
// Table is the rows of one table category. P is what owns them, which a step to a linked
|
||||
// table reaches through Payload.
|
||||
type Table[P any] struct {
|
||||
payload P
|
||||
segment string // path's last segment
|
||||
path string // the category's path from the data root, which a selector is written at
|
||||
file string
|
||||
options Columns
|
||||
header []string
|
||||
col map[string]int
|
||||
cells []string // rows × columns, flat
|
||||
keyIndex int // keyIndex through parentIndex are column indexes, -1 where the option is absent
|
||||
nameIndex int
|
||||
weightIndex int
|
||||
parentIndex int
|
||||
weights []float64 // nil when uniform
|
||||
cum []float64 // cumulative weights, nil when uniform
|
||||
byKey map[string]int
|
||||
parent *Table[P]
|
||||
children map[string]*Table[P]
|
||||
once sync.Once
|
||||
lookup rowLookup
|
||||
}
|
||||
|
||||
// Columns names the column each option reads, "" where the option is absent.
|
||||
type Columns struct {
|
||||
Key, Name, Parent, Weight string
|
||||
}
|
||||
|
||||
// rowLookup is what selection and linked draws look up, built on the first draw
|
||||
// that needs it.
|
||||
type rowLookup struct {
|
||||
byName map[string][]int
|
||||
rowsByParent map[string][]int
|
||||
childCum map[string][]float64 // cumulative weights per parent key, nil when uniform
|
||||
}
|
||||
|
||||
// Parse reads the TSV data, the rows of the table at path, and proves what the options
|
||||
// claim of them. payload is what owns the rows.
|
||||
func Parse[P any](payload P, segment, path, file, data string, o Columns) (*Table[P], error) {
|
||||
t := &Table[P]{payload: payload, segment: segment, path: path, file: file, options: o, keyIndex: -1, nameIndex: -1, weightIndex: -1, parentIndex: -1}
|
||||
if err := t.parseRows(data); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", file, err)
|
||||
}
|
||||
if err := t.bindOptions(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.checkSelectorCells(); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", file, err)
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (t *Table[P]) Payload() P { return t.payload }
|
||||
func (t *Table[P]) Segment() string { return t.segment }
|
||||
func (t *Table[P]) Path() string { return t.path }
|
||||
func (t *Table[P]) File() string { return t.file }
|
||||
func (t *Table[P]) Options() Columns { return t.options }
|
||||
func (t *Table[P]) Header() []string { return t.header }
|
||||
func (t *Table[P]) Parent() *Table[P] { return t.parent }
|
||||
|
||||
// LinkColumn is the index of the column holding the parent's key, -1 where there is none.
|
||||
func (t *Table[P]) LinkColumn() int { return t.parentIndex }
|
||||
|
||||
func (t *Table[P]) Column(name string) (int, bool) {
|
||||
i, ok := t.col[name]
|
||||
return i, ok
|
||||
}
|
||||
|
||||
func (t *Table[P]) Len() int { return len(t.cells) / len(t.header) }
|
||||
|
||||
func (t *Table[P]) Cell(row, col int) string { return t.cells[row*len(t.header)+col] }
|
||||
|
||||
// parseRows reads the TSV: the header line names the columns, each following line
|
||||
// is a row of as many cells. Cells are substrings of data, so the file is held once.
|
||||
func (t *Table[P]) parseRows(data string) error {
|
||||
data = strings.TrimSuffix(strings.TrimPrefix(data, "\xEF\xBB\xBF"), "\n")
|
||||
header, rest, _ := strings.Cut(data, "\n")
|
||||
header = strings.TrimSuffix(header, "\r")
|
||||
if header == "" {
|
||||
return fmt.Errorf("has no header line naming its columns")
|
||||
}
|
||||
t.header = strings.Split(header, "\t")
|
||||
t.col = make(map[string]int, len(t.header))
|
||||
for i, name := range t.header {
|
||||
if err := grammar.CheckName(name); err != nil {
|
||||
return fmt.Errorf("column %w", err)
|
||||
}
|
||||
if _, dup := t.col[name]; dup {
|
||||
return fmt.Errorf("column %q is named twice", name)
|
||||
}
|
||||
t.col[name] = i
|
||||
}
|
||||
if header == data {
|
||||
return fmt.Errorf("has no rows below its header; a table is at least two rows")
|
||||
}
|
||||
m := len(t.header)
|
||||
t.cells = make([]string, 0, m*(strings.Count(rest, "\n")+1))
|
||||
for line := 2; ; line++ {
|
||||
row, more, found := strings.Cut(rest, "\n")
|
||||
row = strings.TrimSuffix(row, "\r")
|
||||
if err := t.appendRow(row, line); err != nil {
|
||||
return err
|
||||
}
|
||||
if !found {
|
||||
break
|
||||
}
|
||||
rest = more
|
||||
}
|
||||
if t.Len() < 2 {
|
||||
return fmt.Errorf("has one row, which is a template; write it as one")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// appendRow splits one line into as many cells as the header has columns.
|
||||
func (t *Table[P]) appendRow(row string, line int) error {
|
||||
if row == "" {
|
||||
return fmt.Errorf("line %d is empty; every line below the header is a row", line)
|
||||
}
|
||||
m := len(t.header)
|
||||
for n := 0; n < m; n++ {
|
||||
cell, next, tab := strings.Cut(row, "\t")
|
||||
if !tab && n < m-1 || tab && n == m-1 {
|
||||
return fmt.Errorf("line %d has %s cells than the %d columns", line, map[bool]string{true: "more", false: "fewer"}[tab], m)
|
||||
}
|
||||
t.cells = append(t.cells, cell)
|
||||
row = next
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// bindOptions resolves each option to its column and proves what it claims of the
|
||||
// cells: a key is unique, a weight a positive number.
|
||||
func (t *Table[P]) bindOptions() error {
|
||||
o := t.options
|
||||
for _, opt := range []struct {
|
||||
name, value string
|
||||
into *int
|
||||
}{{"key", o.Key, &t.keyIndex}, {"name", o.Name, &t.nameIndex}, {"parent", o.Parent, &t.parentIndex}, {"weight", o.Weight, &t.weightIndex}} {
|
||||
if opt.value == "" {
|
||||
continue
|
||||
}
|
||||
i, ok := t.col[opt.value]
|
||||
if !ok {
|
||||
return fmt.Errorf("%s names no column %q of %s; the columns are %v", opt.name, opt.value, t.file, t.header)
|
||||
}
|
||||
*opt.into = i
|
||||
}
|
||||
if t.nameIndex >= 0 && t.keyIndex < 0 && t.parentIndex < 0 {
|
||||
return fmt.Errorf("name selects a row as a key does, and lists the rows it matches by their keys, so it needs a key column, or a parent inside which each name is one row; add key")
|
||||
}
|
||||
if err := t.proveKeys(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := t.proveNamesInsideParent(); err != nil {
|
||||
return err
|
||||
}
|
||||
return t.sumWeights()
|
||||
}
|
||||
|
||||
// proveNamesInsideParent proves a name without a key names one row inside its parent.
|
||||
func (t *Table[P]) proveNamesInsideParent() error {
|
||||
if t.keyIndex >= 0 || t.nameIndex < 0 {
|
||||
return nil
|
||||
}
|
||||
inside := make(map[string]int, t.Len())
|
||||
for r := 0; r < t.Len(); r++ {
|
||||
k := t.Cell(r, t.parentIndex) + "\t" + t.Cell(r, t.nameIndex)
|
||||
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.nameIndex), first+2, t.header[t.parentIndex], t.Cell(r, t.parentIndex))
|
||||
}
|
||||
inside[k] = r
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// proveKeys proves every key names one row, and keeps the map a link is proved by.
|
||||
func (t *Table[P]) proveKeys() error {
|
||||
if t.keyIndex < 0 {
|
||||
return nil
|
||||
}
|
||||
t.byKey = make(map[string]int, t.Len())
|
||||
for r := 0; r < t.Len(); r++ {
|
||||
k := t.Cell(r, t.keyIndex)
|
||||
if k == "" {
|
||||
return fmt.Errorf("%s line %d: the key is empty", t.file, r+2)
|
||||
}
|
||||
if first, dup := t.byKey[k]; dup {
|
||||
return fmt.Errorf("%s line %d: key %q repeats line %d; a key selects one row", t.file, r+2, k, first+2)
|
||||
}
|
||||
t.byKey[k] = r
|
||||
}
|
||||
for r := 0; r < t.Len() && t.nameIndex >= 0; r++ {
|
||||
if n := t.Cell(r, t.nameIndex); t.byKey[n] != r {
|
||||
if other, isKey := t.byKey[n]; isKey {
|
||||
return fmt.Errorf("%s line %d: name %q is the key of line %d, which a selector reads first, so the name could never select this row", t.file, r+2, n, other+2)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sumWeights proves every weight is a positive number, and keeps the weights and their
|
||||
// running sum.
|
||||
func (t *Table[P]) sumWeights() error {
|
||||
if t.weightIndex < 0 {
|
||||
return nil
|
||||
}
|
||||
t.weights, t.cum = make([]float64, t.Len()), make([]float64, t.Len())
|
||||
total := 0.0
|
||||
for r := range t.cum {
|
||||
w, err := strconv.ParseFloat(t.Cell(r, t.weightIndex), 64)
|
||||
if err != nil || math.IsInf(w, 0) || math.IsNaN(w) || w <= 0 {
|
||||
return fmt.Errorf("%s line %d: weight %q is not a positive number", t.file, r+2, t.Cell(r, t.weightIndex))
|
||||
}
|
||||
total += w
|
||||
t.weights[r], t.cum[r] = w, total
|
||||
}
|
||||
if math.IsInf(total, 0) {
|
||||
return fmt.Errorf("%s: the weights sum past the largest number", t.file)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkSelectorCells refuses a key or name cell a selector cannot spell.
|
||||
func (t *Table[P]) checkSelectorCells() error {
|
||||
for r := 0; r < t.Len(); r++ {
|
||||
for _, col := range []int{t.keyIndex, t.nameIndex} {
|
||||
if col < 0 {
|
||||
continue
|
||||
}
|
||||
if c := grammar.UnspellableInSelector(t.Cell(r, col)); c != "" {
|
||||
return fmt.Errorf("line %d: %s %q contains %q, which a selector cannot spell", r+2, t.header[col], t.Cell(r, col), c)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Link links t to p, the table its link column names, after proving p has a key, every
|
||||
// link cell is one, and every row of p is linked to. sibling is the table beside t
|
||||
// named name, nil where none is. It is the only writer of a table's parent and children.
|
||||
// docs/decisions.md#a-parent-row-with-no-child-row-is-a-load-error
|
||||
func (t *Table[P]) Link(p *Table[P], sibling func(name string) *Table[P]) error {
|
||||
name := t.header[t.parentIndex]
|
||||
if p.keyIndex < 0 {
|
||||
return fmt.Errorf("parent %q has no key column to link to", name)
|
||||
}
|
||||
if err := t.checkAncestors(p, sibling); err != nil {
|
||||
return err
|
||||
}
|
||||
linked := make(map[string]bool, p.Len())
|
||||
for r := 0; r < t.Len(); r++ {
|
||||
k := t.Cell(r, t.parentIndex)
|
||||
if _, ok := p.byKey[k]; !ok {
|
||||
return fmt.Errorf("%s line %d: %s %q is no key of %s", t.file, r+2, name, k, p.file)
|
||||
}
|
||||
linked[k] = true
|
||||
}
|
||||
for r := 0; r < p.Len(); r++ {
|
||||
if k := p.Cell(r, p.keyIndex); !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)
|
||||
}
|
||||
}
|
||||
t.parent = p
|
||||
if p.children == nil {
|
||||
p.children = map[string]*Table[P]{}
|
||||
}
|
||||
p.children[t.segment] = t
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkAncestors refuses a cycle in the chain of parents starting at p, t's parent, and
|
||||
// refuses an ancestor that has a column named like t.
|
||||
func (t *Table[P]) checkAncestors(p *Table[P], sibling func(name string) *Table[P]) error {
|
||||
var ancestors []*Table[P]
|
||||
for q, seen := p, map[*Table[P]]bool{t: true}; q != nil; q = sibling(q.header[q.parentIndex]) {
|
||||
if seen[q] {
|
||||
return fmt.Errorf("parent cycle: %s reaches itself through its parents", q.segment)
|
||||
}
|
||||
seen[q] = true
|
||||
ancestors = append(ancestors, q)
|
||||
if q.parentIndex < 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, q := range ancestors {
|
||||
if _, clash := q.col[t.segment]; clash {
|
||||
return fmt.Errorf("%q is named like a column of %q, its ancestor, so %s.%s could read either; rename one", t.segment, q.segment, q.segment, t.segment)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Children is the tables linked to t, by segment.
|
||||
func (t *Table[P]) Children() []*Table[P] {
|
||||
out := make([]*Table[P], 0, len(t.children))
|
||||
for _, c := range t.children {
|
||||
out = append(out, c)
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i].segment < out[j].segment })
|
||||
return out
|
||||
}
|
||||
|
||||
// Descendant is the table named name among those linked to t, at any depth.
|
||||
func (t *Table[P]) Descendant(name string) *Table[P] {
|
||||
if c, ok := t.children[name]; ok {
|
||||
return c
|
||||
}
|
||||
for _, c := range t.children {
|
||||
if d := c.Descendant(name); d != nil {
|
||||
return d
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StepUp proves a ".." after a row of t can step up: t has a parent, rest has no empty
|
||||
// segment and names it, and no selector follows, since a row selected below the parent row
|
||||
// could lie outside it.
|
||||
func (t *Table[P]) StepUp(rest []string) error {
|
||||
if t.parent == nil {
|
||||
return fmt.Errorf(`%s has no parent table for ".." to step up to`, t.segment)
|
||||
}
|
||||
if err := grammar.CheckSegments(rest); err != nil {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case len(rest) == 0 || rest[0] != t.parent.segment:
|
||||
return fmt.Errorf(`".." steps up from %s to its parent table, so name that next: %s`, t.segment, t.climbTo(rest))
|
||||
case grammar.HasSelector(rest):
|
||||
return fmt.Errorf(`no selector after "..": a row selected there could lie outside the row stepped up to; select from the table's path from the data root: %s`, grammar.JoinSegments(append([]string{t.parent.path}, rest[1:]...)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// climbTo spells the ".." steps from t up to the ancestor rest names first, or the one step to
|
||||
// t's parent where no ancestor is named.
|
||||
func (t *Table[P]) climbTo(rest []string) string {
|
||||
var b strings.Builder
|
||||
for a := t.parent; a != nil; a = a.parent {
|
||||
b.WriteString(".." + a.segment)
|
||||
if len(rest) > 0 && a.segment == rest[0] {
|
||||
return b.String()
|
||||
}
|
||||
}
|
||||
return ".." + t.parent.segment
|
||||
}
|
||||
|
||||
func (t *Table[P]) builtLookup() *rowLookup {
|
||||
t.once.Do(func() {
|
||||
if t.nameIndex >= 0 {
|
||||
t.lookup.byName = make(map[string][]int, t.Len())
|
||||
for r := 0; r < t.Len(); r++ {
|
||||
n := t.Cell(r, t.nameIndex)
|
||||
t.lookup.byName[n] = append(t.lookup.byName[n], r)
|
||||
}
|
||||
}
|
||||
if t.parentIndex >= 0 {
|
||||
t.lookup.rowsByParent = map[string][]int{}
|
||||
for r := 0; r < t.Len(); r++ {
|
||||
k := t.Cell(r, t.parentIndex)
|
||||
t.lookup.rowsByParent[k] = append(t.lookup.rowsByParent[k], r)
|
||||
}
|
||||
if t.weights != nil {
|
||||
t.lookup.childCum = make(map[string][]float64, len(t.lookup.rowsByParent))
|
||||
for k, rows := range t.lookup.rowsByParent {
|
||||
cum, total := make([]float64, len(rows)), 0.0
|
||||
for i, r := range rows {
|
||||
total += t.weights[r]
|
||||
cum[i] = total
|
||||
}
|
||||
t.lookup.childCum[k] = cum
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return &t.lookup
|
||||
}
|
||||
|
||||
func (t *Table[P]) Draw(s *drawstate.State) int {
|
||||
if t.cum == nil {
|
||||
return s.IntN(t.Len())
|
||||
}
|
||||
return s.Weighted(t.cum)
|
||||
}
|
||||
|
||||
// drawUnder picks a row among those linked to parent row pr.
|
||||
func (t *Table[P]) drawUnder(s *drawstate.State, pr int) int {
|
||||
lookup := t.builtLookup()
|
||||
k := t.parent.Cell(pr, t.parent.keyIndex)
|
||||
rows := lookup.rowsByParent[k]
|
||||
if lookup.childCum == nil {
|
||||
return rows[s.IntN(len(rows))]
|
||||
}
|
||||
return rows[s.Weighted(lookup.childCum[k])]
|
||||
}
|
||||
|
||||
// DrawIn is the row of t a path or a named pick reads: the one pinned in p, else one drawn inside the nearest
|
||||
// pinned ancestor — its parent drawn inside that first where the ancestor is further up — or
|
||||
// over the whole table, and pinned with its ancestors.
|
||||
func (t *Table[P]) DrawIn(s *drawstate.State, p *Pins[P]) int {
|
||||
if r, ok := p.Pinned(t); ok {
|
||||
return r
|
||||
}
|
||||
r := -1
|
||||
for a := t.parent; a != nil && r < 0; a = a.parent {
|
||||
if _, ok := p.Pinned(a); !ok {
|
||||
continue
|
||||
}
|
||||
if t.parent != a {
|
||||
t.parent.DrawIn(s, p)
|
||||
}
|
||||
pr, _ := p.Pinned(t.parent)
|
||||
r = t.drawUnder(s, pr)
|
||||
}
|
||||
if r < 0 {
|
||||
r = t.Draw(s)
|
||||
}
|
||||
p.pin(t, r)
|
||||
return r
|
||||
}
|
||||
|
||||
// parentRow is the row of t's parent that row r links to.
|
||||
func (t *Table[P]) parentRow(r int) int { return t.parent.byKey[t.Cell(r, t.parentIndex)] }
|
||||
|
||||
// under reports whether row r of t sits inside row pr of ancestor a.
|
||||
func (t *Table[P]) under(r int, a *Table[P], pr int) bool {
|
||||
for c, row := t, r; c.parent != nil; c, row = c.parent, c.parentRow(row) {
|
||||
if c.parent == a {
|
||||
return c.parentRow(row) == pr
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// find is the row a selector names: by key first, then by name, where a name
|
||||
// naming several rows resolves inside the ancestors pinned.
|
||||
func (t *Table[P]) find(sel string, pins *Pins[P]) (int, error) {
|
||||
if t.keyIndex < 0 && t.nameIndex < 0 {
|
||||
return 0, fmt.Errorf("%s has no key or name column to select a row by", t.path)
|
||||
}
|
||||
if r, ok := t.byKey[sel]; ok {
|
||||
return r, nil
|
||||
}
|
||||
rows := t.builtLookup().byName[sel]
|
||||
if len(rows) > 1 {
|
||||
rows = pins.inside(t, rows)
|
||||
}
|
||||
switch len(rows) {
|
||||
case 1:
|
||||
return rows[0], nil
|
||||
case 0:
|
||||
return 0, fmt.Errorf("no row of %s has key or name %q", t.path, sel)
|
||||
}
|
||||
return 0, t.ambiguous(sel, rows)
|
||||
}
|
||||
|
||||
// ambiguous names each row a selector could have meant: a keyed table by its keys,
|
||||
// and one told apart only by its parent by the path that selects it.
|
||||
func (t *Table[P]) ambiguous(sel string, rows []int) error {
|
||||
spellings := make([]string, len(rows))
|
||||
for i, r := range rows {
|
||||
if t.keyIndex < 0 {
|
||||
spellings[i] = t.selectorSpelling(r)
|
||||
} else {
|
||||
spellings[i] = t.Cell(r, t.keyIndex)
|
||||
}
|
||||
}
|
||||
listed := strings.Join(spellings, ", ")
|
||||
if t.keyIndex < 0 {
|
||||
return fmt.Errorf("%q names %d rows of %s; select it inside its %s, one of %s", sel, len(rows), t.path, t.parent.path, listed)
|
||||
}
|
||||
inside := ""
|
||||
if t.parent != nil {
|
||||
inside = fmt.Sprintf(", or select it inside its %s", t.parent.path)
|
||||
}
|
||||
return fmt.Errorf("%q names %d rows of %s; select one by key, one of %s%s", sel, len(rows), t.path, listed, inside)
|
||||
}
|
||||
|
||||
// selectorSpelling is how a path writes a selected row, for messages: by key, or
|
||||
// by name inside its parent's row.
|
||||
func (t *Table[P]) selectorSpelling(r int) string {
|
||||
switch {
|
||||
case t.keyIndex >= 0:
|
||||
return t.path + "[" + t.Cell(r, t.keyIndex) + "]"
|
||||
case t.nameIndex >= 0:
|
||||
return t.parent.selectorSpelling(t.parentRow(r)) + "." + t.segment + "[" + t.Cell(r, t.nameIndex) + "]"
|
||||
}
|
||||
return fmt.Sprintf("%s line %d", t.file, r+2)
|
||||
}
|
||||
Reference in New Issue
Block a user