Merge the two reference-path walks, and correct the bare reference Decision, a stale comment and the draw map spellings
Tests / vet + fmt + tests (pull_request) Successful in 1m8s
Tests / vet + fmt + tests (pull_request) Successful in 1m8s
This commit is contained in:
@@ -602,7 +602,7 @@ tokens add cost in proportion to the output.
|
||||
as JSON; `--data-path` layers over it.
|
||||
- **A bare reference draws each time; a reference path is held.** `{/p} {/p}`
|
||||
is two draws, as `{word} {word}` is, while every `{/p.first}` in one render reads
|
||||
one draw, and a bare `{/p}` rendering it beside them is a load error: a bare token
|
||||
one draw, and a bare `{/p}` beside them is a load error: a bare token
|
||||
is by contract an independent draw, a path pins its level, and a fresh draw of a
|
||||
pinned level could show another row.
|
||||
- **Reference sigils follow the filesystem.** `/` is the root, `.` this file's
|
||||
@@ -691,6 +691,12 @@ tokens add cost in proportion to the output.
|
||||
different things — an operand pins the value its own render produced and stops at a
|
||||
reference, a path pins every level it passes through — so one walk would carry both
|
||||
rules and both scopes anyway, and tell them apart at every step.
|
||||
- **A record makes its draw maps up front, a `Fake` on its first read.** A record's
|
||||
columns always read through the render's draws, so making the maps where the set is
|
||||
declared keeps them on that frame's stack. A `Fake` often reads no reference path at
|
||||
all, and making them anyway cost about a fifth of the cheapest render, so it makes
|
||||
them on the first read instead — which the allocation gate prices at two heap
|
||||
allocations, paid only by a render that shares a draw.
|
||||
- **A category never references itself, and a record's fences run at load.** A category
|
||||
is one unit: a reference back into it — `{/users.first}` inside `users` — describes a
|
||||
draw other than the fields beside it, so `New` refuses it and the sibling path stays
|
||||
|
||||
@@ -115,8 +115,13 @@ func checkNestedDrawGroup(fields map[string]node, group string) error {
|
||||
// drawCheck fences each template of a scope as a render of its own, remembering which nodes read a
|
||||
// reference path.
|
||||
type drawCheck struct {
|
||||
reads map[node]bool
|
||||
splits map[node]bool
|
||||
memo map[readsMemo]bool
|
||||
}
|
||||
|
||||
// readsMemo is one answer reads has given: for a node, and for each place it stops.
|
||||
type readsMemo struct {
|
||||
n node
|
||||
stopAtGroup bool
|
||||
}
|
||||
|
||||
// checkDrawGroup refuses a draw group that splits nothing: one whose render reads every reference
|
||||
@@ -162,41 +167,30 @@ func (c *drawCheck) checkRecordDraws(path string, n node) error {
|
||||
|
||||
// readsPath reports whether rendering n reads a reference path, short of a repeat, which renders
|
||||
// over draws of its own.
|
||||
func (c *drawCheck) readsPath(n node) bool {
|
||||
if r, done := c.reads[n]; done {
|
||||
return r
|
||||
}
|
||||
r := false
|
||||
for _, e := range renderEdges(n) {
|
||||
if a, _, isRef := refRead(n, e.label); (isRef && len(a.tail) > 0) || (!repeats(e.to) && c.readsPath(e.to)) {
|
||||
r = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if c.reads == nil {
|
||||
c.reads = map[node]bool{}
|
||||
}
|
||||
c.reads[n] = r
|
||||
return r
|
||||
}
|
||||
func (c *drawCheck) readsPath(n node) bool { return c.reads(n, false) }
|
||||
|
||||
// splitsDraws reports whether rendering n reads a reference path that n's own draw group answers
|
||||
// for: one outside a repeat and outside a nested draw group, which hold their own draws.
|
||||
func (c *drawCheck) splitsDraws(n node) bool {
|
||||
if r, done := c.splits[n]; done {
|
||||
func (c *drawCheck) splitsDraws(n node) bool { return c.reads(n, true) }
|
||||
|
||||
// reads walks what rendering n renders for a reference path, stopping at a repeat — and at a nested
|
||||
// draw group when stopAtGroup — since each holds draws of its own.
|
||||
func (c *drawCheck) reads(n node, stopAtGroup bool) bool {
|
||||
k := readsMemo{n, stopAtGroup}
|
||||
if r, done := c.memo[k]; done {
|
||||
return r
|
||||
}
|
||||
r := false
|
||||
for _, e := range renderEdges(n) {
|
||||
if a, _, isRef := refRead(n, e.label); (isRef && len(a.tail) > 0) || (!repeats(e.to) && !grouped(e.to) && c.splitsDraws(e.to)) {
|
||||
if a, _, isRef := refRead(n, e.label); (isRef && len(a.tail) > 0) || (!repeats(e.to) && !(stopAtGroup && grouped(e.to)) && c.reads(e.to, stopAtGroup)) {
|
||||
r = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if c.splits == nil {
|
||||
c.splits = map[node]bool{}
|
||||
if c.memo == nil {
|
||||
c.memo = map[readsMemo]bool{}
|
||||
}
|
||||
c.splits[n] = r
|
||||
c.memo[k] = r
|
||||
return r
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ func unreachableInChoice(c *choice, want string) error {
|
||||
|
||||
// checkPath proves a dotted tail resolves whichever way the draws go — a choice
|
||||
// must carry the rest of the path in the set every variant shares — and that no
|
||||
// level a path reads carries a repeat or a group, which one draw of it could not
|
||||
// level a path reads carries a repeat or a drawGroup, which one draw of it could not
|
||||
// apply. So a path that validates here resolves on every render, and a typo is a
|
||||
// New-time error.
|
||||
func checkPath(n node, tail []string, level string) error {
|
||||
|
||||
Reference in New Issue
Block a user