Answer architecture round 2: refuse a self-read only inside what the binding lands on, fix the struct.go Layout line, move loneRef beside its caller, and widen item 83 to the link passes
This commit is contained in:
@@ -959,7 +959,7 @@ scope.go the scope a render reads names and rows through
|
|||||||
path.go the dotted-path walks with their selectors, and proving a path resolves
|
path.go the dotted-path walks with their selectors, and proving a path resolves
|
||||||
render.go Fake, the recursive renderer (choices, format strings, expansions) and the reads its tokens make
|
render.go Fake, the recursive renderer (choices, format strings, expansions) and the reads its tokens make
|
||||||
record.go records: Record, the JSON/CSV/SQL serializers, and their entry points
|
record.go records: Record, the JSON/CSV/SQL serializers, and their entry points
|
||||||
struct.go structs: FakeStruct, fake tags, the one pick a struct's path tags into one category read, and a field's Go type as its column's datatype
|
struct.go structs: FakeStruct, fake tags as one record's columns, and a field's Go type as its column's datatype
|
||||||
inline.go inline templates: Template, NewTemplate, FakeTemplate, IsTemplate, and their compile and link
|
inline.go inline templates: Template, NewTemplate, FakeTemplate, IsTemplate, and their compile and link
|
||||||
format.go the {token} grammar: scanning, tokens, operands, validation, compiling a format
|
format.go the {token} grammar: scanning, tokens, operands, validation, compiling a format
|
||||||
pins.go the pin set: the table rows one path or one named pick fixes
|
pins.go the pin set: the table rows one path or one named pick fixes
|
||||||
|
|||||||
@@ -206,3 +206,11 @@ func linkNodeRefs(scope nodeScope, root map[string]node) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loneRef is the reference a format of one reference token and nothing else reads.
|
||||||
|
func loneRef(toks []formatToken) (string, bool) {
|
||||||
|
if len(toks) != 1 || toks[0].kind != nameRead || len(toks[0].names) != 1 || !isRef(toks[0].names[0]) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return toks[0].names[0], true
|
||||||
|
}
|
||||||
|
|||||||
@@ -417,3 +417,9 @@ func TestANameReadAloneIsTheRecordColumnItReads(t *testing.T) {
|
|||||||
t.Errorf("NewRecordTemplate = %v, want a name-read column accepted inline", err)
|
t.Errorf("NewRecordTemplate = %v, want a name-read column accepted inline", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAFieldBindingReadOutsideWhatItBindsLoads(t *testing.T) {
|
||||||
|
if _, err := engine(1).NewTemplate(`{"format":"{x.y as n}{n}{n}{x}","x":{"format":"{z}","y":["a","b"],"z":"{n}"}}`); err != nil {
|
||||||
|
t.Errorf("NewTemplate = %v, want a read of n from x.z, outside x.y, accepted", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+25
-11
@@ -1,7 +1,6 @@
|
|||||||
package fejkdata
|
package fejkdata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -19,7 +18,7 @@ func checkNameReads(path string, t *template) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return namedReads(t, func(o *op, a *arm) error {
|
return namedReads(t, func(o *op, a *arm) error {
|
||||||
if a.named.bindsField() && rendersInside(a.named.head, t) {
|
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, which would render itself; 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 it binds, which would render itself; read the name outside that field", t.site.label(path), o.body, a.named.name, a.named.ref)
|
||||||
}
|
}
|
||||||
for _, leaf := range a.leaves {
|
for _, leaf := range a.leaves {
|
||||||
@@ -31,18 +30,33 @@ func checkNameReads(path string, t *template) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// rendersInside reports whether t sits in what n contains.
|
// rendersInside reports whether t sits in what one of nodes contains.
|
||||||
func rendersInside(n node, t *template) bool {
|
func rendersInside(nodes []node, t *template) bool {
|
||||||
return eachNode(n, "", func(_ string, m node) error {
|
seen := map[node]bool{}
|
||||||
if m == t {
|
var inside func(n node) bool
|
||||||
return errFound
|
inside = func(n node) bool {
|
||||||
|
if n == node(t) {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
return nil
|
if seen[n] {
|
||||||
}) != nil
|
return false
|
||||||
|
}
|
||||||
|
seen[n] = true
|
||||||
|
for _, c := range contained(n) {
|
||||||
|
if inside(c.node) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, n := range nodes {
|
||||||
|
if inside(n) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var errFound = errors.New("found")
|
|
||||||
|
|
||||||
// checkUses refuses a binding read once at a spot the bound spelling can stand: that spelling
|
// 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 only beside its binding.
|
// draws the same way without the name. A field stands only beside its binding.
|
||||||
func (b *nameBinding) checkUses() error {
|
func (b *nameBinding) checkUses() error {
|
||||||
|
|||||||
@@ -251,11 +251,3 @@ func refTokens(toks []formatToken) []string {
|
|||||||
}
|
}
|
||||||
return refs
|
return refs
|
||||||
}
|
}
|
||||||
|
|
||||||
// loneRef is the reference a format of one reference token and nothing else reads.
|
|
||||||
func loneRef(toks []formatToken) (string, bool) {
|
|
||||||
if len(toks) != 1 || toks[0].kind != nameRead || len(toks[0].names) != 1 || !isRef(toks[0].names[0]) {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
return toks[0].names[0], true
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -131,7 +131,7 @@
|
|||||||
| 95 | 0.3.0 | principle | **Hold a struct's columns and the field each fills in one slice.** | 1 | 2 | 1 | 5 | 3.2 | 9.5 |
|
| 95 | 0.3.0 | principle | **Hold a struct's columns and the field each fills in one slice.** | 1 | 2 | 1 | 5 | 3.2 | 9.5 |
|
||||||
| 19 | 0.3.0 | | **Fill the 398 Swedish localities weighted 200 from SCB småorter.** | 2 | 4 | 4 | 6 | 14.1 | 9.2 |
|
| 19 | 0.3.0 | | **Fill the 398 Swedish localities weighted 200 from SCB småorter.** | 2 | 4 | 4 | 6 | 14.1 | 9.2 |
|
||||||
| 56 | 0.3.0 | defect | **Build on a manual run of `test.yml`.** | 1 | 1 | 1 | 4 | 1 | 8.8 |
|
| 56 | 0.3.0 | defect | **Build on a manual run of `test.yml`.** | 1 | 1 | 1 | 4 | 1 | 8.8 |
|
||||||
| 83 | 0.3.0 | principle | **Read a name binding's head and tail from its binder's link, and delete `linkBindings`.** | 4 | 3 | 1 | 6 | 3.2 | 8.1 |
|
| 83 | 0.3.0 | principle | **Read a name binding's head and tail from its binder's link, and have each link pass return what it compiles, so `linkBindings` goes and no pass reads what an earlier one wrote in place.** | 4 | 4 | 1 | 6 | 3.2 | 7.8 |
|
||||||
| 63 | 0.4.0 | | **Pair a street with its exact postnummer.** | 3 | 6 | 5 | 8 | 4.1 | 19.1 |
|
| 63 | 0.4.0 | | **Pair a street with its exact postnummer.** | 3 | 6 | 5 | 8 | 4.1 | 19.1 |
|
||||||
| 62 | 0.4.0 | | **Add `{btc()}` and `{eth()}`.** | 2 | 4 | 4 | 6 | 4 | 15.2 |
|
| 62 | 0.4.0 | | **Add `{btc()}` and `{eth()}`.** | 2 | 4 | 4 | 6 | 4 | 15.2 |
|
||||||
| 59 | 0.4.0 | | **Promise in `Generator`'s godoc that its renders run one at a time.** | 1 | 1 | 5 | 5 | 12 | 13.2 |
|
| 59 | 0.4.0 | | **Promise in `Generator`'s godoc that its renders run one at a time.** | 1 | 1 | 5 | 5 | 12 | 13.2 |
|
||||||
@@ -446,9 +446,9 @@ Its doc says render failure, and it wraps what `NewTemplate` and `NewRecordTempl
|
|||||||
|
|
||||||
`workflow_dispatch` leaves `github.event.before` empty, so the diff compares `HEAD` with itself and skips `docker build`.
|
`workflow_dispatch` leaves `github.event.before` empty, so the diff compares `HEAD` with itself and skips `docker build`.
|
||||||
|
|
||||||
### 83. Read a name binding's head and tail from its binder's link, and delete `linkBindings`.
|
### 83. Read a name binding's head and tail from its binder's link, and have each link pass return what it compiles, so `linkBindings` goes and no pass reads what an earlier one wrote in place.
|
||||||
|
|
||||||
`linkBindings` writes `nameBinding.head` and `tail` in `linkRefs`'s first pass and `linkName` reads them in its second, so swapping the passes hands `linkName` a nil head. Technical principle: one owner per value.
|
`linkBindings` writes `nameBinding.head` and `tail` in `linkRefs`'s first pass and `linkName` reads them in its second, so swapping the passes hands `linkName` a nil head. `linkPasses` does the same: `linkColumnRead` reads the `arm.named` that `linkNames` wrote, and `checkNameReads` the arms both filled. Technical principle: one owner per value.
|
||||||
|
|
||||||
### 63. Pair a street with its exact postnummer.
|
### 63. Pair a street with its exact postnummer.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user