Answer architecture round 1: exempt only a calc operand from the read-once refusal, run the name passes from one list, read every operand's leaves, show a name operand in a README example, and plan a path through a name as an operand

This commit is contained in:
2026-10-05 01:05:20 +02:00
committed by Lillem4n
parent d1b3b05a0c
commit 94cf741706
12 changed files with 76 additions and 43 deletions
+15 -7
View File
@@ -677,16 +677,23 @@ the SQL example above.
`{calc(expr)}` evaluates `+ - * /`, parentheses and unary minus over number `{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 field names and [names](#names), each rendered then read as a number;
a second argument rounds to that many decimals. A name is the operand `{k}` prints: a second argument rounds to that many decimals. A hyphen is always subtraction, so a
`{/n as k}{k} x 2 = {calc(k * 2)}` doubles the pick it shows. A hyphen is always hyphenated field or name can't be an operand.
subtraction, so a hyphenated field or name can't be an operand.
```json ```json
{ "format": "{net} x {qty} = {calc(net * qty, 2)}", "net": ["19.99", "5.00"], "qty": ["3", "7"] } { "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 result that rounds to zero prints unsigned — `0`, Renders e.g. `19.99 x 3 = 59.97`. A name is the operand `{lat}` prints, so the calc computes
`0.00` — as `{float()}`'s does. An operand that can never be a number (`"abc"`, from the pick shown:
```json
"{/misc.coordinate.lat as lat}{lat}° is {calc(lat * 60, 0)} arcminutes"
```
Renders e.g. `5.338477° is 320 arcminutes`.
A result that rounds to zero prints unsigned — `0`, `0.00` — as `{float()}`'s does. An operand that can never be a number (`"abc"`,
or a choice of such) is rejected at load, as is a division by a constant zero or a choice of such) is rejected at load, as is a division by a constant zero
(`1/0`, or a fixed `"0"` field); an operand that sometimes is not a number yields (`1/0`, or a fixed `"0"` field); an operand that sometimes is not a number yields
`NaN`, and a division by one that is not constant `Inf` — both print rather than `NaN`, and a division by one that is not constant `Inf` — both print rather than
@@ -771,8 +778,9 @@ Refused at `New`, each error naming what to write instead:
- a binding of anything but a reference; - a binding of anything but a reference;
- a binding in a choice's item, which every other item would leave unbound; - 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 - 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}`, while bound, which the bare reference spells: `{/word as w}{w}` is `{/word}`. A calc's
`{/n as k}{calc(k * 2)}` stands, since a calc operand is no reference; operand cannot be a reference, so there the name is the one spelling:
`{/n as k}{calc(k * 2)}` loads;
- a path through a name that selects a row; - a path through a name that selects a row;
- `{n}` beside `{n.w}` where `n`'s category renders `w` twice, as in `{w}-{w}`; - `{n}` beside `{n.w}` where `n`'s category renders `w` twice, as in `{w}-{w}`;
- reads of a name in two draw groups, or inside a `repeat` nested where the name is bound, - reads of a name in two draw groups, or inside a `repeat` nested where the name is bound,
+13 -14
View File
@@ -91,22 +91,21 @@ func checkCalcNames(path string, t *template) error {
if o.fn != "calc" || !slices.ContainsFunc(o.operands, func(a arm) bool { return a.kind == namedRead }) { if o.fn != "calc" || !slices.ContainsFunc(o.operands, func(a arm) bool { return a.kind == namedRead }) {
continue continue
} }
if err := checkOperands(o.args[0], parsedCalc(o.args[0]), operandNodes(t, o)); err != nil { if err := checkOperands(o.args[0], parsedCalc(o.args[0]), operandNodes(o)); err != nil {
return fmt.Errorf("%s: token {%s}: %w", t.site.label(path), o.body, err) return fmt.Errorf("%s: token {%s}: %w", t.site.label(path), o.body, err)
} }
} }
return nil return nil
} }
// operandNodes lists every node each operand of o may render: its field, or each node its name's // operandNodes lists every node each operand of o may render, once linkNames compiled the names.
// path lands on. func operandNodes(o op) func(name string) []node {
func operandNodes(t *template, o op) func(name string) []node {
return func(name string) []node { return func(name string) []node {
i := slices.IndexFunc(o.operands, func(a arm) bool { return a.head == name }) a := o.operands[slices.IndexFunc(o.operands, func(a arm) bool { return a.head == name })]
if a := o.operands[i]; a.kind == namedRead { if len(a.leaves) == 0 {
return a.leaves panic(internalError("calc operand %q was read before it was compiled", name))
} }
return []node{t.fields[name]} return a.leaves
} }
} }
@@ -114,7 +113,7 @@ func operandNodes(t *template, o op) func(name string) []node {
// operand lists every node an operand may render, nil while that is unknown. // operand lists every node an operand may render, nil while that is unknown.
func checkOperands(text string, expr calcNode, operand func(name string) []node) error { func checkOperands(text string, expr calcNode, operand func(name string) []node) error {
for _, name := range calcVars(expr) { for _, name := range calcVars(expr) {
if rendered, never := neverNumber(operand(name)); never { if rendered, never := noneNumeric(operand(name)); never {
return fmt.Errorf("calc(%q): operand %q is never a number: it renders %q", text, name, rendered) return fmt.Errorf("calc(%q): operand %q is never a number: it renders %q", text, name, rendered)
} }
} }
@@ -208,13 +207,13 @@ func neverNumeric(n node) (text string, never bool) {
return lit, true return lit, true
} }
case *choice: case *choice:
return neverNumber(n.items) return noneNumeric(n.items)
} }
return "", false return "", false
} }
// neverNumber reports nodes no render of which is a number, and none where there are none. // noneNumeric reports nodes no render of which is a number, and none where there are none.
func neverNumber(nodes []node) (text string, never bool) { func noneNumeric(nodes []node) (text string, never bool) {
for _, n := range nodes { for _, n := range nodes {
t, nodeNever := neverNumeric(n) t, nodeNever := neverNumeric(n)
if !nodeNever { if !nodeNever {
@@ -420,8 +419,8 @@ func (p *calcParser) number() (calcNode, error) {
return calcNum(v), nil return calcNum(v), nil
} }
// ident reads a field name: a letter or '_', then letters, digits or '_'. A '-' // ident reads a field or name: a letter or '_', then letters, digits or '_'. A '-'
// is always the minus operator, so a hyphenated field name can't be an operand. // is always the minus operator, so a hyphenated one can't be an operand.
func (p *calcParser) ident() (calcNode, error) { func (p *calcParser) ident() (calcNode, error) {
start := p.pos start := p.pos
for p.pos < len(p.rs) { for p.pos < len(p.rs) {
+1 -2
View File
@@ -317,7 +317,7 @@ func TestCalcReadsANameItReadsNowhereElse(t *testing.T) {
} }
} }
func TestCalcRefusesANameNeverANumber(t *testing.T) { func TestCalcRefusesABadOperand(t *testing.T) {
for card, want := range map[string]string{ for card, want := range map[string]string{
`"{/word as w}{w}{calc(w * 2)}"`: `operand "w" is never a number: it renders "def"`, `"{/word as w}{w}{calc(w * 2)}"`: `operand "w" is never a number: it renders "def"`,
`"{/zero as z}{z}{calc(1 / z)}"`: `divides by z, which is always zero`, `"{/zero as z}{z}{calc(1 / z)}"`: `divides by z, which is always zero`,
@@ -338,7 +338,6 @@ func TestCalcRefusesANameNeverANumber(t *testing.T) {
func TestATypedColumnProvesACalcOverAName(t *testing.T) { func TestATypedColumnProvesACalcOverAName(t *testing.T) {
dir := writeData(t, map[string]string{ dir := writeData(t, map[string]string{
"n": `"{int(1,9)}"`, "n": `"{int(1,9)}"`,
"word": `["1","x"]`,
"order": `{"format":"{/n as k}{k}","double":{"format":"{calc(k * 2)}","datatype":"integer"}}`, "order": `{"format":"{/n as k}{k}","double":{"format":"{calc(k * 2)}","datatype":"integer"}}`,
}) })
f := newGenerator(t, dir, WithSeed(1)) f := newGenerator(t, dir, WithSeed(1))
+7 -3
View File
@@ -133,6 +133,9 @@ type builtin struct {
// operands names the fields the call reads, which expand renders for it; nil // operands names the fields the call reads, which expand renders for it; nil
// for a builtin that reads none. // for a builtin that reads none.
operands func(args []string) []string operands func(args []string) []string
// refOperands says an operand may be a reference, so a name read once there has the bare
// reference as a second spelling.
refOperands bool
// proveNumber bounds the number a call's text reads as, token its body, and says which // 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 // datatypes that text is not; set it where every render reads as a finite number, which
// makes the call a calc operand, and leave it nil otherwise. // makes the call a calc operand, and leave it nil otherwise.
@@ -215,10 +218,11 @@ 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. tail is the path read into the name, // 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, and body is the token's. // "" 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.
type unboundRead struct { type unboundRead struct {
head, tail, body string head, tail, body string
operand bool operand, noRef bool
err error err error
} }
@@ -277,7 +281,7 @@ func checkReads(t formatToken, fields map[string]node, operands bool) ([]unbound
return nil, err return nil, err
} }
a := splitArm(name, nil) a := splitArm(name, nil)
unbound = append(unbound, unboundRead{head: a.head, tail: joinSegments(a.tail), body: t.body, operand: operands, err: err}) unbound = append(unbound, unboundRead{head: a.head, tail: joinSegments(a.tail), body: t.body, operand: operands, noRef: operands && !builtins[t.fn].refOperands, err: err})
} }
return unbound, nil return unbound, nil
} }
+1 -1
View File
@@ -200,7 +200,7 @@ func linkNodeRefs(scope nodeScope, root map[string]node) error {
}); err != nil { }); err != nil {
return err return err
} }
for _, pass := range []func(string, *template) error{linkNames, checkNameReads, checkCalcNames} { for _, pass := range namePasses {
if err := scope(func(path string, m node) error { if err := scope(func(path string, m node) error {
if t, ok := m.(*template); ok { if t, ok := m.(*template); ok {
return pass(path, t) return pass(path, t)
+9 -5
View File
@@ -34,11 +34,11 @@ type nameBinding struct {
} }
// nameUse is one read of a name: the path it reads into the name, "" for the name itself; whether // 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; the draw group its template renders in; and whether it sits in // a builtin reads it as an operand, and whether that operand can be no reference; the draw group
// a repeat nested inside the name's scope. // its template renders in; and whether it sits in a repeat nested inside the name's scope.
type nameUse struct { type nameUse struct {
tail, group string tail, group string
operand, nested bool operand, noRef, nested bool
} }
func (sc *nameScope) lookup(name string) *nameBinding { func (sc *nameScope) lookup(name string) *nameBinding {
@@ -184,7 +184,7 @@ func resolveReads(n node, where, group string, scopes []*nameScope) error {
if b == nil { if b == nil {
return unresolved(where, u, t.nameScope, scopes) return unresolved(where, u, t.nameScope, scopes)
} }
b.uses = append(b.uses, nameUse{tail: u.tail, group: group, operand: u.operand, nested: t.nameScope != b.scope}) b.uses = append(b.uses, nameUse{tail: u.tail, group: group, operand: u.operand, noRef: u.noRef, nested: t.nameScope != b.scope})
} }
return nil return nil
} }
@@ -250,6 +250,10 @@ func namedReads(t *template, fn func(o *op, a *arm) error) error {
return nil 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.
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 // 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. // every template is linked, so each binder has resolved that reference.
func linkNames(path string, t *template) error { func linkNames(path string, t *template) error {
+4
View File
@@ -204,6 +204,10 @@ func TestNameErrors(t *testing.T) {
"a binding names nothing"}, "a binding names nothing"},
{"read once whole", map[string]string{"word": `["a","b"]`, "card": `{"format":"{/word as w}{x}","x":"{w}"}`}, {"read once whole", map[string]string{"word": `["a","b"]`, "card": `{"format":"{/word as w}{x}","x":"{w}"}`},
`write {/word}`}, `write {/word}`},
{"read once whole, as a transform's operand", map[string]string{"word": `["a","b"]`, "card": `"{/word as w}{uppercase(w)}"`},
`write /word where it is read`},
{"read once whole, as a calc's operand", map[string]string{"n": `["2","3"]`, "card": `"{/n as k}{calc(k * 2)}"`},
""},
{"rendered twice where a path reads it", map[string]string{"word": `{"format":"{w}-{w}","w":["a","b"]}`, "card": `"{/word as n}{n}|{n.w}"`}, {"rendered twice where a path reads it", map[string]string{"word": `{"format":"{w}-{w}","w":["a","b"]}`, "card": `"{/word as n}{n}|{n.w}"`},
`{n} renders field "w" twice`}, `{n} renders field "w" twice`},
{"a binding in a cell", map[string]string{"word": `["a","b"]`, "t.json": `{"format":"{k}","rows":"t.tsv","key":"k"}`, "t.tsv": "k\tc\nx\t{/word as w}{w}{w}\ny\tz\n"}, {"a binding in a cell", map[string]string{"word": `["a","b"]`, "t.json": `{"format":"{k}","rows":"t.tsv","key":"k"}`, "t.tsv": "k\tc\nx\t{/word as w}{w}{w}\ny\tz\n"},
+8 -4
View File
@@ -26,11 +26,15 @@ func checkNameReads(path string, t *template) error {
}) })
} }
// checkUses refuses a binding of a category read once whole, outside a calc, which takes no reference, and reads of b in two draw groups, // checkUses refuses a binding of a category read once whole where a reference could stand. It
// or inside a repeat, where what b names reads a reference path. // refuses reads of b in two draw groups, or inside a repeat, where what b names reads a reference path.
func (b *nameBinding) checkUses() error { func (b *nameBinding) checkUses() error {
if r := b.uses[0]; len(b.uses) == 1 && r.tail == "" && !r.nested && !r.operand && len(b.tail) == 0 { if r := b.uses[0]; len(b.uses) == 1 && r.tail == "" && !r.nested && !r.noRef && len(b.tail) == 0 {
return fmt.Errorf("name %q is read once, whole, which the bare reference draws the same way; write {%s} where it is read, and drop the token", b.name, b.ref) spelling := b.ref
if !r.operand {
spelling = "{" + spelling + "}"
}
return fmt.Errorf("name %q is read once, whole, which the bare reference draws the same way; write %s where it is read, and drop the token", b.name, spelling)
} }
if !readsHeld(b.head, map[node]bool{}) { if !readsHeld(b.head, map[node]bool{}) {
return nil return nil
+1 -1
View File
@@ -77,7 +77,7 @@ func linkRefs(sites []categorySite, root map[string]node) error {
}); err != nil { }); err != nil {
return err return err
} }
for _, pass := range []func(string, *template) error{linkNames, checkNameReads, checkCalcNames} { for _, pass := range namePasses {
if err := eachTemplate(sites, func(_ categorySite, path string, t *template) error { return pass(path, t) }); err != nil { if err := eachTemplate(sites, func(_ categorySite, path string, t *template) error { return pass(path, t) }); err != nil {
return err return err
} }
+12 -2
View File
@@ -6,7 +6,7 @@
`Bar = 9` `Bar = 9`
`Next ID = 91` `Next ID = 93`
| Goal | W | | Goal | W |
|---|---| |---|---|
@@ -74,6 +74,7 @@
| 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 |
| 34 | 0.1.0 | | **Add `{isin()}`, `{cusip()}`, `{aba()}` and `{vin()}`.** | 2 | 4 | 6 | 7 | 4 | 20.9 | | 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 | | 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 |
| 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 | | 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 | | **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 | | 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,6 +88,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 | | 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 | | 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 | | 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 |
| 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 | | 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 | | 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 | | 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 |
@@ -229,7 +231,7 @@ Shape: T = table, t = template, c = choice.
- Deletes the token-order rules (`drawsApart`, `readFold`, `branches`, the pairwise replay), the expansion hold fence, `drawGroup`, the own-family fence and the cell-reference fences, and revises the decisions that point here. The remaining draw checks are names, paths through names, cycles and the repeat cap. - Deletes the token-order rules (`drawsApart`, `readFold`, `branches`, the pairwise replay), the expansion hold fence, `drawGroup`, the own-family fence and the cell-reference fences, and revises the decisions that point here. The remaining draw checks are names, paths through names, cycles and the repeat cap.
- Fixes on the way: `{/sel}|{/sel}` panics out of `Fake` today where two draws land on rows whose cells select different rows of another table; it must render. - Fixes on the way: `{/sel}|{/sel}` panics out of `Fake` today where two draws land on rows whose cells select different rows of another table; it must render.
- Rewrites the README's Computation, References, Draw group, Correlated fields, Linked tables, Names and "One draw, one spelling" sections; the Linked tables section says which pick's weights govern a family, since the bound row decides. - Rewrites the README's Computation, References, Draw group, Correlated fields, Linked tables, Names and "One draw, one spelling" sections; the Linked tables section says which pick's weights govern a family, since the bound row decides.
- Widens the refusal of a name read once to every binding, a path into it or a reference path bound included, since `{n.x}` read once is then `{/ref.x}`, and deletes the names fence over draw groups and nested repeats (`checkUses`, `readsHeld`). - Keeps `{/n as k}{calc(k * 2)}` loading, since a calc operand cannot be a reference. Widens the refusal of a name read once to every binding, a path into it or a reference path bound included, since `{n.x}` read once is then `{/ref.x}`, and deletes the names fence over draw groups and nested repeats (`checkUses`, `readsHeld`).
- Say how a record's columns and a struct's tags read one pick, so the `first` and `sex` columns of one `sv_SE.person` record, or one struct's tags reading them, still describe one person. - Say how a record's columns and a struct's tags read one pick, so the `first` and `sex` columns of one `sv_SE.person` record, or one struct's tags reading them, still describe one person.
- 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.
@@ -245,6 +247,10 @@ 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. 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.
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.
### 89. Fail `New` without `WithSeed`, naming the option, and seed the CLI from the system. ### 89. Fail `New` without `WithSeed`, naming the option, and seed the CLI from the system.
Goal 10.1 has the caller supply the seed, and today `New` without `WithSeed` seeds itself. This item ships in 0.1.0 beside item 77, so `New` breaks once before anyone depends on it. Mark `WithSeed` required in the Library section's options table. Goal 10.1 has the caller supply the seed, and today `New` without `WithSeed` seeds itself. This item ships in 0.1.0 beside item 77, so `New` breaks once before anyone depends on it. Mark `WithSeed` required in the Library section's options table.
@@ -310,6 +316,10 @@ 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. 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)}`.
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.
### 53. Let a table column carry a `datatype`, so `--format json` writes `"safe": true` and `--format sql` a boolean. ### 53. Let a table column carry a `datatype`, so `--format json` writes `"safe": true` and `--format sql` a boolean.
Today only a JSON field takes one, so `misc.httpmethod`'s booleans are typed in Go and text everywhere else. Typing a shipped column changes what `json` and `sql` write, so the capability is a minor and applying it to `misc.httpmethod` is a major. Today only a JSON field takes one, so `misc.httpmethod`'s booleans are typed in Go and text everywhere else. Typing a shipped column changes what `json` and `sql` write, so the capability is a minor and applying it to `misc.httpmethod` is a major.
+1 -1
View File
@@ -19,7 +19,7 @@ func withTransforms(calls map[string]builtin) map[string]builtin {
if _, clash := calls[name]; clash { if _, clash := calls[name]; clash {
panic(internalError("%s is registered as a builtin and a transform", name)) panic(internalError("%s is registered as a builtin and a transform", name))
} }
calls[name] = builtin{arity: 1, checkArgs: transformArg, prep: transformPrep(fn), operands: transformOperand} calls[name] = builtin{arity: 1, checkArgs: transformArg, prep: transformPrep(fn), operands: transformOperand, refOperands: true}
} }
return calls return calls
} }
+4 -3
View File
@@ -200,7 +200,7 @@ func (p *valueProof) proveTemplate(t *template) proven {
} }
return p.proveUnion(leaves) return p.proveUnion(leaves)
case name == "calc": case name == "calc":
return p.proveCalc(t, body, args) return p.proveCalc(o)
case builtins[name].proveNumber != nil: case builtins[name].proveNumber != nil:
return builtins[name].proveNumber(body, builtins[name].prints, args) return builtins[name].proveNumber(body, builtins[name].prints, args)
case isTransform: case isTransform:
@@ -209,9 +209,10 @@ func (p *valueProof) proveTemplate(t *template) proven {
return printing(body, DataTypeString, proven{notOperand: fmt.Sprintf("{%s} prints text, not a number", body)}) return printing(body, DataTypeString, proven{notOperand: fmt.Sprintf("{%s} prints text, not a number", body)})
} }
func (p *valueProof) proveCalc(t *template, body string, args []string) proven { func (p *valueProof) proveCalc(o op) proven {
body, args := o.body, o.args
expr := parsedCalc(args[0]) expr := parsedCalc(args[0])
nodes := operandNodes(t, t.compiled.ops[0]) nodes := operandNodes(o)
v, doubt := p.proveExpr(expr, func(name string) proven { return p.proveUnion(nodes(name)) }) v, doubt := p.proveExpr(expr, func(name string) proven { return p.proveUnion(nodes(name)) })
if doubt == "" && !(magnitude(v) <= calcLimit) { if doubt == "" && !(magnitude(v) <= calcLimit) {
doubt = calcText(expr) + " is not proven within 1e300" doubt = calcText(expr) + " is not proven within 1e300"