Aim a path token's render edge at what it renders
This commit is contained in:
@@ -495,7 +495,7 @@ fakes.go Fakes, New, List, options, seeding
|
|||||||
node.go the node model and JSON -> node compilation
|
node.go the node model and JSON -> node compilation
|
||||||
render.go Fake and the recursive renderer (choices, format strings, paths, bound draws)
|
render.go Fake and the recursive renderer (choices, format strings, paths, bound draws)
|
||||||
template.go the {token} grammar: scanning, function and path tokens, validation
|
template.go the {token} grammar: scanning, function and path tokens, validation
|
||||||
reference.go {..path} binding across the tree, and cycle detection
|
reference.go {..path} binding across the tree, the render graph, and the walks over it
|
||||||
builtins.go the {name()} function registry and its implementations
|
builtins.go the {name()} function registry and its implementations
|
||||||
calc.go the {calc()} arithmetic evaluator: parser, eval, validation
|
calc.go the {calc()} arithmetic evaluator: parser, eval, validation
|
||||||
data.go data loading: folders/files -> namespace tree, multi-path merge
|
data.go data loading: folders/files -> namespace tree, multi-path merge
|
||||||
|
|||||||
+31
-7
@@ -59,12 +59,15 @@ func checkBoundLevelsHeld(root map[string]node) error {
|
|||||||
for _, head := range heads {
|
for _, head := range heads {
|
||||||
held := map[node]bool{}
|
held := map[node]bool{}
|
||||||
cover(t.fields[head], held)
|
cover(t.fields[head], held)
|
||||||
|
// One seen set across the edges: a node that cannot reach the level
|
||||||
|
// cannot reach it by another route either, so it is walked once here.
|
||||||
|
seen := map[node]bool{}
|
||||||
for _, e := range renderEdges(t) {
|
for _, e := range renderEdges(t) {
|
||||||
if splitArm(e.label).key == head {
|
if splitArm(e.label).key == head {
|
||||||
continue // a path token reading this level, which is the one route allowed
|
continue // a path token reading this level, which is the one route allowed
|
||||||
}
|
}
|
||||||
if renders(e.to, held, map[node]bool{}) {
|
if renders(e.to, held, seen) {
|
||||||
return fmt.Errorf("%s: {%s} renders a level that {%s} reads a path into; name the fields you want instead", path, e.label, t.bound[head])
|
return fmt.Errorf("%s: {%s} renders %q, which {%s} reads a path into; name the fields you want instead", path, e.label, head, t.bound[head])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,11 +261,13 @@ func renderEdges(n node) []renderEdge {
|
|||||||
case *template:
|
case *template:
|
||||||
var es []renderEdge
|
var es []renderEdge
|
||||||
for _, name := range append(fieldTokens(n.format), calcOperands(n.format)...) {
|
for _, name := range append(fieldTokens(n.format), calcOperands(n.format)...) {
|
||||||
// A path token renders its head, so the edge is to the head — over-
|
a := splitArm(name)
|
||||||
// approximating the sub-field it descends to, which is what keeps the
|
c, ok := n.fields[a.key]
|
||||||
// cycle walk conservative rather than blind.
|
if !ok {
|
||||||
if c, ok := n.fields[splitArm(name).key]; ok {
|
continue
|
||||||
es = append(es, renderEdge{c, name})
|
}
|
||||||
|
for _, leaf := range pathLeaves(c, a.tail) {
|
||||||
|
es = append(es, renderEdge{leaf, name})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return es
|
return es
|
||||||
@@ -271,6 +276,25 @@ func renderEdges(n node) []renderEdge {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pathLeaves lists what a token's dotted tail renders. A path draws the levels it
|
||||||
|
// passes through but renders only what it lands on, so the leaf is the edge — a
|
||||||
|
// bare token, whose tail is empty, lands on the field itself. A choice on the way
|
||||||
|
// contributes every variant, since any of them may be the one drawn. checkPath has
|
||||||
|
// already proved the tail resolves in every variant, so the walk drops nothing.
|
||||||
|
func pathLeaves(n node, tail []string) []node {
|
||||||
|
if len(tail) == 0 {
|
||||||
|
return []node{n}
|
||||||
|
}
|
||||||
|
if c, ok := n.(*choice); ok {
|
||||||
|
var out []node
|
||||||
|
for _, it := range c.items {
|
||||||
|
out = append(out, pathLeaves(it, tail)...)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
return pathLeaves(child(n, tail[0]), tail[1:])
|
||||||
|
}
|
||||||
|
|
||||||
// checkNoCycles rejects a reference cycle: a node whose rendering can reach itself
|
// checkNoCycles rejects a reference cycle: a node whose rendering can reach itself
|
||||||
// — directly, mutually, or through a chain — never terminates, so it must fail at
|
// — directly, mutually, or through a chain — never terminates, so it must fail at
|
||||||
// New rather than stack-overflow at render. It is a depth-first walk of the render
|
// New rather than stack-overflow at render. It is a depth-first walk of the render
|
||||||
|
|||||||
@@ -190,9 +190,7 @@ func resolve(s *session, arms []arm, t *template, bound *draws) string {
|
|||||||
bound.variant[a.key] = n
|
bound.variant[a.key] = n
|
||||||
}
|
}
|
||||||
// Hold the draw at every level passed through, so two paths sharing a prefix
|
// Hold the draw at every level passed through, so two paths sharing a prefix
|
||||||
// share it. The leaf needs no hold: no other token can name it, since
|
// share it.
|
||||||
// checkNoOverlap rejects a field or calc spelling and checkNotBound a
|
|
||||||
// reference one.
|
|
||||||
for i, seg := range a.tail {
|
for i, seg := range a.tail {
|
||||||
if i < len(a.steps) {
|
if i < len(a.steps) {
|
||||||
held, drew := bound.variant[a.steps[i]]
|
held, drew := bound.variant[a.steps[i]]
|
||||||
|
|||||||
Reference in New Issue
Block a user