Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33

Open
lilleman wants to merge 966 commits from worktree-todo-relative-dates-exact-counts into main
10 changed files with 55 additions and 71 deletions
Showing only changes of commit 62214370ed - Show all commits
+4 -12
View File
@@ -8,18 +8,10 @@ replacement, and each removed path, column or flag.
### Added
- A name keeps one pick: `{/misc.territory as t}` binds one row of `misc.territory` to `t`
and prints nothing, and `{t}`, `{t.capital}` and `{t.calling-code}` read that one row.
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
`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; a binding of anything but a reference; a binding in a
choice's item; a binding nothing reads, or one of a whole category read once whole, which the bare reference 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; reads of a name in two draw groups, or inside a
`repeat`, where what it names reads a reference path; and a binding in a table. A field, column,
category or folder name may not contain ` as `.
- Names: `{/misc.territory as t}` binds one row of `misc.territory` to `t` and prints
nothing; `{t}`, `{t.capital}` and `{lowercase(t.name)}` read that one row. A name lives
in the category binding it, and a `repeat` binding it picks again on every iteration.
What `New` refuses is listed under [Names](README.md#names).
- 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
`key`, `name`, `weight` and `parent`; a path selects a row by key or name,
+9 -9
View File
@@ -708,23 +708,23 @@ row:
"{/misc.territory as t}{t.flag} {t}: capital {t.capital}, calling code +{t.calling-code}"
```
Renders e.g. `🇳🇴 Norway: capital Oslo, calling code +47`. A name keeps each level a
`{n.path}` reads, and what it lands on, so `{n}` and `{n.path}` agree, and a
[transform](#transforms) of `n.path` reads the same value. Everything else under the name draws
as it would anywhere. So if the category `n` names has the format `{w}-{w}`, it draws `w`
twice, and `{n.w}` beside `{n}` is refused, since it could read either draw.
Renders e.g. `🇳🇴 Norway: capital Oslo, calling code +47`. A name keeps what each `{n.path}`
lands on and every level it passes, so `{n}` and `{n.path}` agree, and so does a
[transform](#transforms) of `n.path`. Everything else under the name draws as it would
anywhere: if `n`'s category has the format `{w}-{w}`, it draws `w` twice, and `{n.w}` beside
`{n}` is refused, since it could read either draw.
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).
bound to `sv_SE.person`, whose fields read `{.first-name.name}`, print the same first and last
name 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
category reads it, and a record's columns read one pick. Each render of the category picks
anew, and so does each bare reference to it. A name a `repeat` binds picks again on every
iteration, and one bound outside the repeat keeps its pick on every line.
Rejected at load, each error naming what to write instead:
Refused at `New`, each error naming what to write instead:
- a name bound twice outside any `repeat`, twice in one `repeat`, or both inside a `repeat`
and outside it;
@@ -734,7 +734,7 @@ Rejected at load, each error naming what to write instead:
- a binding nothing reads, or a binding of a whole category read once whole where it is
bound, which the bare reference spells: `{/word as w}{w}` is `{/word}`;
- 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;
- `{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
reference path, which each draw group and iteration draws apart;
- a binding in a table's format or cell;
+6 -5
View File
@@ -27,11 +27,12 @@ package fejkdata
// 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,
// `readReference` a draw group's.
// - named pick — the draw a token such as {/person as p} binds to a name. It is drawn on its
// first read and kept while its scope renders: a category, or one repeat iteration.
// `nameBinding`, `nameScope`, `namedPick`, `pickFrame`.
// - 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`.
// - named pick — the draw a token such as {/person as p} binds to a name, drawn
// on its first read and kept while its scope renders: a category, or one
// repeat iteration: `nameBinding`, `nameScope`, `namedPick`, `pickFrame`.
// - 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`.
// - draw group — reference draws kept apart inside one render:
// `template.drawGroup` as the data spells it, `templateLink.drawGroupKey` as a
// render reads it, `renderDraws`, `groupDraws`, `renderScope`.
+9 -8
View File
@@ -662,7 +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
on a name.
`todo.md` item 3 replaces it.
`todo.md` item 3 revises this decision: the locale `address` prints `{a}` whole, so the
format appears once.
## A postort's kommun comes from its name, its tätort or its codes, never from distance
@@ -902,13 +903,11 @@ since a read outside the item would see a binding another item leaves out. A pic
its first read, so a record's columns, rendered in name order, read one pick whichever binds
it.
The scope is lexical: a read entering a category, by a reference, through a name or from
`Fake`, sees none of the frames rendering above it, so what a category renders never depends
on who references it. That read opens the frame of each scope around what it lands on, taken
from its own memo, so every read entering by one memo, `{n}` and `{n.path}` alike, reads one
pick: per render and group for a reference path, per pick for a path through a name, per
render for `Fake("cat.field")`. A frame is looked up on the stack of frames rendering, and
nowhere else.
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
the same whoever references it. That read opens the frames around where it lands from its own
memo, so reads sharing a memo, `{n}` and `{n.path}` alike, read one pick. A reference path's
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
under a name as anywhere else (goal 5.2). A reference read under a pick draws in the draw group
@@ -918,3 +917,5 @@ path, its reads stay in one draw group and outside any repeat: a reference path
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
the render.
Valid while names are read only inside the category binding them.
+4 -4
View File
@@ -4,8 +4,8 @@ import "strings"
// drawMemo is what a hold, a draw group or a named pick has drawn: the variant each level was
// drawn as, so every path under it reads one variant; the value each read produced, by its path,
// so the same read written twice reads one value; and the frame of each name scope a read
// entered past its owner.
// so the same read written twice reads one value; and the frames a read opens for the name
// scopes around where it lands (enter).
type drawMemo struct {
variant map[string]node
value map[string]readValue
@@ -48,8 +48,8 @@ type renderScope struct {
// base is the depth of the frame stack when the read rendering the category entered it,
// below which no frame is the category's.
base int
// pick is the named pick what renders is part of, at pickKey, its path from the name; nil
// outside one.
// pick is the named pick being rendered, nil outside one; pickKey is the path from the name
// to what renders.
pick *namedPick
pickKey string
}
+6 -8
View File
@@ -77,7 +77,6 @@ const (
nameBind
)
// asWord separates a binding's reference from the name it binds: {/person as p}.
const asWord = " as "
// parseFormat is the one reading of a format's tokens: a '(' outside a selector makes
@@ -223,9 +222,8 @@ type unboundRead struct {
err error
}
// 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
// back unbound, for bindNames to look up among the names.
// checkTokens proves every token's grammar, functions and field paths, and returns the reads
// whose head no field holds, for bindNames to look up among the names.
func checkTokens(toks []formatToken, fields map[string]node) ([]unboundRead, error) {
var unbound []unboundRead
for _, t := range toks {
@@ -284,8 +282,8 @@ func checkReads(t formatToken, fields map[string]node, operands bool) ([]unbound
return unbound, nil
}
// checkBind proves a {ref as name} token binds a reference to a name no other token
// could read as something else.
// checkBind proves a {ref as name} token is spelled once, binds a reference, and names a valid
// name that is no option.
func checkBind(t formatToken) error {
ref, name := t.boundRef, t.bound
if trimmed := strings.TrimSpace(ref) + asWord + strings.TrimSpace(name); trimmed != t.body {
@@ -301,7 +299,7 @@ func checkBind(t formatToken) error {
return fmt.Errorf("token {%s}: a binding names nothing; write {%s as n}", t.body, ref)
}
if isOption(name) {
return fmt.Errorf("token {%s}: %q is an option and can never be a name", t.body, name)
return fmt.Errorf("token {%s}: %q is an option and can never be a name; rename it", t.body, name)
}
if err := checkName(name); err != nil {
return fmt.Errorf("token {%s}: name %w", t.body, err)
@@ -534,7 +532,7 @@ func (c *formatOps) field(tok formatToken, refs map[string]refBinding, isName fu
c.ops = append(c.ops, op{formatToken: tok, arms: arms})
}
// compileFormat compiles a parsed format, a binding to nothing, since it prints nothing. Call
// compileFormat compiles a parsed format; a {ref as n} token compiles to no op. Call
// checkTokens first: it is what proves every token valid.
func compileFormat(toks []formatToken, refs map[string]refBinding, isName func(head string) bool) formatOps {
var c formatOps
+11 -19
View File
@@ -7,9 +7,9 @@ import (
"strings"
)
// nameScope is where a name is bound: a category, or one iteration of a repeat inside it.
// Every token of the category outside a nested repeat reads the category's names, so one
// field may bind a name and another read it.
// nameScope is where a name is bound: a category, or one iteration of a repeat inside it. A
// token sees its scope's names and every enclosing scope's, so one field may bind a name and
// another read it.
type nameScope struct {
up *nameScope
owner node // the category's root, or the repeat
@@ -17,9 +17,6 @@ type nameScope struct {
order []*nameBinding
}
// nameBinding is one {ref as name} token: the scope it binds name in, its index among
// 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 {
name string
ref string
@@ -56,7 +53,7 @@ func (sc *nameScope) lookup(name string) *nameBinding {
// 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
// choice's item, a name a field spells too, a read no field or name answers, and a binding
// read by nothing, or once whole, which its reference spells.
// read by nothing.
func bindNames(root node) error {
top := &nameScope{owner: root}
scopes := []*nameScope{top}
@@ -100,8 +97,7 @@ func bindNames(root node) error {
return nil
}
// bindAll binds every name t's tokens bind. A choice's item binds none: a read outside the item
// would find the name bound by an item that may never be drawn.
// bindAll binds every name t's tokens bind.
func (sc *nameScope) bindAll(t *template, inChoice bool, where string) error {
for _, tok := range t.tokens {
if tok.kind != nameBind {
@@ -221,7 +217,6 @@ func unresolved(where string, u unboundRead, seen *nameScope, scopes []*nameScop
return fmt.Errorf("%s%w", where, u.err)
}
// isName reports whether a head t reads is a name: neither a reference nor a field.
func (t *template) isName(head string) bool {
return !isRef(head) && t.fields[head] == nil && t.nameScope.lookup(head) != nil
}
@@ -315,10 +310,8 @@ func checkNameReads(path string, t *template) error {
})
}
// checkUses refuses a binding of a category read once whole, which the bare reference spells, and
// reads of b in two draw groups, or inside a repeat, where what b names reads a
// reference path: that path is held per 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 the hold.
// 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
@@ -481,9 +474,9 @@ func (d *renderDraws) depth() int {
return len(d.frameStack.frames)
}
// enter is sc as a read entering a category sees it, landing on n: none of the frames rendering,
// and, from memo, the frame of each scope binding names around n, so every read entering with
// memo reads one pick of each name. It returns the mark that closes those frames.
// enter hides the frames already rendering from a read landing on n, and opens from memo a frame
// for each name scope around n, so every read sharing memo reads 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 {
@@ -566,8 +559,7 @@ func (sc renderScope) drawRowOf(s *generatorState, t *table) int {
return t.drawRow(s)
}
// keeps reports whether the pick sc renders part of keeps a, a read of t: whether a read of the
// name addresses the level a starts at.
// 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
+1 -1
View File
@@ -22,7 +22,7 @@ type Column struct {
// Record is one record rendered from a template: every direct field is a column,
// listed in name order. Each column is its own expansion, so a sibling field is
// local to it, while a reference that reads a path is drawn once for the whole
// record, per group, and a name once for the whole record.
// record, per group, and a pick bound by {ref as n} once for the whole record.
type Record struct {
columns []Column
}
+2 -2
View File
@@ -109,7 +109,7 @@ func renderRepeat(s *generatorState, t *template, sc renderScope) string {
}
// expandAnew expands one repeat iteration of t as a render of its own, in no group, inside the
// name scopes of stack from base. Inlined into render's loop, its draws would move to the heap.
// name scopes of stack from base. Inlined into renderRepeat's loop, its draws would move to the heap.
//
//go:noinline
func expandAnew(s *generatorState, t *template, stack *frameStack, base int) string {
@@ -169,7 +169,7 @@ func expand(s *generatorState, t *template, sc renderScope) string {
// token addresses by a dotted path that is not a reference, or a field an operand
// reads — is drawn once and kept in hold, so {place.postal-code} and {place.locality}
// read one row, either read twice gives one value, and a shown operand is the operand
// computed. A field of a template rendering as part of a named pick is kept in the pick.
// computed. A field a read of a name addresses is kept in that name's pick.
// Every other field is drawn afresh, so {word} {word} still draws twice.
func readField(s *generatorState, t *template, hold *drawMemo, sc renderScope, a arm) readValue {
if a.kind == refPathRead {
+3 -3
View File
@@ -193,13 +193,13 @@ Shape: T = table, t = template, c = choice.
### 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}`.
- `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.
- `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. They print it whole as `{a}`, so the country record's format appears once; revise the decision "A locale's `address` restates its country record's format".
- 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.
### 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, 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.
- 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.
- 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.
@@ -339,7 +339,7 @@ Require the path step to reach a sibling category.
### 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.
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" serves goal 2's built-in data with no download, 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.