Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33
@@ -961,14 +961,14 @@ table.go tables: their options, format and cells, and the link to the par
|
||||
name.go names: {x as n} bindings, the scopes they live in, and the reads they answer
|
||||
namefence.go the load fences over names
|
||||
pick.go a render's named picks: the frames of the name scopes rendering, the memo a pick keeps, and the reads kept under it
|
||||
env.go the env a render reads names and rows through
|
||||
env.go renderEnv: what a render reads names and rows through
|
||||
path.go the dotted-path walks, and proving a path resolves
|
||||
render.go Fake, the recursive renderer (choices, format strings, expansions) and the reads its tokens make
|
||||
record.go records: Record, the JSON/CSV/SQL serializers, and their entry points
|
||||
struct.go structs: FakeStruct, fake tags as one record's columns, and a field's Go type as its column's datatype
|
||||
inline.go inline templates: Template, NewTemplate, FakeTemplate, IsTemplate, and their compile and resolve
|
||||
format.go a format's tokens checked against their fields, and compiled
|
||||
reference.go resolving references across the tree
|
||||
reference.go one reference's resolution: its sigil, its category, its tail
|
||||
resolve.go resolving templates against the assembled tree in steps, each over every template before the next
|
||||
graph.go the node graph: tree walks, containment and render edges, and the cycle and repeat fences
|
||||
calc.go the {calc()} operand checks: never a number, and a constant zero divisor
|
||||
|
||||
+6
-6
@@ -145,7 +145,7 @@ func TestBuiltinArgLimits(t *testing.T) {
|
||||
`{"format":"{x}","x":"1","repeat":2000000000}`, // repeat -> multi-GB join
|
||||
}
|
||||
for _, tmpl := range bad {
|
||||
if _, err := linked(t, tmpl); err == nil {
|
||||
if _, err := resolved(t, tmpl); err == nil {
|
||||
t.Errorf("compile(%s) = nil error, want a limit rejection", tmpl)
|
||||
}
|
||||
}
|
||||
@@ -204,12 +204,12 @@ func mustPanic(t *testing.T, name string, call func()) {
|
||||
|
||||
func TestClassBuiltinArgs(t *testing.T) {
|
||||
for _, bad := range []string{`"{digits(0)}"`, `"{digits(2000000000)}"`, `"{upper(-1)}"`, `"{lower(x)}"`, `"{digits()}"`, `"{upper(1,2)}"`} {
|
||||
if _, err := linked(t, bad); err == nil {
|
||||
if _, err := resolved(t, bad); err == nil {
|
||||
t.Errorf("compile(%s) = nil error, want the arg rejected", bad)
|
||||
}
|
||||
}
|
||||
for _, ok := range []string{`"{digits(1048576)}"`, `"{upper(1)}"`, `"{lower(26)}"`} {
|
||||
if _, err := linked(t, ok); err != nil {
|
||||
if _, err := resolved(t, ok); err != nil {
|
||||
t.Errorf("compile(%s) = %v", ok, err)
|
||||
}
|
||||
}
|
||||
@@ -291,7 +291,7 @@ func TestBuiltinDateArgs(t *testing.T) {
|
||||
`"{time('x')}"`: "text",
|
||||
`"{date(1990-01-01,1990-12-31,'15:04')}"`: "time('15:04')",
|
||||
} {
|
||||
_, err := linked(t, tmpl)
|
||||
_, err := resolved(t, tmpl)
|
||||
if err == nil || !strings.Contains(err.Error(), want) {
|
||||
t.Errorf("compile(%s) = %v, want an error mentioning %q", tmpl, err, want)
|
||||
}
|
||||
@@ -405,7 +405,7 @@ func TestBuiltinCompileErrors(t *testing.T) {
|
||||
`"{iban(US)}"`, // unsupported country
|
||||
`"{seq(a,b)}"`, // seq takes at most one name
|
||||
} {
|
||||
if _, err := linked(t, bad); err == nil {
|
||||
if _, err := resolved(t, bad); err == nil {
|
||||
t.Errorf("compile(%s) = nil error, want error", bad)
|
||||
}
|
||||
}
|
||||
@@ -419,7 +419,7 @@ func TestArgErrorsNameTheSpelling(t *testing.T) {
|
||||
`"{hex(99999999999999999999)}"`: "exceeds the maximum",
|
||||
`"{int(007,9)}"`: "write 7",
|
||||
} {
|
||||
if _, err := linked(t, src); err == nil || !strings.Contains(err.Error(), want) {
|
||||
if _, err := resolved(t, src); err == nil || !strings.Contains(err.Error(), want) {
|
||||
t.Errorf("compile(%s) = %v, want an error saying %q", src, err, want)
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -204,7 +204,7 @@ func TestCalcOverANeverNumericOperandIsRejected(t *testing.T) {
|
||||
`{"format":"{calc(x * 2)}","x":["a","b"]}`: `"x"`,
|
||||
`{"format":"{calc(x + y)}","x":"1","y":"{{2}}"}`: `"{2}"`,
|
||||
} {
|
||||
_, err := linked(t, src)
|
||||
_, err := resolved(t, src)
|
||||
if err == nil || !strings.Contains(err.Error(), want) || !strings.Contains(err.Error(), "never a number") {
|
||||
t.Errorf("compile(%s) = %v, want the operand rejected naming %s", src, err, want)
|
||||
}
|
||||
@@ -215,7 +215,7 @@ func TestCalcOverANeverNumericOperandIsRejected(t *testing.T) {
|
||||
`{"format":"{calc(x * 2)}","x":["1","abc"]}`,
|
||||
`{"format":"{calc(x * 2)}","x":"{digits(2)}"}`,
|
||||
} {
|
||||
if _, err := linked(t, ok); err != nil {
|
||||
if _, err := resolved(t, ok); err != nil {
|
||||
t.Errorf("compile(%s) = %v, want it accepted", ok, err)
|
||||
}
|
||||
}
|
||||
@@ -230,7 +230,7 @@ func TestCalcConstantZeroDivisorIsRejected(t *testing.T) {
|
||||
`{"format":"{calc((a/0)+b)}","a":"1","b":"2"}`: "divides by 0",
|
||||
`{"format":"{calc(a/-0)}","a":"1"}`: "divides by -0",
|
||||
} {
|
||||
if _, err := linked(t, src); err == nil || !strings.Contains(err.Error(), want) {
|
||||
if _, err := resolved(t, src); err == nil || !strings.Contains(err.Error(), want) {
|
||||
t.Errorf("compile(%s) = %v, want the constant zero divisor rejected naming %q", src, err, want)
|
||||
}
|
||||
}
|
||||
@@ -259,7 +259,7 @@ func TestCalcCompileErrors(t *testing.T) {
|
||||
`"{calc(1, x)}"`, // decimals arg not an integer
|
||||
`"{calc(1, -1)}"`, // decimals negative
|
||||
} {
|
||||
if _, err := linked(t, bad); err == nil {
|
||||
if _, err := resolved(t, bad); err == nil {
|
||||
t.Errorf("compile(%s) = nil error, want error", bad)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func mergeChildren(dst, src map[string]node) {
|
||||
}
|
||||
|
||||
// nodeSet is the set of nodes one validation pass covers: the categories one load
|
||||
// binds, or a single inline node.
|
||||
// reads, or a single inline node.
|
||||
type nodeSet func(fn func(label string, n node) error) error
|
||||
|
||||
// categorySite is a loaded category and where it sits: the folder holding it, that
|
||||
@@ -157,10 +157,9 @@ func (p pipeline) run() error {
|
||||
return checkNodeFences(p.nodes)
|
||||
}
|
||||
|
||||
// checkNodeFences runs the per-node fences every pipeline runs over its nodes, each
|
||||
// over all of them before the next, so which of several broken nodes is reported
|
||||
// does not depend on the walk. Its walks recurse unguarded, so run refuses their cycles
|
||||
// first.
|
||||
// checkNodeFences runs the per-node fences over s, each over every node before the next,
|
||||
// so which of several broken nodes is reported does not depend on the walk. Its walks
|
||||
// recurse unguarded, so pipeline.run refuses cycles first.
|
||||
func checkNodeFences(s nodeSet) error {
|
||||
mem := renderCounts{}
|
||||
if err := s(func(label string, n node) error { return repeatCheck(label, n, mem) }); err != nil {
|
||||
|
||||
+2
-2
@@ -18,7 +18,7 @@ func TestDatatypeAndNullSitOnlyInAColumn(t *testing.T) {
|
||||
`{"format":"","age":[null,{"format":"{int(18,99)}","datatype":"integer","weight":9}]}`,
|
||||
`{"format":"","pick":[[null,"a"],"b"]}`,
|
||||
} {
|
||||
if _, err := linked(t, src); err != nil {
|
||||
if _, err := resolved(t, src); err != nil {
|
||||
t.Errorf("compile(%s) = %v, want a column to take a datatype and null", src, err)
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ func TestDatatypeAndNullSitOnlyInAColumn(t *testing.T) {
|
||||
`null`: `so write ""`,
|
||||
`{"format":"{p}","p":{"format":"{x}","x":[null,"a"]}}`: `so write ""`,
|
||||
} {
|
||||
if _, err := linked(t, src); err == nil || !strings.Contains(err.Error(), want) {
|
||||
if _, err := resolved(t, src); err == nil || !strings.Contains(err.Error(), want) {
|
||||
t.Errorf("compile(%s) = %v, want an error containing %q", src, err, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,18 +24,21 @@ package fejkdata
|
||||
// start one each, and `FakeStruct` one per record.
|
||||
// - env — what a render reads names and rows through: `renderEnv`.
|
||||
// - named pick — the draw a token such as {/person as p} binds to a name, drawn
|
||||
// on its first read and kept while its scope renders: a category, or one
|
||||
// repeat iteration: `nameBinding`, `nameScope`, `namedPick`, `pickFrame`.
|
||||
// A scope is only ever a name's.
|
||||
// on its first read and kept while its scope renders: `nameBinding`,
|
||||
// `namedPick`, `pickFrame`.
|
||||
// - scope — where a name is bound: a category, or one repeat iteration: `nameScope`.
|
||||
// - memo — what a named pick keeps its draws in, the variant drawn at each level
|
||||
// and the value each path read: `drawMemo`.
|
||||
// - pin — fixing which row of a table one path, or one named pick, uses:
|
||||
// `pinSet`, `namedPick.pins`, `table.drawStep`.
|
||||
// - family — a table and every table reaching it through a chain of parent
|
||||
// links (`table.linkParent`). Only a table links.
|
||||
// - resolve — tying each template's references and names to the merged tree:
|
||||
// links.
|
||||
// - link — a table's tie to its parent table, each row to its parent row:
|
||||
// `table.linkParent`.
|
||||
// - resolve — tying each template's references and names to the assembled tree:
|
||||
// `resolveTemplates`.
|
||||
// - head — a read's first segment, a string: `arm.head`. The node it names is
|
||||
// - head — the string a read starts from: a field's name, or a reference's
|
||||
// category path with its sigil, /sv_SE.address: `arm.head`. The node it names is
|
||||
// the read's start: `template.startOf`, `nameTarget.start`.
|
||||
// - fence — a load-time check, run over each category, template or struct as it
|
||||
// loads or compiles, so rendering a compiled tree cannot fail.
|
||||
|
||||
+1
-1
@@ -795,7 +795,7 @@ the direct chain is the one a reader can predict from the tables' parents.
|
||||
|
||||
2026-09-30, larv-review on the comprehension round the maintainer approved on 2026-09-29;
|
||||
replaces "The path walks are separate loops".
|
||||
Goals 3 and 13: `drawSteps` draws every path from its steps, compiled at resolve by
|
||||
Goals 3 and 13: `drawSteps` draws every path from its steps, compiled when templates resolve, by
|
||||
`pathCheck` or, for a caller's path, by `probePath` into a stack buffer. Both take each
|
||||
step through `takeStep`, and differ only at a choice: the check walks every variant, the
|
||||
probe the first. On 2026-09-29 one loop over all three walks, switching on a mode field,
|
||||
|
||||
@@ -43,7 +43,7 @@ func (env renderEnv) at(n node, pins *pinSet) renderEnv {
|
||||
// rowOf is the row of t its columns render from.
|
||||
func (env renderEnv) rowOf(t *table) int {
|
||||
if env.row.t != t {
|
||||
panic(invariant.Broken("a column of %s renders in a scope holding no row of it", t.rows.Segment()))
|
||||
panic(invariant.Broken("a column of %s renders in an env holding no row of it", t.rows.Segment()))
|
||||
}
|
||||
return env.row.index
|
||||
}
|
||||
|
||||
+4
-4
@@ -111,8 +111,8 @@ func parse(t *testing.T, s string) any {
|
||||
return v
|
||||
}
|
||||
|
||||
// linked compiles a JSON fragment and links it against no data.
|
||||
func linked(t *testing.T, s string) (node, error) {
|
||||
// resolved compiles a JSON fragment and resolves it against no data.
|
||||
func resolved(t *testing.T, s string) (node, error) {
|
||||
t.Helper()
|
||||
n, err := compile(parse(t, s))
|
||||
if err != nil {
|
||||
@@ -121,10 +121,10 @@ func linked(t *testing.T, s string) (node, error) {
|
||||
return n, resolveInlineTemplates(inlineNodes(n, "template"), nil)
|
||||
}
|
||||
|
||||
// compiled parses, compiles and links a JSON fragment into a node.
|
||||
// compiled parses, compiles and resolves a JSON fragment into a node.
|
||||
func compiled(t *testing.T, s string) node {
|
||||
t.Helper()
|
||||
n, err := linked(t, s)
|
||||
n, err := resolved(t, s)
|
||||
if err != nil {
|
||||
t.Fatalf("compile %q: %v", s, err)
|
||||
}
|
||||
|
||||
@@ -120,8 +120,7 @@ func checkBind(t grammar.Token, fields map[string]node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkBound proves what a binding names is a reference or a path into a field of the binding
|
||||
// template.
|
||||
// checkBound proves what a binding binds is a reference or a path into a field of its binder.
|
||||
func checkBound(ref string, fields map[string]node) error {
|
||||
if grammar.IsRef(ref) {
|
||||
_, _, err := grammar.RefShape(ref)
|
||||
@@ -190,7 +189,7 @@ func checkNoRepeatedArm(body string, names []string) error {
|
||||
seen := make(map[string]bool, len(names))
|
||||
for _, name := range names {
|
||||
if seen[name] {
|
||||
return fmt.Errorf("token {%s}: arm %q is repeated; an alternation picks its arms evenly, so skew the odds with a choice's weights instead", body, name)
|
||||
return fmt.Errorf("token {%s}: arm %q is repeated; an alternation draws its arms evenly, so skew the odds with a choice's weights instead", body, name)
|
||||
}
|
||||
seen[name] = true
|
||||
}
|
||||
@@ -223,17 +222,17 @@ const (
|
||||
// resolveRefs resolved it to; before that, a reference is whole.
|
||||
func splitArm(name string, refs map[string]resolvedRef) arm {
|
||||
if grammar.IsRef(name) {
|
||||
b, linked := refs[name]
|
||||
if !linked || len(b.tail) == 0 {
|
||||
r, resolved := refs[name]
|
||||
if !resolved || len(r.tail) == 0 {
|
||||
head := name
|
||||
if linked {
|
||||
head = b.head
|
||||
if resolved {
|
||||
head = r.head
|
||||
}
|
||||
return arm{spelling: name, head: head, levels: []pickKey{pickKey(head)}}
|
||||
}
|
||||
sigil, rest, _ := grammar.RefShape(name) // resolveRefs proved it, and took b.tail as a suffix of its segments
|
||||
sigil, rest, _ := grammar.RefShape(name) // resolveRefs proved it, and took r.tail as a suffix of its segments
|
||||
written, _ := grammar.SplitPath(rest)
|
||||
return pathArm(name, b.head, sigil+grammar.JoinSegments(written[:len(written)-len(b.tail)]), b.tail)
|
||||
return pathArm(name, r.head, sigil+grammar.JoinSegments(written[:len(written)-len(r.tail)]), r.tail)
|
||||
}
|
||||
segs, err := grammar.SplitPath(name)
|
||||
if err != nil || len(segs) == 1 {
|
||||
@@ -330,7 +329,7 @@ func (t *template) compileArm(name string, targets map[*nameBinding]nameTarget)
|
||||
if !t.isName(a.head) {
|
||||
start := t.startOf(a.head)
|
||||
if start == nil {
|
||||
panic(invariant.Broken("{%s} reads a head nothing bound", a.spelling))
|
||||
panic(invariant.Broken("{%s} reads a head nothing resolved", a.spelling))
|
||||
}
|
||||
w := compilePath(start, a.tail)
|
||||
a.steps, a.leaves = w.steps, w.leaves
|
||||
|
||||
+3
-3
@@ -20,7 +20,7 @@ func TestStringIsAFormat(t *testing.T) {
|
||||
if got := mustRender(t, f, `"{digits(3)}"`); !regexp.MustCompile(`^[0-9]{3}$`).MatchString(got) {
|
||||
t.Errorf(`render("{digits(3)}") = %q, want three digits`, got)
|
||||
}
|
||||
if _, err := linked(t, `"{x}"`); err == nil || !strings.Contains(err.Error(), `no field "x"`) {
|
||||
if _, err := resolved(t, `"{x}"`); err == nil || !strings.Contains(err.Error(), `no field "x"`) {
|
||||
t.Errorf(`compile("{x}") = %v, want a no-field error`, err)
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ func TestBraceEscapes(t *testing.T) {
|
||||
`{"format":"{a{b}","a":"Q"}`: "'{'",
|
||||
`"{x"`: "unterminated",
|
||||
} {
|
||||
if _, err := linked(t, src); err == nil || !strings.Contains(err.Error(), want) {
|
||||
if _, err := resolved(t, src); err == nil || !strings.Contains(err.Error(), want) {
|
||||
t.Errorf("compile(%s) = %v, want an error mentioning %s", src, err, want)
|
||||
}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ func TestFormatCompileErrors(t *testing.T) {
|
||||
`"{int(1)}"`, // wrong arity
|
||||
`"{float(1,2)}"`, // wrong arity
|
||||
} {
|
||||
if _, err := linked(t, bad); err == nil {
|
||||
if _, err := resolved(t, bad); err == nil {
|
||||
t.Errorf("compile(%s) = nil error, want error", bad)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,18 +183,18 @@ func checkNoCycles(s nodeSet) error {
|
||||
grey = 1
|
||||
black = 2
|
||||
)
|
||||
inScope := map[node]bool{}
|
||||
inSet := map[node]bool{}
|
||||
_ = s(func(_ string, n node) error {
|
||||
inScope[n] = true
|
||||
inSet[n] = true
|
||||
if t, isTable := n.(*table); isTable {
|
||||
inScope[t.rowNode] = true // a selector lands on it, and contained lists it nowhere
|
||||
inSet[t.rowNode] = true // a selector lands on it, and contained lists it nowhere
|
||||
}
|
||||
return nil
|
||||
})
|
||||
color := map[node]int{}
|
||||
var visit func(n node, label string) error
|
||||
visit = func(n node, label string) error {
|
||||
if !inScope[n] {
|
||||
if !inSet[n] {
|
||||
return nil // loaded before these nodes and proven then, so it never reaches back into it
|
||||
}
|
||||
switch color[n] {
|
||||
|
||||
@@ -92,7 +92,7 @@ func bindNames(root node) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
read, err := resolveReads(root, scopes)
|
||||
read, err := answerReads(root, scopes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -165,16 +165,16 @@ func eachContained(n node, where string, fn func(c node, where string) error) er
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveReads answers each unbound read under root with a name its template sees, and returns
|
||||
// the set of bindings some read answers to. It resolves a template's fields before its format, the
|
||||
// answerReads answers each unbound read under root with a name its template sees, and returns
|
||||
// the set of bindings some read answers to. It answers a template's fields before its format, the
|
||||
// order compile reports in, and refuses a field spelling a name. A refusal searches scopes, every
|
||||
// scope of the category, for a name bound where the read cannot see it.
|
||||
func resolveReads(root node, scopes []*nameScope) (map[*nameBinding]bool, error) {
|
||||
func answerReads(root node, scopes []*nameScope) (map[*nameBinding]bool, error) {
|
||||
read := map[*nameBinding]bool{}
|
||||
var resolve func(n node, where string) error
|
||||
resolve = func(n node, where string) error {
|
||||
var answer func(n node, where string) error
|
||||
answer = func(n node, where string) error {
|
||||
t, isTemplate := n.(*template)
|
||||
if err := eachContained(n, where, resolve); err != nil {
|
||||
if err := eachContained(n, where, answer); err != nil {
|
||||
return err
|
||||
}
|
||||
if !isTemplate {
|
||||
@@ -192,18 +192,18 @@ func resolveReads(root node, scopes []*nameScope) (map[*nameBinding]bool, error)
|
||||
for _, u := range t.unbound {
|
||||
b := t.nameScope.lookup(u.head)
|
||||
if b == nil {
|
||||
return unresolved(where, u, t.nameScope, scopes)
|
||||
return unanswered(where, u, t.nameScope, scopes)
|
||||
}
|
||||
read[b] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return read, resolve(root, "")
|
||||
return read, answer(root, "")
|
||||
}
|
||||
|
||||
// unresolved is the refusal of u, a read no field or name answers: naming the repeat binding the
|
||||
// unanswered is the refusal of u, a read no field or name answers: naming the repeat binding the
|
||||
// name where the read cannot see it, else the names the read sees.
|
||||
func unresolved(where string, u unboundRead, seen *nameScope, scopes []*nameScope) error {
|
||||
func unanswered(where string, u unboundRead, seen *nameScope, scopes []*nameScope) error {
|
||||
for _, sc := range scopes {
|
||||
if b, ok := sc.bindings[u.head]; ok {
|
||||
at := strings.TrimSuffix(b.where, ": ")
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ func TestANameKeepsWhatItsPathsAddress(t *testing.T) {
|
||||
differ = differ || got[0][0] != got[0][2]
|
||||
}
|
||||
if !differ {
|
||||
t.Fatal("{w}-{w} under a name drew w once in 100 renders, want each {w} a pick of its own")
|
||||
t.Fatal("{w}-{w} under a name drew w once in 100 renders, want each {w} a draw of its own")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ func (*nullItem) isNode() {}
|
||||
// template renders a format string, substituting {tokens} from fields. A bare
|
||||
// JSON string is a template with no fields. repeat (default 1) renders that format
|
||||
// that many times and joins the results with separator (default ""), each render
|
||||
// an independent pick. Every format compiles in `resolveTemplates`.
|
||||
// an independent draw. Every format compiles in `resolveTemplates`.
|
||||
type template struct {
|
||||
// Filled by `compileString`, `compileTemplate` and `table.compileRowFormat`:
|
||||
format string
|
||||
@@ -80,7 +80,7 @@ type templateRefs struct {
|
||||
|
||||
func (*template) isNode() {}
|
||||
|
||||
// startOf is the node an arm's startOf names: a sibling field, or a reference's category.
|
||||
// startOf is the node an arm's head names: a sibling field, or a reference's category.
|
||||
func (t *template) startOf(name string) node {
|
||||
if grammar.IsRef(name) {
|
||||
return t.refs.categories[name]
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func TestWeightZeroIsRejected(t *testing.T) {
|
||||
_, err := linked(t, `[{"format":"X","weight":0},"Y"]`)
|
||||
_, err := resolved(t, `[{"format":"X","weight":0},"Y"]`)
|
||||
if err == nil || !strings.Contains(err.Error(), "weight 0") || !strings.Contains(err.Error(), "remove") {
|
||||
t.Fatalf("compile(weight 0) = %v, want it rejected naming the fix", err)
|
||||
}
|
||||
@@ -75,7 +75,7 @@ func TestNodeCompileErrors(t *testing.T) {
|
||||
`{"format":"x","repeat":2,"separator":5}`, // non-string separator
|
||||
`{"format":"{x}","x":"v","repeat":2,"separator":""}`, // separator "" is the default
|
||||
} {
|
||||
if _, err := linked(t, bad); err == nil {
|
||||
if _, err := resolved(t, bad); err == nil {
|
||||
t.Errorf("compile(%s) = nil error, want error", bad)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
func TestOneItemChoiceIsRejected(t *testing.T) {
|
||||
for _, src := range []string{`["x"]`, `[{"format":"{d}","d":"x"}]`, `{"format":"{w}","w":["only"]}`} {
|
||||
if _, err := linked(t, src); err == nil || !strings.Contains(err.Error(), "one-item choice") {
|
||||
if _, err := resolved(t, src); err == nil || !strings.Contains(err.Error(), "one-item choice") {
|
||||
t.Errorf("compile(%s) = %v, want the one-item choice rejected", src, err)
|
||||
}
|
||||
}
|
||||
@@ -20,11 +20,11 @@ func TestRepeatedChoiceItemIsRejected(t *testing.T) {
|
||||
`{"format":"{w}","w":["", "", "x"]}`: `{ "format": "", "weight": 2 }`,
|
||||
`{"format":"","w":[null,null,"a"]}`: "a null takes no weight",
|
||||
} {
|
||||
if _, err := linked(t, src); err == nil || !strings.Contains(err.Error(), want) {
|
||||
if _, err := resolved(t, src); err == nil || !strings.Contains(err.Error(), want) {
|
||||
t.Errorf("compile(%s) = %v, want an error naming %s", src, err, want)
|
||||
}
|
||||
}
|
||||
if _, err := linked(t, `[{"format":"a","weight":2}, "b"]`); err != nil {
|
||||
if _, err := resolved(t, `[{"format":"a","weight":2}, "b"]`); err != nil {
|
||||
t.Errorf("compile(weighted a, b) = %v", err)
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ func TestInertObjectIsRejected(t *testing.T) {
|
||||
`{"format":"{x}","x":"v","repeat":2,"separator":""}`: "default",
|
||||
`{"format":"","n":{"format":"1","datatype":"string"}}`: `datatype "string" is the default`,
|
||||
} {
|
||||
if _, err := linked(t, src); err == nil || !strings.Contains(err.Error(), want) {
|
||||
if _, err := resolved(t, src); err == nil || !strings.Contains(err.Error(), want) {
|
||||
t.Errorf("compile(%s) = %v, want an error mentioning %s", src, err, want)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -142,7 +142,7 @@ func TestPathKeyIsUnambiguous(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestMissingFieldNamesItself keeps the precise diagnosis for the ordinary typo: a
|
||||
// single-variant choice always picks the same item, so it needs no every-variant
|
||||
// single-variant choice always draws the same item, so it needs no every-variant
|
||||
// guard and the error can name the field that is missing.
|
||||
func TestMissingFieldNamesItself(t *testing.T) {
|
||||
f := newGenerator(t, "data", WithSeed(1))
|
||||
|
||||
+2
-2
@@ -55,7 +55,7 @@ func TestNoRenderAllocRegression(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The repeat shape prices what keeps a render's scope off the heap: a copy dropped costs an alloc an iteration.
|
||||
// The repeat shape prices what keeps a render's env off the heap: a copy dropped costs an alloc an iteration.
|
||||
func TestNoReferenceAllocRegression(t *testing.T) {
|
||||
word := `{"format":"{w}","w":["alpha","beta","gamma","delta"]}`
|
||||
for _, s := range []struct {
|
||||
@@ -74,7 +74,7 @@ func TestNoReferenceAllocRegression(t *testing.T) {
|
||||
t.Fatalf("New(%s): %v", s.name, err)
|
||||
}
|
||||
if allocs := testing.AllocsPerRun(10000, func() { f.Fake("x") }); allocs > s.base*s.slack {
|
||||
t.Errorf("%s: %.1f allocs/op regressed past %.1f (baseline %.1f × %.2f); a render's scope reaching the heap is the usual cause", s.name, allocs, s.base*s.slack, s.base, s.slack)
|
||||
t.Errorf("%s: %.1f allocs/op regressed past %.1f (baseline %.1f × %.2f); a render's env reaching the heap is the usual cause", s.name, allocs, s.base*s.slack, s.base, s.slack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -26,7 +26,7 @@ func varies(t *testing.T, f *Generator, src string, differ func(got string) bool
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Fatalf("400 renders of %s never drew apart, want each {…} a pick of its own", src)
|
||||
t.Fatalf("400 renders of %s never drew apart, want each {…} a draw of its own", src)
|
||||
}
|
||||
|
||||
func TestEveryTokenDrawsAfresh(t *testing.T) {
|
||||
@@ -57,7 +57,7 @@ func TestEveryReferenceDrawsAfresh(t *testing.T) {
|
||||
differ = p[0] != p[1]
|
||||
}
|
||||
if !differ {
|
||||
t.Errorf("400 renders of %s never drew apart, want each reference a pick of its own", src)
|
||||
t.Errorf("400 renders of %s never drew apart, want each reference a draw of its own", src)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,7 +124,7 @@ func TestAFieldBindingIsRefusedWhereNoFieldIs(t *testing.T) {
|
||||
`{"format":"{place as p}{calc(p * 2)}","place":["1","2"]}`: `write place where it is read`,
|
||||
`{"format":"{place as place}{place}{place}","place":["x","y"]}`: `name "place" is a field of the root template too`,
|
||||
} {
|
||||
if _, err := linked(t, src); err == nil || !strings.Contains(err.Error(), want) {
|
||||
if _, err := resolved(t, src); err == nil || !strings.Contains(err.Error(), want) {
|
||||
t.Errorf("%s = %v, want it refused naming %s", src, err, want)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ func TestReferenceCombinesLoadedPaths(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestReferenceChain follows a reference to a node that is itself a reference, so
|
||||
// linking order cannot matter.
|
||||
// resolve order cannot matter.
|
||||
func TestReferenceChain(t *testing.T) {
|
||||
dir := writeData(t, map[string]string{
|
||||
"a": `"{/b}"`,
|
||||
|
||||
@@ -89,7 +89,7 @@ func render(s *drawstate.State, n node, env renderEnv) string {
|
||||
}
|
||||
|
||||
// renderRepeat renders each iteration of t inside the name scopes rendering t, where a name bound
|
||||
// outside t keeps its pick, and a name t binds picks again.
|
||||
// outside t keeps its pick, and a name t binds takes a fresh pick.
|
||||
func renderRepeat(s *drawstate.State, t *template, env renderEnv) string {
|
||||
var b strings.Builder
|
||||
b.Grow(t.repeat * (t.compiled.grow + len(t.separator)))
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ func newShippedWhole(t testing.TB, opts ...Option) *Generator {
|
||||
return f
|
||||
}
|
||||
|
||||
// loadedCategories is the dot path of every category f has parsed and linked.
|
||||
// loadedCategories is the dot path of every category f has parsed and resolved.
|
||||
func loadedCategories(f *Generator) []string {
|
||||
var out []string
|
||||
for _, s := range categorySites(&f.root) {
|
||||
|
||||
+2
-2
@@ -1105,8 +1105,8 @@ func TestAStepSitsAtTheSegmentItConsumes(t *testing.T) {
|
||||
"region[12].name",
|
||||
} {
|
||||
a := splitArm(path, nil)
|
||||
head := f.root.children[a.head].(*table)
|
||||
a.steps = compilePath(head, a.tail).steps
|
||||
start := f.root.children[a.head].(*table)
|
||||
a.steps = compilePath(start, a.tail).steps
|
||||
for _, st := range a.steps {
|
||||
seg := ""
|
||||
switch st.kind {
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/larvit/fejkdata/internal/proven"
|
||||
)
|
||||
|
||||
// valueProof proves what typed columns and their calc operands hold, each node once per nodeSet.
|
||||
// valueProof proves what typed columns and their calc operands hold, each node once per proof.
|
||||
type valueProof struct {
|
||||
memo map[node]proven.Facts
|
||||
columnMemo map[node]proven.Facts
|
||||
|
||||
Reference in New Issue
Block a user