Answer stability round 3, product-owner round 3 and prose round 1: a column reading a bound column takes its datatype and null, a column bound in part draws afresh, and plainer wording across the README, decisions, plan and errors
This commit is contained in:
@@ -63,7 +63,7 @@ In [docs/decisions.md](docs/decisions.md):
|
||||
- A record's column set is fixed before the first draw
|
||||
- Null is a `null` item, not a rate
|
||||
- A typed column holds one value, not composed text
|
||||
- A column of one reference or name read alone is the column it reads
|
||||
- A column that only reads one reference or name is the column it reads
|
||||
- A typed column's calc is refused unless proven
|
||||
- `Column` carries text, not a Go value
|
||||
- A unit takes the stem of what it is, and a file the stem of the units it holds
|
||||
@@ -74,7 +74,7 @@ In [docs/decisions.md](docs/decisions.md):
|
||||
- `parent` names the link column and the table alike
|
||||
- After a row, a path names a column or a linked table
|
||||
- A selected row is fixed, not drawn
|
||||
- `..` steps up to the parent row, and a step down after it draws afresh, or once per pick
|
||||
- `..` steps up to the parent row, and a step down after it draws afresh, or once per name's pick
|
||||
- A table selects by one key column, a code, never a free-form name
|
||||
- A parent row with no child row is a load error
|
||||
- The choice-of-rows fence guards a data file's root, and requires string fields
|
||||
@@ -111,4 +111,4 @@ In [docs/decisions.md](docs/decisions.md):
|
||||
- `List` advertises direct descents only
|
||||
- A path draws through its compiled steps
|
||||
- A name lives in the category binding it, or in the repeat binding it, and is drawn on its first read
|
||||
- comprehension floor: every dimension and the overall at 7.0 or above; the draw restructure and item 90 ship with no scoring run, and item 5 scores them
|
||||
- comprehension floor: every dimension and the overall at 7.0 or above; the change making every `{…}` draw afresh and item 90 ship with no scoring run, and item 5 scores them
|
||||
|
||||
+8
-8
@@ -12,16 +12,16 @@ replacement, and each removed path, column or flag.
|
||||
`{/city[Oslo]..country.name}` renders `Norway`, and `.` after it steps down again,
|
||||
drawing afresh. `New` refuses a `..` naming anything but the parent table, and a
|
||||
selector after a `..`.
|
||||
- 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.
|
||||
A name is a `{calc()}` operand: `{/misc.coordinate.lat as lat}{calc(lat * 60)}`. What
|
||||
`New` refuses is listed under [Names](README.md#names).
|
||||
- Every `{…}` draws afresh, and a name is the one way to keep a pick: `{p.a} {p.b}`,
|
||||
`{w} {uppercase(w)}` and `{/person.first} {/person.last}` are two draws each, which
|
||||
`{p as q}{q.a} {q.b}` and `{/person as n}{n.first} {n.last}` make one. `{x as n}`
|
||||
binds a sibling field as well as a reference, and a struct's tags share its names,
|
||||
`{/sv_SE.person as p}{p.first}` beside `{p.sex}`.
|
||||
`{p as q}{q.a} {q.b}` and `{/person as n}{n.first} {n.last}` make one.
|
||||
`{/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 binds a
|
||||
reference, a sibling field or a path into one, lives in the category binding it, and
|
||||
picks again on every iteration of a `repeat` binding it. A struct's tags share its
|
||||
names, `{/sv_SE.person as p}{p.first}` beside `{p.sex}`. A name is a `{calc()}`
|
||||
operand: `{/misc.coordinate.lat as lat}{calc(lat * 60)}`. 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,
|
||||
|
||||
@@ -137,12 +137,14 @@ it as nothing.
|
||||
A record is one render, so its columns read one pick of each [name](#names) its category
|
||||
binds: `"code": "{/currency as c}{c.code}"` beside `"symbol": "{c.symbol}"` is one
|
||||
currency, while `{/currency.code}` and `{/currency.symbol}` in two columns may name two. A
|
||||
field the format binds whole, `{place as p}`, is a column holding that pick, so it agrees with
|
||||
the columns reading `p`.
|
||||
column the format binds to a name, as `{place as p}` binds `place`, holds that same pick, so it
|
||||
agrees with the columns reading `p`; a column bound only in part, `{place.town as t}`, draws
|
||||
afresh, so bind it whole and read `{p.town}`.
|
||||
|
||||
A category never references itself: `{/users.first}` or a bare `{/users}` inside `users`
|
||||
describes a draw other than the fields beside it. Read a sibling as a field, and put a
|
||||
value two fields share in its own category, which each binds to a name
|
||||
value two fields share in its own category, bind it to a name in one field, and read the
|
||||
name in both
|
||||
([Decisions](docs/decisions.md#a-category-never-references-itself-and-a-records-fences-run-at-load)).
|
||||
|
||||
## Data
|
||||
@@ -342,9 +344,8 @@ naming a kind that holds it. The tags are a record's columns, so they share its
|
||||
[names](#names): `First` tagged `{/sv_SE.person as p}{p.first}` and `Sex` tagged `{p.sex}`
|
||||
describe one person, while two path tags into `sv_SE.person` are two draws. The fields an
|
||||
embedded struct promotes are columns of the same record; a named struct field, or a
|
||||
pointer to one, fills from its own tags as a record of its own, so it picks apart from its
|
||||
parent. `fake:"-"` leaves a
|
||||
struct field, embedded or named, or a pointer to one, unfilled. Untagged fields keep
|
||||
pointer to one, fills from its own tags as a record of its own, so its names and picks are
|
||||
its own. `fake:"-"` leaves a struct field, embedded or named, or a pointer to one, unfilled. Untagged fields keep
|
||||
their values, and so does a pointer back to a struct already being filled; a type
|
||||
whose fields reach more than 1024 structs is refused, naming `fake:"-"` to cut it. The
|
||||
first call for a type compiles its tags and reports what they get wrong, with the same
|
||||
@@ -372,15 +373,15 @@ Every character is literal except a `{…}` token:
|
||||
| Token | Renders |
|
||||
|-------|---------|
|
||||
| `{name}` | the sibling field `name`, or the pick bound to the name ([Names](#names)) |
|
||||
| `{x as name}` | nothing: binds one pick of `x`, a reference or a path into a sibling field, to `name` ([Names](#names)) |
|
||||
| `{x as name}` | nothing: binds one pick of `x`, a reference, a sibling field or a path into one, to `name` ([Names](#names)) |
|
||||
| `{name.field}` | `field` of a fresh draw of the sibling `name`, or of the pick bound to it ([Correlated fields](#correlated-fields)) |
|
||||
| `{a\|b}` | one of the named fields, even odds |
|
||||
| `{fn(args)}` | a builtin ([Functions](#functions)) |
|
||||
| `{/path}`, `{.path}`, `{..path}` | a node reached from the data root, this file's folder, or the folder above ([References](#references)) |
|
||||
| `{{`, `}}` | a literal `{` or `}` |
|
||||
|
||||
Each `{…}` makes a new pick, so `{w} {w}`, `{p.a} {p.b}` and `{/person.first}
|
||||
{/person.last}` are two draws each. A [name](#names) keeps one pick for every read of it.
|
||||
Each `{…}` draws afresh, so `{w} {w}`, `{p.a} {p.b}` and `{/person.first} {/person.last}`
|
||||
are two draws each. Only a [name](#names) keeps one pick for every read of it.
|
||||
|
||||
```json
|
||||
"100 Main St, Apt {int(1,9)}{upper(1)} — tel {digits(3)}-{digits(4)}"
|
||||
@@ -448,7 +449,7 @@ order.id: datatype integer: {digits(3)} prints text, not an integer
|
||||
order.id: datatype integer: "1{digits(2)}" is not one value; write one literal or one {float()}, {int()}, {seq()} or {calc()}, or read one
|
||||
```
|
||||
|
||||
A column of one reference or name read alone to another record's column —
|
||||
A column whose format is one read of another record's column and nothing else —
|
||||
`"score": "{/src.score}"`, or `"score": "{s.score}"` with `s` bound to `/src` — is that column: it takes the column's datatype and is null where the column is, and
|
||||
a struct field tagged `src.score` is nil there. A `datatype` of its own types the
|
||||
column's values where they prove it, and one restating the datatype it takes is refused.
|
||||
@@ -569,7 +570,7 @@ country by its weights, then a city inside it by theirs. Two paths are two draws
|
||||
[name](#names) agree instead, and the table it binds decides whose weights govern:
|
||||
`{/city as c}` draws a city by city weight and `{c..country.name}` is its country, while
|
||||
`{/country as k}` draws a country by country weight and `{k.city.name}` a city inside it.
|
||||
A name keeps every row its reads pin, so `{k.city.name}` and `{k.city.population}` describe
|
||||
A name keeps every row its reads draw, so `{k.city.name}` and `{k.city.population}` describe
|
||||
one city, and `{k.name}` its country.
|
||||
|
||||
`New` refuses a link cell that is no key of the parent, a parent row no child links to, a
|
||||
@@ -717,8 +718,8 @@ without naming `sv_SE`:
|
||||
"Hej, {/en_US.person}!"
|
||||
```
|
||||
|
||||
Renders e.g. `Hej, Pat Smith!`. `{.person.first} {.person.last}` may name two people, as
|
||||
`{/misc.uuid} {/misc.uuid}` is two draws ([Format string](#format-string)). Bind the person to a [name](#names) to read one: `{.person as p}{p.first}
|
||||
Renders e.g. `Hej, Pat Smith!`. Each reference draws afresh ([Format string](#format-string)),
|
||||
so `{.person.first} {.person.last}` may name two people. Bind the person to a [name](#names) to read one: `{.person as p}{p.first}
|
||||
{p.last}`. Rejected at `New`: a path that is
|
||||
unknown, names a folder, has no folder above, reads a field not every variant
|
||||
of a choice carries, or names the category the reference sits in, and a reference
|
||||
@@ -726,8 +727,8 @@ that leads back to its own value, directly, mutually or through a chain.
|
||||
|
||||
### Names
|
||||
|
||||
`{x as n}` binds one pick of `x`, a reference or a path into a sibling field, to the name `n`
|
||||
and prints nothing. `{n}` then
|
||||
`{x as n}` binds one pick of `x`, a reference, a sibling field or a path into one, to the name
|
||||
`n` and prints nothing. `{n}` then
|
||||
prints that pick, and `{n.path}` reads a path through it, so every read of `n` describes one
|
||||
row:
|
||||
|
||||
@@ -739,7 +740,7 @@ Renders e.g. `🇳🇴 Norway: capital Oslo, calling code +47`. A name keeps wha
|
||||
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 field binds the same way, so
|
||||
`{n}` is refused, since it could read either draw. A sibling field is bound the same way, so
|
||||
`{place as p}{p.postal-code} {p.locality}` reads one place ([Correlated fields](#correlated-fields)).
|
||||
|
||||
A name is a pick of its own: `{t.name}` and `{/misc.territory.name}` beside it are two draws,
|
||||
@@ -756,12 +757,12 @@ Refused at `New`, each error naming what to write instead:
|
||||
and outside it;
|
||||
- a name that is a field or an option;
|
||||
- a binding of anything but a reference or a path into a field of the template binding it;
|
||||
- a read of a name inside the field it binds: read it outside the field;
|
||||
- a read of a name inside the field bound to it: read it outside that field;
|
||||
- a binding in a choice's item, which every other item would leave unbound;
|
||||
- a binding nothing reads, or one read once outside a `repeat` nested where it is bound,
|
||||
which the spelling it binds draws the same way: `{/word as w}{w}` is `{/word}`, and
|
||||
`{/region as r}{r.name}` is `{/region.name}`; a calc reads neither a reference nor a
|
||||
path, so there one read is not refused;
|
||||
- a binding nothing reads, or one read only once, unless that read sits in a `repeat`
|
||||
nested where it is bound: write what it binds there instead, `{/word}` for
|
||||
`{/word as w}{w}` and `{/region.name}` for `{/region as r}{r.name}`; a calc cannot read a
|
||||
reference or a path, so a calc's one read of a name bound to either is not refused;
|
||||
- a path through a name that selects a row;
|
||||
- `{n}` beside `{n.w}` where `n`'s category reads `w` twice, as in `{w}-{w}` or
|
||||
`{w.a} {w.b}`;
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ func checkColumns(s nodeScope) error {
|
||||
checked[column] = true
|
||||
items, _ := columnItems(column)
|
||||
for _, it := range items {
|
||||
if r := it.link.readsColumn; r != nil {
|
||||
if r := it.link.readsColumn; r != nil && r.category != "" {
|
||||
if err := check(r.category, r.field, r.column); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+18
-17
@@ -128,19 +128,18 @@ Works against goal 2.3 until `todo.md` item 77 ends it.
|
||||
|
||||
## Every `{…}` draws afresh, and only a name keeps a pick
|
||||
|
||||
2026-10-05, larv-review on the draw restructure the maintainer planned on 2026-10-03.
|
||||
2026-10-05, larv-review on the change the maintainer planned on 2026-10-03.
|
||||
Serves goals 5.2 and 4.1: each `{…}` makes a new pick, and values that belong together come
|
||||
from the one pick a name keeps.
|
||||
|
||||
- `{p.a} {p.b}`, `{w} {uppercase(w)}`, `{/p.first} {/p.last}` and `{/p} {/p}` are two draws
|
||||
each, and `{p as q}{q.a} {q.b}` is one. Nothing is held per format or per render, so no
|
||||
fence guards a hold: the load checks on draws are names, paths through names, cycles and
|
||||
the repeat cap.
|
||||
- A name binds a reference or a path into a field of the template binding it, `{place as
|
||||
each, and `{p as q}{q.a} {q.b}` is one. Nothing is kept per format or per render, so the
|
||||
only load checks on draws are names, paths through names, cycles and the repeat cap.
|
||||
- A name binds a reference, or a field of the template binding it or a path into one, `{place as
|
||||
p}`: an inline template, and a choice of rows inside one category, have no reference to
|
||||
bind, and goal 4.1 holds there too.
|
||||
- A name read once is refused where the spelling it binds stands in, and the error names
|
||||
that spelling (goal 5.7): `{/x as n}{n.y}` is `{/x.y}`. A calc reads no reference or
|
||||
- A name read once is refused where writing what it binds in its place draws the same, and
|
||||
the error names that spelling (goal 5.7): `{/x as n}{n.y}` is `{/x.y}`. A calc reads no reference or
|
||||
path, and a read inside a nested repeat keeps one pick on every line, so neither is
|
||||
refused.
|
||||
|
||||
@@ -299,9 +298,11 @@ not agree on one among themselves; each must only hold that type.
|
||||
The fields an embedded struct promotes are the struct's own — `e.First`, as
|
||||
`encoding/json` and SQL mappers read them — so they are columns of its record and share
|
||||
its names: one tag may bind `{/person as p}{p.first}` and another read `{p.sex}` (goal 4.1),
|
||||
while a path tag draws afresh as `{/path}` does (goal 5.2). A tag binding a name only an
|
||||
embedding type reads is refused on the embedded type alone, as any name read once is, so the
|
||||
embedder binds it in a tag of its own; a tagged field that another field hides is refused, not dropped. A named
|
||||
while a path tag draws afresh as `{/path}` does (goal 5.2).
|
||||
|
||||
A tag binding a name only an embedding type reads is refused on the embedded type alone, as
|
||||
any name read once is, so the embedder binds it in a tag of its own. A tagged field that
|
||||
another field hides is refused, not dropped. A named
|
||||
struct field is another entity and a record of its own. `fake:"-"` leaves a struct
|
||||
field, embedded or named, unfilled, so no name may be `-`; a pointer back to a struct
|
||||
already being filled is left alone, since filling it would never end. `New` cannot see a
|
||||
@@ -316,7 +317,7 @@ handle is needed, as the cache already compiles once.
|
||||
A category is one unit: a reference back into it — `{/users.first}` inside `users` —
|
||||
describes a draw other than the fields beside it, so `New` refuses it and the sibling
|
||||
field stays the one spelling for a field of one's own. A value two fields share goes in its
|
||||
own category, which each binds to a name (goal 4.1). That settled, a record's column fences
|
||||
own category, which one field binds to a name and both read (goal 4.1). That settled, a record's column fences
|
||||
run at `New` too, so a category that loads renders as a value and as a record alike.
|
||||
|
||||
## A record's column set is fixed before the first draw
|
||||
@@ -345,7 +346,7 @@ Its bounds come from a literal or a call's arguments, so a load error names a re
|
||||
value, a range check is one comparison, and `1{digits(2)}` is a second spelling of
|
||||
`{int(100,199)}`.
|
||||
|
||||
## A column of one reference or name read alone is the column it reads
|
||||
## A column that only reads one reference or name is the column it reads
|
||||
|
||||
2026-09-15, Lilleman auf Larv.
|
||||
|
||||
@@ -435,10 +436,10 @@ accepted one changes, so the door stays open for the address records the plan de
|
||||
Decided 2026-09-27 by the maintainer, for goals 4 and 5; valid while a selector names
|
||||
exactly one row.
|
||||
|
||||
Selected rows never conflict: `{/misc.territory[SE].capital} / {/misc.territory[FI].capital}`
|
||||
Each selector reads the row it names: `{/misc.territory[SE].capital} / {/misc.territory[FI].capital}`
|
||||
renders `Stockholm / Helsinki`, and two items of one choice may select different rows.
|
||||
|
||||
## `..` steps up to the parent row, and a step down after it draws afresh, or once per pick
|
||||
## `..` steps up to the parent row, and a step down after it draws afresh, or once per name's pick
|
||||
|
||||
2026-10-05, larv-review in PR #158. Serves goal 5.4, which has `..` go up one level, and goal
|
||||
5.2, which has each `{…}` make a new pick.
|
||||
@@ -810,7 +811,7 @@ it.
|
||||
|
||||
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. It opens the frames around where it lands: a read through a
|
||||
the same whoever references it. Such a read opens the frames around where it lands: a read through a
|
||||
name from its pick's memo, so `{n}` and `{n.path}` read one pick of each name inside, and a
|
||||
reference or `Fake` fresh ones.
|
||||
|
||||
@@ -820,6 +821,6 @@ category's frames, since the field sits inside it.
|
||||
|
||||
Valid while names are read only inside the category binding them.
|
||||
|
||||
## comprehension floor: every dimension and the overall at 7.0 or above; the draw restructure and item 90 ship with no scoring run, and item 5 scores them
|
||||
## comprehension floor: every dimension and the overall at 7.0 or above; the change making every `{…}` draw afresh and item 90 ship with no scoring run, and item 5 scores them
|
||||
|
||||
2026-10-04, Lilleman auf Larv. Serves goal 3. The nine-seat panels in `todo.md`'s git history, 2026-09-22 to 2026-09-30, held the score between 5.2 and 6.1, with Locality lowest at every seat, and the hardest units were the draw fences the restructure deleted. The draw restructure, shipped 2026-10-05, and `todo.md` item 90 delete or move what the earlier chunks of the below-floor round touched, so a scoring run before item 90 merges would score code about to go; the calc change accepting a name as an operand shipped unscored on 2026-10-05 for that reason. The two finish the round `AGENTS.md`'s comprehension rule lets merge below the floor, a restructure the maintainer approved. No scoring run or panel runs until item 5, which runs the nine-seat panel and larv-review's scoring run. The scoring run writes this entry's first scores. Valid until `todo.md` item 5 runs.
|
||||
2026-10-04, Lilleman auf Larv. Serves goal 3. The nine-seat panels in `todo.md`'s git history, 2026-09-22 to 2026-09-30, held the score between 5.2 and 6.1, with Locality lowest at every seat, and the hardest units were the draw fences that change deleted. The change making every `{…}` draw afresh and `todo.md` item 90 delete or move what the earlier chunks of the below-floor round touched, so a scoring run before item 90 merges would score code about to go; the calc change accepting a name as an operand shipped unscored on 2026-10-05 for that reason. The two finish the round `AGENTS.md`'s comprehension rule lets merge below the floor, a restructure the maintainer approved. No scoring run or panel runs until item 5, which runs the nine-seat panel and larv-review's scoring run. The scoring run writes this entry's first scores. Valid until `todo.md` item 5 runs.
|
||||
|
||||
@@ -316,7 +316,7 @@ func checkBound(ref string, fields map[string]node) error {
|
||||
}
|
||||
missing, err := checkArm(ref, fields, false)
|
||||
if missing {
|
||||
return fmt.Errorf("%w; a name binds a pick of a field, or of a reference, which starts with /, . or ..", err)
|
||||
return fmt.Errorf("%w; a binding names a field, or a reference, which starts with /, . or ..", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -222,7 +222,7 @@ func (t *template) isName(head string) bool {
|
||||
return !isRef(head) && t.fields[head] == nil && t.nameScope.lookup(head) != nil
|
||||
}
|
||||
|
||||
// linkBindings gives each binding t's tokens make the head and tail what it binds resolved to.
|
||||
// linkBindings resolves what each of t's bindings binds to a head node and a tail path.
|
||||
func linkBindings(t *template) {
|
||||
for _, tok := range t.tokens {
|
||||
if tok.kind != nameBind {
|
||||
@@ -255,8 +255,8 @@ func namedReads(t *template, fn func(o *op, a *arm) error) error {
|
||||
// starts: a pass reads what the one before filled in.
|
||||
var linkPasses = []func(path string, t *template) error{linkNames, linkColumnRead, checkNameReads, checkCalcNames}
|
||||
|
||||
// linkNames compiles t's reads of a name as paths from the head its binding's reference names, once
|
||||
// every template is linked, so each binder has resolved that reference.
|
||||
// linkNames compiles t's reads of a name as paths from the head its binding resolved to, once every
|
||||
// template is linked, so each binder has resolved what it binds.
|
||||
func linkNames(path string, t *template) error {
|
||||
return namedReads(t, func(o *op, a *arm) error {
|
||||
if err := linkName(t, a); err != nil {
|
||||
|
||||
+3
-3
@@ -185,7 +185,7 @@ func TestNameErrors(t *testing.T) {
|
||||
{"an option", map[string]string{"word": `["a","b"]`, "card": `"{/word as format}{format}"`},
|
||||
`"format" is an option and can never be a name`},
|
||||
{"no reference or field", map[string]string{"card": `{"format":"{word as w}{w}{w}","words":["a","b"]}`},
|
||||
`no field "word"; a name binds a pick of a field, or of a reference`},
|
||||
`no field "word"; a binding names a field, or a reference`},
|
||||
{"read once through a field", map[string]string{"card": `{"format":"{place as p}{p.x}","place":{"format":"{x}","x":["a","b"]}}`},
|
||||
`name "p" is read once, so it keeps no pick for another read; write {place.x} where it is read`},
|
||||
{"read by nothing", map[string]string{"word": `["a","b"]`, "card": `"{/word as w}"`},
|
||||
@@ -380,8 +380,8 @@ func TestOneRenderReadsOneNameAcrossNestedTemplates(t *testing.T) {
|
||||
func TestAFieldBindingReadInsideItsFieldIsRefused(t *testing.T) {
|
||||
f := engine(1)
|
||||
for src, want := range map[string]string{
|
||||
`{"format":"{x as n}{n}{n}","x":"{n}"}`: `name "n" is read inside "x", the field it binds`,
|
||||
`{"format":"{x as n}{n.a}{n.a}","x":{"format":"{a}","a":"{n}"}}`: `name "n" is read inside "x", the field it binds`,
|
||||
`{"format":"{x as n}{n}{n}","x":"{n}"}`: `name "n" is read inside "x", the field bound to it`,
|
||||
`{"format":"{x as n}{n.a}{n.a}","x":{"format":"{a}","a":"{n}"}}`: `name "n" is read inside "x", the field bound to it`,
|
||||
`{"format":"{x as n}{y as m}{n}{n}{m}{m}","x":"{m}","y":"{n}"}`: "cycle",
|
||||
} {
|
||||
if _, err := f.NewTemplate(src); err == nil || !strings.Contains(err.Error(), want) {
|
||||
|
||||
+6
-5
@@ -6,8 +6,9 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// checkNameReads refuses each binding of t that checkUses refuses, and a read such as {n} whose
|
||||
// pick renders twice a field another read, {n.w}, reads once: the pick keeps one draw of it.
|
||||
// checkNameReads refuses each binding of t that checkUses refuses, a read of a name inside the
|
||||
// field bound to it, and a read {n} beside {n.w} where n's pick renders w twice: the pick keeps one
|
||||
// draw of w.
|
||||
func checkNameReads(path string, t *template) error {
|
||||
for _, tok := range t.tokens {
|
||||
if tok.kind != nameBind {
|
||||
@@ -19,7 +20,7 @@ func checkNameReads(path string, t *template) error {
|
||||
}
|
||||
return namedReads(t, func(o *op, a *arm) error {
|
||||
if a.named.bindsField() && rendersInside(compilePath(a.named.head, a.named.tail).leaves, t) {
|
||||
return fmt.Errorf("%s: token {%s}: name %q is read inside %q, the field it binds; read the name outside that field", t.site.label(path), o.body, a.named.name, a.named.ref)
|
||||
return fmt.Errorf("%s: token {%s}: name %q is read inside %q, the field bound to it; read the name outside that field", t.site.label(path), o.body, a.named.name, a.named.ref)
|
||||
}
|
||||
for _, leaf := range a.leaves {
|
||||
if err := a.named.checkOnce(a.spelling, leaf, a.path); err != nil {
|
||||
@@ -58,8 +59,8 @@ func rendersInside(nodes []node, t *template) bool {
|
||||
}
|
||||
|
||||
// checkUses refuses a binding read once at a spot the bound spelling can stand: that spelling
|
||||
// draws the same way without the name. A field stands where the reading template reaches the
|
||||
// binder through fields. Where the spelling would be a CLI argument or tag of one reference alone,
|
||||
// draws the same way without the name. A bound field can stand in only where the reading template
|
||||
// reaches the binder through fields. Where the spelling would be a CLI argument or tag of one reference alone,
|
||||
// that entry point's own refusal then names the bare path.
|
||||
func (b *nameBinding) checkUses() error {
|
||||
r := b.uses[0]
|
||||
|
||||
@@ -183,8 +183,8 @@ func readUnder(s *generatorState, t *template, sc renderScope, a arm) readValue
|
||||
return p.renderAt(s, leaf, pins, key, sc)
|
||||
}
|
||||
|
||||
// underKey is the key of path under the pick key prefix. It never returns prefix itself, which a
|
||||
// memo would keep: anything a renderScope holds reaching the heap moves every render's scope there.
|
||||
// underKey is the key of path under the pick key prefix. It never returns prefix itself: prefix is
|
||||
// sc.pickKey, and a memo keeping it would move every render's scope to the heap.
|
||||
func underKey(prefix, path string) string {
|
||||
if prefix == "" {
|
||||
return path
|
||||
|
||||
@@ -307,3 +307,28 @@ func TestAFieldBoundWholeIsItsPickAsAColumn(t *testing.T) {
|
||||
t.Errorf("place drew only %v in 100 records, want both", seen)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAColumnReadingABoundColumnIsThatColumn(t *testing.T) {
|
||||
f := newGenerator(t, writeData(t, map[string]string{
|
||||
"row": `{"format":"{place as p}","place":[null,{"format":"{int(1,9)}","datatype":"integer"}],"a":"{p}","z":"{p}"}`,
|
||||
}), WithSeed(1))
|
||||
nulls := 0
|
||||
for i := 0; i < 100; i++ {
|
||||
r, err := f.FakeRecord("row")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c := r.Columns()
|
||||
for _, col := range c {
|
||||
if col.DataType != DataTypeInteger || col.Null != c[0].Null || col.Value != c[0].Value {
|
||||
t.Fatalf("%s: want a, place and z one integer pick, null alike", r.JSON())
|
||||
}
|
||||
}
|
||||
if c[0].Null {
|
||||
nulls++
|
||||
}
|
||||
}
|
||||
if nulls == 0 || nulls == 100 {
|
||||
t.Errorf("the pick was null %d times in 100 records, want both outcomes", nulls)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +269,7 @@ func renderRecord(s *generatorState, t *template, columns []Column, sc renderSco
|
||||
field := t.fields[r.columns[i].Name]
|
||||
var column readValue
|
||||
if b := bindingOfWhole(t, field); b != nil {
|
||||
column = readName(s, sc, arm{kind: namedRead, named: b, levels: []string{""}})
|
||||
column = readName(s, sc, arm{kind: namedRead, named: b, levels: wholeLevels})
|
||||
} else {
|
||||
column = renderLeaf(s, field, sc)
|
||||
}
|
||||
@@ -278,6 +278,9 @@ func renderRecord(s *generatorState, t *template, columns []Column, sc renderSco
|
||||
return r
|
||||
}
|
||||
|
||||
// wholeLevels is the levels of a read of a whole name, as linkName compiles {n}.
|
||||
var wholeLevels = []string{""}
|
||||
|
||||
// bindingOfWhole is the name t binds to the whole of field, whose pick the field's column renders
|
||||
// so the row agrees with the columns reading the name; nil where none does.
|
||||
func bindingOfWhole(t *template, field node) *nameBinding {
|
||||
|
||||
+13
-8
@@ -155,7 +155,9 @@ func (t *template) resolveLink(folder []string, path, category string, root map[
|
||||
}
|
||||
|
||||
// columnRead is a record's column read by a format of that one reference or name read alone,
|
||||
// which is the column: it takes the column's datatype and null. category and field name it.
|
||||
// which is the column: it takes the column's datatype and null. category and field name it;
|
||||
// category is "" for a column of the reading template's own record, which checkColumns reaches
|
||||
// on its own.
|
||||
type columnRead struct {
|
||||
a arm
|
||||
category, field string
|
||||
@@ -170,18 +172,21 @@ func linkColumnRead(_ string, t *template) error {
|
||||
return nil
|
||||
}
|
||||
a := ops[0].arms[0]
|
||||
head, category, tail := t.head(a.head), a.head, a.tail
|
||||
switch {
|
||||
case a.kind == namedRead && !a.named.bindsField():
|
||||
ref := a.named.binder.link.refs[a.named.ref]
|
||||
head, category = a.named.head, ref.head
|
||||
head, category, tail := t.head(a.head), a.head[min(1, len(a.head)):], a.tail
|
||||
switch b := a.named; {
|
||||
case a.kind == namedRead && b.bindsField():
|
||||
head, category = b.binder, ""
|
||||
tail = append(append([]string{splitArm(b.ref, nil).head}, b.tail...), a.tail...)
|
||||
case a.kind == namedRead:
|
||||
ref := b.binder.link.refs[b.ref]
|
||||
head, category = b.head, ref.head[1:]
|
||||
tail = append(ref.tail[:len(ref.tail):len(ref.tail)], a.tail...)
|
||||
case a.kind == namedRead || !isRef(a.head):
|
||||
case !isRef(a.head):
|
||||
return nil
|
||||
}
|
||||
target, isTemplate := head.(*template)
|
||||
if isTemplate && target.isRecord && len(tail) == 1 {
|
||||
t.link.readsColumn = &columnRead{a: a, category: category[1:], field: tail[0], column: target.fields[tail[0]]}
|
||||
t.link.readsColumn = &columnRead{a: a, category: category, field: tail[0], column: target.fields[tail[0]]}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -168,8 +168,8 @@ func readField(s *generatorState, t *template, sc renderScope, a arm) readValue
|
||||
return r
|
||||
}
|
||||
|
||||
// renderLeaf draws and renders what a read lands on: null on a null item, or on a column of one
|
||||
// reference alone whose read drew null.
|
||||
// renderLeaf draws and renders what a read lands on: null on a null item, or on a column that only
|
||||
// reads one reference or name whose read drew null.
|
||||
func renderLeaf(s *generatorState, n node, sc renderScope) readValue {
|
||||
n = resolveChoice(s, n)
|
||||
switch leaf := n.(type) {
|
||||
|
||||
@@ -27,8 +27,8 @@ type renderedRow struct {
|
||||
index int
|
||||
}
|
||||
|
||||
// at is the scope n, the leaf of a path that pinned its rows in pins, renders in: where n is a
|
||||
// table's row or column, at the row pins holds for that table.
|
||||
// at returns the scope n renders in, n being the leaf of a path whose rows sit in pins; where n
|
||||
// is a table's row or column, the scope carries the row pins holds for that table.
|
||||
func (sc renderScope) at(n node, pins *pinSet) renderScope {
|
||||
switch n := n.(type) {
|
||||
case *tableRow:
|
||||
|
||||
@@ -72,8 +72,8 @@
|
||||
| 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, 15, 14 | 25.9 |
|
||||
| 57 | 0.1.0 | | **Rewrite `CHANGELOG.md`'s `[Unreleased]` as what v0.1.0 holds.** | 1 | 2 | 6 | 6 | 1.1 | 22.5 |
|
||||
| 34 | 0.1.0 | | **Add `{isin()}`, `{cusip()}`, `{aba()}` and `{vin()}`.** | 2 | 4 | 6 | 7 | 4 | 20.9 |
|
||||
| 96 | 0.1.0 | question | **Refuse two reads of one sibling, one category or one struct category by different spellings, `{place.postal-code} {place.locality}`, showing the spelling for one pick and the one for two.** | 4 | 4 | 7 | 7 | 5.6, 4 | 20.9 |
|
||||
| 5 | 0.1.0 | decision | **Run the nine-seat comprehension panel after the draw restructure, and file what it names.** | 1 | 3 | 3 | 9 | 3 | 20.4 |
|
||||
| 96 | 0.1.0 | question | **Refuse two reads into one sibling, category or struct category that no name joins, `{place.postal-code} {place.locality}`, showing the spelling for one pick and the one for two.** | 4 | 4 | 7 | 7 | 5.6, 4 | 20.9 |
|
||||
| 5 | 0.1.0 | decision | **Run the nine-seat comprehension panel after every `{…}` draws afresh and the code is restructured, and file what it names.** | 1 | 3 | 3 | 9 | 3 | 20.4 |
|
||||
| 91 | 0.1.0 | | **Accept a path through a name as a `calc` operand, so `{/misc.coordinate as c}{c}: {calc(c.lat * 60)}` computes from the coordinate `{c}` prints.** | 3 | 4 | 6 | 7 | 4.1, 5.3 | 19.9 |
|
||||
| 89 | 0.1.0 | | **Fail `New` without `WithSeed`, naming the option, and seed the CLI from the system.** | 3 | 3 | 6 | 7 | 10.1 | 16.0 |
|
||||
| 90 | 0.1.0 | decision | **Restructure the code into folders named for what they hold: the public API stays in the root package, and the implementation moves into `internal/` packages.** | 6 | 9 | 3 | 9 | 3.2, 3.4 | 13.9 |
|
||||
@@ -234,13 +234,13 @@ Nothing has shipped, so "no longer paths", "where it used to fail" and "where it
|
||||
|
||||
`isin`: Luhn over letters expanded to digits. `aba`: 3-7-1 weights. `vin`: position 9 over the whole; a sample taking the WMI, since the check sits mid-string.
|
||||
|
||||
### 96. Refuse two reads of one sibling, one category or one struct category by different spellings, `{place.postal-code} {place.locality}`, showing the spelling for one pick and the one for two.
|
||||
### 96. Refuse two reads into one sibling, category or struct category that no name joins, `{place.postal-code} {place.locality}`, showing the spelling for one pick and the one for two.
|
||||
|
||||
Asked of the maintainer by the product-owner pass of item 4's chunk. Since every `{…}` draws afresh, `{net} x {qty} = {calc(net * qty, 2)}`, `{place.postal-code} {place.locality}` and struct tags `sv_SE.address.street` beside `sv_SE.address.postal-code` load and may render a pair that disagrees, and nothing tells the author to bind a name (goal 4.1). Goal 5.6 asks such a template to fail, showing `{place as p}{p.postal-code} {p.locality}` for one place and two names for two. Item 4 planned the draw checks as names, paths through names, cycles and the repeat cap, so the fence would revise the decision "Every `{…}` draws afresh, and only a name keeps a pick". `{w} {w}`, one spelling twice, stays two draws. Open: whether reads across categories, `{/city.name}, {/country.name}`, count.
|
||||
Asked of the maintainer by the product-owner pass of the chunk that made every `{…}` draw afresh. Since every `{…}` draws afresh, `{net} x {qty} = {calc(net * qty, 2)}`, `{place.postal-code} {place.locality}` and struct tags `sv_SE.address.street` beside `sv_SE.address.postal-code` load and may render a pair that disagrees, and nothing tells the author to bind a name (goal 4.1). Goal 5.6 asks such a template to fail, showing `{place as p}{p.postal-code} {p.locality}` for one place and two names for two. The decision "Every `{…}` draws afresh, and only a name keeps a pick" lists the draw checks as names, paths through names, cycles and the repeat cap, so this fence revises it. `{w} {w}`, one spelling twice, stays two draws. Open: whether reads across categories, `{/city.name}, {/country.name}`, count.
|
||||
|
||||
### 5. Run the nine-seat comprehension panel after the draw restructure, and file what it names.
|
||||
### 5. Run the nine-seat comprehension panel after every `{…}` draws afresh and the code is restructured, and file what it names.
|
||||
|
||||
Needs item 90. The decision "comprehension floor: every dimension and the overall at 7.0 or above; the draw restructure and item 90 ship with no scoring run, and item 5 scores them" suspends every scoring run and panel until then.
|
||||
Needs item 90. The decision "comprehension floor: every dimension and the overall at 7.0 or above; the change making every `{…}` draw afresh and item 90 ship with no scoring run, and item 5 scores them" suspends every scoring run and panel until then.
|
||||
|
||||
### 91. Accept a path through a name as a `calc` operand, so `{/misc.coordinate as c}{c}: {calc(c.lat * 60)}` computes from the coordinate `{c}` prints.
|
||||
|
||||
@@ -301,7 +301,7 @@ With several `--data-path` layers the author has to work out which directory won
|
||||
|
||||
### 28. Accept a middle name, and draw a `personnummer` whose sex matches a sex selected through a named pick.
|
||||
|
||||
Today `personnummer` draws its own sex, so a person bound to `{/sv_SE.sex[f] as s}` needs the template the README's Data section shows to agree with a number.
|
||||
Today `personnummer` draws its own sex, so a name and a number agree only through the README Data section's template, which reads both through one bound `sex`.
|
||||
|
||||
### 24. Stop `misc.territory[EH].tld` rendering `.eh`, the one shipped TLD `misc.tld` does not hold.
|
||||
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ type valueProof struct {
|
||||
|
||||
// checkDatatype rejects a typed column item some render of which is not text of its datatype,
|
||||
// and one restating the datatype of the column it is.
|
||||
// docs/decisions.md#a-column-of-one-reference-alone-is-the-column-it-reads
|
||||
// docs/decisions.md#a-column-that-only-reads-one-reference-or-name-is-the-column-it-reads
|
||||
func (p *valueProof) checkDatatype(path string, n node) error {
|
||||
t, ok := n.(*template)
|
||||
if !ok || t.datatype == DataTypeString {
|
||||
|
||||
Reference in New Issue
Block a user