One path walk under checkPath, descend, readField, pathLeaves and coverPath; split the hold and the render graph into their own files
Tests / vet + fmt + tests (pull_request) Successful in 53s
Tests / vet + fmt + tests (pull_request) Successful in 53s
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
package fejkdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// pathWalk is what one walk of a dotted path does at each kind of level: choice
|
||||
// returns the variants to continue into (none stops the walk); level runs at each
|
||||
// template a segment descends into; leaf runs where the tail ends. A nil action
|
||||
// is skipped.
|
||||
type pathWalk struct {
|
||||
choice func(c *choice, rest []string) ([]node, error)
|
||||
level func(t *template, rest []string) error
|
||||
leaf func(n node) error
|
||||
}
|
||||
|
||||
// walkPath descends tail from n: a group or template by its next segment, a
|
||||
// choice by w.choice, which consumes no segment. A missing segment is an error,
|
||||
// so no walk reaches past what the data holds.
|
||||
func walkPath(n node, tail []string, w pathWalk) error {
|
||||
if len(tail) == 0 {
|
||||
if w.leaf != nil {
|
||||
return w.leaf(n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
switch n := n.(type) {
|
||||
case *group:
|
||||
child, ok := n.children[tail[0]]
|
||||
if !ok {
|
||||
return fmt.Errorf("no entry %q", tail[0])
|
||||
}
|
||||
return walkPath(child, tail[1:], w)
|
||||
case *template:
|
||||
if w.level != nil {
|
||||
if err := w.level(n, tail); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
child, ok := n.fields[tail[0]]
|
||||
if !ok {
|
||||
return fmt.Errorf("no field %q", tail[0])
|
||||
}
|
||||
return walkPath(child, tail[1:], w)
|
||||
case *choice:
|
||||
if w.choice == nil {
|
||||
return nil
|
||||
}
|
||||
next, err := w.choice(n, tail)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, item := range next {
|
||||
if err := walkPath(item, tail, w); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("cannot descend into %T at %q", n, tail[0])
|
||||
}
|
||||
|
||||
// carriedByAll is the choice rule a path that must resolve on every call obeys:
|
||||
// the rest of the tail must be one every variant carries.
|
||||
func carriedByAll(c *choice, rest []string) error {
|
||||
if want := strings.Join(rest, "."); !c.shared[want] {
|
||||
return unreachableInChoice(c, want)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// unreachableInChoice reports that a path cannot step through this choice, listing
|
||||
// what every variant does carry. It reads the precomputed set, so a failing path
|
||||
// costs no more than a rendering one.
|
||||
func unreachableInChoice(c *choice, want string) error {
|
||||
if len(c.shared) == 0 {
|
||||
return fmt.Errorf("no variant of this %d-way choice carries %q", len(c.items), want)
|
||||
}
|
||||
offered := make([]string, 0, len(c.shared))
|
||||
for p := range c.shared {
|
||||
offered = append(offered, p)
|
||||
}
|
||||
sort.Strings(offered)
|
||||
return fmt.Errorf("not every variant of this %d-way choice carries %q; all carry %v", len(c.items), want, offered)
|
||||
}
|
||||
|
||||
// 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, 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 {
|
||||
return walkPath(n, tail, pathWalk{
|
||||
choice: func(c *choice, rest []string) ([]node, error) {
|
||||
if err := carriedByAll(c, rest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.items, nil
|
||||
},
|
||||
level: func(t *template, rest []string) error {
|
||||
if t.repeat > 1 {
|
||||
return fmt.Errorf("the level %q carries a repeat, which a path reading one draw of it cannot apply", join(level, strings.Join(tail[:len(tail)-len(rest)], ".")))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user