Answer stability round 1, product-owner round 1 and prose round 1: say calc reads names in the Functions table, use shipped data in the examples, plainer comments and item wording, mark items 5 and 90 as decision-cited, and ask whether a field spelling of a calc operand is refused

This commit is contained in:
2026-10-05 01:17:33 +02:00
committed by Lillem4n
parent 0743a049d7
commit 426517e66d
7 changed files with 32 additions and 28 deletions
+2 -1
View File
@@ -15,7 +15,8 @@ replacement, and each removed path, column or flag.
- 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: `{/n as k}{k} x 2 = {calc(k * 2)}`. What `New` refuses is listed under [Names](README.md#names).
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,
+6 -7
View File
@@ -647,7 +647,7 @@ stays reproducible.
| `{date(from,to,'layout')}` | sample | a second between two `YYYY-MM-DD` days, both included, in a quoted Go layout: `'2006-01-02'`, `'January 2, 2006'`, `'060102'`, `'2006-01-02T15:04:05Z'` |
| `{time('layout')}` | sample | a second within a day: `'15:04'`, `'3:04 PM'` |
| `{seq()}`, `{seq(name)}` | counter | next integer from 1 in this generator; `name` selects an independent counter |
| `{calc(expr)}`, `{calc(expr,dp)}` | computation | an arithmetic expression over sibling fields ([Computation](#computation)) |
| `{calc(expr)}`, `{calc(expr,dp)}` | computation | an arithmetic expression over sibling fields and names ([Computation](#computation)) |
| `{lowercase(x)}`, `{uppercase(x)}`, `{ascii(x)}` | transform | a field's value rewritten ([Transforms](#transforms)) |
A derivation reads what is to its left, so place it after its payload; the
@@ -676,7 +676,7 @@ the SQL example above.
### Computation
`{calc(expr)}` evaluates `+ - * /`, parentheses and unary minus over number
literals, sibling field names and [names](#names), each rendered then read as a number;
literals, sibling fields and [names](#names), each rendered then read as a number;
a second argument rounds to that many decimals. A hyphen is always subtraction, so a
hyphenated field or name can't be an operand.
@@ -684,8 +684,8 @@ hyphenated field or name can't be an operand.
{ "format": "{net} x {qty} = {calc(net * qty, 2)}", "net": ["19.99", "5.00"], "qty": ["3", "7"] }
```
Renders e.g. `19.99 x 3 = 59.97`. A name is the operand `{lat}` prints, so the calc
computes from the pick shown:
Renders e.g. `19.99 x 3 = 59.97`. A name reads its one pick, so the calc computes from the
value `{lat}` prints:
```json
"{/misc.coordinate.lat as lat}{lat}° is {calc(lat * 60, 0)} arcminutes"
@@ -778,9 +778,8 @@ Refused at `New`, each error naming what to write instead:
- a binding of anything but a reference;
- a binding in a choice's item, which every other item would leave unbound;
- 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 calc's
operand cannot be a reference, so there the name is the one spelling:
`{/n as k}{calc(k * 2)}` loads;
bound, which the bare reference spells: `{/word as w}{w}` is `{/word}`; a calc cannot
read a reference, so `{/misc.coordinate.lat as lat}{calc(lat * 60)}` loads;
- a path through a name that selects a row;
- `{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` nested where the name is bound,
+3 -3
View File
@@ -212,7 +212,7 @@ func neverNumeric(n node) (text string, never bool) {
return "", false
}
// allNeverNumeric reports nodes no render of which is a number, and none where there are none.
// allNeverNumeric reports whether no render of any of nodes is a number; false for no nodes.
func allNeverNumeric(nodes []node) (text string, never bool) {
for _, n := range nodes {
t, nodeNever := neverNumeric(n)
@@ -240,8 +240,8 @@ func calcPrep(args []string) callFn {
}
}
// parsedCalc parses an expression checkCalc accepted. A nil AST would be a nil dereference
// per render, with no message.
// parsedCalc parses an expression checkCalc accepted. A nil AST would dereference later, with
// no message.
func parsedCalc(expr string) calcNode {
n, err := parseCalc(expr)
if err != nil {
+1 -1
View File
@@ -927,4 +927,4 @@ Valid while names are read only inside the category binding them.
## comprehension floor: every dimension and the overall at 7.0 or above; items 4 and 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 are the draw fences item 4 deletes. Items 4 and 90 delete or move what the round's earlier chunks 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. Items 4 and 90 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 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 are the draw fences item 4 deletes. Items 4 and 90 delete or move what the earlier chunks of the below-floor round touched, so a scoring run before `todo.md` 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. Items 4 and 90 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.
+3 -4
View File
@@ -133,8 +133,7 @@ type builtin struct {
// operands names the fields the call reads, which expand renders for it; nil
// for a builtin that reads none.
operands func(args []string) []string
// noRefOperands says no operand can be a reference, so a name read once there has no
// second spelling.
// noRefOperands says no operand can be a reference, so a name read once there is not refused.
noRefOperands bool
// proveNumber bounds the number a call's text reads as, token its body, and says which
// datatypes that text is not; set it where every render reads as a finite number, which
@@ -218,8 +217,8 @@ 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
// template can answer; err is the refusal where none does. tail is the path read into the name,
// "" for the name itself, operand marks a builtin's read of it, noRef one where no reference can
// stand, and body is the token's.
// "" for the name itself, operand marks a builtin's read of it, noRef marks such a read where no
// reference can stand, and body is the token's.
type unboundRead struct {
head, tail, body string
operand, noRef bool
+3 -3
View File
@@ -34,7 +34,7 @@ type nameBinding struct {
}
// nameUse is one read of a name: the path it reads into the name, "" for the name itself; whether
// a builtin reads it as an operand, and whether that operand can be no reference; the draw group
// a builtin reads it as an operand, and whether no reference can stand there; the draw group
// its template renders in; and whether it sits in a repeat nested inside the name's scope.
type nameUse struct {
tail, group string
@@ -250,8 +250,8 @@ func namedReads(t *template, fn func(o *op, a *arm) error) error {
return nil
}
// namePasses run over every linked template in turn, each over all of them before the next: a pass
// reads what the one before it filled in.
// namePasses run in order, each over every linked template before the next starts: a pass reads
// what the one before filled in.
var namePasses = []func(path string, t *template) error{linkNames, checkNameReads, checkCalcNames}
// linkNames compiles t's reads of a name as paths from the head its binding's reference names, once
+14 -9
View File
@@ -6,7 +6,7 @@
`Bar = 9`
`Next ID = 93`
`Next ID = 94`
| Goal | W |
|---|---|
@@ -73,12 +73,13 @@
| 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 |
| 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 |
| 5 | 0.1.0 | | **Run the nine-seat comprehension panel after the draw restructure, 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 `{calc(t.population * 2)}` computes from the row `{t}` prints.** | 3 | 4 | 6 | 7 | 4.1, 5.3 | 19.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 |
| 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 | | **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 |
| 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 |
| 79 | 0.1.0 | decision | **Beside a `--data-path`, load in `New` the user's categories, the shipped ones they override or read, and the shipped ones whose reads reach an overridden one; load the rest on first reach.** | 6 | 6 | 7 | 8 | 13 | 12.9 |
| 87 | 0.1.0 | | **Split the README's last Records paragraph into one paragraph per rule, name the two shapes "either shape" means, and define or replace "a field hold".** | 1 | 2 | 4 | 6 | 12, 12.3 | 11.9 |
| 93 | 0.1.0 | question | **Decide whether a field read only as a `calc` operand, whose whole format is one reference, is refused beside the name that spells the same pick.** | 3 | 3 | 3 | 5 | 5.7 | 10.2 |
| 58 | 0.2.0 | | **Ship prebuilt binaries, a container image, and packages for Homebrew, Scoop and the biggest Linux package managers, so the CLI needs no Go.** | 3 | 7 | 8 | 9 | 1.1, 7 | 29.2 |
| 26 | 0.2.0 | defect | **Spell `sv_SE.personnummer` with the `+` Skatteverket uses from the year the holder turns 100, judged by a date the caller gives.** | 4 | 6 | 7 | 8 | 4 | 22.1 |
| 81 | 0.2.0 | decision | **Refuse at load every `calc` operand not proven numeric and every divisor not proven nonzero, in a string column too.** | 4 | 4 | 6 | 8 | 6.1, 4 | 20.6 |
@@ -88,7 +89,7 @@
| 52 | 0.2.0 | | **Name the file a link error comes from beside its category path, `sub.x (/d/a/sub/x.json): …`.** | 1 | 2 | 5 | 7 | 6 | 19.0 |
| 28 | 0.2.0 | decision | **Accept a middle name, and draw a `personnummer` whose sex matches a sex selected through a named pick.** | 4 | 4 | 6 | 7 | 4.1 | 18.9 |
| 24 | 0.2.0 | defect | **Stop `misc.territory[EH].tld` rendering `.eh`, the one shipped TLD `misc.tld` does not hold.** | 1 | 1 | 4 | 7 | 4 | 18.6 |
| 92 | 0.2.0 | | **Name the rename in a `calc`'s refusal of a hyphenated operand, which today reports `no field or name "calling"` for `{calc(calling-code * 2)}`.** | 1 | 2 | 5 | 6 | 6 | 17.5 |
| 92 | 0.2.0 | | **Name the hyphenated field in a `calc`'s refusal, and say to rename it: today `{calc(postal-code * 2)}` reports `no field "postal"`.** | 1 | 2 | 5 | 6 | 6 | 17.5 |
| 53 | 0.2.0 | | **Let a table column carry a `datatype`, so `--format json` writes `"safe": true` and `--format sql` a boolean.** | 4 | 5 | 6 | 6 | 4 | 16.9 |
| 55 | 0.2.0 | | **Document `NewRecordTemplate`, `ErrNoColumns`, `ErrNoData` and `MaxRepeat` in the README's Library section.** | 1 | 2 | 6 | 7 | 12 | 16.8 |
| 18 | 0.2.0 | | **Give the address records one column set across countries: `region` and `municipality` as columns on `geo.SE.address` too.** | 4 | 4 | 6 | 6 | 5 | 16.6 |
@@ -247,9 +248,9 @@ Nothing has shipped, so "no longer paths", "where it used to fail" and "where it
Needs items 4 and 90. The decision "comprehension floor: every dimension and the overall at 7.0 or above; items 4 and 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 `{calc(t.population * 2)}` computes from the row `{t}` prints.
### 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.
Needs item 4. A calc operand parses as a bare identifier, so `{/misc.territory as t}{calc(t.population * 2)}` fails with `unexpected ".population"`, and no binding can hold the column alone beside the row. Item 4 removes today's route, a field holding the reference path, which the expansion hold keeps for the render. Resolve the path through `linkName`'s leaves, as `operandNodes` does for a bare name.
Needs item 4. A calc operand parses as a bare identifier, so `{calc(c.lat * 60)}` fails with `unexpected ".lat * 60"`, and a second binding of `/misc.coordinate.lat` would draw another coordinate. Item 4 removes today's route, a field holding the reference path, which the expansion hold keeps for the render. Resolve the path through `linkName`'s leaves, as `operandNodes` does for a bare name.
### 89. Fail `New` without `WithSeed`, naming the option, and seed the CLI from the system.
@@ -269,6 +270,10 @@ The decision "With only the shipped set, a category loads on the first call reac
Needs item 4, which rewrites the draw-group rule this paragraph states. The last paragraph under Records, from "A record written only to emit columns" to the Decisions link, holds seven rules, so "put a value two fields share in its own category" is hard to find. "Renders as either shape" names no shape nearby, and the README defines no "field hold".
### 93. Decide whether a field read only as a `calc` operand, whose whole format is one reference, is refused beside the name that spells the same pick.
`{"format":"{calc(x * 2)}","x":"{/n}"}` and `"{/n as k}{calc(k * 2)}"` load and render alike, so goal 5.7's one spelling does not hold for a calc operand. A name read once whole is refused where the bare reference spells it, but a calc cannot read a reference, so the field is the remaining second spelling. Item 4 keeps both loading.
### 58. Ship prebuilt binaries, a container image, and packages for Homebrew, Scoop and the biggest Linux package managers, so the CLI needs no Go.
GoReleaser attaches the binaries to the release the tag workflow publishes, and builds deb and rpm packages, an Alpine APK and an AUR package beside Homebrew and Scoop. A checkout build prints `devel` for `--version`; the binaries carry the stamped tag. v0.1.0 ships first, so it can be tested before it is packaged.
@@ -316,9 +321,9 @@ Needs item 4.
ISO 3166 reserves it for Western Sahara and the root zone has never been delegated it, so no resolver answers for it.
### 92. Name the rename in a `calc`'s refusal of a hyphenated operand, which today reports `no field or name "calling"` for `{calc(calling-code * 2)}`.
### 92. Name the hyphenated field in a `calc`'s refusal, and say to rename it: today `{calc(postal-code * 2)}` reports `no field "postal"`.
A hyphen is always subtraction in a calc, and the shipped data is full of hyphenated fields (`calling-code`, `birth-number`). The refusal names the first fragment, not the field the author meant.
A hyphen is always subtraction in a calc, and the shipped data has hyphenated fields (`postal-code` and `street-number` in `geo/SE/address`). The refusal names the first fragment, not the field the author meant.
### 53. Let a table column carry a `datatype`, so `--format json` writes `"safe": true` and `--format sql` a boolean.