Answer stability round 1 and product-owner round 1: List advertises no path into a level carrying a repeat, a refusal that says to read the level whole, and plainer walk-position docs
This commit is contained in:
+4
-3
@@ -12,9 +12,10 @@ replacement, and each removed path, column or flag.
|
||||
`{/city[Oslo]..country.name}` renders `Norway`, and `.` after it steps down again,
|
||||
drawing afresh. `New` refuses a `..` naming anything but the parent table, and a
|
||||
selector after a `..`.
|
||||
- A path given to `Fake` or the CLI may not read into a level carrying a
|
||||
`repeat`, as a reference may not: `Fake("x.a")` where `x` carries `"repeat": 3` fails,
|
||||
naming the level `x`.
|
||||
- A path, given to `Fake` or the CLI or referenced in a template, may not read into a
|
||||
level carrying a `repeat`, and `List` advertises none that does: where `x` carries
|
||||
`"repeat": 3`, `Fake("x.a")` fails naming the level `x`, and `Fake("x")` renders it
|
||||
whole.
|
||||
- Every `{…}` draws afresh, and a name is the one way to keep a pick: `{p.a} {p.b}`,
|
||||
`{w} {uppercase(w)}` and `{/person.first} {/person.last}` are two draws each, which
|
||||
`{p as q}{q.a} {q.b}` and `{/person as n}{n.first} {n.last}` make one.
|
||||
|
||||
@@ -795,7 +795,8 @@ token {p.postal-code}: not every variant of this 2-way choice carries "postal-co
|
||||
```
|
||||
|
||||
The sub-fields stay addressable — `Fake("address.place.locality")` renders, and
|
||||
`List` advertises it. A path may not read into a level carrying a `repeat`.
|
||||
`List` advertises it. A path may not read into a level carrying a `repeat`, and `List`
|
||||
advertises none that does; read that level whole.
|
||||
|
||||
### Performance
|
||||
|
||||
|
||||
+35
-31
@@ -113,67 +113,71 @@ func (c config) load() (folder, error) {
|
||||
}
|
||||
|
||||
// List returns the sorted dotted paths Fake renders: each category and every field,
|
||||
// column and linked table below it, a direct descent at a time. A choice consumes no
|
||||
// segment, so a path continues through one only where every variant carries it.
|
||||
// column and linked table below it, a direct descent at a time, stopping at a level
|
||||
// carrying a repeat. A choice consumes no segment, so a path continues through one
|
||||
// only where every variant carries it.
|
||||
func (f *Generator) List() []string {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
out := paths(&f.root)
|
||||
out := paths(&f.root, false)
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// paths lists the dot paths addressable from n, relative to it, where "" is n
|
||||
// itself. A folder has no value of its own, so it contributes only its children's.
|
||||
func paths(n node) []string {
|
||||
// intoRepeats lists the fields under a level carrying a repeat too, which a path
|
||||
// reaches only to be refused.
|
||||
func paths(n node, intoRepeats bool) []string {
|
||||
switch n := n.(type) {
|
||||
case *folder:
|
||||
var out []string
|
||||
for _, name := range sortedNames(n.children) {
|
||||
for _, p := range paths(n.children[name]) {
|
||||
out = append(out, join(name, p))
|
||||
}
|
||||
out = appendUnder(out, name, paths(n.children[name], intoRepeats))
|
||||
}
|
||||
for _, name := range sortedNames(n.unloaded) {
|
||||
for _, p := range n.unloaded[name].paths {
|
||||
out = append(out, join(name, p))
|
||||
}
|
||||
out = appendUnder(out, name, n.unloaded[name].paths)
|
||||
}
|
||||
return out
|
||||
case *template:
|
||||
out := []string{""}
|
||||
if n.repeat > 1 && !intoRepeats {
|
||||
return out
|
||||
}
|
||||
for _, name := range sortedNames(n.fields) {
|
||||
for _, p := range paths(n.fields[name]) {
|
||||
out = append(out, join(name, p))
|
||||
}
|
||||
out = appendUnder(out, name, paths(n.fields[name], intoRepeats))
|
||||
}
|
||||
return out
|
||||
case *nullItem:
|
||||
return []string{""}
|
||||
case *table:
|
||||
return tablePaths(n)
|
||||
case *tableColumn, *tableRow:
|
||||
return tablePaths(n, intoRepeats)
|
||||
case *tableColumn, *tableRow, *nullItem:
|
||||
return []string{""}
|
||||
case *choice:
|
||||
out := []string{""}
|
||||
for p := range n.shared {
|
||||
out = append(out, p)
|
||||
shared := n.shared
|
||||
if !intoRepeats {
|
||||
shared = sharedPaths(n.items, false)
|
||||
}
|
||||
return out
|
||||
return append([]string{""}, sortedNames(shared)...)
|
||||
default:
|
||||
panic(invariant.Broken("paths has no case for node %T", n))
|
||||
}
|
||||
}
|
||||
|
||||
// appendUnder appends each of ps to out under name.
|
||||
func appendUnder(out []string, name string, ps []string) []string {
|
||||
for _, p := range ps {
|
||||
out = append(out, join(name, p))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// tablePaths is a table's columns, then each table linked to it under its name: the
|
||||
// direct descents, a step at a time.
|
||||
func tablePaths(t *table) []string {
|
||||
func tablePaths(t *table, intoRepeats bool) []string {
|
||||
out := append([]string{""}, t.rows.Header()...)
|
||||
sort.Strings(out[1:])
|
||||
for _, c := range t.rows.Children() {
|
||||
for _, p := range paths(c.Owner()) {
|
||||
out = append(out, join(c.Segment(), p))
|
||||
}
|
||||
out = appendUnder(out, c.Segment(), paths(c.Owner(), intoRepeats))
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -181,13 +185,13 @@ func tablePaths(t *table) []string {
|
||||
// sharedPaths is the sub-paths every item carries — the only ones a path may step
|
||||
// through a choice to reach. It intersects, bailing as soon as the set
|
||||
// is empty, which is immediate for a choice of plain strings.
|
||||
func sharedPaths(items []node) map[string]bool {
|
||||
shared := subPaths(items[0])
|
||||
func sharedPaths(items []node, intoRepeats bool) map[string]bool {
|
||||
shared := subPaths(items[0], intoRepeats)
|
||||
for _, it := range items[1:] {
|
||||
if len(shared) == 0 {
|
||||
return nil
|
||||
}
|
||||
next := subPaths(it)
|
||||
next := subPaths(it, intoRepeats)
|
||||
for p := range shared {
|
||||
if !next[p] {
|
||||
delete(shared, p)
|
||||
@@ -197,10 +201,10 @@ func sharedPaths(items []node) map[string]bool {
|
||||
return shared
|
||||
}
|
||||
|
||||
// subPaths is paths(n) as a set, without the empty path that means n itself.
|
||||
func subPaths(n node) map[string]bool {
|
||||
// subPaths is paths(n, intoRepeats) as a set, without the empty path that means n itself.
|
||||
func subPaths(n node, intoRepeats bool) map[string]bool {
|
||||
out := map[string]bool{}
|
||||
for _, p := range paths(n) {
|
||||
for _, p := range paths(n, intoRepeats) {
|
||||
if p != "" {
|
||||
out[p] = true
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ func compileChoice(items []any, pos position) (node, error) {
|
||||
}
|
||||
c.cum = cum
|
||||
}
|
||||
c.shared = sharedPaths(c.items)
|
||||
c.shared = sharedPaths(c.items, true)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -142,9 +142,12 @@ func (w *pathCheck) walk(n node, tail []string) (node, error) {
|
||||
return at.n, nil
|
||||
}
|
||||
|
||||
func repeatLevelError(level string) error {
|
||||
return fmt.Errorf("the level %q carries a repeat, so a path cannot read one draw of it; read %q whole", level, level)
|
||||
}
|
||||
|
||||
// pathPos is where a walk stands: the node reached, the tail left to walk from it, and whether
|
||||
// the step into it came from a row of an ancestor table, so a table there reads a row even
|
||||
// where the tail is empty.
|
||||
// it descended there, as route reads it.
|
||||
type pathPos struct {
|
||||
n node
|
||||
tail []string
|
||||
@@ -154,8 +157,8 @@ type pathPos struct {
|
||||
func (p pathPos) more() bool { return len(p.tail) > 0 || p.descended }
|
||||
|
||||
// takeStep moves at past the step its tail starts with, from its node, which is no choice, and
|
||||
// appends what it takes to steps: a field, or the route a table passes. whole is the path at's
|
||||
// tail ends, and level names its head in errors.
|
||||
// appends what it takes to steps: a field, or the route a table passes. whole is the full path,
|
||||
// at.tail its unwalked end, and level names its head in errors.
|
||||
func takeStep(at *pathPos, whole []string, level string, steps []pathStep, pins *pinSet) ([]pathStep, error) {
|
||||
i := len(whole) - len(at.tail)
|
||||
switch x := at.n.(type) {
|
||||
@@ -168,7 +171,7 @@ func takeStep(at *pathPos, whole []string, level string, steps []pathStep, pins
|
||||
return routeSteps(steps, pins, x, r, i)
|
||||
case *template:
|
||||
if x.repeat > 1 && !grammar.IsSelector(at.tail[0]) {
|
||||
return steps, fmt.Errorf("the level %q carries a repeat, which a path reading one draw of it cannot apply", join(level, strings.Join(whole[:i], ".")))
|
||||
return steps, repeatLevelError(join(level, strings.Join(whole[:i], ".")))
|
||||
}
|
||||
}
|
||||
next, err := stepInto(at.n, at.tail[0])
|
||||
|
||||
+1
-1
@@ -109,7 +109,7 @@ func TestNodeSwitchesHandleEveryKind(t *testing.T) {
|
||||
for name, call := range map[string]func(node){
|
||||
"columnItems": func(n node) { columnItems(n) },
|
||||
"contained": func(n node) { contained(n) },
|
||||
"paths": func(n node) { paths(n) },
|
||||
"paths": func(n node) { paths(n, false) },
|
||||
"prove": func(n node) { (&valueProof{}).prove(n) },
|
||||
"render": func(n node) {
|
||||
var frames frameStack
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ func shippedIndexSource(whole *Generator) ([]byte, error) {
|
||||
if t, isTable := s.n.(*table); isTable && t.rows.Options().Parent != "" {
|
||||
fmt.Fprintf(&b, "parent: %q, ", t.rows.Options().Parent)
|
||||
}
|
||||
ps := paths(s.n)
|
||||
ps := paths(s.n, false)
|
||||
sort.Strings(ps)
|
||||
fmt.Fprintf(&b, "paths: %#v},\n", ps)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user