diff --git a/doc.go b/doc.go index f6a59dd..8109d45 100644 --- a/doc.go +++ b/doc.go @@ -16,8 +16,8 @@ package fejkdata // Vocabulary // -// - draw — one pick from a choice, or one row taken from a table, and the value a -// read produced: `pick`, `resolveChoice`, `table.drawRow`, `draw`. +// - draw — one pick from a choice, or one row taken from a table: `pick`, +// `resolveChoice`, `table.drawRow`. // - expansion — one render of one format: `expand`. // - render — what owns one `holdSet`, and so what one reference draw spans: // `Generator.Fake`, `FakeRecord`, `Template.Fake` and `RecordTemplate.Fake` @@ -31,7 +31,7 @@ package fejkdata // - pin — fixing which row of a table the render uses, which the draw fences replay: // `pinSet`, `pinSet.pin`, `table.drawIn`. // - family — a table and every table reaching it through a chain of -// `table.parentT`, named by the root that chain ends at: `table.family`. +// `table.parentT`, named by the root that chain ends at: `table.familyRoot`. // - whole — a read of a table with no selector and no descent, {/city}, landing on // the `table` itself: it draws a row apart from every pin, so the fences keep the rows // its cells stand in as `drawAt.wholePins`, beside `drawAt.wholeTable`. diff --git a/drawfence.go b/drawfence.go index 01adc1e..2988095 100644 --- a/drawfence.go +++ b/drawfence.go @@ -97,7 +97,7 @@ func checkOwnFamily(t *template) error { seen[n] = true for _, e := range renderEdges(n) { if a, isRef := refRead(n, e.label); isRef { - if head, isTable := n.(*template).head(a.key).(*table); isTable && head.family() == own.family() { + if head, isTable := n.(*template).head(a.key).(*table); isTable && head.familyRoot() == own.familyRoot() { return e, head, true } } @@ -357,7 +357,7 @@ func checkFamilies(reads []pathRead) error { continue } for _, o := range reads[:i] { - if o.tr == nil || o.at.group != r.at.group || alternatives(o.at, r.at) || o.tr.headTable.family() != r.tr.headTable.family() { + if o.tr == nil || o.at.group != r.at.group || alternatives(o.at, r.at) || o.tr.headTable.familyRoot() != r.tr.headTable.familyRoot() { continue } if err := checkFamilyPair(o, r); err != nil { diff --git a/format.go b/format.go index 1fac5ad..7fcc43c 100644 --- a/format.go +++ b/format.go @@ -8,9 +8,9 @@ import ( // ftoken is one unit of a scanned format string: a literal rune or the body of a // {…} token. type ftoken struct { - kind byte // 'l' literal rune, 't' token body - char rune - body string + isToken bool // a {…} body, else a literal rune + char rune + body string } // eachToken scans a format string once and calls fn for each unit, the single @@ -24,7 +24,7 @@ func eachToken(format string, fn func(ftoken) error) error { switch c := rs[i]; c { case '{': if i+1 < len(rs) && rs[i+1] == '{' { - t.kind, t.char = 'l', '{' + t.char = '{' i++ break } @@ -38,17 +38,17 @@ func eachToken(format string, fn func(ftoken) error) error { if end >= len(rs) { return fmt.Errorf("unterminated '{' in %q", format) } - t.kind, t.body = 't', string(rs[i+1:end]) + t.isToken, t.body = true, string(rs[i+1:end]) i = end case '}': if i+1 < len(rs) && rs[i+1] == '}' { - t.kind, t.char = 'l', '}' + t.char = '}' i++ break } return fmt.Errorf("lone '}' in %q; a literal brace is written }}", format) default: - t.kind, t.char = 'l', c + t.char = c } if err := fn(t); err != nil { return err @@ -57,17 +57,24 @@ func eachToken(format string, fn func(ftoken) error) error { return nil } -// formatToken is one parsed unit of a format: a literal run, a field alternation or a -// builtin call. +// formatToken is one parsed unit of a format. type formatToken struct { - kind byte // 'l' literal run, 'f' field alternation, 'b' builtin call - lit string // kind 'l' + kind tokenKind + lit string // literalRun body string // the braces' content, as written - fn string // kind 'b' - args []string // kind 'b' - names []string // kind 'f': the '|' arms; kind 'b': the operands its builtin reads + fn string // builtinCall + args []string // builtinCall + names []string // fieldAlternation: the '|' arms; builtinCall: the operands its builtin reads } +type tokenKind uint8 + +const ( + builtinCall tokenKind = iota + fieldAlternation + literalRun +) + // parseFormat is the one reading of a format's tokens: a '(' outside a selector makes // a token a call. func parseFormat(format string) ([]formatToken, error) { @@ -75,25 +82,25 @@ func parseFormat(format string) ([]formatToken, error) { var lit strings.Builder flush := func() { if lit.Len() > 0 { - toks = append(toks, formatToken{kind: 'l', lit: lit.String()}) + toks = append(toks, formatToken{kind: literalRun, lit: lit.String()}) lit.Reset() } } err := eachToken(format, func(t ftoken) error { - if t.kind == 'l' { + if !t.isToken { lit.WriteRune(t.char) return nil } flush() if indexOutside(t.body, '(') < 0 { - toks = append(toks, formatToken{kind: 'f', body: t.body, names: splitOutside(t.body, '|')}) + toks = append(toks, formatToken{kind: fieldAlternation, body: t.body, names: splitOutside(t.body, '|')}) return nil } name, args, ok := funcCall(t.body) if !ok { return fmt.Errorf("malformed function token {%s}", t.body) } - toks = append(toks, formatToken{kind: 'b', body: t.body, fn: name, args: args, names: builtinOperands(name, args)}) + toks = append(toks, formatToken{kind: builtinCall, body: t.body, fn: name, args: args, names: builtinOperands(name, args)}) return nil }) if err != nil { @@ -207,9 +214,9 @@ func checkTokens(toks []formatToken, fields map[string]node) error { func checkToken(t formatToken, fields map[string]node) error { switch t.kind { - case 'b': + case builtinCall: return checkFunc(t, fields) - case 'f': + case fieldAlternation: for _, name := range t.names { if isRef(name) { if _, _, err := refShape(name); err != nil { @@ -236,7 +243,7 @@ func checkArm(name string, fields map[string]node, wholeToken bool) error { if err := checkSegments(a); err != nil { return err } - head, ok := fields[a.key] + field, ok := fields[a.key] if !ok { if a.key == "" { return fmt.Errorf("a name is never empty, so this token can name no field") @@ -253,7 +260,7 @@ func checkArm(name string, fields map[string]node, wholeToken bool) error { } return fmt.Errorf("no field %q", a.key) } - if err := checkPath(head, a.tail, a.key); err != nil { + if err := checkPath(field, a.tail, a.key); err != nil { return fmt.Errorf("field %q: %w", a.key, err) } return nil @@ -294,24 +301,24 @@ func checkNoRepeatedArm(body string, names []string) error { // arm is one alternative of a {a|b} token or one operand, split into the key // `template.head` resolves and the tail of a dotted path into it. A non-empty tail -// is what makes the arm a bound draw: its head is drawn once per expansion (see +// is what makes the arm a held draw: its head is drawn once per expansion (see // compileOps). type arm struct { spelling string // as written, for messages key string tail []string - levels []string // the key at each level the tail passes through, the head's first + levels []string // the key at each level the tail passes through, the arm's key first path string // key and tail, the one path every way of writing this read shares } // splitArm splits one name into key and tail. refs maps a reference to what -// linkRefs bound it to; before linking, a reference is whole. +// linkRefs resolved it to; before linking, a reference is whole. func splitArm(name string, refs map[string]refBinding) arm { if isRef(name) { - b, bound := refs[name] - if !bound || len(b.tail) == 0 { + b, linked := refs[name] + if !linked || len(b.tail) == 0 { key := name - if bound { + if linked { key = b.key } return arm{spelling: name, key: key, path: key} @@ -351,17 +358,17 @@ func checkSegments(a arm) error { return nil } -// callFn is a builtin bound to one call site: its args already parsed. It reads the +// callFn is a builtin prepared for one call site: its args already parsed. It reads the // output emitted so far in the current expansion (a derivation's payload) and the // values of the operands it named, which expand read for it. type callFn func(s *session, emitted string, operands []string) string // op is one compiled unit of a format string: a literal run, a field alternation, -// or a builtin already bound to its args. compile builds these so render never +// or a builtin already prepared with its args. compile builds these so render never // re-scans the format. type op struct { formatToken - arms []arm // kind 'f': the '|' alternatives, split into key and path once + arms []arm // fieldAlternation: the '|' alternatives, split into key and path once call callFn // operands are the fields the builtin reads, in the order its operands func // fixed; expand reads them before the call. nil for a builtin that reads none. @@ -369,18 +376,18 @@ type op struct { } // formatOps is a compiled format: its ops, the size of its literal text (to size -// the render buffer), and the names drawn once per expansion. bound maps each level +// the render buffer), and the names drawn once per expansion. pathLevels maps each level // a path reads into to the first such path; held is every such level plus the // fields an operand reads; holder maps each held name to the first reader holding // it, for error messages. The maps are nil when the format holds nothing, so data // that holds nothing carries no render-time cost. type formatOps struct { - ops []op - grow int - bound map[string]string - held map[string]bool - holder map[string]string - heldLocal bool // some held name is kept by the expansion itself rather than the render's hold + ops []op + grow int + pathLevels map[string]string + held map[string]bool + holder map[string]string + heldLocal bool // some held name is kept by the expansion itself rather than the render's hold } func (c *formatOps) holdName(a arm, label string) { @@ -396,11 +403,11 @@ func (c *formatOps) holdName(a arm, label string) { c.holder[a.key] = label } if len(a.tail) > 0 { - if c.bound == nil { - c.bound = map[string]string{} + if c.pathLevels == nil { + c.pathLevels = map[string]string{} } - if _, named := c.bound[a.key]; !named { - c.bound[a.key] = a.spelling + if _, named := c.pathLevels[a.key]; !named { + c.pathLevels[a.key] = a.spelling } } } @@ -432,12 +439,12 @@ func compileOps(toks []formatToken, refs map[string]refBinding) formatOps { var c formatOps for _, tok := range toks { switch tok.kind { - case 'l': + case literalRun: c.grow += len(tok.lit) c.ops = append(c.ops, op{formatToken: tok}) - case 'b': + case builtinCall: c.function(tok, refs) - case 'f': + case fieldAlternation: c.field(tok, refs) } } diff --git a/graph.go b/graph.go index bc7856d..fbaee34 100644 --- a/graph.go +++ b/graph.go @@ -18,7 +18,7 @@ func walkNodes(root map[string]node, fn func(path string, n node) error) error { } // eachNode visits n and every node contained within it once, passing the dot path -// that reaches each. It never crosses a reference edge — a bound {/path} field is +// that reaches each. It never crosses a reference edge — a {/path} reference is // skipped — so a single inline node is walked on its own. func eachNode(n node, path string, fn func(path string, n node) error) error { seen := map[node]bool{} @@ -105,7 +105,7 @@ type renderEdge struct { operand string } -// reached names an edge as the author spelled it, the vocabulary boundReaders uses +// reached names an edge as the author spelled it, the vocabulary levelReaders uses // for the sibling fence. func (e renderEdge) reached() string { if e.operand != "" { diff --git a/hold.go b/hold.go index 81ee2a0..18e1236 100644 --- a/hold.go +++ b/hold.go @@ -11,12 +11,12 @@ import ( // its sibling names, and a render one per group for its reference paths. type hold struct { variant map[string]node - value map[string]draw + value map[string]readValue pins pinSet } -// draw is what one read drew: its text, and whether it landed on a null. -type draw struct { +// readValue is what one read drew: its text, and whether it landed on a null. +type readValue struct { text string null bool } @@ -26,7 +26,7 @@ type draw struct { // kept, so {place.postal-code} and {place.locality} read one row, either read twice // gives one value, and a shown operand is the operand computed. Every other name is // drawn afresh, so {word} {word} still draws twice. -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) readValue { if s.trace != nil { traceRead(s.trace, t, sc, a) } @@ -34,7 +34,7 @@ func readField(s *session, t *template, held *hold, sc renderScope, a arm) draw if len(a.tail) > 0 { 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 readValue{text: render(s, t.head(a.key), sc)} } d := readHold(held, sc, a) if r, done := d.value[a.path]; done { @@ -45,7 +45,7 @@ func readField(s *session, t *template, held *hold, sc renderScope, a arm) draw } r := renderLeaf(s, drawPath(t.head(a.key), a.tail, a.key, &pathDraw{s: s, pins: &d.pins, held: d, a: &a}), sc) if d.value == nil { - d.value = map[string]draw{} + d.value = map[string]readValue{} } d.value[a.path] = r return r @@ -81,20 +81,20 @@ func readHold(held *hold, sc renderScope, a arm) *hold { // renderLeaf draws and renders what a read lands on: null on a null item, or on a column of one // reference alone whose read drew null. -func renderLeaf(s *session, n node, sc renderScope) draw { +func renderLeaf(s *session, n node, sc renderScope) readValue { n = resolveChoice(s, n) switch leaf := n.(type) { case *nullItem: - return draw{null: true} + return readValue{null: true} case *template: if leaf.readsColumn != nil { return readField(s, leaf, nil, sc.in(leaf), leaf.readsColumn.a) } } - return draw{text: render(s, n, sc)} + return readValue{text: render(s, n, sc)} } -// resolveChoice resolves a choice to one variant, so a bound head is a concrete node the +// resolveChoice resolves a choice to one variant, so a held head is a concrete node the // rest of the expansion shares. Nested choices unwrap too: a draw is one value, not // another set to pick from. func resolveChoice(s *session, n node) node { diff --git a/holdfence.go b/holdfence.go index e3e749f..1327f7b 100644 --- a/holdfence.go +++ b/holdfence.go @@ -15,81 +15,81 @@ func heldCheck(path string, n node) error { if !ok || len(t.held) == 0 { return nil } - readers := boundReaders(t.ops, t.bound) - for _, head := range heldHeads(t) { - if _, isPath := t.bound[head]; isPath && isRef(head) { + readers := levelReaders(t.ops, t.pathLevels) + for _, name := range heldNames(t) { + if _, isPath := t.pathLevels[name]; isPath && isRef(name) { continue // held for the render: drawCheck compares every read of it, by path, across the render and its groups } - if err := checkHeadHeld(t, head, readers); err != nil { + if err := checkNameHeld(t, name, readers); err != nil { return fmt.Errorf("%s: %w", path, err) } } return nil } -// heldHeads lists a template's held names, operand heads first, then paths, each +// heldNames lists a template's held names, operands' first, then paths', each // in name order: a level read both ways is reported by the operand's fence, and // which overlap is reported does not vary. -func heldHeads(t *template) []string { - heads := make([]string, 0, len(t.held)) - for head := range t.held { - heads = append(heads, head) +func heldNames(t *template) []string { + names := make([]string, 0, len(t.held)) + for name := range t.held { + names = append(names, name) } - sort.Slice(heads, func(i, j int) bool { - _, pi := t.bound[heads[i]] - _, pj := t.bound[heads[j]] + sort.Slice(names, func(i, j int) bool { + _, pi := t.pathLevels[names[i]] + _, pj := t.pathLevels[names[j]] if pi != pj { return !pi } - return heads[i] < heads[j] + return names[i] < names[j] }) - return heads + return names } -// heldNodes is what the hold of head answers for: a path pins the levels it +// heldNodes is what the hold of name answers for: a path pins the levels it // passes through and the leaf it lands on, an operand exactly the value its render // produces. -func heldNodes(t *template, head string, readers []reader) map[node]bool { +func heldNodes(t *template, name string, readers []reader) map[node]bool { held := map[node]bool{} - if _, isPath := t.bound[head]; !isPath { - operandDraw(t.head(head), held) + if _, isPath := t.pathLevels[name]; !isPath { + operandDraw(t.head(name), held) return held } for _, r := range readers { - if a := splitArm(r.spelling, t.refs); a.key == head { - coverPath(t.head(head), a.tail, held) + if a := splitArm(r.spelling, t.refs); a.key == name { + coverPath(t.head(name), a.tail, held) } } return held } -// checkHeadHeld rejects every route to what head's hold pins except the readers +// checkNameHeld rejects every route to what name's hold pins except the readers // holding it. One seen set across the edges: a node that cannot reach the level // cannot reach it by another route either, so it is walked once. -func checkHeadHeld(t *template, head string, readers []reader) error { - held := heldNodes(t, head, readers) +func checkNameHeld(t *template, name string, readers []reader) error { + held := heldNodes(t, name, readers) if len(held) == 0 { return nil // a fixed head holds nothing to reach } - reader, isPath := t.bound[head] + reader, isPath := t.pathLevels[name] seen := map[node]bool{} for _, e := range renderEdges(t) { - if splitArm(e.label, t.refs).key == head || !renders(e.to, held, seen) { + if splitArm(e.label, t.refs).key == name || !renders(e.to, held, seen) { continue } if isPath { - return fmt.Errorf("%s renders %q, which {%s} reads a path into; name the fields you want instead", e.reached(), head, reader) + return fmt.Errorf("%s renders %q, which {%s} reads a path into; name the fields you want instead", e.reached(), name, reader) } - return fmt.Errorf("%s renders %q, which a {%s()} also reads; reach it one way so it is drawn once", e.reached(), head, operandReader(t, head)) + return fmt.Errorf("%s renders %q, which a {%s()} also reads; reach it one way so it is drawn once", e.reached(), name, operandReader(t, name)) } return nil } -// operandReader names the builtin whose operand holds head. -func operandReader(t *template, head string) string { +// operandReader names the builtin whose operand holds name. +func operandReader(t *template, name string) string { for _, o := range t.ops { for _, a := range o.operands { - if a.key == head { + if a.key == name { return o.fn } } @@ -177,8 +177,8 @@ func renders(n node, want, seen map[node]bool) bool { // disagree. Reads are compared in sorted order, so which pair is reported does not // depend on where the tokens sit. // docs/decisions.md#a-bare-reference-draws-each-time-a-reference-path-is-held -func checkNoOverlap(ops []op, bound map[string]string) error { - names := boundReaders(ops, bound) +func checkNoOverlap(ops []op, pathLevels map[string]string) error { + names := levelReaders(ops, pathLevels) // Stable over one format-order scan, so two readers of one name (a token and a // calc operand both naming "p") are reported as the format writes them. sort.SliceStable(names, func(i, j int) bool { return names[i].path < names[j].path }) @@ -192,23 +192,23 @@ func checkNoOverlap(ops []op, bound map[string]string) error { return nil } -// reader is one way a format reaches a bound field: as written, by its path, +// reader is one way a format reaches a path level: as written, by its path, // and how to name it. type reader struct{ spelling, path, label string } -// boundReaders lists every way a format reaches a bound sibling field, in the order the +// levelReaders lists every way a format reaches a sibling path level, in the order the // format writes them. An operand renders its field, so it names a level exactly // as a token does; one scan finds both, which is what puts them in one order. -func boundReaders(ops []op, bound map[string]string) []reader { +func levelReaders(ops []op, pathLevels map[string]string) []reader { var names []reader for _, o := range ops { for _, a := range o.operands { - if _, isBound := bound[a.key]; isBound && !isRef(a.key) { + if _, isLevel := pathLevels[a.key]; isLevel && !isRef(a.key) { names = append(names, reader{a.spelling, a.path, fmt.Sprintf("%s operand %q", o.fn, a.spelling)}) } } for _, a := range o.arms { - if _, isBound := bound[a.key]; isBound && !isRef(a.key) { + if _, isLevel := pathLevels[a.key]; isLevel && !isRef(a.key) { names = append(names, reader{a.spelling, a.path, "token {" + a.spelling + "}"}) } } diff --git a/holdset.go b/holdset.go index bae0d47..b03fb4a 100644 --- a/holdset.go +++ b/holdset.go @@ -21,7 +21,7 @@ type renderScope struct { } func eagerHoldSet() holdSet { - return holdSet{unnamed: hold{variant: map[string]node{}, value: map[string]draw{}}} + return holdSet{unnamed: hold{variant: map[string]node{}, value: map[string]readValue{}}} } // renderOnce renders n as one render, over a hold set of its own. @@ -67,7 +67,7 @@ func (sc renderScope) hold() *hold { if sc.set.named == nil { sc.set.named = map[string]*hold{} } - d = &hold{variant: map[string]node{}, value: map[string]draw{}} + d = &hold{variant: map[string]node{}, value: map[string]readValue{}} sc.set.named[strings.Clone(sc.group)] = d } return d diff --git a/node.go b/node.go index bb0975c..8e9f1a1 100644 --- a/node.go +++ b/node.go @@ -62,17 +62,17 @@ type template struct { // Filled by `template.compileFormat` at link, from `template.tokens` and `template.refs`: ops []op // what expand walks grow int // minimum output size, to size the render buffer - // bound maps each field the format addresses by dotted path to one path token + // pathLevels maps each field the format addresses by dotted path to one path token // reading it, which is the half of an overlap the fences name. nil when the // format takes no path. - bound map[string]string - // held is every name drawn once per expansion: the bound levels above, plus the + pathLevels map[string]string + // held is every name drawn once per expansion: the path levels above, plus the // siblings a {calc()} reads. nil when the format holds nothing (see expand). held map[string]bool heldLocal bool // some held name is kept by the expansion itself, so expand makes its hold // Filled by `linkTemplateRefs` and `keyDrawGroup`, from the assembled tree: - refs map[string]refBinding // each reference the format reads -> what it is bound to + refs map[string]refBinding // each reference the format reads -> what it resolves to refHeads map[string]node // each refBinding.key -> the category it names readsColumn *columnRead // set when the format is one reference alone reading a record's column drawGroupKey string // its draw group keyed by its category: what a render reads its reference paths under @@ -214,7 +214,7 @@ func (t *template) fixedText() (string, bool) { switch { case len(t.tokens) == 0: return "", true - case len(t.tokens) == 1 && t.tokens[0].kind == 'l': + case len(t.tokens) == 1 && t.tokens[0].kind == literalRun: return t.tokens[0].lit, true } return "", false @@ -224,8 +224,8 @@ func (t *template) fixedText() (string, bool) { // compiled reads. func (t *template) compileFormat() error { c := compileOps(t.tokens, t.refs) - t.ops, t.grow, t.bound, t.held, t.heldLocal = c.ops, c.grow, c.bound, c.held, c.heldLocal - if err := checkNoOverlap(t.ops, t.bound); err != nil { + t.ops, t.grow, t.pathLevels, t.held, t.heldLocal = c.ops, c.grow, c.pathLevels, c.held, c.heldLocal + if err := checkNoOverlap(t.ops, t.pathLevels); err != nil { return err } return checkNoRepeatedRead(c) diff --git a/path.go b/path.go index 17f9640..e07156b 100644 --- a/path.go +++ b/path.go @@ -343,11 +343,11 @@ type pathDraw struct { } // drawPath walks w over a path proved first, by a probe or at load, so the walk -// cannot fail. head names n in the panic. -func drawPath(n node, tail []string, head string, w *pathDraw) node { +// cannot fail. level names n in the panic. +func drawPath(n node, tail []string, level string, w *pathDraw) node { leaf, err := w.walk(n, tail) if err != nil { - panic(fmt.Sprintf("fejkdata: %s: %v; the path should have been proved before it was drawn", join(head, joinSegments(tail)), err)) + panic(fmt.Sprintf("fejkdata: %s: %v; the path should have been proved before it was drawn", join(level, joinSegments(tail)), err)) } return leaf } diff --git a/reference.go b/reference.go index fceff0f..089c8cb 100644 --- a/reference.go +++ b/reference.go @@ -9,7 +9,7 @@ import ( // data root, {.a} from the folder this file sits in, {..a} from the folder above. func isRef(name string) bool { return strings.HasPrefix(name, ".") || strings.HasPrefix(name, "/") } -// refBinding is what a reference was bound to: the head key its category is held +// refBinding is what a reference resolves to: the key its category is held // under, and the tail read into it. type refBinding struct { key string @@ -105,11 +105,11 @@ func (t *template) bindRefs(folder []string, path, category string, names []stri if err != nil { return fmt.Errorf("%s: reference {%s}: %w", path, name, err) } - head, target, tail, err := resolveCategory(root, segments) + categorySegs, target, tail, err := resolveCategory(root, segments) if err != nil { return fmt.Errorf("%s: reference {%s}: %w", path, name, err) } - key := "/" + strings.Join(head, ".") + key := "/" + strings.Join(categorySegs, ".") if category != "" && key == "/"+category { return fmt.Errorf("%s: reference {%s}: names the category it sits in; read a sibling field as a path, or move the shared value into its own category and reference that", path, name) } @@ -144,7 +144,7 @@ func columnReadOf(t *template) *columnRead { // loneRef is the reference a format of one reference token and nothing else reads. func loneRef(toks []formatToken) (string, bool) { - if len(toks) != 1 || toks[0].kind != 'f' || len(toks[0].names) != 1 || !isRef(toks[0].names[0]) { + if len(toks) != 1 || toks[0].kind != fieldAlternation || len(toks[0].names) != 1 || !isRef(toks[0].names[0]) { return "", false } return toks[0].names[0], true @@ -187,8 +187,8 @@ func eachTemplate(root map[string]node, fn func(folder []string, path string, t } // resolveCategory walks a dotted path through the folders to the category it -// names, returning that head, the node, and the tail left to read into it. -func resolveCategory(root map[string]node, segments []string) (head []string, target node, tail []string, err error) { +// names, returning that category's segments, the node, and the tail left to read into it. +func resolveCategory(root map[string]node, segments []string) (category []string, target node, tail []string, err error) { var n node = &folder{children: root} i := 0 for ; i < len(segments); i++ { diff --git a/render.go b/render.go index 1275386..de13d17 100644 --- a/render.go +++ b/render.go @@ -114,17 +114,17 @@ func expand(s *session, t *template, sc renderScope) string { if t.heldLocal { held = &hold{ variant: make(map[string]node, len(t.held)), - value: make(map[string]draw, len(t.held)), + value: make(map[string]readValue, len(t.held)), } } for i := range t.ops { o := &t.ops[i] switch o.kind { - case 'l': + case literalRun: b.WriteString(o.lit) - case 'f': + case fieldAlternation: b.WriteString(readField(s, t, held, sc, o.arms[s.IntN(len(o.arms))]).text) - case 'b': + case builtinCall: // Read before the call, so the value a calc computes is the value the // format showed. calcVars fixed the order op.operands holds. var operands []string diff --git a/table.go b/table.go index 9c407d9..78e8383 100644 --- a/table.go +++ b/table.go @@ -32,12 +32,12 @@ type table struct { parentT *table children map[string]*table once sync.Once - index tableIndex + lookup rowLookup } -// tableIndex is what selection and linked draws look up, built on the first draw +// rowLookup is what selection and linked draws look up, built on the first draw // that needs it. -type tableIndex struct { +type rowLookup struct { byName map[string][]int rowsByParent map[string][]int childCum map[string][]float64 // cumulative weights per parent key, nil when uniform @@ -226,7 +226,7 @@ func (t *table) bindOptions(o tableOptionValues) error { 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.indexKeys(); err != nil { + if err := t.mapKeys(); err != nil { return err } if err := t.proveNamesInsideParent(); err != nil { @@ -251,8 +251,8 @@ func (t *table) proveNamesInsideParent() error { return nil } -// indexKeys proves every key names one row, and keeps the index a link is proved by. -func (t *table) indexKeys() error { +// mapKeys proves every key names one row, and keeps the map a link is proved by. +func (t *table) mapKeys() error { if t.keyIndex < 0 { return nil } @@ -329,7 +329,7 @@ func (t *table) compileRowFormat(format string) error { return err } for _, tok := range toks { - if tok.kind != 'f' { + if tok.kind != fieldAlternation { continue } for _, name := range tok.names { @@ -440,36 +440,36 @@ func (t *table) linkParent(path string, siblings map[string]node) error { return nil } -func (t *table) indexed() *tableIndex { +func (t *table) builtLookup() *rowLookup { t.once.Do(func() { if t.nameIndex >= 0 { - t.index.byName = make(map[string][]int, t.rowCount()) + t.lookup.byName = make(map[string][]int, t.rowCount()) for r := 0; r < t.rowCount(); r++ { n := t.cell(r, t.nameIndex) - t.index.byName[n] = append(t.index.byName[n], r) + t.lookup.byName[n] = append(t.lookup.byName[n], r) } } if t.parentIndex >= 0 { - t.index.rowsByParent = map[string][]int{} + t.lookup.rowsByParent = map[string][]int{} for r := 0; r < t.rowCount(); r++ { k := t.cell(r, t.parentIndex) - t.index.rowsByParent[k] = append(t.index.rowsByParent[k], r) + t.lookup.rowsByParent[k] = append(t.lookup.rowsByParent[k], r) } if t.cum != nil { - t.index.childCum = make(map[string][]float64, len(t.index.rowsByParent)) - for k, rows := range t.index.rowsByParent { + 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 { w, _ := strconv.ParseFloat(t.cell(r, t.weightIndex), 64) // sumWeights proved it total += w cum[i] = total } - t.index.childCum[k] = cum + t.lookup.childCum[k] = cum } } } }) - return &t.index + return &t.lookup } // drawRow picks a row over the whole table. The session is concrete rather than the rng @@ -488,7 +488,7 @@ func pickCum(s *session, cum []float64) int { // drawUnder picks a row among those linked to parent row pr. func (t *table) drawUnder(s *session, pr int) int { - ix := t.indexed() + ix := t.builtLookup() k := t.parentT.cell(pr, t.parentT.keyIndex) rows := ix.rowsByParent[k] if ix.childCum == nil { @@ -567,7 +567,7 @@ func (t *table) find(sel string, pins *pinSet) (int, error) { if r, ok := t.byKey[sel]; ok { return r, nil } - rows := t.indexed().byName[sel] + rows := t.builtLookup().byName[sel] if len(rows) > 1 { rows = pins.inside(t, rows) } @@ -614,8 +614,8 @@ func (t *table) selectorSpelling(r int) string { return fmt.Sprintf("%s line %d", t.file, r+2) } -// family is the table a chain of parents ends at. -func (t *table) family() *table { +// familyRoot is the table a chain of parents ends at. +func (t *table) familyRoot() *table { for t.parentT != nil { t = t.parentT } diff --git a/todo.md b/todo.md index ce50901..f6fbef1 100644 --- a/todo.md +++ b/todo.md @@ -21,9 +21,6 @@ Self-sufficiency 5.6), every seat capped by Locality, and all nine naming This round, in order: -- Give `head`, `index`, `bound` and `draw` one meaning each, name `table.family` - for the root it returns, and replace the token kind bytes `'l'`, `'t'`, `'f'` and - `'b'`, whose `'l'` means different things in `ftoken` and `formatToken`. - Start every render from one spelling of an empty hold set: `Fake` uses `holdSet{}`, the record and struct paths `eagerHoldSet()`. - Correct the false comments: `funcCall` names `checkFunc` where `parseFormat` diff --git a/value.go b/value.go index 89de4d8..4b405c6 100644 --- a/value.go +++ b/value.go @@ -157,7 +157,7 @@ func (p *valueProof) proveTemplate(t *template) proven { o := t.ops[0] body, name, args := o.body, o.fn, o.args switch _, isTransform := transforms[name]; { - case o.kind == 'f': + case o.kind == fieldAlternation: var leaves []node for _, a := range o.arms { leaves = append(leaves, pathLeaves(t.head(a.key), a.tail)...)