From 72fe01e89b6bb19b4f4c4de6eea51114856c8c8c Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Tue, 15 Sep 2026 21:28:52 +0200 Subject: [PATCH] Refuse only a datatype restating the column read, name the text spelling for datatypes taken from column reads keeping an item's other keys, and keep the fence Decision true --- README.md | 21 +++++++++++---------- datatype.go | 26 ++++++++++++++++++++++++-- graph.go | 1 - value.go | 4 ++-- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index df5dd38..de34fa8 100644 --- a/README.md +++ b/README.md @@ -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}"` — 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 -column's values, and over a typed column is refused naming the datatype it takes. Any -other read renders the column's text, a null as `""`. +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. +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 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 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. -- **An inline template skips the cycle fence, and only that one.** `New` proves the - loaded tree acyclic, an inline node is a finite tree of its own, and nothing in - the tree can reference it, so no render of it reaches itself. Every other fence - runs over both, from one `checkScope`. +- **An inline template skips the cycle fence.** `New` proves the loaded tree + acyclic, an inline node is a finite tree of its own, and nothing in the tree can + reference it, so no render of it reaches itself. Every other fence runs over both, + 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 reference that resolves to nothing** — the whole argument is the spelling under 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)}`. - **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 - than restating them, and a `datatype` restating a typed column is a second spelling. - Over a string column a `datatype` still types the values — the one way to type a - column someone else wrote. + than restating them, and a `datatype` restating the one it takes is a second + spelling. Any other `datatype` still types the values — the one way to type a column + someone else wrote. - **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 than trusted, since a bare `NaN` breaks the JSON and SQL it lands in. diff --git a/datatype.go b/datatype.go index ec05352..6221b29 100644 --- a/datatype.go +++ b/datatype.go @@ -165,11 +165,33 @@ func disagreement(a *template, da DataType, b *template, db DataType) error { } switch { 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] != "": - 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 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) } + +// 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) +} diff --git a/graph.go b/graph.go index be5a593..962a51c 100644 --- a/graph.go +++ b/graph.go @@ -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) } } -// checkScope runs every fence over a scope: checkColumns, then checkRenders. func checkScope(s nodeScope) error { if err := checkColumns(s); err != nil { return err diff --git a/value.go b/value.go index faa715f..cf59cdf 100644 --- a/value.go +++ b/value.go @@ -26,13 +26,13 @@ type valueProof struct { } // 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 { t, ok := n.(*template) if !ok || t.datatype == DataTypeString { 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) } if reason := p.columnItem(t).not[t.datatype]; reason != "" {