Split the picks and the name fences out of name.go, state the repeat refusal and the naming rule where they hold, and draw a memo's variants one way
This commit is contained in:
@@ -156,8 +156,8 @@ work with no data on disk. A directory is a namespace: each JSON file is a
|
|||||||
category named after the file, each subdirectory a dot-path segment, so
|
category named after the file, each subdirectory a dot-path segment, so
|
||||||
`mydata/sv_SE/person.json` is `sv_SE.person` and replaces the shipped one.
|
`mydata/sv_SE/person.json` is `sv_SE.person` and replaces the shipped one.
|
||||||
Sources merge in order; matching folders combine, any other clash is won by the
|
Sources merge in order; matching folders combine, any other clash is won by the
|
||||||
last loaded. Names may not use `.`, `|`, `(`, `{`, `}`, `[`, `]`, `"` or `/`, nor be
|
last loaded. Names may not use `.`, `|`, `(`, `{`, `}`, `[`, `]`, `"`, `/` or ` as `,
|
||||||
`-`, which a struct tag reserves; dot-prefixed entries are skipped, so a data directory can also be a checkout.
|
which binds a [name](#names), nor be `-`, which a struct tag reserves; dot-prefixed entries are skipped, so a data directory can also be a checkout.
|
||||||
|
|
||||||
Each locale carries `address`, `color`, `company`, `date`, `email`, `first-name`,
|
Each locale carries `address`, `color`, `company`, `date`, `email`, `first-name`,
|
||||||
`ip`, `last-name`, `person`, `phone`, `price`, `sentence`, `sex`, `time`, `url`,
|
`ip`, `last-name`, `person`, `phone`, `price`, `sentence`, `sex`, `time`, `url`,
|
||||||
@@ -735,10 +735,10 @@ Refused at `New`, each error naming what to write instead:
|
|||||||
bound, which the bare reference spells: `{/word as w}{w}` is `{/word}`;
|
bound, which the bare reference spells: `{/word as w}{w}` is `{/word}`;
|
||||||
- a path through a name that selects a row;
|
- a path through a name that selects a row;
|
||||||
- `{n}` beside `{n.w}` where `n`'s category renders `w` twice, as in `{w}-{w}`;
|
- `{n}` beside `{n.w}` where `n`'s category renders `w` twice, as in `{w}-{w}`;
|
||||||
- reads of a name in two draw groups, or inside a `repeat`, where what it names reads a
|
- reads of a name in two draw groups, or inside a `repeat` nested where the name is bound,
|
||||||
reference path, which each draw group and iteration draws apart;
|
where what it names reads a reference path, which each draw group and iteration draws
|
||||||
- a binding in a table's format or cell;
|
apart;
|
||||||
- a field, column, category or folder name containing ` as `.
|
- a binding in a table's format or cell.
|
||||||
|
|
||||||
### Draw group
|
### Draw group
|
||||||
|
|
||||||
@@ -816,8 +816,8 @@ The target is about a microsecond per value (goal 12).
|
|||||||
Each file is parsed, validated and weight-indexed once, in `New`, bar a table's name and
|
Each file is parsed, validated and weight-indexed once, in `New`, bar a table's name and
|
||||||
parent columns, which are mapped on the first `Fake` that selects by name or descends
|
parent columns, which are mapped on the first `Fake` that selects by name or descends
|
||||||
through it. Proving the draw fences adds a pass over the loaded tree per
|
through it. Proving the draw fences adds a pass over the loaded tree per
|
||||||
fence, and walks what a render reads only where data binds a reference, so a set that
|
fence, and walks what a render reads only where data holds a reference, so a set that
|
||||||
binds none pays for the passes alone. A `Fake` call then costs about what its output
|
holds none pays for the passes alone. A `Fake` call then costs about what its output
|
||||||
costs: an unweighted pick is O(1) whatever the list's length, a weighted one
|
costs: an unweighted pick is O(1) whatever the list's length, a weighted one
|
||||||
O(log n), and long formats, deep nesting and many tokens add cost in proportion to
|
O(log n), and long formats, deep nesting and many tokens add cost in proportion to
|
||||||
the output.
|
the output.
|
||||||
@@ -933,7 +933,9 @@ doc.go the package doc, and the vocabulary the package is written in
|
|||||||
fejkdata.go Generator, New, options, the embedded data set, List
|
fejkdata.go Generator, New, options, the embedded data set, List
|
||||||
node.go the node model and JSON -> node compilation, with its fence on a nested drawGroup
|
node.go the node model and JSON -> node compilation, with its fence on a nested drawGroup
|
||||||
table.go tables: the rows TSV, its options and links, row selection and draws
|
table.go tables: the rows TSV, its options and links, row selection and draws
|
||||||
name.go names: {ref as n} bindings, the scopes they live in, and the picks a render keeps under them
|
name.go names: {ref as n} bindings, the scopes they live in, and the reads they answer
|
||||||
|
namefence.go the load fences over names: reads in two draw groups or a nested repeat, and a field rendered twice under a pick
|
||||||
|
pick.go a render's named picks: the frames of the name scopes rendering, and the reads kept under a name
|
||||||
path.go the dotted-path walks with their selectors, and proving a path resolves
|
path.go the dotted-path walks with their selectors, and proving a path resolves
|
||||||
render.go Fake, the recursive renderer (choices, format strings, expansions) and the reads its tokens make
|
render.go Fake, the recursive renderer (choices, format strings, expansions) and the reads its tokens make
|
||||||
record.go records: Record, the JSON/CSV/SQL serializers, and their entry points
|
record.go records: Record, the JSON/CSV/SQL serializers, and their entry points
|
||||||
@@ -945,7 +947,7 @@ holdfence.go the load fences over the hold: the routes and spellings that rea
|
|||||||
drawfence.go the load fences over reference draws and their refusals: a drawGroup's, a render's and a record's columns judged on what they read, and a table reaching its own family
|
drawfence.go the load fences over reference draws and their refusals: a drawGroup's, a render's and a record's columns judged on what they read, and a table reaching its own family
|
||||||
reads.go the load-time fold of what a render reads, and what a path reads of a table family
|
reads.go the load-time fold of what a render reads, and what a path reads of a table family
|
||||||
pins.go the pin set: the table rows a render fixes, which the draw fences replay
|
pins.go the pin set: the table rows a render fixes, which the draw fences replay
|
||||||
reference.go reference sigils, and binding references across the tree
|
reference.go reference sigils, and resolving references across the tree
|
||||||
graph.go the node graph: tree walks, containment and render edges, and the cycle and repeat fences
|
graph.go the node graph: tree walks, containment and render edges, and the cycle and repeat fences
|
||||||
builtins.go the {name()} function registry and its implementations
|
builtins.go the {name()} function registry and its implementations
|
||||||
layout.go date and time layouts: the instants one is proved against, and the two samples
|
layout.go date and time layouts: the instants one is proved against, and the two samples
|
||||||
|
|||||||
+4
-3
@@ -905,15 +905,16 @@ it.
|
|||||||
|
|
||||||
The scope is lexical. A read entering a category, by a reference, through a name or from
|
The scope is lexical. A read entering a category, by a reference, through a name or from
|
||||||
`Fake`, sees no frame (one render of a scope's picks) opened above it, so a category renders
|
`Fake`, sees no frame (one render of a scope's picks) opened above it, so a category renders
|
||||||
the same whoever references it. That read opens the frames around where it lands from its own
|
the same whoever references it. A reference path, a read through a name or `Fake` opens the
|
||||||
memo, so reads sharing a memo, `{n}` and `{n.path}` alike, read one pick. A reference path's
|
frames around where it lands from its own memo, so reads sharing a memo, `{n}` and `{n.path}`
|
||||||
|
alike, read one pick; a bare reference lands on a category's root, which opens fresh ones. A reference path's
|
||||||
memo lasts per render and draw group, a name's per pick, and `Fake("cat.field")`'s per render.
|
memo lasts per render and draw group, a name's per pick, and `Fake("cat.field")`'s per render.
|
||||||
|
|
||||||
A pick keeps only the levels a read of the name addresses, so a template draws the same way
|
A pick keeps only the levels a read of the name addresses, so a template draws the same way
|
||||||
under a name as anywhere else (goal 5.2). A reference read under a pick draws in the draw group
|
under a name as anywhere else (goal 5.2). A reference read under a pick draws in the draw group
|
||||||
of the read of the name, the group the draw fences survey it in, so two names bound to one
|
of the read of the name, the group the draw fences survey it in, so two names bound to one
|
||||||
category share what it reads by reference path. Where what a name names reads a reference
|
category share what it reads by reference path. Where what a name names reads a reference
|
||||||
path, its reads stay in one draw group and outside any repeat: a reference path is held per
|
path, its reads stay in one draw group and outside any repeat nested where it is bound: a reference path is held per
|
||||||
draw group and per iteration, so what the pick kept and what it renders afresh would come from
|
draw group and per iteration, so what the pick kept and what it renders afresh would come from
|
||||||
two draws of it. `todo.md` item 4 ends both: once every `{…}` draws afresh, nothing is held for
|
two draws of it. `todo.md` item 4 ends both: once every `{…}` draws afresh, nothing is held for
|
||||||
the render.
|
the render.
|
||||||
|
|||||||
@@ -287,336 +287,3 @@ func linkName(t *template, a *arm) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkNameReads refuses each binding of t that checkUses refuses, and a read such as {n} whose
|
|
||||||
// pick renders twice a field another read, {n.w}, reads once: the pick keeps one draw of it.
|
|
||||||
func checkNameReads(path string, t *template) error {
|
|
||||||
for _, tok := range t.tokens {
|
|
||||||
if tok.kind != nameBind {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := t.nameScope.bindings[tok.bound].checkUses(); err != nil {
|
|
||||||
return fmt.Errorf("%s: token {%s}: %w", t.site.label(path), tok.body, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return namedReads(t, func(o *op, a *arm) error {
|
|
||||||
for _, leaf := range a.leaves {
|
|
||||||
if err := a.named.checkOnce(a.spelling, leaf, a.path); err != nil {
|
|
||||||
return fmt.Errorf("%s: token {%s}: %w", t.site.label(path), o.body, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkUses refuses a binding of a category read once whole, and reads of b in two draw groups,
|
|
||||||
// or inside a repeat, where what b names reads a reference path.
|
|
||||||
func (b *nameBinding) checkUses() error {
|
|
||||||
if r := b.uses[0]; len(b.uses) == 1 && r.tail == "" && !r.nested && len(b.tail) == 0 {
|
|
||||||
spelling := b.ref
|
|
||||||
if !r.operand {
|
|
||||||
spelling = "{" + spelling + "}"
|
|
||||||
}
|
|
||||||
return fmt.Errorf("name %q is read once, whole, which the bare reference draws the same way; write %s where it is read, and drop the token", b.name, spelling)
|
|
||||||
}
|
|
||||||
if !readsHeld(b.head, map[node]bool{}) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, u := range b.uses {
|
|
||||||
if u.nested {
|
|
||||||
return fmt.Errorf("name %q is read inside a repeat, and what it names reads a reference path, which each iteration draws apart; bind the name inside the repeat, or bind a second name there", b.name)
|
|
||||||
}
|
|
||||||
if u.group != b.uses[0].group {
|
|
||||||
return fmt.Errorf("name %q is read in two draw groups, %s and %s, and what it names reads a reference path, which each draw group draws apart; read the name in one draw group, or bind a name in each", b.name, groupSpelling(b.uses[0].group), groupSpelling(u.group))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func groupSpelling(group string) string {
|
|
||||||
if group == "" {
|
|
||||||
return "the unnamed one"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%q", group)
|
|
||||||
}
|
|
||||||
|
|
||||||
// readsHeld reports whether rendering n, or any field under it, can read a reference path.
|
|
||||||
func readsHeld(n node, seen map[node]bool) bool {
|
|
||||||
if seen[n] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
seen[n] = true
|
|
||||||
if t, isTemplate := n.(*template); isTemplate {
|
|
||||||
for _, o := range t.compiled.ops {
|
|
||||||
for _, a := range append(o.arms[:len(o.arms):len(o.arms)], o.operands...) {
|
|
||||||
if a.kind == refPathRead {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, c := range contained(n) {
|
|
||||||
if readsHeld(c.node, seen) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, e := range renderEdges(n) {
|
|
||||||
if readsHeld(e.to, seen) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkOnce walks n, rendering at key under b's pick, into each level a read of b addresses.
|
|
||||||
func (b *nameBinding) checkOnce(read string, n node, key string) error {
|
|
||||||
switch n := n.(type) {
|
|
||||||
case *choice:
|
|
||||||
for _, item := range n.items {
|
|
||||||
if err := b.checkOnce(read, item, key); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *template:
|
|
||||||
return b.checkTemplateOnce(read, n, key)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *nameBinding) checkTemplateOnce(read string, t *template, key string) error {
|
|
||||||
fresh := map[string]int{}
|
|
||||||
var into []arm
|
|
||||||
for _, o := range t.compiled.ops {
|
|
||||||
for _, a := range append(o.arms[:len(o.arms):len(o.arms)], o.operands...) {
|
|
||||||
if isRef(a.head) || a.kind == namedRead {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, kept := b.addressed[join(key, a.path)]; kept {
|
|
||||||
into = append(into, a)
|
|
||||||
}
|
|
||||||
if a.kind == freshRead {
|
|
||||||
fresh[a.head]++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
heads := make([]string, 0, len(fresh))
|
|
||||||
for head := range fresh {
|
|
||||||
heads = append(heads, head)
|
|
||||||
}
|
|
||||||
sort.Strings(heads)
|
|
||||||
for _, head := range heads {
|
|
||||||
if by, kept := b.addressed[join(key, head)]; kept && fresh[head] > 1 {
|
|
||||||
return fmt.Errorf("{%s} renders field %q twice, so {%s} cannot say which draw it reads; drop {%s} or {%s}", read, head, by, read, by)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, a := range into {
|
|
||||||
for _, leaf := range a.leaves {
|
|
||||||
if err := b.checkOnce(read, leaf, join(key, a.path)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// namedPick is one draw of a name: the variant drawn at each level a read of it addresses, the
|
|
||||||
// value each read there produced, keyed by the path from the name, and the table rows they
|
|
||||||
// pinned. A reference read under it draws in the draw group of the read of the name, which the
|
|
||||||
// draw fences survey it in.
|
|
||||||
type namedPick struct {
|
|
||||||
named *nameBinding
|
|
||||||
memo drawMemo
|
|
||||||
pins pinSet
|
|
||||||
}
|
|
||||||
|
|
||||||
// pickFrame is one render of a name scope: a pick per binding, each drawn on its first read.
|
|
||||||
type pickFrame struct {
|
|
||||||
scope *nameScope
|
|
||||||
picks []namedPick
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPickFrame(scope *nameScope) *pickFrame {
|
|
||||||
f := &pickFrame{scope: scope, picks: make([]namedPick, len(scope.order))}
|
|
||||||
for i, b := range scope.order {
|
|
||||||
f.picks[i].named = b
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
// frameStack is the frames of the name scopes rendering, innermost last. A render's draws point
|
|
||||||
// at it, and a repeat iteration's share it: holding the frames in the draws, or in a renderScope,
|
|
||||||
// would move every render's draws to the heap.
|
|
||||||
type frameStack struct {
|
|
||||||
frames []*pickFrame
|
|
||||||
}
|
|
||||||
|
|
||||||
// pushFrame opens f until popFrames closes it, returning the mark popFrames takes.
|
|
||||||
func (d *renderDraws) pushFrame(f *pickFrame) int {
|
|
||||||
mark := d.depth()
|
|
||||||
if d.frameStack == nil {
|
|
||||||
d.frameStack = &frameStack{}
|
|
||||||
}
|
|
||||||
d.frameStack.frames = append(d.frameStack.frames, f)
|
|
||||||
return mark
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *renderDraws) popFrames(mark int) {
|
|
||||||
if d.frameStack != nil {
|
|
||||||
d.frameStack.frames = d.frameStack.frames[:mark]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *renderDraws) depth() int {
|
|
||||||
if d.frameStack == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return len(d.frameStack.frames)
|
|
||||||
}
|
|
||||||
|
|
||||||
// enter starts a read landing on n: the read sees no frame opened before it, and gets from memo a
|
|
||||||
// frame for each name scope around n, so reads sharing memo read one pick of each name. It
|
|
||||||
// returns the mark that closes those frames.
|
|
||||||
func (sc renderScope) enter(n node, memo *drawMemo) (renderScope, int) {
|
|
||||||
sc.base, sc.pick = sc.draws.depth(), nil
|
|
||||||
for scope := scopeAround(n); scope != nil; scope = scope.up {
|
|
||||||
if len(scope.order) > 0 {
|
|
||||||
sc.draws.pushFrame(memo.enteredFrame(scope))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sc, sc.base
|
|
||||||
}
|
|
||||||
|
|
||||||
// scopeAround is the innermost name scope a render of n reads names in, short of the frames n
|
|
||||||
// renders itself, one per iteration of a repeat.
|
|
||||||
func scopeAround(n node) *nameScope {
|
|
||||||
switch n := n.(type) {
|
|
||||||
case *template:
|
|
||||||
if n.repeat > 1 && n.nameScope != nil && n.nameScope.owner == n {
|
|
||||||
return n.nameScope.up
|
|
||||||
}
|
|
||||||
return n.nameScope
|
|
||||||
case *choice:
|
|
||||||
return n.nameScope
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *drawMemo) enteredFrame(scope *nameScope) *pickFrame {
|
|
||||||
f, ok := m.enteredFrames[scope]
|
|
||||||
if !ok {
|
|
||||||
f = newPickFrame(scope)
|
|
||||||
if m.enteredFrames == nil {
|
|
||||||
m.enteredFrames = map[*nameScope]*pickFrame{}
|
|
||||||
}
|
|
||||||
m.enteredFrames[scope] = f
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
// renderFrame opens a fresh frame of t's scope, where t binds names and no read entering the
|
|
||||||
// category opened one, returning the mark that closes it, or -1.
|
|
||||||
func (sc renderScope) renderFrame(t *template) int {
|
|
||||||
if t.ownNameScope == nil || sc.frameOf(t.ownNameScope) != nil {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return sc.draws.pushFrame(newPickFrame(t.ownNameScope))
|
|
||||||
}
|
|
||||||
|
|
||||||
// frameOf is the frame of scope rendering since the read entering the category, nil where none is.
|
|
||||||
func (sc renderScope) frameOf(scope *nameScope) *pickFrame {
|
|
||||||
if stack := sc.draws.frameStack; stack != nil {
|
|
||||||
for i := len(stack.frames) - 1; i >= sc.base; i-- {
|
|
||||||
if f := stack.frames[i]; f.scope == scope {
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readName(s *generatorState, sc renderScope, a arm) readValue {
|
|
||||||
f := sc.frameOf(a.named.scope)
|
|
||||||
if f == nil {
|
|
||||||
panic(internalError("name %q is read where no frame of its scope renders", a.named.name))
|
|
||||||
}
|
|
||||||
p := &f.picks[a.named.index]
|
|
||||||
if r, done := p.memo.value[a.path]; done {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
leaf := p.draw(s, a.named.head, a.steps, a.levels, a.path)
|
|
||||||
sc, mark := sc.enter(leaf, &p.memo)
|
|
||||||
r := p.renderAt(s, leaf, a.path, sc)
|
|
||||||
sc.draws.popFrames(mark)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// drawRowOf draws the row a render of t reads: inside the pick's rows where t renders as part of one.
|
|
||||||
func (sc renderScope) drawRowOf(s *generatorState, t *table) int {
|
|
||||||
if sc.pick != nil {
|
|
||||||
return t.drawIn(s, &sc.pick.pins)
|
|
||||||
}
|
|
||||||
return t.drawRow(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// keeps reports whether a read of sc's name addresses the level a starts at.
|
|
||||||
func (sc renderScope) keeps(a arm) bool {
|
|
||||||
_, kept := sc.pick.named.addressed[underKey(sc.pickKey, a.head)]
|
|
||||||
return kept
|
|
||||||
}
|
|
||||||
|
|
||||||
// readUnder reads a of t, which renders as part of the pick sc.pick at sc.pickKey, where a read of
|
|
||||||
// the name addresses it: once per pick, by its path from the name.
|
|
||||||
func readUnder(s *generatorState, t *template, sc renderScope, a arm) readValue {
|
|
||||||
p, key := sc.pick, underKey(sc.pickKey, a.path)
|
|
||||||
if r, done := p.memo.value[key]; done {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
levels := make([]string, len(a.levels))
|
|
||||||
for i, l := range a.levels {
|
|
||||||
levels[i] = underKey(sc.pickKey, l)
|
|
||||||
}
|
|
||||||
return p.renderAt(s, p.draw(s, t.head(a.head), a.steps, levels, key), key, sc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// underKey is the key of path under the pick key prefix. It never returns prefix itself, which a
|
|
||||||
// memo would keep: anything a renderScope holds reaching the heap moves every render's draws there.
|
|
||||||
func underKey(prefix, path string) string {
|
|
||||||
if prefix == "" {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
return prefix + "." + path
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw draws the path key names under p, its variant at key kept too.
|
|
||||||
func (p *namedPick) draw(s *generatorState, head node, steps []pathStep, levels []string, key string) node {
|
|
||||||
leaf := drawSteps(s, head, steps, &p.pins, &p.memo, levels)
|
|
||||||
if c, isChoice := leaf.(*choice); isChoice {
|
|
||||||
leaf = p.memo.variantOf(s, c, key)
|
|
||||||
}
|
|
||||||
return leaf
|
|
||||||
}
|
|
||||||
|
|
||||||
// renderAt renders leaf, drawn at key, as part of p, and keeps what it rendered.
|
|
||||||
func (p *namedPick) renderAt(s *generatorState, leaf node, key string, sc renderScope) readValue {
|
|
||||||
sc = sc.at(leaf, &p.pins)
|
|
||||||
sc.pick, sc.pickKey = p, key
|
|
||||||
r := renderLeaf(s, leaf, sc)
|
|
||||||
if p.memo.value == nil {
|
|
||||||
p.memo.value = map[string]readValue{}
|
|
||||||
}
|
|
||||||
p.memo.value[key] = r
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// variantOf is the variant of c drawn at level, drawn now where none was.
|
|
||||||
func (m *drawMemo) variantOf(s *generatorState, c *choice, level string) node {
|
|
||||||
n, drew := m.variant[level]
|
|
||||||
if !drew {
|
|
||||||
n = resolveChoice(s, c)
|
|
||||||
if m.variant == nil {
|
|
||||||
m.variant = map[string]node{}
|
|
||||||
}
|
|
||||||
m.variant[strings.Clone(level)] = n
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|||||||
+138
@@ -0,0 +1,138 @@
|
|||||||
|
package fejkdata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// checkNameReads refuses each binding of t that checkUses refuses, and a read such as {n} whose
|
||||||
|
// pick renders twice a field another read, {n.w}, reads once: the pick keeps one draw of it.
|
||||||
|
func checkNameReads(path string, t *template) error {
|
||||||
|
for _, tok := range t.tokens {
|
||||||
|
if tok.kind != nameBind {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := t.nameScope.bindings[tok.bound].checkUses(); err != nil {
|
||||||
|
return fmt.Errorf("%s: token {%s}: %w", t.site.label(path), tok.body, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return namedReads(t, func(o *op, a *arm) error {
|
||||||
|
for _, leaf := range a.leaves {
|
||||||
|
if err := a.named.checkOnce(a.spelling, leaf, a.path); err != nil {
|
||||||
|
return fmt.Errorf("%s: token {%s}: %w", t.site.label(path), o.body, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkUses refuses a binding of a category read once whole, and reads of b in two draw groups,
|
||||||
|
// or inside a repeat, where what b names reads a reference path.
|
||||||
|
func (b *nameBinding) checkUses() error {
|
||||||
|
if r := b.uses[0]; len(b.uses) == 1 && r.tail == "" && !r.nested && len(b.tail) == 0 {
|
||||||
|
spelling := b.ref
|
||||||
|
if !r.operand {
|
||||||
|
spelling = "{" + spelling + "}"
|
||||||
|
}
|
||||||
|
return fmt.Errorf("name %q is read once, whole, which the bare reference draws the same way; write %s where it is read, and drop the token", b.name, spelling)
|
||||||
|
}
|
||||||
|
if !readsHeld(b.head, map[node]bool{}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, u := range b.uses {
|
||||||
|
if u.nested {
|
||||||
|
return fmt.Errorf("name %q is read inside a repeat, and what it names reads a reference path, which each iteration draws apart; bind the name inside the repeat, or bind a second name there", b.name)
|
||||||
|
}
|
||||||
|
if u.group != b.uses[0].group {
|
||||||
|
return fmt.Errorf("name %q is read in two draw groups, %s and %s, and what it names reads a reference path, which each draw group draws apart; read the name in one draw group, or bind a name in each", b.name, groupSpelling(b.uses[0].group), groupSpelling(u.group))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func groupSpelling(group string) string {
|
||||||
|
if group == "" {
|
||||||
|
return "the unnamed one"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%q", group)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readsHeld reports whether rendering n, or any field under it, can read a reference path.
|
||||||
|
func readsHeld(n node, seen map[node]bool) bool {
|
||||||
|
if seen[n] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
seen[n] = true
|
||||||
|
if t, isTemplate := n.(*template); isTemplate {
|
||||||
|
for _, o := range t.compiled.ops {
|
||||||
|
for _, a := range slices.Concat(o.arms, o.operands) {
|
||||||
|
if a.kind == refPathRead {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, c := range contained(n) {
|
||||||
|
if readsHeld(c.node, seen) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, e := range renderEdges(n) {
|
||||||
|
if readsHeld(e.to, seen) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkOnce walks n, rendering at key under b's pick, into each level a read of b addresses.
|
||||||
|
func (b *nameBinding) checkOnce(read string, n node, key string) error {
|
||||||
|
switch n := n.(type) {
|
||||||
|
case *choice:
|
||||||
|
for _, item := range n.items {
|
||||||
|
if err := b.checkOnce(read, item, key); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *template:
|
||||||
|
return b.checkTemplateOnce(read, n, key)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *nameBinding) checkTemplateOnce(read string, t *template, key string) error {
|
||||||
|
fresh := map[string]int{}
|
||||||
|
var into []arm
|
||||||
|
for _, o := range t.compiled.ops {
|
||||||
|
for _, a := range slices.Concat(o.arms, o.operands) {
|
||||||
|
if isRef(a.head) || a.kind == namedRead {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, kept := b.addressed[join(key, a.path)]; kept {
|
||||||
|
into = append(into, a)
|
||||||
|
}
|
||||||
|
if a.kind == freshRead {
|
||||||
|
fresh[a.head]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
heads := make([]string, 0, len(fresh))
|
||||||
|
for head := range fresh {
|
||||||
|
heads = append(heads, head)
|
||||||
|
}
|
||||||
|
sort.Strings(heads)
|
||||||
|
for _, head := range heads {
|
||||||
|
if by, kept := b.addressed[join(key, head)]; kept && fresh[head] > 1 {
|
||||||
|
return fmt.Errorf("{%s} renders field %q twice, so {%s} cannot say which draw it reads; drop {%s} or {%s}", read, head, by, read, by)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, a := range into {
|
||||||
|
for _, leaf := range a.leaves {
|
||||||
|
if err := b.checkOnce(read, leaf, join(key, a.path)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -235,10 +235,12 @@ func compileString(s string, site tableSite) (*template, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if site.table != nil {
|
if site.table != nil {
|
||||||
if err := checkTableToks(toks, unbound); err != nil {
|
if err := refuseTableBinding(toks); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
unbound = nil
|
if len(unbound) > 0 {
|
||||||
|
return nil, unbound[0].err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return &template{format: s, tokens: toks, repeat: 1, fromString: true, site: site, unbound: unbound}, nil
|
return &template{format: s, tokens: toks, repeat: 1, fromString: true, site: site, unbound: unbound}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -427,16 +427,7 @@ func drawVariant(s *generatorState, c *choice, memo *drawMemo, levels []string,
|
|||||||
if memo == nil {
|
if memo == nil {
|
||||||
return resolveChoice(s, c)
|
return resolveChoice(s, c)
|
||||||
}
|
}
|
||||||
level := levels[at]
|
return memo.variantOf(s, c, levels[at])
|
||||||
n, drew := memo.variant[level]
|
|
||||||
if !drew {
|
|
||||||
n = resolveChoice(s, c)
|
|
||||||
if memo.variant == nil {
|
|
||||||
memo.variant = map[string]node{}
|
|
||||||
}
|
|
||||||
memo.variant[level] = n
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *table) selector(tail []string) (sel string, rest []string, err error) {
|
func (t *table) selector(tail []string) (sel string, rest []string, err error) {
|
||||||
|
|||||||
@@ -0,0 +1,209 @@
|
|||||||
|
package fejkdata
|
||||||
|
|
||||||
|
// namedPick is one draw of a name: the variant drawn at each level a read of it addresses, the
|
||||||
|
// value each read there produced, keyed by the path from the name, and the table rows they
|
||||||
|
// pinned. A reference read under it draws in the draw group of the read of the name, which the
|
||||||
|
// draw fences survey it in.
|
||||||
|
type namedPick struct {
|
||||||
|
named *nameBinding
|
||||||
|
memo drawMemo
|
||||||
|
pins pinSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// pickFrame is one render of a name scope: a pick per binding, each drawn on its first read.
|
||||||
|
type pickFrame struct {
|
||||||
|
scope *nameScope
|
||||||
|
picks []namedPick
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPickFrame(scope *nameScope) *pickFrame {
|
||||||
|
f := &pickFrame{scope: scope, picks: make([]namedPick, len(scope.order))}
|
||||||
|
for i, b := range scope.order {
|
||||||
|
f.picks[i].named = b
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// frameStack is the frames of the name scopes rendering, innermost last. A render's draws point
|
||||||
|
// at it, and a repeat iteration's share it: holding the frames in the draws, or in a renderScope,
|
||||||
|
// would move every render's draws to the heap.
|
||||||
|
type frameStack struct {
|
||||||
|
frames []*pickFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
// pushFrame opens f until popFrames closes it, returning the mark popFrames takes.
|
||||||
|
func (d *renderDraws) pushFrame(f *pickFrame) int {
|
||||||
|
mark := d.depth()
|
||||||
|
if d.frameStack == nil {
|
||||||
|
d.frameStack = &frameStack{}
|
||||||
|
}
|
||||||
|
d.frameStack.frames = append(d.frameStack.frames, f)
|
||||||
|
return mark
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *renderDraws) popFrames(mark int) {
|
||||||
|
if d.frameStack != nil {
|
||||||
|
d.frameStack.frames = d.frameStack.frames[:mark]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *renderDraws) depth() int {
|
||||||
|
if d.frameStack == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return len(d.frameStack.frames)
|
||||||
|
}
|
||||||
|
|
||||||
|
// enter starts a read landing on n: the read sees no frame opened before it, and gets from memo a
|
||||||
|
// frame for each name scope around n, so reads sharing memo read one pick of each name. It
|
||||||
|
// returns the mark that closes those frames.
|
||||||
|
func (sc renderScope) enter(n node, memo *drawMemo) (renderScope, int) {
|
||||||
|
sc = sc.entering()
|
||||||
|
for scope := scopeAround(n); scope != nil; scope = scope.up {
|
||||||
|
if len(scope.order) > 0 {
|
||||||
|
sc.draws.pushFrame(memo.enteredFrame(scope))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sc, sc.base
|
||||||
|
}
|
||||||
|
|
||||||
|
// entering is sc as a read entering a category sees it: no frame opened before it, and no pick.
|
||||||
|
func (sc renderScope) entering() renderScope {
|
||||||
|
sc.base, sc.pick = sc.draws.depth(), nil
|
||||||
|
return sc
|
||||||
|
}
|
||||||
|
|
||||||
|
// scopeAround is the innermost name scope a render of n reads names in, short of the frames n
|
||||||
|
// renders itself, one per iteration of a repeat.
|
||||||
|
func scopeAround(n node) *nameScope {
|
||||||
|
switch n := n.(type) {
|
||||||
|
case *template:
|
||||||
|
if n.repeat > 1 && n.nameScope != nil && n.nameScope.owner == n {
|
||||||
|
return n.nameScope.up
|
||||||
|
}
|
||||||
|
return n.nameScope
|
||||||
|
case *choice:
|
||||||
|
return n.nameScope
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *drawMemo) enteredFrame(scope *nameScope) *pickFrame {
|
||||||
|
f, ok := m.enteredFrames[scope]
|
||||||
|
if !ok {
|
||||||
|
f = newPickFrame(scope)
|
||||||
|
if m.enteredFrames == nil {
|
||||||
|
m.enteredFrames = map[*nameScope]*pickFrame{}
|
||||||
|
}
|
||||||
|
m.enteredFrames[scope] = f
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// renderFrame opens a fresh frame of t's scope, where t binds names and no read entering the
|
||||||
|
// category opened one, returning the mark that closes it, or -1.
|
||||||
|
func (sc renderScope) renderFrame(t *template) int {
|
||||||
|
if t.ownNameScope == nil || sc.frameOf(t.ownNameScope) != nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return sc.draws.pushFrame(newPickFrame(t.ownNameScope))
|
||||||
|
}
|
||||||
|
|
||||||
|
// frameOf is the frame of scope rendering since the read entering the category, nil where none is.
|
||||||
|
func (sc renderScope) frameOf(scope *nameScope) *pickFrame {
|
||||||
|
if stack := sc.draws.frameStack; stack != nil {
|
||||||
|
for i := len(stack.frames) - 1; i >= sc.base; i-- {
|
||||||
|
if f := stack.frames[i]; f.scope == scope {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readName(s *generatorState, sc renderScope, a arm) readValue {
|
||||||
|
f := sc.frameOf(a.named.scope)
|
||||||
|
if f == nil {
|
||||||
|
panic(internalError("name %q is read where no frame of its scope renders", a.named.name))
|
||||||
|
}
|
||||||
|
p := &f.picks[a.named.index]
|
||||||
|
if r, done := p.memo.value[a.path]; done {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
leaf := p.draw(s, a.named.head, a.steps, a.levels, a.path)
|
||||||
|
sc, mark := sc.enter(leaf, &p.memo)
|
||||||
|
r := p.renderAt(s, leaf, a.path, sc)
|
||||||
|
sc.draws.popFrames(mark)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// drawRowOf draws the row a render of t reads: inside the pick's rows where t renders as part of one.
|
||||||
|
func (sc renderScope) drawRowOf(s *generatorState, t *table) int {
|
||||||
|
if sc.pick != nil {
|
||||||
|
return t.drawIn(s, &sc.pick.pins)
|
||||||
|
}
|
||||||
|
return t.drawRow(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// keeps reports whether a read of sc's name addresses the level a starts at.
|
||||||
|
func (sc renderScope) keeps(a arm) bool {
|
||||||
|
_, kept := sc.pick.named.addressed[underKey(sc.pickKey, a.head)]
|
||||||
|
return kept
|
||||||
|
}
|
||||||
|
|
||||||
|
// readUnder reads a of t, which renders as part of the pick sc.pick at sc.pickKey, where a read of
|
||||||
|
// the name addresses it: once per pick, by its path from the name.
|
||||||
|
func readUnder(s *generatorState, t *template, sc renderScope, a arm) readValue {
|
||||||
|
p, key := sc.pick, underKey(sc.pickKey, a.path)
|
||||||
|
if r, done := p.memo.value[key]; done {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
levels := make([]string, len(a.levels))
|
||||||
|
for i, l := range a.levels {
|
||||||
|
levels[i] = underKey(sc.pickKey, l)
|
||||||
|
}
|
||||||
|
return p.renderAt(s, p.draw(s, t.head(a.head), a.steps, levels, key), key, sc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// underKey is the key of path under the pick key prefix. It never returns prefix itself, which a
|
||||||
|
// memo would keep: anything a renderScope holds reaching the heap moves every render's draws there.
|
||||||
|
func underKey(prefix, path string) string {
|
||||||
|
if prefix == "" {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
return prefix + "." + path
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw draws the path key names under p, its variant at key kept too.
|
||||||
|
func (p *namedPick) draw(s *generatorState, head node, steps []pathStep, levels []string, key string) node {
|
||||||
|
leaf := drawSteps(s, head, steps, &p.pins, &p.memo, levels)
|
||||||
|
if c, isChoice := leaf.(*choice); isChoice {
|
||||||
|
leaf = p.memo.variantOf(s, c, key)
|
||||||
|
}
|
||||||
|
return leaf
|
||||||
|
}
|
||||||
|
|
||||||
|
// renderAt renders leaf, drawn at key, as part of p, and keeps what it rendered.
|
||||||
|
func (p *namedPick) renderAt(s *generatorState, leaf node, key string, sc renderScope) readValue {
|
||||||
|
sc = sc.at(leaf, &p.pins)
|
||||||
|
sc.pick, sc.pickKey = p, key
|
||||||
|
r := renderLeaf(s, leaf, sc)
|
||||||
|
if p.memo.value == nil {
|
||||||
|
p.memo.value = map[string]readValue{}
|
||||||
|
}
|
||||||
|
p.memo.value[key] = r
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// variantOf is the variant of c drawn at level, drawn now where none was.
|
||||||
|
func (m *drawMemo) variantOf(s *generatorState, c *choice, level string) node {
|
||||||
|
n, drew := m.variant[level]
|
||||||
|
if !drew {
|
||||||
|
n = resolveChoice(s, c)
|
||||||
|
if m.variant == nil {
|
||||||
|
m.variant = map[string]node{}
|
||||||
|
}
|
||||||
|
m.variant[level] = n
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
@@ -187,8 +187,7 @@ func readField(s *generatorState, t *template, hold *drawMemo, sc renderScope, a
|
|||||||
sc.pick = nil
|
sc.pick = nil
|
||||||
switch {
|
switch {
|
||||||
case a.kind == freshRead && isRef(a.head):
|
case a.kind == freshRead && isRef(a.head):
|
||||||
sc.base = sc.draws.depth()
|
return readValue{text: render(s, t.head(a.head), sc.entering())}
|
||||||
return readValue{text: render(s, t.head(a.head), sc)}
|
|
||||||
case a.kind == freshRead:
|
case a.kind == freshRead:
|
||||||
return readValue{text: render(s, t.head(a.head), sc)}
|
return readValue{text: render(s, t.head(a.head), sc)}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ func (t *table) compileRowFormat(format string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := checkTableToks(toks, nil); err != nil {
|
if err := refuseTableBinding(toks); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, tok := range toks {
|
for _, tok := range toks {
|
||||||
@@ -345,25 +345,21 @@ func (t *table) compileRowFormat(format string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := checkTableToks(nil, unbound); err != nil {
|
if len(unbound) > 0 {
|
||||||
return err
|
return unbound[0].err
|
||||||
}
|
}
|
||||||
t.formatTemplate = &template{format: format, tokens: toks, fields: fields, repeat: 1, isRecord: true, site: tableSite{t, formatRow}}
|
t.formatTemplate = &template{format: format, tokens: toks, fields: fields, repeat: 1, isRecord: true, site: tableSite{t, formatRow}}
|
||||||
t.rowNode = &tableRow{t}
|
t.rowNode = &tableRow{t}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkTableToks refuses a table's format or cell binding a name, or reading a head no column
|
// refuseTableBinding refuses a table's format or cell binding a name.
|
||||||
// holds: a table holds no names.
|
func refuseTableBinding(toks []formatToken) error {
|
||||||
func checkTableToks(toks []formatToken, unbound []unboundRead) error {
|
|
||||||
for _, tok := range toks {
|
for _, tok := range toks {
|
||||||
if tok.kind == nameBind {
|
if tok.kind == nameBind {
|
||||||
return fmt.Errorf("token {%s}: a table binds no name; bind it in a template reading the table", tok.body)
|
return fmt.Errorf("token {%s}: a table binds no name; bind it in a template reading the table", tok.body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(unbound) > 0 {
|
|
||||||
return unbound[0].err
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -203,6 +203,7 @@ Shape: T = table, t = template, c = choice.
|
|||||||
- Deletes the token-order rules (`drawsApart`, `readFold`, `branches`, the pairwise replay), the expansion hold fence, `drawGroup`, the own-family fence and the cell-reference fences, and revises the decisions that point here. The remaining draw checks are names, paths through names, cycles and the repeat cap.
|
- Deletes the token-order rules (`drawsApart`, `readFold`, `branches`, the pairwise replay), the expansion hold fence, `drawGroup`, the own-family fence and the cell-reference fences, and revises the decisions that point here. The remaining draw checks are names, paths through names, cycles and the repeat cap.
|
||||||
- Fixes on the way: `{/sel}|{/sel}` panics out of `Fake` today where two draws land on rows whose cells select different rows of another table; it must render.
|
- Fixes on the way: `{/sel}|{/sel}` panics out of `Fake` today where two draws land on rows whose cells select different rows of another table; it must render.
|
||||||
- Rewrites the README's References, Draw group, Correlated fields, Linked tables, Names and "One draw, one spelling" sections; the Linked tables section says which pick's weights govern a family, since the bound row decides.
|
- Rewrites the README's References, Draw group, Correlated fields, Linked tables, Names and "One draw, one spelling" sections; the Linked tables section says which pick's weights govern a family, since the bound row decides.
|
||||||
|
- Widens the refusal of a name read once to every binding, a path into it or a reference path bound included, since `{n.x}` read once is then `{/ref.x}`, and deletes the names fence over draw groups and nested repeats (`checkUses`, `readsHeld`).
|
||||||
- Re-pin seeded output and `testdata/shipped_shape.txt` in their own commits; the CHANGELOG names the grammar change.
|
- Re-pin seeded output and `testdata/shipped_shape.txt` in their own commits; the CHANGELOG names the grammar change.
|
||||||
|
|
||||||
### 25. Draw `en_US.phone`'s `exch` as a NANP central office code, and assert the rule in `TestShippedUSPhone`.
|
### 25. Draw `en_US.phone`'s `exch` as a NANP central office code, and assert the rule in `TestShippedUSPhone`.
|
||||||
|
|||||||
Reference in New Issue
Block a user