Give head, index, bound and draw one meaning each, name familyRoot, and type the token kinds
This commit is contained in:
@@ -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`.
|
||||
|
||||
+2
-2
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 != "" {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+37
-37
@@ -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 + "}"})
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+6
-6
@@ -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++ {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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)...)
|
||||
|
||||
Reference in New Issue
Block a user