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
|
||||
render.go Fake and the recursive renderer (choices, format strings, paths, bound draws)
|
||||
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
|
||||
calc.go the {calc()} arithmetic evaluator: parser, eval, validation
|
||||
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 {
|
||||
held := map[node]bool{}
|
||||
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) {
|
||||
if splitArm(e.label).key == head {
|
||||
continue // a path token reading this level, which is the one route allowed
|
||||
}
|
||||
if renders(e.to, held, map[node]bool{}) {
|
||||
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])
|
||||
if renders(e.to, held, seen) {
|
||||
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:
|
||||
var es []renderEdge
|
||||
for _, name := range append(fieldTokens(n.format), calcOperands(n.format)...) {
|
||||
// A path token renders its head, so the edge is to the head — over-
|
||||
// approximating the sub-field it descends to, which is what keeps the
|
||||
// cycle walk conservative rather than blind.
|
||||
if c, ok := n.fields[splitArm(name).key]; ok {
|
||||
es = append(es, renderEdge{c, name})
|
||||
a := splitArm(name)
|
||||
c, ok := n.fields[a.key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for _, leaf := range pathLeaves(c, a.tail) {
|
||||
es = append(es, renderEdge{leaf, name})
|
||||
}
|
||||
}
|
||||
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
|
||||
// — 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
|
||||
|
||||
@@ -190,9 +190,7 @@ func resolve(s *session, arms []arm, t *template, bound *draws) string {
|
||||
bound.variant[a.key] = n
|
||||
}
|
||||
// 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
|
||||
// checkNoOverlap rejects a field or calc spelling and checkNotBound a
|
||||
// reference one.
|
||||
// share it.
|
||||
for i, seg := range a.tail {
|
||||
if i < len(a.steps) {
|
||||
held, drew := bound.variant[a.steps[i]]
|
||||
|
||||
Reference in New Issue
Block a user