Keep in a pick only what reads of its name address, look frames up on the stack alone, and refuse a name read once whole
This commit is contained in:
+6
-4
@@ -13,10 +13,12 @@ replacement, and each removed path, column or flag.
|
|||||||
A name lives in the category binding it, so every field and record column of the
|
A name lives in the category binding it, so every field and record column of the
|
||||||
category reads one pick, a transform reads it as `{lowercase(t.name)}`, and a name a
|
category reads one pick, a transform reads it as `{lowercase(t.name)}`, and a name a
|
||||||
`repeat` binds picks again on every iteration. Refused at `New`: a name bound twice
|
`repeat` binds picks again on every iteration. Refused at `New`: a name bound twice
|
||||||
outside any `repeat`, twice in one `repeat`, or both inside a `repeat` and outside it; a name that is a field or an option;
|
outside any `repeat`, twice in one `repeat`, or both inside a `repeat` and outside it; a
|
||||||
a binding of anything but a reference; a binding in a choice's item; a binding nothing
|
name that is a field or an option; a binding of anything but a reference; a binding in a
|
||||||
reads; a path through a name that selects a row; and a binding in a table. A field,
|
choice's item; a binding nothing reads, or one read once whole, which the reference
|
||||||
column, category or folder name may not contain ` as `.
|
spells; a path through a name that selects a row; a read of a name rendering a field
|
||||||
|
twice where a path through the name reads it; and a binding in a table. A field, column,
|
||||||
|
category or folder name may not contain ` as `.
|
||||||
- First release: the CLI, the library and the shipped data set.
|
- First release: the CLI, the library and the shipped data set.
|
||||||
- Table categories: a category JSON naming a `rows` TSV beside it, with the options
|
- Table categories: a category JSON naming a `rows` TSV beside it, with the options
|
||||||
`key`, `name`, `weight` and `parent`; a path selects a row by key or name,
|
`key`, `name`, `weight` and `parent`; a path selects a row by key or name,
|
||||||
|
|||||||
@@ -708,12 +708,16 @@ row:
|
|||||||
"{/misc.territory as t}{t.flag} {t}: capital {t.capital}, calling code +{t.calling-code}"
|
"{/misc.territory as t}{t.flag} {t}: capital {t.capital}, calling code +{t.calling-code}"
|
||||||
```
|
```
|
||||||
|
|
||||||
Renders e.g. `🇳🇴 Norway: capital Oslo, calling code +47`. Everything under a name is drawn
|
Renders e.g. `🇳🇴 Norway: capital Oslo, calling code +47`. A name keeps each level a
|
||||||
once, so `{n}` and `{n.path}` agree, a [transform](#transforms) of `n.path` reads the same
|
`{n.path}` reads, and what it lands on, so `{n}` and `{n.path}` agree, and a
|
||||||
value, and a field `n` renders twice prints the same text twice. A name is a pick of its own:
|
[transform](#transforms) of `n.path` reads the same value. Everything else under the name draws
|
||||||
`{t.name}` and `{/misc.territory.name}` beside it are two draws. A reference path read under a
|
as it would anywhere: `{w}-{w}` in the category `n` names draws `w` twice, so `{n.w}` beside
|
||||||
name draws in the draw group of the read of the name, so give the reads of two names bound to
|
`{n}` is refused there, since it could read either draw.
|
||||||
one category a [draw group](#draw-group) each to draw what it references apart.
|
|
||||||
|
A name is a pick of its own: `{t.name}` and `{/misc.territory.name}` beside it are two draws. A
|
||||||
|
reference path the picked category reads is still held for the render as usual, so two names
|
||||||
|
bound to `sv_SE.person`, whose fields read `{.first-name.name}`, print one person unless their
|
||||||
|
reads sit in two [draw groups](#draw-group).
|
||||||
|
|
||||||
A name is drawn on its first read, and lives in the category binding it: any field of the
|
A name is drawn on its first read, and lives in the category binding it: any field of the
|
||||||
category reads it, and a record's columns read one pick. Each render of the category picks
|
category reads it, and a record's columns read one pick. Each render of the category picks
|
||||||
@@ -721,10 +725,12 @@ anew, and so does each bare reference to it. A name a `repeat` binds picks again
|
|||||||
iteration, and one bound outside the repeat keeps its pick on every line.
|
iteration, and one bound outside the repeat keeps its pick on every line.
|
||||||
|
|
||||||
Rejected at load: a name bound twice outside any `repeat`, twice in one `repeat`, or both
|
Rejected at load: a name bound twice outside any `repeat`, twice in one `repeat`, or both
|
||||||
inside a `repeat` and outside it; a name that is a field or an option; a binding of anything but a reference; a binding in
|
inside a `repeat` and outside it; a name that is a field or an option; a binding of anything
|
||||||
a choice's item, which every other item would leave unbound; a binding nothing reads; a path
|
but a reference; a binding in a choice's item, which every other item would leave unbound; a
|
||||||
through a name that selects a row, which the reference selects instead; and a binding in a
|
binding nothing reads, or one read once whole outside a `repeat`, which the reference spells;
|
||||||
table's format or cell. A field, column, category or folder name may not contain ` as `.
|
a path through a name that selects a row, which the reference selects instead; a read of a name
|
||||||
|
rendering a field twice where a path through the name reads it; and a binding in a table's
|
||||||
|
format or cell. A field, column, category or folder name may not contain ` as `.
|
||||||
|
|
||||||
### Draw group
|
### Draw group
|
||||||
|
|
||||||
@@ -926,7 +932,7 @@ record.go records: Record, the JSON/CSV/SQL serializers, and their entry p
|
|||||||
struct.go structs: FakeStruct, fake tags, and a field's Go type as its column's datatype
|
struct.go structs: FakeStruct, fake tags, and a field's Go type as its column's datatype
|
||||||
inline.go inline templates: Template, NewTemplate, FakeTemplate, IsTemplate, and their compile and link
|
inline.go inline templates: Template, NewTemplate, FakeTemplate, IsTemplate, and their compile and link
|
||||||
format.go the {token} grammar: scanning, tokens, operands, validation, compiling a format
|
format.go the {token} grammar: scanning, tokens, operands, validation, compiling a format
|
||||||
draws.go the memo a hold or draw group keeps its draws in; and a render's draws and the scope it reads its reference paths through
|
draws.go the memo a hold, draw group or named pick keeps its draws in; and a render's draws and the scope it reads its reference paths and names through
|
||||||
holdfence.go the load fences over the hold: the routes and spellings that read one held name two ways
|
holdfence.go the load fences over the hold: the routes and spellings that read one held name two ways
|
||||||
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
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ package fejkdata
|
|||||||
// an expansion holds a sibling path's head and an operand's field, its draw
|
// an expansion holds a sibling path's head and an operand's field, its draw
|
||||||
// group a reference path's: `formatOps.held` and `heldCheck` an expansion's,
|
// group a reference path's: `formatOps.held` and `heldCheck` an expansion's,
|
||||||
// `readReference` a draw group's.
|
// `readReference` a draw group's.
|
||||||
// - named pick — one draw of a reference a binding token, {ref as n}, gives a name, kept for one
|
// - named pick — the draw a token such as {/person as p} binds to a name. It is drawn on its
|
||||||
// render of the scope it is bound in, a category or a repeat iteration, and drawn on its
|
// first read and kept while its scope renders: a category, or one repeat iteration.
|
||||||
// first read: `nameBinding`, `nameScope`, `namedPick`, `pickFrame`.
|
// `nameBinding`, `nameScope`, `namedPick`, `pickFrame`.
|
||||||
// - memo — what a hold, a draw group or a named pick keeps its draws in, the variant drawn at
|
// - memo — what a hold, a draw group or a named pick keeps its draws in, the variant drawn at
|
||||||
// each level and the value each path read: `drawMemo`, `readMemo`.
|
// each level and the value each path read: `drawMemo`, `readMemo`.
|
||||||
// - draw group — reference draws kept apart inside one render:
|
// - draw group — reference draws kept apart inside one render:
|
||||||
|
|||||||
+19
-10
@@ -662,6 +662,8 @@ same four columns as `geo.SE.address`, each a reference into it, and the format
|
|||||||
twice; a column is spelled the same in both, `street-number`, so the two never disagree
|
twice; a column is spelled the same in both, `street-number`, so the two never disagree
|
||||||
on a name.
|
on a name.
|
||||||
|
|
||||||
|
`todo.md` item 3 replaces it.
|
||||||
|
|
||||||
## A postort's kommun comes from its name, its tätort or its codes, never from distance
|
## A postort's kommun comes from its name, its tätort or its codes, never from distance
|
||||||
|
|
||||||
2026-09-24, Lilleman auf Larv.
|
2026-09-24, Lilleman auf Larv.
|
||||||
@@ -896,13 +898,20 @@ a struct's fields as one.
|
|||||||
field binds a name where another reads it, `"first": "{/person as p}{p.first}"` beside
|
field binds a name where another reads it, `"first": "{/person as p}{p.first}"` beside
|
||||||
`"last": "{p.last}"`, so the scope cannot be the template holding the binding; the category
|
`"last": "{p.last}"`, so the scope cannot be the template holding the binding; the category
|
||||||
is the smallest unit holding every field and record column. A choice's item binds no name,
|
is the smallest unit holding every field and record column. A choice's item binds no name,
|
||||||
since a read outside the item would see a binding another item leaves out. The scope is
|
since a read outside the item would see a binding another item leaves out. A pick is drawn on
|
||||||
lexical: a read entering a category, by a reference or through a name, sees none of the
|
its first read, so a record's columns, rendered in name order, read one pick whichever binds
|
||||||
frames rendering above it, so what a category renders never depends on who references it. A
|
it.
|
||||||
pick is drawn on its first read, so a record's columns, rendered in name order, read one pick
|
|
||||||
whichever binds it. A read entering a category keeps the frame of each scope it enters in its
|
The scope is lexical: a read entering a category, by a reference, through a name or from
|
||||||
own memo, and the category's root renders that frame when the read lands on the root, so
|
`Fake`, sees none of the frames rendering above it, so what a category renders never depends
|
||||||
`{n}` and `{n.path}` read one pick: per render and group for a reference path, per pick for a
|
on who references it. That read opens the frame of each scope around what it lands on, taken
|
||||||
path through a name, and per render for `Fake("cat.field")`. A reference read under a pick
|
from its own memo, so every read entering by one memo, `{n}` and `{n.path}` alike, reads one
|
||||||
draws in the draw group of the read of the name, the group the draw fences survey it in; a pick
|
pick: per render and group for a reference path, per pick for a path through a name, per
|
||||||
with draw groups of its own would draw where no fence looks.
|
render for `Fake("cat.field")`. A frame is looked up on the stack of frames rendering, and
|
||||||
|
nowhere else.
|
||||||
|
|
||||||
|
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
|
||||||
|
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. `todo.md` item 4 ends that: once every `{…}`
|
||||||
|
draws afresh, nothing is held for the render.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import "strings"
|
|||||||
type drawMemo struct {
|
type drawMemo struct {
|
||||||
variant map[string]node
|
variant map[string]node
|
||||||
value map[string]readValue
|
value map[string]readValue
|
||||||
frames map[*nameScope]*pickFrame
|
enteredFrames map[*nameScope]*pickFrame
|
||||||
}
|
}
|
||||||
|
|
||||||
// groupDraws is what one render's draw group has drawn for its reference paths: a memo, and the
|
// groupDraws is what one render's draw group has drawn for its reference paths: a memo, and the
|
||||||
@@ -34,7 +34,7 @@ type renderDraws struct {
|
|||||||
unnamed groupDraws
|
unnamed groupDraws
|
||||||
named map[string]*groupDraws
|
named map[string]*groupDraws
|
||||||
trace renderTrace
|
trace renderTrace
|
||||||
frames *frameStack
|
frameStack *frameStack
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderScope is where a render reads its reference paths: its reference draws, in the draw group of the
|
// renderScope is where a render reads its reference paths: its reference draws, in the draw group of the
|
||||||
@@ -45,10 +45,8 @@ type renderScope struct {
|
|||||||
draws *renderDraws
|
draws *renderDraws
|
||||||
group string
|
group string
|
||||||
row renderedRow
|
row renderedRow
|
||||||
// entry is the memo of the read that entered the category rendering, nil where none did;
|
// base is the depth of the frame stack when the read rendering the category entered it,
|
||||||
// base, the depth of the frame stack when it entered, below which no frame is the
|
// below which no frame is the category's.
|
||||||
// category's.
|
|
||||||
entry *drawMemo
|
|
||||||
base int
|
base int
|
||||||
// pick is the named pick what renders is part of, at pickKey, its path from the name; nil
|
// pick is the named pick what renders is part of, at pickKey, its path from the name; nil
|
||||||
// outside one.
|
// outside one.
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package fejkdata
|
package fejkdata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -64,8 +63,9 @@ type formatToken struct {
|
|||||||
body string // the braces' content, as written
|
body string // the braces' content, as written
|
||||||
fn string // builtinCall
|
fn string // builtinCall
|
||||||
args []string // builtinCall
|
args []string // builtinCall
|
||||||
names []string // nameRead: the '|' arms; builtinCall: the operands its builtin reads; nameBind: the reference it binds
|
names []string // nameRead: the '|' arms; builtinCall: the operands its builtin reads
|
||||||
bound string // nameBind: the name
|
// nameBind: the reference it binds, and the name.
|
||||||
|
boundRef, bound string
|
||||||
}
|
}
|
||||||
|
|
||||||
type tokenKind uint8
|
type tokenKind uint8
|
||||||
@@ -98,7 +98,7 @@ func parseFormat(format string) ([]formatToken, error) {
|
|||||||
}
|
}
|
||||||
flush()
|
flush()
|
||||||
if ref, name, binds := cutOutside(u.body, asWord); binds && indexOutside(u.body, '(') < 0 {
|
if ref, name, binds := cutOutside(u.body, asWord); binds && indexOutside(u.body, '(') < 0 {
|
||||||
toks = append(toks, formatToken{kind: nameBind, body: u.body, names: []string{ref}, bound: name})
|
toks = append(toks, formatToken{kind: nameBind, body: u.body, boundRef: ref, bound: name})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if indexOutside(u.body, '(') < 0 {
|
if indexOutside(u.body, '(') < 0 {
|
||||||
@@ -215,20 +215,15 @@ func parseChecked(format string, fields map[string]node) ([]formatToken, []unbou
|
|||||||
}
|
}
|
||||||
|
|
||||||
// unboundRead is a token reading a head no field holds, which only a name bound around the
|
// unboundRead is a token reading a head no field holds, which only a name bound around the
|
||||||
// template can answer; err is the refusal where none does.
|
// template can answer; err is the refusal where none does. whole is a read of the name itself,
|
||||||
|
// no path into it, and operand a builtin's read of it.
|
||||||
type unboundRead struct {
|
type unboundRead struct {
|
||||||
head string
|
head string
|
||||||
|
whole bool
|
||||||
|
operand bool
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
// fieldMiss is a head no field of the template holds.
|
|
||||||
type fieldMiss struct {
|
|
||||||
head string
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *fieldMiss) Error() string { return m.err.Error() }
|
|
||||||
|
|
||||||
// checkTokens proves every parsed token names an existing field or a known function,
|
// checkTokens proves every parsed token names an existing field or a known function,
|
||||||
// so a typo'd or dangling reference is a New-time error. A head no field holds comes
|
// so a typo'd or dangling reference is a New-time error. A head no field holds comes
|
||||||
// back unbound, for bindNames to look up among the names.
|
// back unbound, for bindNames to look up among the names.
|
||||||
@@ -247,32 +242,16 @@ func checkTokens(toks []formatToken, fields map[string]node) ([]unboundRead, err
|
|||||||
func checkToken(t formatToken, fields map[string]node) ([]unboundRead, error) {
|
func checkToken(t formatToken, fields map[string]node) ([]unboundRead, error) {
|
||||||
switch t.kind {
|
switch t.kind {
|
||||||
case builtinCall:
|
case builtinCall:
|
||||||
err := checkFunc(t, fields)
|
if err := checkFunc(t, fields); err != nil {
|
||||||
var miss *fieldMiss
|
|
||||||
if errors.As(err, &miss) {
|
|
||||||
return []unboundRead{{miss.head, err}}, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
return nil, err
|
||||||
|
}
|
||||||
|
return checkReads(t, fields, true)
|
||||||
case nameBind:
|
case nameBind:
|
||||||
return nil, checkBind(t)
|
return nil, checkBind(t)
|
||||||
case nameRead:
|
case nameRead:
|
||||||
var unbound []unboundRead
|
unbound, err := checkReads(t, fields, false)
|
||||||
for _, name := range t.names {
|
|
||||||
if isRef(name) {
|
|
||||||
if _, _, err := refShape(name); err != nil {
|
|
||||||
return nil, fmt.Errorf("token {%s}: %w", t.body, err)
|
|
||||||
}
|
|
||||||
continue // its target is checked at New (see linkRefs)
|
|
||||||
}
|
|
||||||
err := checkArm(name, fields, len(t.names) == 1)
|
|
||||||
var miss *fieldMiss
|
|
||||||
if errors.As(err, &miss) {
|
|
||||||
unbound = append(unbound, unboundRead{miss.head, fmt.Errorf("token {%s}: %w", t.body, err)})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("token {%s}: %w", t.body, err)
|
return nil, err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Last, so an arm broken on its own terms is reported as that: a repeat is
|
// Last, so an arm broken on its own terms is reported as that: a repeat is
|
||||||
// the consequence of such a mistake, not the mistake itself.
|
// the consequence of such a mistake, not the mistake itself.
|
||||||
@@ -281,10 +260,35 @@ func checkToken(t formatToken, fields map[string]node) ([]unboundRead, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkReads proves each name t reads, an arm or an operand, is a reference or a path into a
|
||||||
|
// field, returning those whose head no field holds.
|
||||||
|
func checkReads(t formatToken, fields map[string]node, operands bool) ([]unboundRead, error) {
|
||||||
|
var unbound []unboundRead
|
||||||
|
for _, name := range t.names {
|
||||||
|
if isRef(name) {
|
||||||
|
if _, _, err := refShape(name); err != nil {
|
||||||
|
return nil, fmt.Errorf("token {%s}: %w", t.body, err)
|
||||||
|
}
|
||||||
|
continue // its target is checked at New (see linkRefs)
|
||||||
|
}
|
||||||
|
missing, err := checkArm(name, fields, !operands && len(t.names) == 1)
|
||||||
|
if err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("token {%s}: %w", t.body, err)
|
||||||
|
if !missing {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
a := splitArm(name, nil)
|
||||||
|
unbound = append(unbound, unboundRead{head: a.head, whole: len(a.tail) == 0, operand: operands, err: err})
|
||||||
|
}
|
||||||
|
return unbound, nil
|
||||||
|
}
|
||||||
|
|
||||||
// checkBind proves a {ref as name} token binds a reference to a name no other token
|
// checkBind proves a {ref as name} token binds a reference to a name no other token
|
||||||
// could read as something else.
|
// could read as something else.
|
||||||
func checkBind(t formatToken) error {
|
func checkBind(t formatToken) error {
|
||||||
ref, name := t.names[0], t.bound
|
ref, name := t.boundRef, t.bound
|
||||||
if trimmed := strings.TrimSpace(ref) + asWord + strings.TrimSpace(name); trimmed != t.body {
|
if trimmed := strings.TrimSpace(ref) + asWord + strings.TrimSpace(name); trimmed != t.body {
|
||||||
return fmt.Errorf("token {%s}: write {%s}", t.body, trimmed)
|
return fmt.Errorf("token {%s}: write {%s}", t.body, trimmed)
|
||||||
}
|
}
|
||||||
@@ -305,34 +309,34 @@ func checkBind(t formatToken) error {
|
|||||||
|
|
||||||
// checkArm validates one sibling name or path against a template's fields.
|
// checkArm validates one sibling name or path against a template's fields.
|
||||||
// wholeToken says the name is the token's entire body, so {/name} would render
|
// wholeToken says the name is the token's entire body, so {/name} would render
|
||||||
// the same value and can be offered as the reference spelling. A head no field
|
// the same value and can be offered as the reference spelling. missing says no
|
||||||
// holds is a *fieldMiss.
|
// field holds the head, which a name may yet answer.
|
||||||
func checkArm(name string, fields map[string]node, wholeToken bool) error {
|
func checkArm(name string, fields map[string]node, wholeToken bool) (missing bool, err error) {
|
||||||
a := splitArm(name, nil)
|
a := splitArm(name, nil)
|
||||||
if err := checkSegments(a); err != nil {
|
if err := checkSegments(a); err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
field, ok := fields[a.head]
|
field, ok := fields[a.head]
|
||||||
if !ok {
|
if !ok {
|
||||||
if a.head == "" {
|
if a.head == "" {
|
||||||
return fmt.Errorf("a name is never empty, so this token can name no field")
|
return false, fmt.Errorf("a name is never empty, so this token can name no field")
|
||||||
}
|
}
|
||||||
if isOption(a.head) {
|
if isOption(a.head) {
|
||||||
return fmt.Errorf("%q is an option and can never be a field", a.head)
|
return false, fmt.Errorf("%q is an option and can never be a field", a.head)
|
||||||
}
|
}
|
||||||
if len(fields) == 0 {
|
if len(fields) == 0 {
|
||||||
hint := ""
|
hint := ""
|
||||||
if wholeToken && hintableRef(name) {
|
if wholeToken && hintableRef(name) {
|
||||||
hint = fmt.Sprintf(" — write {/%s} to reference the data", name)
|
hint = fmt.Sprintf(" — write {/%s} to reference the data", name)
|
||||||
}
|
}
|
||||||
return &fieldMiss{a.head, fmt.Errorf("no field %q; a token names a sibling field, and this template has none%s", a.head, hint)}
|
return true, fmt.Errorf("no field %q; a token names a sibling field, and this template has none%s", a.head, hint)
|
||||||
}
|
}
|
||||||
return &fieldMiss{a.head, fmt.Errorf("no field %q", a.head)}
|
return true, fmt.Errorf("no field %q", a.head)
|
||||||
}
|
}
|
||||||
if err := checkPathResolves(field, a.tail, a.head); err != nil {
|
if err := checkPathResolves(field, a.tail, a.head); err != nil {
|
||||||
return fmt.Errorf("field %q: %w", a.head, err)
|
return false, fmt.Errorf("field %q: %w", a.head, err)
|
||||||
}
|
}
|
||||||
return nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// hintableRef reports whether {/name} is a reference the grammar accepts, so the
|
// hintableRef reports whether {/name} is a reference the grammar accepts, so the
|
||||||
@@ -383,7 +387,7 @@ type arm struct {
|
|||||||
leaves []node // every node the path may land on, one per variant it passes
|
leaves []node // every node the path may land on, one per variant it passes
|
||||||
cover node // what holding the path pins: the first choice it passes, else its leaf
|
cover node // what holding the path pins: the first choice it passes, else its leaf
|
||||||
kind armKind
|
kind armKind
|
||||||
bind *nameBinding // namedRead: the binding of the name it reads through
|
named *nameBinding // namedRead: the binding of the name it reads through
|
||||||
}
|
}
|
||||||
|
|
||||||
// armKind is how expand reads an arm, fixed at compile.
|
// armKind is how expand reads an arm, fixed at compile.
|
||||||
|
|||||||
@@ -181,10 +181,15 @@ func linkNodeRefs(scope nodeScope, root map[string]node) error {
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return scope(func(path string, m node) error {
|
for _, pass := range []func(string, *template) error{linkNames, checkNameReads} {
|
||||||
|
if err := scope(func(path string, m node) error {
|
||||||
if t, ok := m.(*template); ok {
|
if t, ok := m.(*template); ok {
|
||||||
return linkNames(path, t)
|
return pass(path, t)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package fejkdata
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,7 +17,8 @@ type nameScope struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// nameBinding is one {ref as name} token: the scope it binds name in, its index among
|
// nameBinding is one {ref as name} token: the scope it binds name in, its index among
|
||||||
// that scope's bindings, and, once linked, the head node and tail its reference reads.
|
// that scope's bindings, its uses, and, once linked, the head node and tail its
|
||||||
|
// reference reads and the keys its reads address.
|
||||||
type nameBinding struct {
|
type nameBinding struct {
|
||||||
name string
|
name string
|
||||||
ref string
|
ref string
|
||||||
@@ -25,9 +27,18 @@ type nameBinding struct {
|
|||||||
scope *nameScope
|
scope *nameScope
|
||||||
index int
|
index int
|
||||||
binder *template
|
binder *template
|
||||||
read bool
|
uses []nameUse
|
||||||
head node
|
head node
|
||||||
tail []string
|
tail []string
|
||||||
|
// addressed is every key a read of the name lands on or passes, the spelling of the
|
||||||
|
// first read reaching it beside it; a pick keeps the draws at these keys, and only these.
|
||||||
|
addressed map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// nameUse is one read of a name: whole, with no path into it, an operand of a builtin, or in a
|
||||||
|
// repeat nested inside the name's scope.
|
||||||
|
type nameUse struct {
|
||||||
|
whole, operand, nested bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *nameScope) lookup(name string) *nameBinding {
|
func (sc *nameScope) lookup(name string) *nameBinding {
|
||||||
@@ -42,22 +53,25 @@ func (sc *nameScope) lookup(name string) *nameBinding {
|
|||||||
// bindNames gives every template of a compiled category or inline template the scope its
|
// bindNames gives every template of a compiled category or inline template the scope its
|
||||||
// names live in, and refuses a name bound twice along one chain of scopes, a binding in a
|
// names live in, and refuses a name bound twice along one chain of scopes, a binding in a
|
||||||
// choice's item, a name a field spells too, a read no field or name answers, and a binding
|
// choice's item, a name a field spells too, a read no field or name answers, and a binding
|
||||||
// nothing reads.
|
// read by nothing, or once whole, which its reference spells.
|
||||||
func bindNames(root node) error {
|
func bindNames(root node) error {
|
||||||
top := &nameScope{owner: root}
|
top := &nameScope{owner: root}
|
||||||
scopes := []*nameScope{top}
|
scopes := []*nameScope{top}
|
||||||
var gather func(n node, scope *nameScope, inChoice bool, where string) error
|
var gather func(n node, scope *nameScope, inChoice bool, where string) error
|
||||||
gather = func(n node, scope *nameScope, inChoice bool, where string) error {
|
gather = func(n node, scope *nameScope, inChoice bool, where string) error {
|
||||||
if t, isTemplate := n.(*template); isTemplate {
|
switch t := n.(type) {
|
||||||
|
case *template:
|
||||||
if t.repeat > 1 && n != root {
|
if t.repeat > 1 && n != root {
|
||||||
scope = &nameScope{up: scope, owner: t}
|
scope = &nameScope{up: scope, owner: t}
|
||||||
scopes = append(scopes, scope)
|
scopes = append(scopes, scope)
|
||||||
inChoice = false
|
inChoice = false
|
||||||
}
|
}
|
||||||
t.scope = scope
|
t.nameScope = scope
|
||||||
if err := scope.bindAll(t, inChoice, where); err != nil {
|
if err := scope.bindAll(t, inChoice, where); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
case *choice:
|
||||||
|
t.nameScope = scope
|
||||||
}
|
}
|
||||||
_, isChoice := n.(*choice)
|
_, isChoice := n.(*choice)
|
||||||
return eachContained(n, where, func(c node, where string) error { return gather(c, scope, inChoice || isChoice, where) })
|
return eachContained(n, where, func(c node, where string) error { return gather(c, scope, inChoice || isChoice, where) })
|
||||||
@@ -72,11 +86,11 @@ func bindNames(root node) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := resolveReads(root, ""); err != nil {
|
if err := resolveReads(root, "", scopes); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, sc := range scopes {
|
for _, sc := range scopes {
|
||||||
if err := sc.close(); err != nil {
|
if err := sc.settle(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,7 +110,7 @@ func (sc *nameScope) bindAll(t *template, inChoice bool, where string) error {
|
|||||||
if b, twice := sc.bindings[tok.bound]; twice {
|
if b, twice := sc.bindings[tok.bound]; twice {
|
||||||
return fmt.Errorf("%stoken {%s}: name %q is bound twice %s, by {%s} too; rename one", where, tok.body, tok.bound, sc.spelled(), b.body)
|
return fmt.Errorf("%stoken {%s}: name %q is bound twice %s, by {%s} too; rename one", where, tok.body, tok.bound, sc.spelled(), b.body)
|
||||||
}
|
}
|
||||||
b := &nameBinding{name: tok.bound, ref: tok.names[0], body: tok.body, where: where, scope: sc, index: len(sc.order), binder: t}
|
b := &nameBinding{name: tok.bound, ref: tok.boundRef, body: tok.body, where: where, scope: sc, index: len(sc.order), binder: t}
|
||||||
if sc.bindings == nil {
|
if sc.bindings == nil {
|
||||||
sc.bindings = map[string]*nameBinding{}
|
sc.bindings = map[string]*nameBinding{}
|
||||||
}
|
}
|
||||||
@@ -114,15 +128,24 @@ func (sc *nameScope) spelled() string {
|
|||||||
return "in one repeat"
|
return "in one repeat"
|
||||||
}
|
}
|
||||||
|
|
||||||
// close refuses a binding nothing reads, and gives the owner of a scope binding names the scope.
|
// settle refuses a binding read by nothing, or read once whole, outside a repeat, where its
|
||||||
func (sc *nameScope) close() error {
|
// reference spells the same pick; and hands a scope binding names to its owner, which renders a
|
||||||
|
// frame of it.
|
||||||
|
func (sc *nameScope) settle() error {
|
||||||
for _, b := range sc.order {
|
for _, b := range sc.order {
|
||||||
if !b.read {
|
if len(b.uses) == 0 {
|
||||||
return fmt.Errorf("%stoken {%s}: nothing reads name %q; drop the token", b.where, b.body, b.name)
|
return fmt.Errorf("%stoken {%s}: nothing reads name %q; drop the token", b.where, b.body, b.name)
|
||||||
}
|
}
|
||||||
|
if r := b.uses[0]; len(b.uses) == 1 && r.whole && !r.nested {
|
||||||
|
spelling := "{" + b.ref + "}"
|
||||||
|
if r.operand {
|
||||||
|
spelling = b.ref
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%stoken {%s}: name %q is read once, so it keeps nothing; write %s where it is read, and drop the token", b.where, b.body, b.name, spelling)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if t, isTemplate := sc.owner.(*template); isTemplate && len(sc.order) > 0 {
|
if t, isTemplate := sc.owner.(*template); isTemplate && len(sc.order) > 0 {
|
||||||
t.ownScope = sc
|
t.ownNameScope = sc
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -143,9 +166,10 @@ func eachContained(n node, where string, fn func(c node, where string) error) er
|
|||||||
}
|
}
|
||||||
|
|
||||||
// resolveReads answers each unbound read under n with a name its template sees, fields before the
|
// resolveReads answers each unbound read under n with a name its template sees, fields before the
|
||||||
// format as compile reports them, and refuses a field spelling a name.
|
// format as compile reports them, and refuses a field spelling a name. scopes are every scope of
|
||||||
func resolveReads(n node, where string) error {
|
// the category, which a refusal searches for a name bound where the read cannot see it.
|
||||||
if err := eachContained(n, where, resolveReads); err != nil {
|
func resolveReads(n node, where string, scopes []*nameScope) error {
|
||||||
|
if err := eachContained(n, where, func(c node, where string) error { return resolveReads(c, where, scopes) }); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
t, isTemplate := n.(*template)
|
t, isTemplate := n.(*template)
|
||||||
@@ -153,7 +177,7 @@ func resolveReads(n node, where string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, name := range sortedNames(t.fields) {
|
for _, name := range sortedNames(t.fields) {
|
||||||
if b := t.scope.lookup(name); b != nil {
|
if b := t.nameScope.lookup(name); b != nil {
|
||||||
holder := "the root template"
|
holder := "the root template"
|
||||||
if where != "" {
|
if where != "" {
|
||||||
holder = strings.TrimSuffix(where, ": ")
|
holder = strings.TrimSuffix(where, ": ")
|
||||||
@@ -162,32 +186,52 @@ func resolveReads(n node, where string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, u := range t.unbound {
|
for _, u := range t.unbound {
|
||||||
b := t.scope.lookup(u.head)
|
b := t.nameScope.lookup(u.head)
|
||||||
if b == nil {
|
if b == nil {
|
||||||
return fmt.Errorf("%s%w", where, u.err)
|
return fmt.Errorf("%s%w%s", where, u.err, boundElsewhere(u.head, scopes))
|
||||||
}
|
}
|
||||||
b.read = true
|
b.uses = append(b.uses, nameUse{whole: u.whole, operand: u.operand, nested: t.nameScope != b.scope})
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// boundElsewhere names the repeat binding name, for a read outside it.
|
||||||
|
func boundElsewhere(name string, scopes []*nameScope) string {
|
||||||
|
for _, sc := range scopes {
|
||||||
|
if b, ok := sc.bindings[name]; ok {
|
||||||
|
return fmt.Sprintf("; name %q is bound inside the repeat at %s, which a read outside the repeat cannot see", name, strings.TrimSuffix(b.where, ": "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// isName reports whether a head t reads is a name: neither a reference nor a field.
|
// isName reports whether a head t reads is a name: neither a reference nor a field.
|
||||||
func (t *template) isName(head string) bool {
|
func (t *template) isName(head string) bool {
|
||||||
return !isRef(head) && t.fields[head] == nil && t.scope.lookup(head) != nil
|
return !isRef(head) && t.fields[head] == nil && t.nameScope.lookup(head) != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// linkNames compiles t's reads of a name as paths from the head its binding's reference names, once
|
// linkBindings gives each binding t's tokens make the head and tail its reference resolved to.
|
||||||
// every template is linked, so the binder has resolved that reference.
|
func linkBindings(t *template) {
|
||||||
func linkNames(path string, t *template) error {
|
for _, tok := range t.tokens {
|
||||||
|
if tok.kind != nameBind {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b := t.nameScope.bindings[tok.bound]
|
||||||
|
ref := t.link.refs[b.ref]
|
||||||
|
b.head, b.tail = t.link.refHeads[ref.head], ref.tail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// namedReads calls fn with every read of a name t's format makes, and the token holding it.
|
||||||
|
func namedReads(t *template, fn func(o *op, a *arm) error) error {
|
||||||
for i := range t.compiled.ops {
|
for i := range t.compiled.ops {
|
||||||
o := &t.compiled.ops[i]
|
o := &t.compiled.ops[i]
|
||||||
for _, reads := range [][]arm{o.arms, o.operands} {
|
for _, reads := range [][]arm{o.arms, o.operands} {
|
||||||
for j := range reads {
|
for j := range reads {
|
||||||
if reads[j].kind != namedRead {
|
if reads[j].kind == namedRead {
|
||||||
continue
|
if err := fn(o, &reads[j]); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
if err := linkName(t, &reads[j]); err != nil {
|
|
||||||
return fmt.Errorf("%s: token {%s}: %w", t.site.label(path), o.body, err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,12 +239,19 @@ func linkNames(path string, t *template) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func linkName(t *template, a *arm) error {
|
// linkNames compiles t's reads of a name as paths from the head its binding's reference names, once
|
||||||
b := t.scope.lookup(a.head)
|
// every template is linked, so each binder has resolved that reference.
|
||||||
if b.head == nil {
|
func linkNames(path string, t *template) error {
|
||||||
ref := b.binder.link.refs[b.ref]
|
return namedReads(t, func(o *op, a *arm) error {
|
||||||
b.head, b.tail = b.binder.link.refHeads[ref.head], ref.tail
|
if err := linkName(t, a); err != nil {
|
||||||
|
return fmt.Errorf("%s: token {%s}: %w", t.site.label(path), o.body, err)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func linkName(t *template, a *arm) error {
|
||||||
|
b := t.nameScope.lookup(a.head)
|
||||||
if hasSelector(a.tail) {
|
if hasSelector(a.tail) {
|
||||||
return fmt.Errorf("a path through name %q selects no row; select it in the reference %q binds", a.head, a.head)
|
return fmt.Errorf("a path through name %q selects no row; select it in the reference %q binds", a.head, a.head)
|
||||||
}
|
}
|
||||||
@@ -209,20 +260,94 @@ func linkName(t *template, a *arm) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
w := compilePath(b.head, full)
|
w := compilePath(b.head, full)
|
||||||
a.bind, a.steps, a.leaves, a.cover = b, w.steps, w.leaves, w.cover
|
a.named, a.steps, a.leaves, a.cover = b, w.steps, w.leaves, w.cover
|
||||||
a.levels = make([]string, len(full)+1)
|
a.levels = make([]string, len(full)+1)
|
||||||
for i := range a.levels {
|
for i := range a.levels {
|
||||||
a.levels[i] = joinSegments(full[:i])
|
a.levels[i] = joinSegments(full[:i])
|
||||||
}
|
}
|
||||||
a.path = joinSegments(full)
|
a.path = joinSegments(full)
|
||||||
|
if b.addressed == nil {
|
||||||
|
b.addressed = map[string]string{}
|
||||||
|
}
|
||||||
|
for _, key := range append(a.levels[len(b.tail):], a.path) {
|
||||||
|
if _, seen := b.addressed[key]; !seen {
|
||||||
|
b.addressed[key] = a.spelling
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// namedPick is one draw of a name: the variant drawn at each level under it, the value each
|
// checkNameReads refuses a read of a name rendering, under the pick, a field it renders twice where
|
||||||
// path through it read, keyed by the path from the name, and the table rows they pinned. A
|
// a path through the name reads one draw of it: the pick keeps one draw there, and the two
|
||||||
// reference read under it draws in the draw group of the read of the name, which the draw
|
// renders would disagree with it.
|
||||||
// fences survey it in.
|
func checkNameReads(path string, t *template) error {
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 one of them", read, head, 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 {
|
type namedPick struct {
|
||||||
|
named *nameBinding
|
||||||
memo drawMemo
|
memo drawMemo
|
||||||
pins pinSet
|
pins pinSet
|
||||||
}
|
}
|
||||||
@@ -234,7 +359,11 @@ type pickFrame struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newPickFrame(scope *nameScope) *pickFrame {
|
func newPickFrame(scope *nameScope) *pickFrame {
|
||||||
return &pickFrame{scope: scope, picks: make([]namedPick, len(scope.order))}
|
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
|
// frameStack is the frames of the name scopes rendering, innermost last. A render's draws point
|
||||||
@@ -244,79 +373,104 @@ type frameStack struct {
|
|||||||
frames []*pickFrame
|
frames []*pickFrame
|
||||||
}
|
}
|
||||||
|
|
||||||
// pushFrame opens f until popFrame closes it, returning the mark popFrame takes.
|
// pushFrame opens f until popFrames closes it, returning the mark popFrames takes.
|
||||||
func (d *renderDraws) pushFrame(f *pickFrame) int {
|
func (d *renderDraws) pushFrame(f *pickFrame) int {
|
||||||
mark := d.depth()
|
mark := d.depth()
|
||||||
if d.frames == nil {
|
if d.frameStack == nil {
|
||||||
d.frames = &frameStack{}
|
d.frameStack = &frameStack{}
|
||||||
}
|
}
|
||||||
d.frames.frames = append(d.frames.frames, f)
|
d.frameStack.frames = append(d.frameStack.frames, f)
|
||||||
return mark
|
return mark
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *renderDraws) popFrame(mark int) { d.frames.frames = d.frames.frames[:mark] }
|
func (d *renderDraws) popFrames(mark int) {
|
||||||
|
if d.frameStack != nil {
|
||||||
|
d.frameStack.frames = d.frameStack.frames[:mark]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (d *renderDraws) depth() int {
|
func (d *renderDraws) depth() int {
|
||||||
if d.frames == nil {
|
if d.frameStack == nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return len(d.frames.frames)
|
return len(d.frameStack.frames)
|
||||||
}
|
}
|
||||||
|
|
||||||
// entering is sc as a read entering another category sees it: none of the frames rendering, and
|
// enter is sc as a read entering a category sees it, landing on n: none of the frames rendering,
|
||||||
// memo, which keeps the frame of each scope the read enters past its owner.
|
// and, from memo, the frame of each scope binding names around n, so every read entering with
|
||||||
func (sc renderScope) entering(memo *drawMemo) renderScope {
|
// memo reads one pick of each name. It returns the mark that closes those frames.
|
||||||
sc.base, sc.entry, sc.pick = sc.draws.depth(), memo, nil
|
func (sc renderScope) enter(n node, memo *drawMemo) (renderScope, int) {
|
||||||
return sc
|
sc.base, sc.pick = sc.draws.depth(), nil
|
||||||
}
|
for scope := scopeAround(n); scope != nil; scope = scope.up {
|
||||||
|
if len(scope.order) > 0 {
|
||||||
// frameOf is the frame a render of scope's owner opens: the one the read entering the category
|
sc.draws.pushFrame(memo.enteredFrame(scope))
|
||||||
// keeps, so {n} and {n.path} read one frame, else a fresh one.
|
|
||||||
func (sc renderScope) frameOf(scope *nameScope) *pickFrame {
|
|
||||||
if sc.entry == nil {
|
|
||||||
return newPickFrame(scope)
|
|
||||||
}
|
}
|
||||||
return sc.entry.frameOf(scope)
|
}
|
||||||
|
return sc, sc.base
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *drawMemo) frameOf(scope *nameScope) *pickFrame {
|
// scopeAround is the innermost name scope a render of n reads names in, short of the frames n
|
||||||
f, ok := m.frames[scope]
|
// 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 {
|
if !ok {
|
||||||
f = newPickFrame(scope)
|
f = newPickFrame(scope)
|
||||||
if m.frames == nil {
|
if m.enteredFrames == nil {
|
||||||
m.frames = map[*nameScope]*pickFrame{}
|
m.enteredFrames = map[*nameScope]*pickFrame{}
|
||||||
}
|
}
|
||||||
m.frames[scope] = f
|
m.enteredFrames[scope] = f
|
||||||
}
|
}
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
// pickOf is b's pick: in the frame rendering its scope since the read entering the category, else
|
// renderFrame opens a fresh frame of t's scope, where t binds names and no read entering the
|
||||||
// in the frame that read keeps, as for Fake("cat.field") or {/cat.field}.
|
// category opened one, returning the mark that closes it, or -1.
|
||||||
func (sc renderScope) pickOf(b *nameBinding) *namedPick {
|
func (sc renderScope) renderFrame(t *template) int {
|
||||||
if stack := sc.draws.frames; stack != nil {
|
if t.ownNameScope == nil || sc.frameOf(t.ownNameScope) != nil {
|
||||||
for i := len(stack.frames) - 1; i >= sc.base; i-- {
|
return -1
|
||||||
if f := stack.frames[i]; f.scope == b.scope {
|
|
||||||
return &f.picks[b.index]
|
|
||||||
}
|
}
|
||||||
}
|
return sc.draws.pushFrame(newPickFrame(t.ownNameScope))
|
||||||
}
|
|
||||||
return &sc.entryMemo().frameOf(b.scope).picks[b.index]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// entryMemo is the memo of the read that entered the category rendering, else the unnamed draw
|
// frameOf is the frame of scope rendering since the read entering the category, nil where none is.
|
||||||
// group's of the render: one memo for a render begun below a category's root, whatever draw group
|
func (sc renderScope) frameOf(scope *nameScope) *pickFrame {
|
||||||
// its templates name.
|
if stack := sc.draws.frameStack; stack != nil {
|
||||||
func (sc renderScope) entryMemo() *drawMemo {
|
for i := len(stack.frames) - 1; i >= sc.base; i-- {
|
||||||
if sc.entry != nil {
|
if f := stack.frames[i]; f.scope == scope {
|
||||||
return sc.entry
|
return f
|
||||||
}
|
}
|
||||||
return &sc.draws.unnamed.memo
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readName(s *generatorState, sc renderScope, a arm) readValue {
|
func readName(s *generatorState, sc renderScope, a arm) readValue {
|
||||||
p := sc.pickOf(a.bind)
|
f := sc.frameOf(a.named.scope)
|
||||||
return readPicked(s, p, a.bind.head, a.steps, a.levels, a.path, sc.entering(&p.memo))
|
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.
|
// drawRowOf draws the row a render of t reads: inside the pick's rows where t renders as part of one.
|
||||||
@@ -327,14 +481,25 @@ func (sc renderScope) drawRowOf(s *generatorState, t *table) int {
|
|||||||
return t.drawRow(s)
|
return t.drawRow(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// readUnder reads a of t, which renders as part of the pick sc.pick at sc.pickKey: once per pick,
|
// keeps reports whether the pick sc renders part of keeps a, a read of t: whether a read of the
|
||||||
// by its path from the name.
|
// 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 {
|
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))
|
levels := make([]string, len(a.levels))
|
||||||
for i, l := range a.levels {
|
for i, l := range a.levels {
|
||||||
levels[i] = underKey(sc.pickKey, l)
|
levels[i] = underKey(sc.pickKey, l)
|
||||||
}
|
}
|
||||||
return readPicked(s, sc.pick, t.head(a.head), a.steps, levels, underKey(sc.pickKey, a.path), sc)
|
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
|
// underKey is the key of path under the pick key prefix. It never returns prefix itself, which a
|
||||||
@@ -346,15 +511,17 @@ func underKey(prefix, path string) string {
|
|||||||
return prefix + "." + path
|
return prefix + "." + path
|
||||||
}
|
}
|
||||||
|
|
||||||
// readPicked draws the path key names under pick p, once, and renders what it lands on as part of p.
|
// draw draws the path key names under p, its variant at key kept too.
|
||||||
func readPicked(s *generatorState, p *namedPick, head node, steps []pathStep, levels []string, key string, sc renderScope) readValue {
|
func (p *namedPick) draw(s *generatorState, head node, steps []pathStep, levels []string, key string) node {
|
||||||
if r, done := p.memo.value[key]; done {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
leaf := drawSteps(s, head, steps, &p.pins, &p.memo, levels)
|
leaf := drawSteps(s, head, steps, &p.pins, &p.memo, levels)
|
||||||
if c, isChoice := leaf.(*choice); isChoice {
|
if c, isChoice := leaf.(*choice); isChoice {
|
||||||
leaf = p.memo.variantOf(s, c, key)
|
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 = sc.at(leaf, &p.pins)
|
||||||
sc.pick, sc.pickKey = p, key
|
sc.pick, sc.pickKey = p, key
|
||||||
r := renderLeaf(s, leaf, sc)
|
r := renderLeaf(s, leaf, sc)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ type choice struct {
|
|||||||
items []node
|
items []node
|
||||||
cum []float64
|
cum []float64
|
||||||
shared map[string]bool
|
shared map[string]bool
|
||||||
|
nameScope *nameScope // where its items look a name up, set by bindNames
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*choice) isNode() {}
|
func (*choice) isNode() {}
|
||||||
@@ -56,8 +57,8 @@ type template struct {
|
|||||||
unbound []unboundRead // the heads its tokens read that no field holds
|
unbound []unboundRead // the heads its tokens read that no field holds
|
||||||
|
|
||||||
// Filled by `bindNames`, from the compiled category:
|
// Filled by `bindNames`, from the compiled category:
|
||||||
scope *nameScope // where its tokens look a name up
|
nameScope *nameScope // where its tokens look a name up
|
||||||
ownScope *nameScope // the scope it renders a frame of: a category's, on its root, or a repeat's, per iteration
|
ownNameScope *nameScope // the scope it renders a frame of: a category's, on its root, or a repeat's, per iteration
|
||||||
|
|
||||||
// Filled by `linkTemplate`, from the assembled tree:
|
// Filled by `linkTemplate`, from the assembled tree:
|
||||||
link templateLink
|
link templateLink
|
||||||
|
|||||||
@@ -263,8 +263,8 @@ func recordOf(n node) (*template, []Column, error) {
|
|||||||
// over sc's draws; a table's columns read the row pinned there.
|
// over sc's draws; a table's columns read the row pinned there.
|
||||||
func renderRecord(s *generatorState, t *template, columns []Column, sc renderScope) *Record {
|
func renderRecord(s *generatorState, t *template, columns []Column, sc renderScope) *Record {
|
||||||
sc = sc.in(t)
|
sc = sc.in(t)
|
||||||
if t.ownScope != nil {
|
if mark := sc.renderFrame(t); mark >= 0 {
|
||||||
defer sc.draws.popFrame(sc.draws.pushFrame(sc.frameOf(t.ownScope)))
|
defer sc.draws.popFrames(mark)
|
||||||
}
|
}
|
||||||
if t.site.isFormat() {
|
if t.site.isFormat() {
|
||||||
sc = sc.at(t.site.table.rowNode, &sc.groupDraws().pins)
|
sc = sc.at(t.site.table.rowNode, &sc.groupDraws().pins)
|
||||||
|
|||||||
+11
-3
@@ -78,7 +78,10 @@ func linkRefs(root map[string]node) error {
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return eachTemplate(root, func(_ []string, path string, t *template) error { return linkNames(path, t) })
|
if err := eachTemplate(root, func(_ []string, path string, t *template) error { return linkNames(path, t) }); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return eachTemplate(root, func(_ []string, path string, t *template) error { return checkNameReads(path, t) })
|
||||||
}
|
}
|
||||||
|
|
||||||
// linkTemplate resolves t's references in category, "" for an inline template, and
|
// linkTemplate resolves t's references in category, "" for an inline template, and
|
||||||
@@ -89,6 +92,7 @@ func linkTemplate(folder []string, path, category string, t *template, root map[
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
t.link, t.compiled = link, compileFormat(t.tokens, link.refs, t.isName)
|
t.link, t.compiled = link, compileFormat(t.tokens, link.refs, t.isName)
|
||||||
|
linkBindings(t)
|
||||||
compileArms(t)
|
compileArms(t)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -253,12 +257,16 @@ func resolveCategory(root map[string]node, segments []string) (categorySegs []st
|
|||||||
return segments[:i], n, segments[i:], nil
|
return segments[:i], n, segments[i:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// refTokens returns the reference names a format reads, as tokens or as operands.
|
// refTokens returns the reference names a format reads, as tokens or as operands, or binds.
|
||||||
func refTokens(toks []formatToken) []string {
|
func refTokens(toks []formatToken) []string {
|
||||||
var refs []string
|
var refs []string
|
||||||
seen := map[string]bool{}
|
seen := map[string]bool{}
|
||||||
for _, tok := range toks {
|
for _, tok := range toks {
|
||||||
for _, name := range tok.names {
|
names := tok.names
|
||||||
|
if tok.kind == nameBind {
|
||||||
|
names = []string{tok.boundRef}
|
||||||
|
}
|
||||||
|
for _, name := range names {
|
||||||
if isRef(name) && !seen[name] {
|
if isRef(name) && !seen[name] {
|
||||||
seen[name] = true
|
seen[name] = true
|
||||||
refs = append(refs, name)
|
refs = append(refs, name)
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ func (f *Generator) Fake(path string) (string, error) {
|
|||||||
if _, ok := n.(*folder); ok {
|
if _, ok := n.(*folder); ok {
|
||||||
return "", fmt.Errorf("fejkdata: %s names a folder, not a value", path)
|
return "", fmt.Errorf("fejkdata: %s names a folder, not a value", path)
|
||||||
}
|
}
|
||||||
return render(f.rand, n, sc.at(n, &sc.groupDraws().pins)), nil
|
group := sc.groupDraws()
|
||||||
|
sc, _ = sc.at(n, &group.pins).enter(n, &group.memo)
|
||||||
|
return render(f.rand, n, sc), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// descend walks a caller's path to the node it names, pinning in sc the rows it
|
// descend walks a caller's path to the node it names, pinning in sc the rows it
|
||||||
@@ -78,8 +80,8 @@ func render(s *generatorState, n node, sc renderScope) string {
|
|||||||
case *template:
|
case *template:
|
||||||
sc = sc.in(n)
|
sc = sc.in(n)
|
||||||
if n.repeat == 1 {
|
if n.repeat == 1 {
|
||||||
if n.ownScope != nil {
|
if mark := sc.renderFrame(n); mark >= 0 {
|
||||||
defer sc.draws.popFrame(sc.draws.pushFrame(sc.frameOf(n.ownScope)))
|
defer sc.draws.popFrames(mark)
|
||||||
}
|
}
|
||||||
if lit, fixed := n.fixedText(); fixed {
|
if lit, fixed := n.fixedText(); fixed {
|
||||||
return lit
|
return lit
|
||||||
@@ -93,30 +95,29 @@ func render(s *generatorState, n node, sc renderScope) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// renderRepeat renders each iteration of t as a render of its own, inside the name scopes
|
// renderRepeat renders each iteration of t as a render of its own, inside the name scopes
|
||||||
// rendering t, where a name bound outside t keeps its pick in the frame sc.entryMemo keeps.
|
// rendering t, where a name bound outside t keeps its pick.
|
||||||
func renderRepeat(s *generatorState, t *template, sc renderScope) string {
|
func renderRepeat(s *generatorState, t *template, sc renderScope) string {
|
||||||
entry := sc.entryMemo()
|
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
b.Grow(t.repeat * (t.compiled.grow + len(t.separator)))
|
b.Grow(t.repeat * (t.compiled.grow + len(t.separator)))
|
||||||
for i := 0; i < t.repeat; i++ {
|
for i := 0; i < t.repeat; i++ {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
b.WriteString(t.separator)
|
b.WriteString(t.separator)
|
||||||
}
|
}
|
||||||
b.WriteString(expandAnew(s, t, sc.draws.frames, sc.base, entry))
|
b.WriteString(expandAnew(s, t, sc.draws.frameStack, sc.base))
|
||||||
}
|
}
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// expandAnew expands one repeat iteration of t as a render of its own, in no group, inside the
|
// expandAnew expands one repeat iteration of t as a render of its own, in no group, inside the
|
||||||
// name scopes of frames from base. Inlined into render's loop, its draws would move to the heap.
|
// name scopes of stack from base. Inlined into render's loop, its draws would move to the heap.
|
||||||
//
|
//
|
||||||
//go:noinline
|
//go:noinline
|
||||||
func expandAnew(s *generatorState, t *template, frames *frameStack, base int, entry *drawMemo) string {
|
func expandAnew(s *generatorState, t *template, stack *frameStack, base int) string {
|
||||||
draws := renderDraws{frames: frames}
|
draws := renderDraws{frameStack: stack}
|
||||||
if t.ownScope != nil {
|
if t.ownNameScope != nil {
|
||||||
defer draws.popFrame(draws.pushFrame(newPickFrame(t.ownScope)))
|
defer draws.popFrames(draws.pushFrame(newPickFrame(t.ownNameScope)))
|
||||||
}
|
}
|
||||||
return expand(s, t, renderScope{draws: &draws, base: base, entry: entry})
|
return expand(s, t, renderScope{draws: &draws, base: base})
|
||||||
}
|
}
|
||||||
|
|
||||||
// pick selects one item. Uniform choices are O(1); weighted choices are an
|
// pick selects one item. Uniform choices are O(1); weighted choices are an
|
||||||
@@ -180,10 +181,14 @@ func readField(s *generatorState, t *template, hold *drawMemo, sc renderScope, a
|
|||||||
switch {
|
switch {
|
||||||
case a.kind == namedRead:
|
case a.kind == namedRead:
|
||||||
return readName(s, sc, a)
|
return readName(s, sc, a)
|
||||||
case sc.pick != nil && !isRef(a.head):
|
case sc.pick != nil && !isRef(a.head) && sc.keeps(a):
|
||||||
return readUnder(s, t, sc, a)
|
return readUnder(s, t, sc, a)
|
||||||
|
}
|
||||||
|
sc.pick = nil
|
||||||
|
switch {
|
||||||
case a.kind == freshRead && isRef(a.head):
|
case a.kind == freshRead && isRef(a.head):
|
||||||
return readValue{text: render(s, t.head(a.head), sc.entering(nil))}
|
sc.base = sc.draws.depth()
|
||||||
|
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)}
|
||||||
}
|
}
|
||||||
@@ -205,10 +210,14 @@ func readMemo(s *generatorState, t *template, memo *drawMemo, pins *pinSet, sc r
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
leaf := drawSteps(s, t.head(a.head), a.steps, pins, memo, a.levels)
|
leaf := drawSteps(s, t.head(a.head), a.steps, pins, memo, a.levels)
|
||||||
|
mark := -1
|
||||||
if pins != nil {
|
if pins != nil {
|
||||||
sc = sc.at(leaf, pins).entering(memo)
|
sc, mark = sc.at(leaf, pins).enter(leaf, memo)
|
||||||
}
|
}
|
||||||
r := renderLeaf(s, leaf, sc)
|
r := renderLeaf(s, leaf, sc)
|
||||||
|
if mark >= 0 {
|
||||||
|
sc.draws.popFrames(mark)
|
||||||
|
}
|
||||||
if memo.value == nil {
|
if memo.value == nil {
|
||||||
memo.value = map[string]readValue{}
|
memo.value = map[string]readValue{}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -345,8 +345,8 @@ func (t *table) compileRowFormat(format string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(unbound) > 0 {
|
if err := checkTableToks(nil, unbound); err != nil {
|
||||||
return unbound[0].err
|
return 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}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
`Bar = 9`
|
`Bar = 9`
|
||||||
|
|
||||||
`Next ID = 72`
|
`Next ID = 76`
|
||||||
|
|
||||||
| Goal | W |
|
| Goal | W |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
| 61 | 0.1.0 | | **Add a locale, with its `geo/` tree, for each of the ten most-spoken languages and each Nordic country.** | 4 | 9 | 9 | 10 | 2.1 | 30.8 |
|
| 61 | 0.1.0 | | **Add a locale, with its `geo/` tree, for each of the ten most-spoken languages and each Nordic country.** | 4 | 9 | 9 | 10 | 2.1 | 30.8 |
|
||||||
| 16 | 0.1.0 | | **Add the remaining locale categories: company, phone, finance, vehicle, words.** | 3 | 8 | 8 | 9 | 2.2, 14 | 28.1 |
|
| 16 | 0.1.0 | | **Add the remaining locale categories: company, phone, finance, vehicle, words.** | 3 | 8 | 8 | 9 | 2.2, 14 | 28.1 |
|
||||||
| 15 | 0.1.0 | | **Add the remaining `misc` tables and templates, one row of its detail's table per chunk.** | 3 | 9 | 8 | 8 | 2, 14, 13 | 25.9 |
|
| 15 | 0.1.0 | | **Add the remaining `misc` tables and templates, one row of its detail's table per chunk.** | 3 | 9 | 8 | 8 | 2, 14, 13 | 25.9 |
|
||||||
| 3 | 0.1.0 | | **Rewrite the shipped categories so facts that belong together come from one named pick.** | 5 | 5 | 8 | 9 | 4.1 | 25.0 |
|
| 3 | 0.1.0 | decision | **Rewrite the shipped categories so facts that belong together come from one named pick.** | 5 | 5 | 8 | 9 | 4.1 | 25.0 |
|
||||||
| 4 | 0.1.0 | decision | **Draw every `{…}` afresh, keep a pick only by name, and delete `drawGroup` with the fences that held picks implicitly.** | 7 | 8 | 8 | 10 | 5.2, 4.1 | 24.0 |
|
| 4 | 0.1.0 | decision | **Draw every `{…}` afresh, keep a pick only by name, and delete `drawGroup` with the fences that held picks implicitly.** | 7 | 8 | 8 | 10 | 5.2, 4.1 | 24.0 |
|
||||||
| 25 | 0.1.0 | defect | **Draw `en_US.phone`'s `exch` as a NANP central office code, and assert the rule in `TestShippedUSPhone`.** | 2 | 2 | 6 | 8 | 4 | 23.1 |
|
| 25 | 0.1.0 | defect | **Draw `en_US.phone`'s `exch` as a NANP central office code, and assert the rule in `TestShippedUSPhone`.** | 2 | 2 | 6 | 8 | 4 | 23.1 |
|
||||||
| 57 | 0.1.0 | | **Rewrite `CHANGELOG.md`'s `[Unreleased]` as what v0.1.0 holds.** | 1 | 2 | 6 | 6 | 1.1 | 22.5 |
|
| 57 | 0.1.0 | | **Rewrite `CHANGELOG.md`'s `[Unreleased]` as what v0.1.0 holds.** | 1 | 2 | 6 | 6 | 1.1 | 22.5 |
|
||||||
@@ -88,6 +88,8 @@
|
|||||||
| 32 | 0.2.0 | | **Add `misc.browser` as the parent of `misc.useragent`, so `misc.browser[Chrome].useragent` resolves.** | 3 | 3 | 4 | 6 | 4.1 | 14.4 |
|
| 32 | 0.2.0 | | **Add `misc.browser` as the parent of `misc.useragent`, so `misc.browser[Chrome].useragent` resolves.** | 3 | 3 | 4 | 6 | 4.1 | 14.4 |
|
||||||
| 27 | 0.2.0 | | **Merge `en_US.ip` and `sv_SE.ip`, today byte-identical, into one `misc.ip`.** | 3 | 2 | 4 | 6 | 5.7 | 14.1 |
|
| 27 | 0.2.0 | | **Merge `en_US.ip` and `sv_SE.ip`, today byte-identical, into one `misc.ip`.** | 3 | 2 | 4 | 6 | 5.7 | 14.1 |
|
||||||
| 46 | 0.2.0 | | **Report the same error every load for a table with two bad options, and for a folder with two unnamed rows files.** | 2 | 2 | 3 | 7 | 6, 9 | 14.0 |
|
| 46 | 0.2.0 | | **Report the same error every load for a table with two bad options, and for a folder with two unnamed rows files.** | 2 | 2 | 3 | 7 | 6, 9 | 14.0 |
|
||||||
|
| 73 | 0.2.0 | question | **Decide whether the default embed's partial Swedish and US place tables stand against goal 13.1, or have the goals say which goal wins.** | 1 | 1 | 3 | 6 | 13.1 | 9.6 |
|
||||||
|
| 74 | 0.2.0 | question | **Decide whether `misc.timezone` may drop the 17 zones of territories `misc.territory` leaves out, against goal 13.1, or have the goals say which goal wins.** | 1 | 1 | 3 | 6 | 13.1 | 9.6 |
|
||||||
| 14 | 0.3.0 | | **Spell `misc.creditcard`'s digit runs `{digits(n)}`, and refuse a repeat over a lone `{digits(1)}` at `New`, naming that spelling.** | 3 | 3 | 4 | 6 | 5.7 | 13.9 |
|
| 14 | 0.3.0 | | **Spell `misc.creditcard`'s digit runs `{digits(n)}`, and refuse a repeat over a lone `{digits(1)}` at `New`, naming that spelling.** | 3 | 3 | 4 | 6 | 5.7 | 13.9 |
|
||||||
| 38 | 0.3.0 | defect | **Compare `calcParser.binary`'s operator as a rune.** | 1 | 1 | 3 | 6 | 6 | 13.8 |
|
| 38 | 0.3.0 | defect | **Compare `calcParser.binary`'s operator as a rune.** | 1 | 1 | 3 | 6 | 6 | 13.8 |
|
||||||
| 45 | 0.3.0 | | **Name the node a selector follows in `stepInto`'s refusal.** | 1 | 1 | 3 | 6 | 6 | 13.8 |
|
| 45 | 0.3.0 | | **Name the node a selector follows in `stepInto`'s refusal.** | 1 | 1 | 3 | 6 | 6 | 13.8 |
|
||||||
@@ -99,6 +101,7 @@
|
|||||||
| 11 | 0.3.0 | | **Test that every `geo` tree holds the five table names and the `address` columns that port across countries.** | 1 | 2 | 3 | 5 | 4 | 13.0 |
|
| 11 | 0.3.0 | | **Test that every `geo` tree holds the five table names and the `address` columns that port across countries.** | 1 | 2 | 3 | 5 | 4 | 13.0 |
|
||||||
| 35 | 0.3.0 | | **Add `{base64url(n)}` for JWT shapes.** | 1 | 2 | 4 | 5 | 8 | 13.0 |
|
| 35 | 0.3.0 | | **Add `{base64url(n)}` for JWT shapes.** | 1 | 2 | 4 | 5 | 8 | 13.0 |
|
||||||
| 21 | 0.3.0 | | **List `title` in both locales, and `sv_SE`'s `birth-number`, in the README's Data list of what each locale carries.** | 1 | 1 | 4 | 6 | 11 | 12.8 |
|
| 21 | 0.3.0 | | **List `title` in both locales, and `sv_SE`'s `birth-number`, in the README's Data list of what each locale carries.** | 1 | 1 | 4 | 6 | 11 | 12.8 |
|
||||||
|
| 75 | 0.3.0 | | **Run every README `sh` example as a test, and compare each "Renders" line to a seeded render.** | 2 | 5 | 4 | 7 | 11.2 | 11.8 |
|
||||||
| 23 | 0.3.0 | | **Give every other shipped category that could reach something real a path that never does.** | 3 | 6 | 6 | 7 | 15 | 11.7 |
|
| 23 | 0.3.0 | | **Give every other shipped category that could reach something real a path that never does.** | 3 | 6 | 6 | 7 | 15 | 11.7 |
|
||||||
| 8 | 0.3.0 | | **Move `binding`, `bind` and `checkNodeFences` out of `data.go` into a file of their own.** | 1 | 2 | 2 | 5 | 3.1 | 11.5 |
|
| 8 | 0.3.0 | | **Move `binding`, `bind` and `checkNodeFences` out of `data.go` into a file of their own.** | 1 | 2 | 2 | 5 | 3.1 | 11.5 |
|
||||||
| 9 | 0.3.0 | | **Move `table.route`, `selector`, `step` and `drawStep` from `path.go` to `table.go`.** | 1 | 2 | 2 | 5 | 3.1 | 11.5 |
|
| 9 | 0.3.0 | | **Move `table.route`, `selector`, `step` and `drawStep` from `path.go` to `table.go`.** | 1 | 2 | 2 | 5 | 3.1 | 11.5 |
|
||||||
@@ -106,6 +109,7 @@
|
|||||||
| 40 | 0.3.0 | | **Parse `defaultTable`'s path with the library's grammar.** | 2 | 2 | 3 | 5 | 5.7 | 11.5 |
|
| 40 | 0.3.0 | | **Parse `defaultTable`'s path with the library's grammar.** | 2 | 2 | 3 | 5 | 5.7 | 11.5 |
|
||||||
| 33 | 0.3.0 | | **Group `data/misc` into folders where a group name makes a path easier to guess.** | 4 | 5 | 4 | 5 | 5 | 10.8 |
|
| 33 | 0.3.0 | | **Group `data/misc` into folders where a group name makes a path easier to guess.** | 4 | 5 | 4 | 5 | 5 | 10.8 |
|
||||||
| 47 | 0.3.0 | defect | **Refuse a category naming a hidden rows file, `"rows": ".x.tsv"`, as a rows file that is not there.** | 1 | 1 | 2 | 5 | 6 | 10.2 |
|
| 47 | 0.3.0 | defect | **Refuse a category naming a hidden rows file, `"rows": ".x.tsv"`, as a rows file that is not there.** | 1 | 1 | 2 | 5 | 6 | 10.2 |
|
||||||
|
| 72 | 0.3.0 | principle | **Fail the merge gate when test coverage falls below the last recorded figure.** | 2 | 3 | 2 | 5 | 3 | 10.2 |
|
||||||
| 7 | 0.3.0 | | **Fill `arm` in one place.** | 4 | 4 | 2 | 6 | 3.2 | 9.8 |
|
| 7 | 0.3.0 | | **Fill `arm` in one place.** | 4 | 4 | 2 | 6 | 3.2 | 9.8 |
|
||||||
| 19 | 0.3.0 | | **Fill the 398 Swedish localities weighted 200 from SCB småorter.** | 2 | 4 | 4 | 6 | 13.1 | 9.8 |
|
| 19 | 0.3.0 | | **Fill the 398 Swedish localities weighted 200 from SCB småorter.** | 2 | 4 | 4 | 6 | 13.1 | 9.8 |
|
||||||
| 37 | 0.3.0 | | **Rename `newRand` to `newGeneratorState` and `Generator.rand` to `state`.** | 1 | 1 | 1 | 5 | 3.3 | 9.8 |
|
| 37 | 0.3.0 | | **Rename `newRand` to `newGeneratorState` and `Generator.rand` to `state`.** | 1 | 1 | 1 | 5 | 3.3 | 9.8 |
|
||||||
@@ -189,16 +193,16 @@ Shape: T = table, t = template, c = choice.
|
|||||||
### 3. Rewrite the shipped categories so facts that belong together come from one named pick.
|
### 3. Rewrite the shipped categories so facts that belong together come from one named pick.
|
||||||
|
|
||||||
- `sv_SE.person` and `en_US.person` bind the first name and read sex and title through it: `"first": "{.first-name as n}{n.name}"`, `"sex": "{n..sex.name}"`, a title from `{n..sex.title.name}`.
|
- `sv_SE.person` and `en_US.person` bind the first name and read sex and title through it: `"first": "{.first-name as n}{n.name}"`, `"sex": "{n..sex.name}"`, a title from `{n..sex.title.name}`.
|
||||||
- `geo.SE.address` and `geo.US.address` bind the street and read locality, postal code and region through it; the locale `address` categories bind `/geo.XX.address` once.
|
- `geo.SE.address` and `geo.US.address` bind the street and read locality, postal code and region through it; the locale `address` categories bind `/geo.XX.address` once and print it whole as `{a}`, so the country record's format appears once, and the decision "A locale's `address` restates its country record's format" is revised.
|
||||||
- Needs item 2. Every shipped value must still pass its consumer's check before and after item 4.
|
- Needs item 2. Every shipped value must still pass its consumer's check before and after item 4.
|
||||||
- Re-pin seeded output in its own commit.
|
- Re-pin seeded output in its own commit.
|
||||||
|
|
||||||
### 4. Draw every `{…}` afresh, keep a pick only by name, and delete `drawGroup` with the fences that held picks implicitly.
|
### 4. Draw every `{…}` afresh, keep a pick only by name, and delete `drawGroup` with the fences that held picks implicitly.
|
||||||
|
|
||||||
- Needs items 2, 3 and 71. After it, `{/person} ({/person.first})` renders two people, `{w} {uppercase(w)}` renders `b G` where `w` is a choice of letters, and `{/city.name}, {/country[SE].name}` and `{/country[SE].name} / {/country[NO].name}` both load.
|
- Needs items 2, 3 and 71. After it, `{/person} ({/person.first})` renders two people, as does `{/sv_SE.person as a}{/sv_SE.person as b}{a} & {b}`, `{w} {uppercase(w)}` renders `b G` where `w` is a choice of letters, and `{/city.name}, {/country[SE].name}` and `{/country[SE].name} / {/country[NO].name}` both load.
|
||||||
- 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 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.
|
||||||
- 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`.
|
||||||
@@ -333,6 +337,14 @@ Require the path step to reach a sibling category.
|
|||||||
|
|
||||||
`readTableOptions` and `loadDir` return on the first in Go's map order.
|
`readTableOptions` and `loadDir` return on the first in Go's map order.
|
||||||
|
|
||||||
|
### 73. Decide whether the default embed's partial Swedish and US place tables stand against goal 13.1, or have the goals say which goal wins.
|
||||||
|
|
||||||
|
The decision "The default embed holds every Swedish postort the import can place and give a street-delivery code and a street, and the US places of 25,000 or more" ships less than its registers hold, and names no item that ends it. Item 60 ships the full registers as packs, beside the partial default.
|
||||||
|
|
||||||
|
### 74. Decide whether `misc.timezone` may drop the 17 zones of territories `misc.territory` leaves out, against goal 13.1, or have the goals say which goal wins.
|
||||||
|
|
||||||
|
The decision "`misc.territory` is the spine, and a `misc` table naming a territory links to it" serves goal 4.1 and drops those tzdb rows, and names no item that ends it.
|
||||||
|
|
||||||
### 14. Spell `misc.creditcard`'s digit runs `{digits(n)}`, and refuse a repeat over a lone `{digits(1)}` at `New`, naming that spelling.
|
### 14. Spell `misc.creditcard`'s digit runs `{digits(n)}`, and refuse a repeat over a lone `{digits(1)}` at `New`, naming that spelling.
|
||||||
|
|
||||||
Both render the same run.
|
Both render the same run.
|
||||||
@@ -369,6 +381,10 @@ Needs item 4; recount the switches then. Today ten, from `render` to `columnItem
|
|||||||
|
|
||||||
Nothing checks it, and a new country breaks it silently.
|
Nothing checks it, and a new country breaks it silently.
|
||||||
|
|
||||||
|
### 75. Run every README `sh` example as a test, and compare each "Renders" line to a seeded render.
|
||||||
|
|
||||||
|
`readme_test.go` loads and renders each `json` block, but runs no `sh` example (CLI, Records, Table, Linked tables) and compares no "Renders e.g." output, so goal 11.2 is not met.
|
||||||
|
|
||||||
### 23. Give every other shipped category that could reach something real a path that never does.
|
### 23. Give every other shipped category that could reach something real a path that never does.
|
||||||
|
|
||||||
`phone` draws live PTS and NANP ranges — PTS's five fiction series are the Swedish inert set, sourced in [research-sources-se.md](docs/research/research-sources-se.md), and the NANP one still wants a source — and `bankgiro`, `plusgiro` and `routing` draw live prefixes.
|
`phone` draws live PTS and NANP ranges — PTS's five fiction series are the Swedish inert set, sourced in [research-sources-se.md](docs/research/research-sources-se.md), and the NANP one still wants a source — and `bankgiro`, `plusgiro` and `routing` draw live prefixes.
|
||||||
@@ -389,6 +405,10 @@ The CLI counts brackets of its own, so a change to the selector grammar desyncs
|
|||||||
|
|
||||||
`loadDir` indexes hidden TSVs and skips them in two places, so one loads where its comment says a hidden file is never data.
|
`loadDir` indexes hidden TSVs and skips them in two places, so one loads where its comment says a hidden file is never data.
|
||||||
|
|
||||||
|
### 72. Fail the merge gate when test coverage falls below the last recorded figure.
|
||||||
|
|
||||||
|
`technical-principles.md` holds that test coverage should not decline. `docker compose run --rm cover` prints the figure, and neither the `Dockerfile` gate nor `.github/workflows/test.yml` checks it.
|
||||||
|
|
||||||
### 7. Fill `arm` in one place.
|
### 7. Fill `arm` in one place.
|
||||||
|
|
||||||
`splitArm` runs before the link and again after it, and `compileArm` finishes it from `reference.go`, so five seats traced three phases. Needs item 4.
|
`splitArm` runs before the link and again after it, and `compileArm` finishes it from `reference.go`, so five seats traced three phases. Needs item 4.
|
||||||
|
|||||||
+4
-9
@@ -43,16 +43,11 @@ func unwrapTransform(arg string) (leaf string, chain []func(string) string, err
|
|||||||
arg = args[0]
|
arg = args[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func transformArg(fields map[string]node, a []string) error {
|
|
||||||
leaf, _, err := unwrapTransform(a[0])
|
// transformArg proves a transform's chain; checkReads proves the operand it reads.
|
||||||
if err != nil {
|
func transformArg(_ map[string]node, a []string) error {
|
||||||
|
_, _, err := unwrapTransform(a[0])
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
if isRef(leaf) {
|
|
||||||
_, _, err := refShape(leaf)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return checkArm(leaf, fields, false)
|
|
||||||
}
|
}
|
||||||
func transformOperand(a []string) []string {
|
func transformOperand(a []string) []string {
|
||||||
if len(a) != 1 {
|
if len(a) != 1 {
|
||||||
|
|||||||
Reference in New Issue
Block a user