One fence sequence over a node scope, and inline templates reject the folder sigils
This commit is contained in:
@@ -62,19 +62,13 @@ func loadData(sources []dataSource) (map[string]node, error) {
|
|||||||
if len(root) == 0 {
|
if len(root) == 0 {
|
||||||
return nil, fmt.Errorf("no .json data found")
|
return nil, fmt.Errorf("no .json data found")
|
||||||
}
|
}
|
||||||
// The fences here and the scoped ones [Generator.NewTemplate] runs are one
|
|
||||||
// sequence split across two entry points; a new fence lands a twin below and
|
|
||||||
// in NewTemplate (see its comment for the one omission, checkNoCycles).
|
|
||||||
if err := linkRefs(root); err != nil {
|
if err := linkRefs(root); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := checkNoCycles(root); err != nil {
|
if err := checkNoCycles(root); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := checkRepeatReach(root); err != nil {
|
if err := checkScope(treeScope(root)); err != nil {
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := checkBoundLevelsHeld(root); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return root, nil
|
return root, nil
|
||||||
|
|||||||
@@ -165,24 +165,28 @@ func pathLeaves(n node, tail []string) []node {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkRepeatReach bounds the renders a repeat multiplies to along any root-to-leaf
|
// nodeScope is the set of nodes one validation pass covers: a whole loaded tree,
|
||||||
// path, so nested repeats cannot build what one repeat may not. It runs after
|
// or a single inline node.
|
||||||
// checkNoCycles, whose guarantee is what lets the walk terminate.
|
type nodeScope func(fn func(path string, n node) error) error
|
||||||
func checkRepeatReach(root map[string]node) error {
|
|
||||||
mem := reachMemo{}
|
func treeScope(root map[string]node) nodeScope {
|
||||||
return walkNodes(root, func(path string, n node) error {
|
return func(fn func(path string, n node) error) error { return walkNodes(root, fn) }
|
||||||
return repeatCheck(path, n, mem)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkNodeRepeatReach is checkRepeatReach for one inline node: each template in
|
func inlineScope(n node) nodeScope {
|
||||||
// it is bounded, following reference edges into the already-validated tree the same
|
return func(fn func(path string, m node) error) error { return eachNode(n, "template", fn) }
|
||||||
// way a repeat through a reference multiplies along a path.
|
}
|
||||||
func checkNodeRepeatReach(n node) error {
|
|
||||||
|
// checkScope runs the per-node fences over a scope, each over the whole scope
|
||||||
|
// before the next, so which of several broken nodes is reported does not depend on
|
||||||
|
// the walk. It runs after checkNoCycles, whose guarantee is what lets the walks
|
||||||
|
// terminate.
|
||||||
|
func checkScope(s nodeScope) error {
|
||||||
mem := reachMemo{}
|
mem := reachMemo{}
|
||||||
return eachNode(n, "template", func(path string, m node) error {
|
if err := s(func(path string, n node) error { return repeatCheck(path, n, mem) }); err != nil {
|
||||||
return repeatCheck(path, m, mem)
|
return err
|
||||||
})
|
}
|
||||||
|
return s(heldCheck)
|
||||||
}
|
}
|
||||||
|
|
||||||
type reachMemo map[node]int
|
type reachMemo map[node]int
|
||||||
@@ -204,6 +208,8 @@ func (m reachMemo) of(n node) int {
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// repeatCheck bounds the renders a repeat multiplies to along any root-to-leaf
|
||||||
|
// path, so nested repeats cannot build what one repeat may not.
|
||||||
func repeatCheck(path string, n node, mem reachMemo) error {
|
func repeatCheck(path string, n node, mem reachMemo) error {
|
||||||
if t, ok := n.(*template); ok && t.repeat > 1 && mem.of(n) > MaxRepeat {
|
if t, ok := n.(*template); ok && t.repeat > 1 && mem.of(n) > MaxRepeat {
|
||||||
return fmt.Errorf("%s: repeat %d multiplies to %d renders along one path, above the maximum %d", path, t.repeat, mem.of(n), MaxRepeat)
|
return fmt.Errorf("%s: repeat %d multiplies to %d renders along one path, above the maximum %d", path, t.repeat, mem.of(n), MaxRepeat)
|
||||||
|
|||||||
@@ -6,27 +6,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// checkBoundLevelsHeld rejects every route to a held name except the ones that read
|
// heldCheck rejects every route to a held name except the ones that read its draw.
|
||||||
// its draw. An expansion holds one draw of that name; anything else that renders it
|
// An expansion holds one draw of that name; anything else that renders it draws
|
||||||
// draws again, and the two disagree. checkNoOverlap settles the spellings within one
|
// again, and the two disagree. checkNoOverlap settles the spellings within one
|
||||||
// format (a token, an operand); this settles the rest — a reference, whether it
|
// format (a token, an operand); this settles the rest — a reference, whether it
|
||||||
// sits in that format or in anything the format renders, however deep.
|
// sits in that format or in anything the format renders, however deep.
|
||||||
//
|
|
||||||
// It runs after checkNoCycles, whose guarantee is what lets the walk terminate.
|
|
||||||
func checkBoundLevelsHeld(root map[string]node) error {
|
|
||||||
return walkNodes(root, func(path string, n node) error {
|
|
||||||
return heldCheck(path, n)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkNodeBoundLevelsHeld is checkBoundLevelsHeld for one inline node: the same
|
|
||||||
// held fence, over its own templates and the shared tree they reference.
|
|
||||||
func checkNodeBoundLevelsHeld(n node) error {
|
|
||||||
return eachNode(n, "template", func(path string, m node) error {
|
|
||||||
return heldCheck(path, m)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func heldCheck(path string, n node) error {
|
func heldCheck(path string, n node) error {
|
||||||
t, ok := n.(*template)
|
t, ok := n.(*template)
|
||||||
if !ok || len(t.held) == 0 {
|
if !ok || len(t.held) == 0 {
|
||||||
|
|||||||
+11
-2
@@ -111,14 +111,23 @@ func linkTemplateRefs(folder []string, path string, t *template, root map[string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// linkNodeRefs binds the references in an inline node's templates against the
|
// linkNodeRefs binds the references in an inline node's templates against the
|
||||||
// loaded tree. The node has no folder of its own, so every sigil is root-relative:
|
// loaded tree. An inline template sits in no folder, so . and .. name nothing and
|
||||||
// / and . mean the root, .. has no folder above it.
|
// are rejected for the root spelling they would otherwise silently mean.
|
||||||
func linkNodeRefs(n node, root map[string]node) error {
|
func linkNodeRefs(n node, root map[string]node) error {
|
||||||
return eachNode(n, "template", func(path string, m node) error {
|
return eachNode(n, "template", func(path string, m node) error {
|
||||||
t, ok := m.(*template)
|
t, ok := m.(*template)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
for _, name := range refTokens(t.format) {
|
||||||
|
sigil, rest, err := refShape(name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: reference {%s}: %w", path, name, err)
|
||||||
|
}
|
||||||
|
if sigil != "/" {
|
||||||
|
return fmt.Errorf("%s: reference {%s}: an inline template has no folder; write {/%s}", path, name, rest)
|
||||||
|
}
|
||||||
|
}
|
||||||
return linkTemplateRefs(nil, path, t, root)
|
return linkTemplateRefs(nil, path, t, root)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,10 +51,10 @@ func (t *Template) Fake() string {
|
|||||||
// binds its references against the loaded tree, so repeated renders pay the
|
// binds its references against the loaded tree, so repeated renders pay the
|
||||||
// compile and validation once. It shares [New]'s guarantees: a bad template errors
|
// compile and validation once. It shares [New]'s guarantees: a bad template errors
|
||||||
// here, and rendering cannot fail.
|
// here, and rendering cannot fail.
|
||||||
// NewTemplate runs the same fences loadData does for a category, scoped to one
|
//
|
||||||
// inline node with everything but checkNoCycles: a reference binds only into the
|
// checkNoCycles is the one fence loadData runs that an inline node does not need:
|
||||||
// loaded tree, which has no path into this node, so rendering it cannot reach
|
// the loaded tree is proven acyclic at [New], the node is a finite tree, and no
|
||||||
// itself. A new fence belongs in both places (loadData and here).
|
// tree node can reference it, so nothing it renders can reach itself.
|
||||||
func (f *Generator) NewTemplate(input string) (*Template, error) {
|
func (f *Generator) NewTemplate(input string) (*Template, error) {
|
||||||
n, err := compileInput(input)
|
n, err := compileInput(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -63,10 +63,7 @@ func (f *Generator) NewTemplate(input string) (*Template, error) {
|
|||||||
if err := linkNodeRefs(n, f.categories); err != nil {
|
if err := linkNodeRefs(n, f.categories); err != nil {
|
||||||
return nil, fmt.Errorf("fejkdata: %w", err)
|
return nil, fmt.Errorf("fejkdata: %w", err)
|
||||||
}
|
}
|
||||||
if err := checkNodeRepeatReach(n); err != nil {
|
if err := checkScope(inlineScope(n)); err != nil {
|
||||||
return nil, fmt.Errorf("fejkdata: %w", err)
|
|
||||||
}
|
|
||||||
if err := checkNodeBoundLevelsHeld(n); err != nil {
|
|
||||||
return nil, fmt.Errorf("fejkdata: %w", err)
|
return nil, fmt.Errorf("fejkdata: %w", err)
|
||||||
}
|
}
|
||||||
return &Template{g: f, n: n}, nil
|
return &Template{g: f, n: n}, nil
|
||||||
|
|||||||
Reference in New Issue
Block a user