A column of one reference alone takes the datatype and null of the column it reads #15

Merged
lilleman merged 12 commits from reference-inheritance into main 2026-09-15 21:46:18 +02:00
4 changed files with 37 additions and 15 deletions
Showing only changes of commit 72fe01e89b - Show all commits
+11 -10
View File
@@ -312,9 +312,9 @@ order.id: datatype integer: "1{digits(2)}" is not one value; write one literal o
A column of one reference alone to another record's column — `"score": "{/src.score}"` A column of one reference alone to another record's column — `"score": "{/src.score}"`
— is that column: it takes the column's datatype and is null where the column is, and — 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 a string a struct field tagged `src.score` is nil there. A `datatype` of its own types the
column's values, and over a typed column is refused naming the datatype it takes. Any column's values where they prove it, and one restating the datatype it takes is refused.
other read renders the column's text, a null as `""`. Any other read renders the column's text, a null as `""`.
A typed column's `{calc()}` must be proven to print a number: each operand a number A typed column's `{calc()}` must be proven to print a number: each operand a number
literal, an `{int()}`, `{float()}`, `{seq()}` or `{digits()}` call, a calc, or a read of literal, an `{int()}`, `{float()}`, `{seq()}` or `{digits()}` call, a calc, or a read of
@@ -549,10 +549,11 @@ tokens add cost in proportion to the output.
tag of one reference alone, `{/users}`, is refused naming the path `users`: both tag of one reference alone, `{/users}`, is refused naming the path `users`: both
render the same text, and only the path names a record. `IsTemplate` exports the render the same text, and only the path names a record. `IsTemplate` exports the
rule, so the CLI, struct tags and any other caller read one. rule, so the CLI, struct tags and any other caller read one.
- **An inline template skips the cycle fence, and only that one.** `New` proves the - **An inline template skips the cycle fence.** `New` proves the loaded tree
loaded tree acyclic, an inline node is a finite tree of its own, and nothing in acyclic, an inline node is a finite tree of its own, and nothing in the tree can
the tree can reference it, so no render of it reaches itself. Every other fence reference it, so no render of it reaches itself. Every other fence runs over both,
runs over both, from one `checkScope`. from one `checkScope`, except that struct tags leave column agreement to their Go
types.
- **An inline template that does not compile is misuse (exit 2), including a - **An inline template that does not compile is misuse (exit 2), including a
reference that resolves to nothing** — the whole argument is the spelling under reference that resolves to nothing** — the whole argument is the spelling under
test, and `NewTemplate` compiles, links and validates as one step. An unknown test, and `NewTemplate` compiles, links and validates as one step. An unknown
@@ -672,9 +673,9 @@ tokens add cost in proportion to the output.
one comparison, and `1{digits(2)}` is a second spelling of `{int(100,199)}`. one comparison, and `1{digits(2)}` is a second spelling of `{int(100,199)}`.
- **A column of one reference alone is the column it reads.** `{/src.score}` renders - **A column of one reference alone is the column it reads.** `{/src.score}` renders
exactly what `src.score` draws, so it takes that column's datatype and null rather exactly what `src.score` draws, so it takes that column's datatype and null rather
than restating them, and a `datatype` restating a typed column is a second spelling. than restating them, and a `datatype` restating the one it takes is a second
Over a string column a `datatype` still types the values — the one way to type a spelling. Any other `datatype` still types the values — the one way to type a column
column someone else wrote. someone else wrote.
- **A typed column's calc is refused unless proven.** Operand bounds must keep each - **A typed column's calc is refused unless proven.** Operand bounds must keep each
divisor from zero and the result finite; what they cannot show is refused rather divisor from zero and the result finite; what they cannot show is refused rather
than trusted, since a bare `NaN` breaks the JSON and SQL it lands in. than trusted, since a bare `NaN` breaks the JSON and SQL it lands in.
+24 -2
View File
@@ -165,11 +165,33 @@ func disagreement(a *template, da DataType, b *template, db DataType) error {
} }
switch { switch {
case da != DataTypeString && db != DataTypeString: case da != DataTypeString && db != DataTypeString:
return fmt.Errorf("its items hold %s and %s; a column holds one datatype", da, db) return bothTyped(a, da, b, db)
case typed.datatype == DataTypeString && (&valueProof{}).columnItem(bare).not[want] != "": case typed.datatype == DataTypeString && (&valueProof{}).columnItem(bare).not[want] != "":
return fmt.Errorf(`item %q is not %s, the datatype item %q takes from the column it reads; to read that column as text, write {"format":"{text}","text":%q}`, bare.format, dataTypeNouns[want], typed.format, typed.format) return fmt.Errorf(`item %q is not %s, the datatype item %q takes from the column it reads; to read that column as text, %s`, bare.format, dataTypeNouns[want], typed.format, asText(typed))
case bare.fromString: // an object may carry a weight, which this spelling would drop case bare.fromString: // an object may carry a weight, which this spelling would drop
return fmt.Errorf(`item %q declares no datatype, and a column holds one; write it as {"format":%q,"datatype":%q}`, bare.format, bare.format, want) return fmt.Errorf(`item %q declares no datatype, and a column holds one; write it as {"format":%q,"datatype":%q}`, bare.format, bare.format, want)
} }
return fmt.Errorf(`item %q declares no datatype beside one declaring %s; a column holds one, so give it "datatype": %q`, bare.format, want, want) return fmt.Errorf(`item %q declares no datatype beside one declaring %s; a column holds one, so give it "datatype": %q`, bare.format, want, want)
} }
// bothTyped names the fix for two items holding different datatypes: reading one that takes its
// datatype from the column it reads as text, since only a declared datatype can be edited away.
func bothTyped(a *template, da DataType, b *template, db DataType) error {
read := a
if a.datatype != DataTypeString {
read = b
}
if read.datatype != DataTypeString {
return fmt.Errorf("its items hold %s and %s; a column holds one datatype", da, db)
}
return fmt.Errorf("its items hold %s and %s; a column holds one datatype, so to read %q as text, %s", da, db, read.format, asText(read))
}
// asText names the spelling that reads the column a column-read item reads as text, keeping the
// other keys an object item carries.
func asText(t *template) string {
if t.fromString {
return fmt.Sprintf(`write {"format":"{text}","text":%q}`, t.format)
}
return fmt.Sprintf(`set its "format" to "{text}" and add "text": %q`, t.format)
}
-1
View File
@@ -177,7 +177,6 @@ func inlineScope(n node, label string) nodeScope {
return func(fn func(path string, m node) error) error { return eachNode(n, label, fn) } return func(fn func(path string, m node) error) error { return eachNode(n, label, fn) }
} }
// checkScope runs every fence over a scope: checkColumns, then checkRenders.
func checkScope(s nodeScope) error { func checkScope(s nodeScope) error {
if err := checkColumns(s); err != nil { if err := checkColumns(s); err != nil {
return err return err
+2 -2
View File
@@ -26,13 +26,13 @@ type valueProof struct {
} }
// checkDatatype rejects a typed column item some render of which is not text of its datatype, // checkDatatype rejects a typed column item some render of which is not text of its datatype,
// and one declaring a datatype over the typed column it is. // and one restating the datatype of the column it is.
func (p *valueProof) checkDatatype(path string, n node) error { func (p *valueProof) checkDatatype(path string, n node) error {
t, ok := n.(*template) t, ok := n.(*template)
if !ok || t.datatype == DataTypeString { if !ok || t.datatype == DataTypeString {
return nil return nil
} }
if d := readDatatype(t); d != DataTypeString { if d := readDatatype(t); d == t.datatype {
return fmt.Errorf(`%s: %s takes datatype %s from the column it reads; drop "datatype"`, path, t.format, d) return fmt.Errorf(`%s: %s takes datatype %s from the column it reads; drop "datatype"`, path, t.format, d)
} }
if reason := p.columnItem(t).not[t.datatype]; reason != "" { if reason := p.columnItem(t).not[t.datatype]; reason != "" {