Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33
@@ -961,20 +961,20 @@ 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
|
name.go names: {x as n} bindings, the scopes they live in, and the reads they answer
|
||||||
namefence.go the load fences over names
|
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
|
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
|
||||||
scope.go the scope a render reads names and rows through
|
env.go the env a render reads names and rows through
|
||||||
path.go the dotted-path walks, and proving a path resolves
|
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
|
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
|
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
|
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 bind
|
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
|
format.go a format's tokens checked against their fields, and compiled
|
||||||
reference.go resolving references across the tree
|
reference.go resolving references across the tree
|
||||||
link.go linking templates to the assembled tree in steps, each over every template before the next
|
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
|
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
|
calc.go the {calc()} operand checks: never a number, and a constant zero divisor
|
||||||
datatype.go column datatypes: DataType, where datatype and null may sit, a column's datatype
|
datatype.go column datatypes: DataType, where datatype and null may sit, a column's datatype
|
||||||
valueproof.go the value proof: what a typed column, calc operand or struct field holds, checked at load
|
valueproof.go the value proof: what a typed column, calc operand or struct field holds, checked at load
|
||||||
data.go the load path: the categories internal/datafiles hands over, compiled into one merged tree, and the bind pipeline every entry point runs
|
data.go the load path: the categories internal/datafiles hands over, compiled into one merged tree, and the load pipeline every entry point runs
|
||||||
cmd/fejkdata/ the fejkdata CLI
|
cmd/fejkdata/ the fejkdata CLI
|
||||||
internal/builtinfunc/ the {name()} functions: their checks and draws, checksums, date and time layouts, transforms and calc arithmetic
|
internal/builtinfunc/ the {name()} functions: their checks and draws, checksums, date and time layouts, transforms and calc arithmetic
|
||||||
internal/datafiles/ the walk of a data tree: its folders, its category files and the rows files beside them
|
internal/datafiles/ the walk of a data tree: its folders, its category files and the rows files beside them
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// checkCalcFields runs checkOperands over the calc operands fields hold. An operand no field
|
// checkCalcFields runs checkOperands over the calc operands fields hold. An operand no field
|
||||||
// holds is left for a name, which checkCalcNames checks once names link.
|
// holds is left for a name, which checkCalcNames checks once names resolve.
|
||||||
func checkCalcFields(args []string, fields map[string]node) error {
|
func checkCalcFields(args []string, fields map[string]node) error {
|
||||||
return checkOperands(args[0], builtinfunc.ParsedCalc(args[0]), func(name string) []node {
|
return checkOperands(args[0], builtinfunc.ParsedCalc(args[0]), func(name string) []node {
|
||||||
if n, ok := fields[name]; ok {
|
if n, ok := fields[name]; ok {
|
||||||
@@ -35,7 +35,7 @@ func checkCalcNames(label string, t *template) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// operandNodes lists every node each operand of o may render, once its template and names link.
|
// operandNodes lists every node each operand of o may render, once its template and names resolve.
|
||||||
func operandNodes(o op) func(name string) []node {
|
func operandNodes(o op) func(name string) []node {
|
||||||
return func(name string) []node {
|
return func(name string) []node {
|
||||||
a := o.operands[slices.IndexFunc(o.operands, func(a arm) bool { return a.head == name })]
|
a := o.operands[slices.IndexFunc(o.operands, func(a arm) bool { return a.head == name })]
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func loadData(sources []datafiles.Source) (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")
|
||||||
}
|
}
|
||||||
if err := categoryBinding(categorySites(&folder{children: root}), root).bind(); err != nil {
|
if err := categoryPipeline(categorySites(&folder{children: root}), root).run(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return root, nil
|
return root, nil
|
||||||
@@ -66,9 +66,9 @@ func mergeChildren(dst, src map[string]node) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// nodeScope is the set of nodes one validation pass covers: the categories one load
|
// nodeSet is the set of nodes one validation pass covers: the categories one load
|
||||||
// binds, or a single inline node.
|
// binds, or a single inline node.
|
||||||
type nodeScope func(fn func(label string, n node) error) error
|
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
|
// categorySite is a loaded category and where it sits: the folder holding it, that
|
||||||
// folder's path, and its own dot path.
|
// folder's path, and its own dot path.
|
||||||
@@ -103,7 +103,7 @@ func siteIn(dir []string, in *folder, name string) categorySite {
|
|||||||
// categoryPath is the dot path of the category name in the folder dir.
|
// categoryPath is the dot path of the category name in the folder dir.
|
||||||
func categoryPath(dir []string, name string) string { return join(strings.Join(dir, "."), name) }
|
func categoryPath(dir []string, name string) string { return join(strings.Join(dir, "."), name) }
|
||||||
|
|
||||||
func sitesScope(sites []categorySite) nodeScope {
|
func siteNodes(sites []categorySite) nodeSet {
|
||||||
return func(fn func(label string, n node) error) error {
|
return func(fn func(label string, n node) error) error {
|
||||||
for _, s := range sites {
|
for _, s := range sites {
|
||||||
if err := eachNode(s.n, s.path, fn); err != nil {
|
if err := eachNode(s.n, s.path, fn); err != nil {
|
||||||
@@ -114,54 +114,54 @@ func sitesScope(sites []categorySite) nodeScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func inlineScope(n node, label string) nodeScope {
|
func inlineNodes(n node, label string) nodeSet {
|
||||||
return func(fn func(label string, m node) error) error { return eachNode(n, label, fn) }
|
return func(fn func(label string, m node) error) error { return eachNode(n, label, fn) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// binding is one scope's way through bind, the one load pipeline; its fields are where
|
// pipeline is the one load pipeline, run over one nodeSet; its fields are where
|
||||||
// scopes differ.
|
// entry points differ.
|
||||||
type binding struct {
|
type pipeline struct {
|
||||||
scope nodeScope
|
nodes nodeSet
|
||||||
link func() error
|
resolve func() error
|
||||||
// Set by a caller that proves the columns against their Go types itself.
|
// Set by a caller that proves the columns against their Go types itself.
|
||||||
typedByGo bool
|
typedByGo bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// categoryBinding binds the categories of one load, their references resolving
|
// categoryPipeline is the pipeline over the categories of one load, their references
|
||||||
// against root.
|
// resolving against root.
|
||||||
func categoryBinding(sites []categorySite, root map[string]node) binding {
|
func categoryPipeline(sites []categorySite, root map[string]node) pipeline {
|
||||||
return binding{
|
return pipeline{
|
||||||
scope: sitesScope(sites),
|
nodes: siteNodes(sites),
|
||||||
link: func() error {
|
resolve: func() error {
|
||||||
if err := linkTables(sites); err != nil {
|
if err := linkTables(sites); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return linkCategories(sites, root)
|
return resolveCategoryTemplates(sites, root)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b binding) bind() error {
|
func (p pipeline) run() error {
|
||||||
if err := b.link(); err != nil {
|
if err := p.resolve(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := checkNoCycles(b.scope); err != nil {
|
if err := checkNoCycles(p.nodes); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
settleRecords(b.scope)
|
settleRecords(p.nodes)
|
||||||
if !b.typedByGo {
|
if !p.typedByGo {
|
||||||
if err := checkColumns(b.scope); err != nil {
|
if err := checkColumns(p.nodes); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return checkNodeFences(b.scope)
|
return checkNodeFences(p.nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkNodeFences runs the per-node fences every binding needs over a scope, each
|
// checkNodeFences runs the per-node fences every pipeline runs over its nodes, each
|
||||||
// over the whole scope before the next, so which of several broken nodes is reported
|
// 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 bind refuses the scope's cycles
|
// does not depend on the walk. Its walks recurse unguarded, so run refuses their cycles
|
||||||
// first.
|
// first.
|
||||||
func checkNodeFences(s nodeScope) error {
|
func checkNodeFences(s nodeSet) error {
|
||||||
mem := renderCounts{}
|
mem := renderCounts{}
|
||||||
if err := s(func(label string, n node) error { return repeatCheck(label, n, mem) }); err != nil {
|
if err := s(func(label string, n node) error { return repeatCheck(label, n, mem) }); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
+1
-1
@@ -57,7 +57,7 @@ func datatypeOf(m map[string]any, pos position) (DataType, error) {
|
|||||||
|
|
||||||
// checkColumns rejects a record column whose items hold different datatypes, checking a column
|
// checkColumns rejects a record column whose items hold different datatypes, checking a column
|
||||||
// after the columns its items read, so a column read is named before its readers.
|
// after the columns its items read, so a column read is named before its readers.
|
||||||
func checkColumns(s nodeScope) error {
|
func checkColumns(s nodeSet) error {
|
||||||
checked := map[node]bool{}
|
checked := map[node]bool{}
|
||||||
var check func(label, name string, column node) error
|
var check func(label, name string, column node) error
|
||||||
check = func(label, name string, column node) error {
|
check = func(label, name string, column node) error {
|
||||||
|
|||||||
@@ -16,20 +16,26 @@ package fejkdata
|
|||||||
|
|
||||||
// Vocabulary
|
// Vocabulary
|
||||||
//
|
//
|
||||||
// - draw — one pick from a choice, or one row taken from a table: `pick`,
|
// - draw — one item taken from a choice, or one row from a table: `drawItem`,
|
||||||
// `resolveChoice`, `renderScope.drawRowOf`. Every {…} draws afresh, bar a read of a name.
|
// `drawThroughChoices`, `renderEnv.drawRowOf`. Every {…} draws afresh, bar a read of a name.
|
||||||
// - expansion — one render of one format: `expand`.
|
// - expansion — one render of one format: `expand`.
|
||||||
// - render — one walk of the tree from an entry point, over one `frameStack`:
|
// - render — one walk of the tree from an entry point, over one `frameStack`:
|
||||||
// `Generator.Fake`, `FakeRecord`, `Template.Fake` and `RecordTemplate.Fake`
|
// `Generator.Fake`, `FakeRecord`, `Template.Fake` and `RecordTemplate.Fake`
|
||||||
// start one each, and `FakeStruct` one per record.
|
// 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
|
// - 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
|
// on its first read and kept while its scope renders: a category, or one
|
||||||
// repeat iteration: `nameBinding`, `nameScope`, `namedPick`, `pickFrame`.
|
// repeat iteration: `nameBinding`, `nameScope`, `namedPick`, `pickFrame`.
|
||||||
|
// A scope is only ever a name's.
|
||||||
// - memo — what a named pick keeps its draws in, the variant drawn at each level
|
// - memo — what a named pick keeps its draws in, the variant drawn at each level
|
||||||
// and the value each path read: `drawMemo`.
|
// and the value each path read: `drawMemo`.
|
||||||
// - pin — fixing which row of a table one path, or one named pick, uses:
|
// - pin — fixing which row of a table one path, or one named pick, uses:
|
||||||
// `pinSet`, `namedPick.pins`, `table.drawStep`.
|
// `pinSet`, `namedPick.pins`, `table.drawStep`.
|
||||||
// - family — a table and every table reaching it through a chain of parent
|
// - family — a table and every table reaching it through a chain of parent
|
||||||
// links (`table.linkParent`).
|
// links (`table.linkParent`). Only a table links.
|
||||||
|
// - resolve — tying each template's references and names to the merged tree:
|
||||||
|
// `resolveTemplates`.
|
||||||
|
// - head — a read's first segment, a string: `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
|
// - fence — a load-time check, run over each category, template or struct as it
|
||||||
// loads or compiles, so rendering a compiled tree cannot fail.
|
// loads or compiles, so rendering a compiled tree cannot fail.
|
||||||
|
|||||||
+2
-2
@@ -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;
|
2026-09-30, larv-review on the comprehension round the maintainer approved on 2026-09-29;
|
||||||
replaces "The path walks are separate loops".
|
replaces "The path walks are separate loops".
|
||||||
Goals 3 and 13: `drawSteps` draws every path from its steps, compiled at link by
|
Goals 3 and 13: `drawSteps` draws every path from its steps, compiled at resolve by
|
||||||
`pathCheck` or, for a caller's path, by `probePath` into a stack buffer. Both take each
|
`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
|
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,
|
probe the first. On 2026-09-29 one loop over all three walks, switching on a mode field,
|
||||||
@@ -839,7 +839,7 @@ Last scoring run, 2026-10-05, on commit bcc6858: Navigation 7.00, Locality 5.75,
|
|||||||
2026-10-05, Lilleman auf Larv. Serves goals 3.2 and 3.4. Two architect reviews found the
|
2026-10-05, Lilleman auf Larv. Serves goals 3.2 and 3.4. Two architect reviews found the
|
||||||
engine's types, `template`, `table`, `arm`, `op` and `nameBinding`, bound in one cycle
|
engine's types, `template`, `table`, `arm`, `op` and `nameBinding`, bound in one cycle
|
||||||
because several passes each write part of the same structs; the template language's
|
because several passes each write part of the same structs; the template language's
|
||||||
recursion reads few of those types. Moving the engine while several link passes still
|
recursion reads few of those types. Moving the engine while several resolve passes still
|
||||||
wrote each value would have spread that knot across packages, behind forwarding calls from
|
wrote each value would have spread that knot across packages, behind forwarding calls from
|
||||||
every public method. The draw state, the grammar, `DataType`, what a proof knows of a
|
every public method. The draw state, the grammar, `DataType`, what a proof knows of a
|
||||||
value, the builtins, the reading of data files and a table's rows read no engine type, so
|
value, the builtins, the reading of data files and a table's rows read no engine type, so
|
||||||
|
|||||||
+12
-12
@@ -8,11 +8,11 @@ type readValue struct {
|
|||||||
null bool
|
null bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderScope is where a render reads: the frames of the name scopes rendering; and row, the row
|
// renderEnv is where a render reads: the frames of the name scopes rendering; and row, the row
|
||||||
// of the table rendering, which its columns read. It passes by value, and what is read from it
|
// of the table rendering, which its columns read. It passes by value, and what is read from it
|
||||||
// reaches a map key, an interface or a func value only as a copy; else sc, and with it every
|
// reaches a map key, an interface or a func value only as a copy; else env, and with it every
|
||||||
// render's frames, moves to the heap.
|
// render's frames, moves to the heap.
|
||||||
type renderScope struct {
|
type renderEnv struct {
|
||||||
frames *frameStack
|
frames *frameStack
|
||||||
row renderedRow
|
row renderedRow
|
||||||
// base is the depth of the frame stack when the read rendering the category entered it,
|
// base is the depth of the frame stack when the read rendering the category entered it,
|
||||||
@@ -28,22 +28,22 @@ type renderedRow struct {
|
|||||||
index int
|
index int
|
||||||
}
|
}
|
||||||
|
|
||||||
// at returns the scope n renders in, n being the leaf of a path whose rows sit in pins; where n
|
// at returns the env n renders in, n being the leaf of a path whose rows sit in pins; where n
|
||||||
// is a table's row or column, the scope carries the row pins holds for that table.
|
// is a table's row or column, the env carries the row pins holds for that table.
|
||||||
func (sc renderScope) at(n node, pins *pinSet) renderScope {
|
func (env renderEnv) at(n node, pins *pinSet) renderEnv {
|
||||||
switch n := n.(type) {
|
switch n := n.(type) {
|
||||||
case *tableRow:
|
case *tableRow:
|
||||||
sc.row = renderedRow{n.t, pins.MustRow(n.t.rows)}
|
env.row = renderedRow{n.t, pins.MustRow(n.t.rows)}
|
||||||
case *tableColumn:
|
case *tableColumn:
|
||||||
sc.row = renderedRow{n.t, pins.MustRow(n.t.rows)}
|
env.row = renderedRow{n.t, pins.MustRow(n.t.rows)}
|
||||||
}
|
}
|
||||||
return sc
|
return env
|
||||||
}
|
}
|
||||||
|
|
||||||
// rowOf is the row of t its columns render from.
|
// rowOf is the row of t its columns render from.
|
||||||
func (sc renderScope) rowOf(t *table) int {
|
func (env renderEnv) rowOf(t *table) int {
|
||||||
if sc.row.t != t {
|
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 a scope holding no row of it", t.rows.Segment()))
|
||||||
}
|
}
|
||||||
return sc.row.index
|
return env.row.index
|
||||||
}
|
}
|
||||||
+1
-1
@@ -118,7 +118,7 @@ func linked(t *testing.T, s string) (node, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return n, linkInline(inlineScope(n, "template"), nil)
|
return n, resolveInlineTemplates(inlineNodes(n, "template"), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// compiled parses, compiles and links a JSON fragment into a node.
|
// compiled parses, compiles and links a JSON fragment into a node.
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ func checkReads(t grammar.Token, fields map[string]node, operands bool) ([]unbou
|
|||||||
if _, _, err := grammar.RefShape(name); err != nil {
|
if _, _, err := grammar.RefShape(name); err != nil {
|
||||||
return nil, fmt.Errorf("token {%s}: %w", t.Body, err)
|
return nil, fmt.Errorf("token {%s}: %w", t.Body, err)
|
||||||
}
|
}
|
||||||
continue // resolveLink checks its target once the tree is assembled
|
continue // resolveRefs checks its target once the tree is assembled
|
||||||
}
|
}
|
||||||
missing, err := checkArm(name, fields, !operands && len(names) == 1)
|
missing, err := checkArm(name, fields, !operands && len(names) == 1)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -120,7 +120,7 @@ func checkBind(t grammar.Token, fields map[string]node) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkBound proves what a binding picks is a reference or a path into a field of the binding
|
// checkBound proves what a binding names is a reference or a path into a field of the binding
|
||||||
// template.
|
// template.
|
||||||
func checkBound(ref string, fields map[string]node) error {
|
func checkBound(ref string, fields map[string]node) error {
|
||||||
if grammar.IsRef(ref) {
|
if grammar.IsRef(ref) {
|
||||||
@@ -180,9 +180,9 @@ func tokenReads(t grammar.Token) []string {
|
|||||||
return t.Arms
|
return t.Arms
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkNoRepeatedArm rejects {a|a|b}: an alternation picks its arms evenly, so a
|
// checkNoRepeatedArm rejects {a|a|b}: an alternation draws its arms evenly, so a
|
||||||
// repeated one is a second spelling of weight. The error names the spelling that
|
// repeated one is a second spelling of weight. The error names the spelling that
|
||||||
// does skew a pick.
|
// does skew a draw.
|
||||||
func checkNoRepeatedArm(body string, names []string) error {
|
func checkNoRepeatedArm(body string, names []string) error {
|
||||||
if len(names) < 2 {
|
if len(names) < 2 {
|
||||||
return nil
|
return nil
|
||||||
@@ -197,8 +197,8 @@ func checkNoRepeatedArm(body string, names []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// arm is one alternative of a {a|b} token or one operand, split into the head
|
// arm is one alternative of a {a|b} token or one operand, split into the head,
|
||||||
// `template.head` resolves and the tail of a dotted path into it.
|
// whose node `template.startOf` finds, and the tail of a dotted path into it.
|
||||||
type arm struct {
|
type arm struct {
|
||||||
spelling string // as written, for messages
|
spelling string // as written, for messages
|
||||||
head string
|
head string
|
||||||
@@ -220,8 +220,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// splitArm splits one name into head and tail. refs maps a reference to what
|
// splitArm splits one name into head and tail. refs maps a reference to what
|
||||||
// resolveLink resolved it to; before linking, a reference is whole.
|
// resolveRefs resolved it to; before that, a reference is whole.
|
||||||
func splitArm(name string, refs map[string]refBinding) arm {
|
func splitArm(name string, refs map[string]resolvedRef) arm {
|
||||||
if grammar.IsRef(name) {
|
if grammar.IsRef(name) {
|
||||||
b, linked := refs[name]
|
b, linked := refs[name]
|
||||||
if !linked || len(b.tail) == 0 {
|
if !linked || len(b.tail) == 0 {
|
||||||
@@ -231,7 +231,7 @@ func splitArm(name string, refs map[string]refBinding) arm {
|
|||||||
}
|
}
|
||||||
return arm{spelling: name, head: head, levels: []pickKey{pickKey(head)}}
|
return arm{spelling: name, head: head, levels: []pickKey{pickKey(head)}}
|
||||||
}
|
}
|
||||||
sigil, rest, _ := grammar.RefShape(name) // resolveLink proved it, and took b.tail as a suffix of its segments
|
sigil, rest, _ := grammar.RefShape(name) // resolveRefs proved it, and took b.tail as a suffix of its segments
|
||||||
written, _ := grammar.SplitPath(rest)
|
written, _ := grammar.SplitPath(rest)
|
||||||
return pathArm(name, b.head, sigil+grammar.JoinSegments(written[:len(written)-len(b.tail)]), b.tail)
|
return pathArm(name, b.head, sigil+grammar.JoinSegments(written[:len(written)-len(b.tail)]), b.tail)
|
||||||
}
|
}
|
||||||
@@ -282,8 +282,8 @@ type formatOps struct {
|
|||||||
grow int
|
grow int
|
||||||
}
|
}
|
||||||
|
|
||||||
// compileFormat compiles t's parsed format against its link and its names' targets; a {ref as n}
|
// compileFormat compiles t's parsed format against its resolved references and its names'
|
||||||
// token compiles to no op. checkTokens proved every token valid.
|
// targets; a {ref as n} token compiles to no op. checkTokens proved every token valid.
|
||||||
func compileFormat(t *template, targets map[*nameBinding]nameTarget) (formatOps, error) {
|
func compileFormat(t *template, targets map[*nameBinding]nameTarget) (formatOps, error) {
|
||||||
var c formatOps
|
var c formatOps
|
||||||
for _, tok := range t.tokens {
|
for _, tok := range t.tokens {
|
||||||
@@ -326,13 +326,13 @@ func (t *template) compileArms(names []string, targets map[*nameBinding]nameTarg
|
|||||||
// compileArm compiles one read into a path: from the head it names, or, for a read through a
|
// compileArm compiles one read into a path: from the head it names, or, for a read through a
|
||||||
// name, from what that name binds.
|
// name, from what that name binds.
|
||||||
func (t *template) compileArm(name string, targets map[*nameBinding]nameTarget) (arm, error) {
|
func (t *template) compileArm(name string, targets map[*nameBinding]nameTarget) (arm, error) {
|
||||||
a := splitArm(name, t.link.refs)
|
a := splitArm(name, t.refs.byName)
|
||||||
if !t.isName(a.head) {
|
if !t.isName(a.head) {
|
||||||
head := t.head(a.head)
|
start := t.startOf(a.head)
|
||||||
if head == nil {
|
if start == nil {
|
||||||
panic(invariant.Broken("{%s} reads a head nothing bound", a.spelling))
|
panic(invariant.Broken("{%s} reads a head nothing bound", a.spelling))
|
||||||
}
|
}
|
||||||
w := compilePath(head, a.tail)
|
w := compilePath(start, a.tail)
|
||||||
a.steps, a.leaves = w.steps, w.leaves
|
a.steps, a.leaves = w.steps, w.leaves
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
@@ -342,10 +342,10 @@ func (t *template) compileArm(name string, targets map[*nameBinding]nameTarget)
|
|||||||
}
|
}
|
||||||
target := targets[b]
|
target := targets[b]
|
||||||
full := append(target.tail[:len(target.tail):len(target.tail)], a.tail...)
|
full := append(target.tail[:len(target.tail):len(target.tail)], a.tail...)
|
||||||
if err := checkPathResolves(target.head, full, a.head); err != nil {
|
if err := checkPathResolves(target.start, full, a.head); err != nil {
|
||||||
return a, err
|
return a, err
|
||||||
}
|
}
|
||||||
w := compilePath(target.head, full)
|
w := compilePath(target.start, full)
|
||||||
a.kind, a.named, a.steps, a.leaves = namedRead, b, w.steps, w.leaves
|
a.kind, a.named, a.steps, a.leaves = namedRead, b, w.steps, w.leaves
|
||||||
a.levels = levelKeys(full, 0)
|
a.levels = levelKeys(full, 0)
|
||||||
return a, nil
|
return a, nil
|
||||||
|
|||||||
+1
-1
@@ -62,7 +62,7 @@ func TestTokenSubstitution(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAlternationPicksOneField(t *testing.T) {
|
func TestAlternationDrawsOneField(t *testing.T) {
|
||||||
f := engine(3)
|
f := engine(3)
|
||||||
seen := map[string]bool{}
|
seen := map[string]bool{}
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ func repeatCheck(label string, n node, mem renderCounts) error {
|
|||||||
// graph (renderEdges). Every node is a root: a field its parent's format never
|
// graph (renderEdges). Every node is a root: a field its parent's format never
|
||||||
// renders is still reachable by dot path, so a cycle in one would otherwise reach
|
// renders is still reachable by dot path, so a cycle in one would otherwise reach
|
||||||
// render and be fatal there.
|
// render and be fatal there.
|
||||||
func checkNoCycles(s nodeScope) error {
|
func checkNoCycles(s nodeSet) error {
|
||||||
const (
|
const (
|
||||||
grey = 1
|
grey = 1
|
||||||
black = 2
|
black = 2
|
||||||
@@ -195,7 +195,7 @@ func checkNoCycles(s nodeScope) error {
|
|||||||
var visit func(n node, label string) error
|
var visit func(n node, label string) error
|
||||||
visit = func(n node, label string) error {
|
visit = func(n node, label string) error {
|
||||||
if !inScope[n] {
|
if !inScope[n] {
|
||||||
return nil // loaded before this scope and proven then, so it never reaches back into it
|
return nil // loaded before these nodes and proven then, so it never reaches back into it
|
||||||
}
|
}
|
||||||
switch color[n] {
|
switch color[n] {
|
||||||
case grey:
|
case grey:
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func (t *Template) Fake() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewTemplate compiles an inline template — a format string or a JSON value — and
|
// NewTemplate compiles an inline template — a format string or a JSON value — and
|
||||||
// binds its references against the loaded tree, so repeated renders pay the
|
// resolves 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.
|
||||||
func (f *Generator) NewTemplate(input string) (*Template, error) {
|
func (f *Generator) NewTemplate(input string) (*Template, error) {
|
||||||
@@ -36,7 +36,7 @@ func (f *Generator) NewTemplate(input string) (*Template, error) {
|
|||||||
}
|
}
|
||||||
f.mu.Lock()
|
f.mu.Lock()
|
||||||
defer f.mu.Unlock()
|
defer f.mu.Unlock()
|
||||||
if err := bindInline(&f.root, n, "template", false); err != nil {
|
if err := loadInline(&f.root, n, "template", false); 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
|
||||||
@@ -87,24 +87,24 @@ func inputValue(input string) (any, error) {
|
|||||||
return raw, nil
|
return raw, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// bindInline loads the shipped categories n reads, then binds n against root.
|
// loadInline loads the shipped categories n reads, then runs n through the pipeline against root.
|
||||||
func bindInline(root *folder, n node, label string, typedByGo bool) error {
|
func loadInline(root *folder, n node, label string, typedByGo bool) error {
|
||||||
scope := inlineScope(n, label)
|
nodes := inlineNodes(n, label)
|
||||||
if err := refuseFolderRefs(scope); err != nil {
|
if err := refuseFolderRefs(nodes); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
loadShipped(root, unloadedReads(root, nil, scope))
|
loadShipped(root, unloadedReads(root, nil, nodes))
|
||||||
return binding{
|
return pipeline{
|
||||||
scope: scope,
|
nodes: nodes,
|
||||||
link: func() error { return linkInline(scope, root.children) },
|
resolve: func() error { return resolveInlineTemplates(nodes, root.children) },
|
||||||
typedByGo: typedByGo,
|
typedByGo: typedByGo,
|
||||||
}.bind()
|
}.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// refuseFolderRefs refuses each reference in scope not written from the root, {/x}: an
|
// refuseFolderRefs refuses each reference in nodes not written from the root, {/x}: an
|
||||||
// inline node sits in no folder.
|
// inline node sits in no folder.
|
||||||
func refuseFolderRefs(scope nodeScope) error {
|
func refuseFolderRefs(nodes nodeSet) error {
|
||||||
return scope(func(label string, m node) error {
|
return nodes(func(label string, m node) error {
|
||||||
t, ok := m.(*template)
|
t, ok := m.(*template)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -400,7 +400,7 @@ func (t *Table[O]) Draw(s *drawstate.State) int {
|
|||||||
return s.Weighted(t.cum)
|
return s.Weighted(t.cum)
|
||||||
}
|
}
|
||||||
|
|
||||||
// drawUnder picks a row among those linked to parent row pr.
|
// drawUnder draws a row among those linked to parent row pr.
|
||||||
func (t *Table[O]) drawUnder(s *drawstate.State, pr int) int {
|
func (t *Table[O]) drawUnder(s *drawstate.State, pr int) int {
|
||||||
lookup := t.builtLookup()
|
lookup := t.builtLookup()
|
||||||
k := t.parent.Cell(pr, t.parent.keyIndex)
|
k := t.parent.Cell(pr, t.parent.keyIndex)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ type nameBinding struct {
|
|||||||
index int
|
index int
|
||||||
binder *template
|
binder *template
|
||||||
|
|
||||||
// Filled by `linkTemplates`, once every check of the link passed:
|
// Filled by `resolveTemplates`, once every check it runs passed:
|
||||||
nameTarget
|
nameTarget
|
||||||
// addressed is every key a read of the name lands on or passes, the spelling of the
|
// addressed is every key a read of the name lands on or passes, the spelling of the
|
||||||
// first read reaching it beside it; a pick keeps the draws at these keys, and only these.
|
// first read reaching it beside it; a pick keeps the draws at these keys, and only these.
|
||||||
|
|||||||
+1
-1
@@ -428,7 +428,7 @@ func TestAFieldBindingReadOutsideWhatItBindsLoads(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestANameReadBeforeItsBinderLinks(t *testing.T) {
|
func TestANameReadBeforeItsBinderKeepsOnePick(t *testing.T) {
|
||||||
dir := writeData(t, map[string]string{
|
dir := writeData(t, map[string]string{
|
||||||
"person": `{"format":"{first} {last}","first":["Ada","Bo","Cy"],"last":["Byron","Ek","Lind"]}`,
|
"person": `{"format":"{first} {last}","first":["Ada","Bo","Cy"],"last":["Byron","Ek","Lind"]}`,
|
||||||
"row": `{"format":"{a}|{b}","a":"{p}","b":"{/person as p}{p.first} {p.last}"}`,
|
"row": `{"format":"{a}|{b}","a":"{p}","b":"{/person as p}{p.first} {p.last}"}`,
|
||||||
|
|||||||
+3
-3
@@ -12,7 +12,7 @@ import (
|
|||||||
// checkNameReads refuses each binding of t that checkUses refuses, a read of a name inside the
|
// checkNameReads refuses each binding of t that checkUses refuses, a read of a name inside the
|
||||||
// field bound to it, and a read {n} beside {n.w} where n's pick renders w twice: the pick keeps one
|
// field bound to it, and a read {n} beside {n.w} where n's pick renders w twice: the pick keeps one
|
||||||
// draw of w.
|
// draw of w.
|
||||||
func checkNameReads(label string, t *template, names linkedNames) error {
|
func checkNameReads(label string, t *template, names resolvedNames) error {
|
||||||
for _, tok := range t.tokens {
|
for _, tok := range t.tokens {
|
||||||
if tok.Kind != grammar.NameBind {
|
if tok.Kind != grammar.NameBind {
|
||||||
continue
|
continue
|
||||||
@@ -24,7 +24,7 @@ func checkNameReads(label string, t *template, names linkedNames) error {
|
|||||||
}
|
}
|
||||||
for _, r := range namedReads(t) {
|
for _, r := range namedReads(t) {
|
||||||
b, target := r.a.named, names.targets[r.a.named]
|
b, target := r.a.named, names.targets[r.a.named]
|
||||||
if b.bindsField() && rendersInside(compilePath(target.head, target.tail).leaves, t) {
|
if b.bindsField() && rendersInside(compilePath(target.start, target.tail).leaves, t) {
|
||||||
return fmt.Errorf("%s: token {%s}: name %q is read inside %q, the field bound to it; read the name outside that field", label, r.o.Body, b.name, b.ref)
|
return fmt.Errorf("%s: token {%s}: name %q is read inside %q, the field bound to it; read the name outside that field", label, r.o.Body, b.name, b.ref)
|
||||||
}
|
}
|
||||||
for _, leaf := range r.a.leaves {
|
for _, leaf := range r.a.leaves {
|
||||||
@@ -69,7 +69,7 @@ func rendersInside(nodes []node, t *template) bool {
|
|||||||
// or tag of one reference alone, that entry point's own refusal then names the bare path.
|
// or tag of one reference alone, that entry point's own refusal then names the bare path.
|
||||||
func (b *nameBinding) checkUses(uses []nameUse) error {
|
func (b *nameBinding) checkUses(uses []nameUse) error {
|
||||||
if len(uses) == 0 {
|
if len(uses) == 0 {
|
||||||
panic(invariant.Broken("name %q has no read at link, though its compile found one", b.name))
|
panic(invariant.Broken("name %q has no read at resolve, though its compile found one", b.name))
|
||||||
}
|
}
|
||||||
r := uses[0]
|
r := uses[0]
|
||||||
if len(uses) > 1 || r.nested {
|
if len(uses) > 1 || r.nested {
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ type folder struct {
|
|||||||
|
|
||||||
func (*folder) isNode() {}
|
func (*folder) isNode() {}
|
||||||
|
|
||||||
// choice picks one of its items. cum holds cumulative weights for a weighted
|
// choice draws one of its items. cum holds cumulative weights for a weighted
|
||||||
// pick; when nil the choice is uniform and selection is O(1). shared is the set of
|
// draw; when nil the choice is uniform and selection is O(1). shared is the set of
|
||||||
// relative dot paths every item can address, so carriedByAll and List both read the one
|
// relative dot paths every item can address, so carriedByAll and List both read the one
|
||||||
// answer to what a path may reach through this choice.
|
// answer to what a path may reach through this choice.
|
||||||
type choice struct {
|
type choice struct {
|
||||||
@@ -47,7 +47,7 @@ func (*nullItem) isNode() {}
|
|||||||
// template renders a format string, substituting {tokens} from fields. A bare
|
// 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
|
// 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
|
// that many times and joins the results with separator (default ""), each render
|
||||||
// an independent pick. Every format compiles in `linkTemplates`.
|
// an independent pick. Every format compiles in `resolveTemplates`.
|
||||||
type template struct {
|
type template struct {
|
||||||
// Filled by `compileString`, `compileTemplate` and `table.compileRowFormat`:
|
// Filled by `compileString`, `compileTemplate` and `table.compileRowFormat`:
|
||||||
format string
|
format string
|
||||||
@@ -63,8 +63,8 @@ type template struct {
|
|||||||
// Filled by `bindNames`, from the compiled category:
|
// Filled by `bindNames`, from the compiled category:
|
||||||
nameScope *nameScope // where its tokens look a name up
|
nameScope *nameScope // where its tokens look a name up
|
||||||
|
|
||||||
// Filled by `linkTemplates`, from the assembled tree:
|
// Filled by `resolveTemplates`, from the assembled tree:
|
||||||
link templateLink
|
refs templateRefs
|
||||||
compiled formatOps
|
compiled formatOps
|
||||||
readsColumn *columnRead // set when the format only reads one reference or name, and that read is a record's column
|
readsColumn *columnRead // set when the format only reads one reference or name, and that read is a record's column
|
||||||
|
|
||||||
@@ -72,18 +72,18 @@ type template struct {
|
|||||||
columns []recordColumn // a record's, where it has fields, in name order
|
columns []recordColumn // a record's, where it has fields, in name order
|
||||||
}
|
}
|
||||||
|
|
||||||
// templateLink is what a template resolves to in the assembled tree.
|
// templateRefs is what a template resolves to in the assembled tree.
|
||||||
type templateLink struct {
|
type templateRefs struct {
|
||||||
refs map[string]refBinding // each reference the format reads -> what it resolves to
|
byName map[string]resolvedRef // each reference the format reads -> what it resolves to
|
||||||
refHeads map[string]node // each refBinding.head -> the category it names
|
categories map[string]node // each resolvedRef.head -> the category it names
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*template) isNode() {}
|
func (*template) isNode() {}
|
||||||
|
|
||||||
// head is the node an arm's head names: a sibling field, or a reference's category.
|
// startOf is the node an arm's startOf names: a sibling field, or a reference's category.
|
||||||
func (t *template) head(name string) node {
|
func (t *template) startOf(name string) node {
|
||||||
if grammar.IsRef(name) {
|
if grammar.IsRef(name) {
|
||||||
return t.link.refHeads[name]
|
return t.refs.categories[name]
|
||||||
}
|
}
|
||||||
return t.fields[name]
|
return t.fields[name]
|
||||||
}
|
}
|
||||||
@@ -262,7 +262,7 @@ func compileChoice(items []any, pos position) (node, error) {
|
|||||||
}
|
}
|
||||||
c.items[i] = n
|
c.items[i] = n
|
||||||
}
|
}
|
||||||
if weighted { // uniform choices skip the weight table and pick in O(1)
|
if weighted { // uniform choices skip the weight table and draw in O(1)
|
||||||
if math.IsInf(total, 1) {
|
if math.IsInf(total, 1) {
|
||||||
return nil, fmt.Errorf("choice weights must sum to a finite number, got %v", total)
|
return nil, fmt.Errorf("choice weights must sum to a finite number, got %v", total)
|
||||||
}
|
}
|
||||||
@@ -272,9 +272,9 @@ func compileChoice(items []any, pos position) (node, error) {
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkNoRepeatedItem rejects a choice that lists one item twice: a pick is even
|
// checkNoRepeatedItem rejects a choice that lists one item twice: a draw is even
|
||||||
// over the items, so a repeat is a second spelling of weight. The error names the
|
// over the items, so a repeat is a second spelling of weight. The error names the
|
||||||
// spelling that does skew a pick.
|
// spelling that does skew a draw.
|
||||||
func checkNoRepeatedItem(items []any) error {
|
func checkNoRepeatedItem(items []any) error {
|
||||||
seen := make(map[string]int, len(items))
|
seen := make(map[string]int, len(items))
|
||||||
for i, raw := range items {
|
for i, raw := range items {
|
||||||
|
|||||||
+4
-4
@@ -81,7 +81,7 @@ func TestNodeCompileErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEveryFormatCompilesAtLink(t *testing.T) {
|
func TestEveryFormatCompilesAtResolve(t *testing.T) {
|
||||||
n, err := compile(parse(t, `{"format":"{a} {b}","a":{"format":"x{c}","c":["1","2"]},"b":"{/w}"}`))
|
n, err := compile(parse(t, `{"format":"{a} {b}","a":{"format":"x{c}","c":["1","2"]},"b":"{/w}"}`))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -104,15 +104,15 @@ func TestEveryFormatCompilesAtLink(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for format, has := range compiledByPath() {
|
for format, has := range compiledByPath() {
|
||||||
if has {
|
if has {
|
||||||
t.Errorf("%s compiled before link", format)
|
t.Errorf("%s compiled before resolve", format)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := linkInline(inlineScope(n, "t"), map[string]node{"w": w}); err != nil {
|
if err := resolveInlineTemplates(inlineNodes(n, "t"), map[string]node{"w": w}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for format, has := range compiledByPath() {
|
for format, has := range compiledByPath() {
|
||||||
if !has {
|
if !has {
|
||||||
t.Errorf("%s not compiled at link", format)
|
t.Errorf("%s not compiled at resolve", format)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ func (t *table) route(tail []string, descended bool) (tableRoute, error) {
|
|||||||
return tableRoute{}, err
|
return tableRoute{}, err
|
||||||
}
|
}
|
||||||
// A selector further down pins this table by ancestry, so the walk draws only
|
// A selector further down pins this table by ancestry, so the walk draws only
|
||||||
// where none follows; drawing first could pick a row the selector is not inside.
|
// where none follows; drawing first could draw a row the selector is not inside.
|
||||||
r := tableRoute{sel: sel, draw: sel == "" && (descended || len(tail) > 0) && !grammar.HasSelector(tail)}
|
r := tableRoute{sel: sel, draw: sel == "" && (descended || len(tail) > 0) && !grammar.HasSelector(tail)}
|
||||||
switch {
|
switch {
|
||||||
case len(tail) == 0 && sel == "" && !descended:
|
case len(tail) == 0 && sel == "" && !descended:
|
||||||
@@ -89,7 +89,7 @@ func (t *table) route(tail []string, descended bool) (tableRoute, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// pathStep is one step of a compiled path, taken at the node the steps before it
|
// pathStep is one step of a compiled path, taken at the node the steps before it
|
||||||
// reached, a choice there resolved first; at indexes the tail where it is taken, and
|
// reached, a choice there drawn first; at indexes the tail where it is taken, and
|
||||||
// only a table's step sits past the tail's end. A step holds no node, so a
|
// only a table's step sits past the tail's end. A step holds no node, so a
|
||||||
// caller's steps stay on its stack.
|
// caller's steps stay on its stack.
|
||||||
type pathStep struct {
|
type pathStep struct {
|
||||||
@@ -315,7 +315,7 @@ func (t *table) drawStep(s *drawstate.State, st pathStep, pins *pinSet) node {
|
|||||||
|
|
||||||
func drawVariant(s *drawstate.State, c *choice, memo *drawMemo, levels []pickKey, at int) node {
|
func drawVariant(s *drawstate.State, c *choice, memo *drawMemo, levels []pickKey, at int) node {
|
||||||
if memo == nil {
|
if memo == nil {
|
||||||
return resolveChoice(s, c)
|
return drawThroughChoices(s, c)
|
||||||
}
|
}
|
||||||
return memo.variantOf(s, c, levels[at])
|
return memo.variantOf(s, c, levels[at])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
type pickKey string
|
type pickKey string
|
||||||
|
|
||||||
// under is rel, a key from a fresh read's levels, moved under k, the key of what renders. It never
|
// under is rel, a key from a fresh read's levels, moved under k, the key of what renders. It never
|
||||||
// returns k itself: on a render k is sc.pickAt, and a memo keeping it would move every render's scope to the heap.
|
// returns k itself: on a render k is env.pickAt, and a memo keeping it would move every render's env to the heap.
|
||||||
func (k pickKey) under(rel pickKey) pickKey {
|
func (k pickKey) under(rel pickKey) pickKey {
|
||||||
if k == "" {
|
if k == "" {
|
||||||
return rel
|
return rel
|
||||||
@@ -69,9 +69,9 @@ func newPickFrame(scope *nameScope) *pickFrame {
|
|||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
// frameStack is the frames of the name scopes rendering, innermost last. A render's scope points
|
// frameStack is the frames of the name scopes rendering, innermost last. A render's env points
|
||||||
// at it, and a repeat iteration's shares it: holding the frames in a renderScope would move every
|
// at it, and a repeat iteration's shares it: holding the frames in a renderEnv would move every
|
||||||
// render's scope to the heap.
|
// render's env to the heap.
|
||||||
type frameStack struct {
|
type frameStack struct {
|
||||||
frames []*pickFrame
|
frames []*pickFrame
|
||||||
}
|
}
|
||||||
@@ -88,20 +88,20 @@ func (st *frameStack) pop(mark int) { st.frames = st.frames[:mark] }
|
|||||||
// enter starts a read landing on n: the read sees no frame opened before it, and gets a frame for
|
// enter starts a read landing on n: the read sees no frame opened before it, and gets a frame for
|
||||||
// each name scope around n, from memo where there is one, so reads sharing memo read one pick of
|
// each name scope around n, from memo where there is one, so reads sharing memo read one pick of
|
||||||
// each name. It returns the mark that closes those frames.
|
// each name. It returns the mark that closes those frames.
|
||||||
func (sc renderScope) enter(n node, memo *drawMemo) (renderScope, int) {
|
func (env renderEnv) enter(n node, memo *drawMemo) (renderEnv, int) {
|
||||||
sc = sc.entering()
|
env = env.entering()
|
||||||
for scope := scopeAround(n); scope != nil; scope = scope.up {
|
for scope := scopeAround(n); scope != nil; scope = scope.up {
|
||||||
if len(scope.order) > 0 {
|
if len(scope.order) > 0 {
|
||||||
sc.frames.push(memo.enteredFrame(scope))
|
env.frames.push(memo.enteredFrame(scope))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sc, sc.base
|
return env, env.base
|
||||||
}
|
}
|
||||||
|
|
||||||
// entering is sc as a read entering a category sees it: no frame opened before it, and no pick.
|
// entering is env as a read entering a category sees it: no frame opened before it, and no pick.
|
||||||
func (sc renderScope) entering() renderScope {
|
func (env renderEnv) entering() renderEnv {
|
||||||
sc.base, sc.pick = len(sc.frames.frames), nil
|
env.base, env.pick = len(env.frames.frames), nil
|
||||||
return sc
|
return env
|
||||||
}
|
}
|
||||||
|
|
||||||
// scopeAround is the innermost name scope a render of n reads names in, short of the frames n
|
// scopeAround is the innermost name scope a render of n reads names in, short of the frames n
|
||||||
@@ -136,18 +136,18 @@ func (m *drawMemo) enteredFrame(scope *nameScope) *pickFrame {
|
|||||||
|
|
||||||
// renderFrame opens a fresh frame of t's scope, where t binds names and no read entering the
|
// renderFrame opens a fresh frame of t's scope, where t binds names and no read entering the
|
||||||
// category opened one, returning the mark that closes it, or -1.
|
// category opened one, returning the mark that closes it, or -1.
|
||||||
func (sc renderScope) renderFrame(t *template) int {
|
func (env renderEnv) renderFrame(t *template) int {
|
||||||
own := t.ownScope()
|
own := t.ownScope()
|
||||||
if own == nil || sc.frameOf(own) != nil {
|
if own == nil || env.frameOf(own) != nil {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
return sc.frames.push(newPickFrame(own))
|
return env.frames.push(newPickFrame(own))
|
||||||
}
|
}
|
||||||
|
|
||||||
// frameOf is the frame of scope rendering since the read entering the category, nil where none is.
|
// frameOf is the frame of scope rendering since the read entering the category, nil where none is.
|
||||||
func (sc renderScope) frameOf(scope *nameScope) *pickFrame {
|
func (env renderEnv) frameOf(scope *nameScope) *pickFrame {
|
||||||
for i := len(sc.frames.frames) - 1; i >= sc.base; i-- {
|
for i := len(env.frames.frames) - 1; i >= env.base; i-- {
|
||||||
if f := sc.frames.frames[i]; f.scope == scope {
|
if f := env.frames.frames[i]; f.scope == scope {
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,8 +160,8 @@ func (sc renderScope) frameOf(scope *nameScope) *pickFrame {
|
|||||||
// So every read through one pick sees one pick of each name the other category binds:
|
// So every read through one pick sees one pick of each name the other category binds:
|
||||||
// {a.street} and {a.postal-code} in data/sv_SE/address.json read one pick of the name l
|
// {a.street} and {a.postal-code} in data/sv_SE/address.json read one pick of the name l
|
||||||
// that data/geo/SE/address.json binds.
|
// that data/geo/SE/address.json binds.
|
||||||
func readName(s *drawstate.State, sc renderScope, a arm) readValue {
|
func readName(s *drawstate.State, env renderEnv, a arm) readValue {
|
||||||
f := sc.frameOf(a.named.scope)
|
f := env.frameOf(a.named.scope)
|
||||||
if f == nil {
|
if f == nil {
|
||||||
panic(invariant.Broken("name %q is read where no frame of its scope renders", a.named.name))
|
panic(invariant.Broken("name %q is read where no frame of its scope renders", a.named.name))
|
||||||
}
|
}
|
||||||
@@ -169,50 +169,50 @@ func readName(s *drawstate.State, sc renderScope, a arm) readValue {
|
|||||||
if r, done := p.memo.value[a.key()]; done {
|
if r, done := p.memo.value[a.key()]; done {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
leaf, pins := p.draw(s, a.named.head, a.steps, a.levels)
|
leaf, pins := p.draw(s, a.named.start, a.steps, a.levels)
|
||||||
if a.named.bindsField() {
|
if a.named.bindsField() {
|
||||||
return p.renderAt(s, leaf, pins, a.key(), sc)
|
return p.renderAt(s, leaf, pins, a.key(), env)
|
||||||
}
|
}
|
||||||
sc, mark := sc.enter(leaf, &p.memo)
|
env, mark := env.enter(leaf, &p.memo)
|
||||||
r := p.renderAt(s, leaf, pins, a.key(), sc)
|
r := p.renderAt(s, leaf, pins, a.key(), env)
|
||||||
sc.frames.pop(mark)
|
env.frames.pop(mark)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// drawRowOf draws the row a render of t reads: inside the pick's rows where t renders as part of one.
|
// drawRowOf draws the row a render of t reads: inside the pick's rows where t renders as part of one.
|
||||||
func (sc renderScope) drawRowOf(s *drawstate.State, t *table) int {
|
func (env renderEnv) drawRowOf(s *drawstate.State, t *table) int {
|
||||||
if sc.pick != nil {
|
if env.pick != nil {
|
||||||
return t.rows.DrawIn(s, &sc.pick.pins)
|
return t.rows.DrawIn(s, &env.pick.pins)
|
||||||
}
|
}
|
||||||
return t.rows.Draw(s)
|
return t.rows.Draw(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// keeps reports whether a read of sc's name addresses the level a starts at.
|
// keeps reports whether a read of env's name addresses the level a starts at.
|
||||||
func (sc renderScope) keeps(a arm) bool {
|
func (env renderEnv) keeps(a arm) bool {
|
||||||
_, kept := sc.pick.named.addressed[sc.pickAt.under(a.levels[0])]
|
_, kept := env.pick.named.addressed[env.pickAt.under(a.levels[0])]
|
||||||
return kept
|
return kept
|
||||||
}
|
}
|
||||||
|
|
||||||
// readUnder reads a, an arm of t, once per pick: t renders in sc.pick at sc.pickAt, and a read of
|
// readUnder reads a, an arm of t, once per pick: t renders in env.pick at env.pickAt, and a read of
|
||||||
// the name addresses the level a starts at. It keys the value by a's key under sc.pickAt.
|
// the name addresses the level a starts at. It keys the value by a's key under env.pickAt.
|
||||||
func readUnder(s *drawstate.State, t *template, sc renderScope, a arm) readValue {
|
func readUnder(s *drawstate.State, t *template, env renderEnv, a arm) readValue {
|
||||||
p, key := sc.pick, sc.pickAt.under(a.key())
|
p, key := env.pick, env.pickAt.under(a.key())
|
||||||
if r, done := p.memo.value[key]; done {
|
if r, done := p.memo.value[key]; done {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
levels := make([]pickKey, len(a.levels))
|
levels := make([]pickKey, len(a.levels))
|
||||||
for i, l := range a.levels[:len(levels)-1] {
|
for i, l := range a.levels[:len(levels)-1] {
|
||||||
levels[i] = sc.pickAt.under(l)
|
levels[i] = env.pickAt.under(l)
|
||||||
}
|
}
|
||||||
levels[len(levels)-1] = key
|
levels[len(levels)-1] = key
|
||||||
leaf, pins := p.draw(s, t.head(a.head), a.steps, levels)
|
leaf, pins := p.draw(s, t.startOf(a.head), a.steps, levels)
|
||||||
return p.renderAt(s, leaf, pins, key, sc)
|
return p.renderAt(s, leaf, pins, key, env)
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw draws the path from head under p, keeping the variant drawn at each of levels, whose last
|
// draw draws the path from start under p, keeping the variant drawn at each of levels, whose last
|
||||||
// is the leaf's key, and returns the leaf and the pins its row is in.
|
// is the leaf's key, and returns the leaf and the pins its row is in.
|
||||||
func (p *namedPick) draw(s *drawstate.State, head node, steps []pathStep, levels []pickKey) (node, *pinSet) {
|
func (p *namedPick) draw(s *drawstate.State, start node, steps []pathStep, levels []pickKey) (node, *pinSet) {
|
||||||
leaf, pins := drawSteps(s, head, steps, &p.pins, &p.memo, levels)
|
leaf, pins := drawSteps(s, start, steps, &p.pins, &p.memo, levels)
|
||||||
if c, isChoice := leaf.(*choice); isChoice {
|
if c, isChoice := leaf.(*choice); isChoice {
|
||||||
leaf = p.memo.variantOf(s, c, levels[len(levels)-1])
|
leaf = p.memo.variantOf(s, c, levels[len(levels)-1])
|
||||||
}
|
}
|
||||||
@@ -220,10 +220,10 @@ func (p *namedPick) draw(s *drawstate.State, head node, steps []pathStep, levels
|
|||||||
}
|
}
|
||||||
|
|
||||||
// renderAt renders leaf, drawn at key into pins, as part of p, and keeps what it rendered.
|
// renderAt renders leaf, drawn at key into pins, as part of p, and keeps what it rendered.
|
||||||
func (p *namedPick) renderAt(s *drawstate.State, leaf node, pins *pinSet, key pickKey, sc renderScope) readValue {
|
func (p *namedPick) renderAt(s *drawstate.State, leaf node, pins *pinSet, key pickKey, env renderEnv) readValue {
|
||||||
sc = sc.at(leaf, pins)
|
env = env.at(leaf, pins)
|
||||||
sc.pick, sc.pickAt = p, key
|
env.pick, env.pickAt = p, key
|
||||||
r := renderLeaf(s, leaf, sc)
|
r := renderLeaf(s, leaf, env)
|
||||||
if p.memo.value == nil {
|
if p.memo.value == nil {
|
||||||
p.memo.value = map[pickKey]readValue{}
|
p.memo.value = map[pickKey]readValue{}
|
||||||
}
|
}
|
||||||
@@ -235,7 +235,7 @@ func (p *namedPick) renderAt(s *drawstate.State, leaf node, pins *pinSet, key pi
|
|||||||
func (m *drawMemo) variantOf(s *drawstate.State, c *choice, level pickKey) node {
|
func (m *drawMemo) variantOf(s *drawstate.State, c *choice, level pickKey) node {
|
||||||
n, drew := m.variant[level]
|
n, drew := m.variant[level]
|
||||||
if !drew {
|
if !drew {
|
||||||
n = resolveChoice(s, c)
|
n = drawThroughChoices(s, c)
|
||||||
if m.variant == nil {
|
if m.variant == nil {
|
||||||
m.variant = map[pickKey]node{}
|
m.variant = map[pickKey]node{}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,9 +135,9 @@ func (f *Generator) FakeRecord(path string) (*Record, error) {
|
|||||||
return nil, fmt.Errorf("fejkdata: %s: %w", path, err)
|
return nil, fmt.Errorf("fejkdata: %s: %w", path, err)
|
||||||
}
|
}
|
||||||
var frames frameStack
|
var frames frameStack
|
||||||
sc := renderScope{frames: &frames}
|
env := renderEnv{frames: &frames}
|
||||||
if t, isTable := n.(*table); isTable {
|
if t, isTable := n.(*table); isTable {
|
||||||
if n, sc.row, err = tableRecord(f.drawState, t, tail); err != nil {
|
if n, env.row, err = tableRecord(f.drawState, t, tail); err != nil {
|
||||||
return nil, fmt.Errorf("fejkdata: %s: %w", path, err)
|
return nil, fmt.Errorf("fejkdata: %s: %w", path, err)
|
||||||
}
|
}
|
||||||
} else if len(tail) > 0 {
|
} else if len(tail) > 0 {
|
||||||
@@ -151,7 +151,7 @@ func (f *Generator) FakeRecord(path string) (*Record, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("fejkdata: %s %w", path, err)
|
return nil, fmt.Errorf("fejkdata: %s %w", path, err)
|
||||||
}
|
}
|
||||||
return renderRecord(f.drawState, record, sc), nil
|
return renderRecord(f.drawState, record, env), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// tableRecord walks a path's tail from a table to the table whose row is the record, and that
|
// tableRecord walks a path's tail from a table to the table whose row is the record, and that
|
||||||
@@ -184,11 +184,11 @@ func (t *RecordTemplate) Fake() *Record {
|
|||||||
t.g.mu.Lock()
|
t.g.mu.Lock()
|
||||||
defer t.g.mu.Unlock()
|
defer t.g.mu.Unlock()
|
||||||
var frames frameStack
|
var frames frameStack
|
||||||
return renderRecord(t.g.drawState, t.template, renderScope{frames: &frames})
|
return renderRecord(t.g.drawState, t.template, renderEnv{frames: &frames})
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRecordTemplate compiles an inline record — a JSON object with a format and
|
// NewRecordTemplate compiles an inline record — a JSON object with a format and
|
||||||
// fields — and binds its references against the loaded tree.
|
// fields — and resolves its references against the loaded tree.
|
||||||
func (f *Generator) NewRecordTemplate(input string) (*RecordTemplate, error) {
|
func (f *Generator) NewRecordTemplate(input string) (*RecordTemplate, error) {
|
||||||
t, err := f.NewTemplate(input)
|
t, err := f.NewTemplate(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -246,10 +246,10 @@ type recordColumn struct {
|
|||||||
boundWhole *nameBinding
|
boundWhole *nameBinding
|
||||||
}
|
}
|
||||||
|
|
||||||
// settleRecords fixes the columns of every record in scope. bind runs it after checkNoCycles: a
|
// settleRecords fixes the columns of every record in nodes. The pipeline runs it after checkNoCycles: a
|
||||||
// column's datatype walks the column, and a cycle would walk forever.
|
// column's datatype walks the column, and a cycle would walk forever.
|
||||||
func settleRecords(scope nodeScope) {
|
func settleRecords(nodes nodeSet) {
|
||||||
_ = scope(func(_ string, n node) error {
|
_ = nodes(func(_ string, n node) error {
|
||||||
if t, isTemplate := n.(*template); isTemplate {
|
if t, isTemplate := n.(*template); isTemplate {
|
||||||
t.columns = recordColumns(t)
|
t.columns = recordColumns(t)
|
||||||
}
|
}
|
||||||
@@ -267,8 +267,8 @@ func recordColumns(t *template) []recordColumn {
|
|||||||
if tok.Kind != grammar.NameBind {
|
if tok.Kind != grammar.NameBind {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if b := t.nameScope.bindings[tok.Bound]; b.bindsField() && len(b.tail) == 0 && whole[b.head] == nil {
|
if b := t.nameScope.bindings[tok.Bound]; b.bindsField() && len(b.tail) == 0 && whole[b.start] == nil {
|
||||||
whole[b.head] = b
|
whole[b.start] = b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
names := sortedNames(t.fields)
|
names := sortedNames(t.fields)
|
||||||
@@ -281,18 +281,18 @@ func recordColumns(t *template) []recordColumn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// renderRecord draws each column of t once, in name order, as one render, so the columns read one
|
// renderRecord draws each column of t once, in name order, as one render, so the columns read one
|
||||||
// pick of each name; a table's columns read sc's row.
|
// pick of each name; a table's columns read env's row.
|
||||||
func renderRecord(s *drawstate.State, t *template, sc renderScope) *Record {
|
func renderRecord(s *drawstate.State, t *template, env renderEnv) *Record {
|
||||||
if mark := sc.renderFrame(t); mark >= 0 {
|
if mark := env.renderFrame(t); mark >= 0 {
|
||||||
defer sc.frames.pop(mark)
|
defer env.frames.pop(mark)
|
||||||
}
|
}
|
||||||
r := &Record{columns: make([]Column, len(t.columns))}
|
r := &Record{columns: make([]Column, len(t.columns))}
|
||||||
for i, c := range t.columns {
|
for i, c := range t.columns {
|
||||||
var column readValue
|
var column readValue
|
||||||
if c.boundWhole != nil {
|
if c.boundWhole != nil {
|
||||||
column = readName(s, sc, arm{kind: namedRead, named: c.boundWhole, levels: wholeLevels})
|
column = readName(s, env, arm{kind: namedRead, named: c.boundWhole, levels: wholeLevels})
|
||||||
} else {
|
} else {
|
||||||
column = renderLeaf(s, c.field, sc)
|
column = renderLeaf(s, c.field, env)
|
||||||
}
|
}
|
||||||
r.columns[i] = Column{Name: c.name, DataType: c.datatype, Value: column.text, Null: column.null}
|
r.columns[i] = Column{Name: c.name, DataType: c.datatype, Value: column.text, Null: column.null}
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-15
@@ -7,9 +7,9 @@ import (
|
|||||||
"github.com/larvit/fejkdata/internal/grammar"
|
"github.com/larvit/fejkdata/internal/grammar"
|
||||||
)
|
)
|
||||||
|
|
||||||
// refBinding is what a reference resolves to: the head its category is held
|
// resolvedRef is what a reference resolves to: the head its category is held
|
||||||
// under, and the tail read into it.
|
// under, and the tail read into it.
|
||||||
type refBinding struct {
|
type resolvedRef struct {
|
||||||
head string
|
head string
|
||||||
tail []string
|
tail []string
|
||||||
}
|
}
|
||||||
@@ -34,36 +34,36 @@ func refSegments(name string, folder []string) ([]string, error) {
|
|||||||
return append(append([]string{}, base...), segs...), nil
|
return append(append([]string{}, base...), segs...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveLink binds every reference t reads, refusing one to t's own category:
|
// resolveRefs resolves every reference t reads, refusing one to t's own category:
|
||||||
// docs/decisions.md#a-category-never-references-itself-and-a-records-fences-run-at-load
|
// docs/decisions.md#a-category-never-references-itself-and-a-records-fences-run-at-load
|
||||||
func (t *template) resolveLink(folder []string, label, category string, root map[string]node) (templateLink, error) {
|
func (t *template) resolveRefs(folder []string, label, category string, root map[string]node) (templateRefs, error) {
|
||||||
var link templateLink
|
var refs templateRefs
|
||||||
names := refTokens(t.tokens)
|
names := refTokens(t.tokens)
|
||||||
if len(names) == 0 {
|
if len(names) == 0 {
|
||||||
return link, nil
|
return refs, nil
|
||||||
}
|
}
|
||||||
link.refs = make(map[string]refBinding, len(names))
|
refs.byName = make(map[string]resolvedRef, len(names))
|
||||||
link.refHeads = make(map[string]node, len(names))
|
refs.categories = make(map[string]node, len(names))
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
segments, err := refSegments(name, folder)
|
segments, err := refSegments(name, folder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return link, fmt.Errorf("%s: reference {%s}: %w", label, name, err)
|
return refs, fmt.Errorf("%s: reference {%s}: %w", label, name, err)
|
||||||
}
|
}
|
||||||
categorySegs, target, tail, err := resolveCategory(root, segments)
|
categorySegs, target, tail, err := resolveCategory(root, segments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return link, fmt.Errorf("%s: reference {%s}: %w", label, name, err)
|
return refs, fmt.Errorf("%s: reference {%s}: %w", label, name, err)
|
||||||
}
|
}
|
||||||
head := "/" + strings.Join(categorySegs, ".")
|
head := "/" + strings.Join(categorySegs, ".")
|
||||||
if category != "" && head == "/"+category {
|
if category != "" && head == "/"+category {
|
||||||
return link, fmt.Errorf("%s: reference {%s}: names the category it sits in; read a sibling field as a path, or move the shared value into its own category and reference that", label, name)
|
return refs, fmt.Errorf("%s: reference {%s}: names the category it sits in; read a sibling field as a path, or move the shared value into its own category and reference that", label, name)
|
||||||
}
|
}
|
||||||
if err := checkPathResolves(target, tail, head); err != nil {
|
if err := checkPathResolves(target, tail, head); err != nil {
|
||||||
return link, fmt.Errorf("%s: reference {%s}: %w", label, name, err)
|
return refs, fmt.Errorf("%s: reference {%s}: %w", label, name, err)
|
||||||
}
|
}
|
||||||
link.refHeads[head] = target
|
refs.categories[head] = target
|
||||||
link.refs[name] = refBinding{head, tail}
|
refs.byName[name] = resolvedRef{head, tail}
|
||||||
}
|
}
|
||||||
return link, nil
|
return refs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveCategory walks a dotted path through the folders to the category it
|
// resolveCategory walks a dotted path through the folders to the category it
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
// Fake generates a value for a dot path. Each segment descends one level: folder
|
// Fake generates a value for a dot path. Each segment descends one level: folder
|
||||||
// names and the category (JSON file) come first, then named fields within it,
|
// names and the category (JSON file) come first, then named fields within it,
|
||||||
// e.g. "sv_SE.address" or "sv_SE.address.street". Choices along the way are
|
// e.g. "sv_SE.address" or "sv_SE.address.street". Choices along the way are
|
||||||
// resolved at random. A path naming a folder (no value of its own) is an error.
|
// drawn at random. A path naming a folder (no value of its own) is an error.
|
||||||
func (f *Generator) Fake(path string) (string, error) {
|
func (f *Generator) Fake(path string) (string, error) {
|
||||||
f.mu.Lock()
|
f.mu.Lock()
|
||||||
defer f.mu.Unlock()
|
defer f.mu.Unlock()
|
||||||
@@ -29,8 +29,8 @@ func (f *Generator) Fake(path string) (string, error) {
|
|||||||
return "", fmt.Errorf("fejkdata: %s names a folder, not a value", path)
|
return "", fmt.Errorf("fejkdata: %s names a folder, not a value", path)
|
||||||
}
|
}
|
||||||
var frames frameStack
|
var frames frameStack
|
||||||
sc, _ := renderScope{frames: &frames}.at(n, &pins).enter(n, nil)
|
env, _ := renderEnv{frames: &frames}.at(n, &pins).enter(n, nil)
|
||||||
return render(f.drawState, n, sc), nil
|
return render(f.drawState, n, env), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// descend walks a caller's path to the node it names, returning the rows its leaf renders in.
|
// descend walks a caller's path to the node it names, returning the rows its leaf renders in.
|
||||||
@@ -49,40 +49,40 @@ func descend(s *drawstate.State, root node, segments []string) (node, pinSet, er
|
|||||||
// renderOnce renders n as one render, over frames of its own.
|
// renderOnce renders n as one render, over frames of its own.
|
||||||
func renderOnce(s *drawstate.State, n node) string {
|
func renderOnce(s *drawstate.State, n node) string {
|
||||||
var frames frameStack
|
var frames frameStack
|
||||||
return render(s, n, renderScope{frames: &frames})
|
return render(s, n, renderEnv{frames: &frames})
|
||||||
}
|
}
|
||||||
|
|
||||||
// render evaluates a compiled node to a string. compile validates every node up
|
// render evaluates a compiled node to a string. compile validates every node up
|
||||||
// front, so rendering a compiled tree cannot fail.
|
// front, so rendering a compiled tree cannot fail.
|
||||||
// A child this switch renders is one renderEdges must list too, or the fences miss it.
|
// A child this switch renders is one renderEdges must list too, or the fences miss it.
|
||||||
func render(s *drawstate.State, n node, sc renderScope) string {
|
func render(s *drawstate.State, n node, env renderEnv) string {
|
||||||
switch n := n.(type) {
|
switch n := n.(type) {
|
||||||
case *choice:
|
case *choice:
|
||||||
return render(s, pick(s, n), sc)
|
return render(s, drawItem(s, n), env)
|
||||||
case *nullItem:
|
case *nullItem:
|
||||||
return ""
|
return ""
|
||||||
case *table:
|
case *table:
|
||||||
sc.row = renderedRow{n, sc.drawRowOf(s, n)}
|
env.row = renderedRow{n, env.drawRowOf(s, n)}
|
||||||
return expand(s, n.formatTemplate, sc)
|
return expand(s, n.formatTemplate, env)
|
||||||
case *tableRow:
|
case *tableRow:
|
||||||
return expand(s, n.t.formatTemplate, sc)
|
return expand(s, n.t.formatTemplate, env)
|
||||||
case *tableColumn:
|
case *tableColumn:
|
||||||
row := sc.rowOf(n.t)
|
row := env.rowOf(n.t)
|
||||||
if cell := n.t.cellTemplate(row, n.i); cell != nil {
|
if cell := n.t.cellTemplate(row, n.i); cell != nil {
|
||||||
return render(s, cell, sc)
|
return render(s, cell, env)
|
||||||
}
|
}
|
||||||
return n.t.rows.Cell(row, n.i)
|
return n.t.rows.Cell(row, n.i)
|
||||||
case *template:
|
case *template:
|
||||||
if n.repeat == 1 {
|
if n.repeat == 1 {
|
||||||
if mark := sc.renderFrame(n); mark >= 0 {
|
if mark := env.renderFrame(n); mark >= 0 {
|
||||||
defer sc.frames.pop(mark)
|
defer env.frames.pop(mark)
|
||||||
}
|
}
|
||||||
if lit, fixed := n.fixedText(); fixed {
|
if lit, fixed := n.fixedText(); fixed {
|
||||||
return lit
|
return lit
|
||||||
}
|
}
|
||||||
return expand(s, n, sc)
|
return expand(s, n, env)
|
||||||
}
|
}
|
||||||
return renderRepeat(s, n, sc)
|
return renderRepeat(s, n, env)
|
||||||
default:
|
default:
|
||||||
panic(invariant.Broken("uncompiled node %T", n))
|
panic(invariant.Broken("uncompiled node %T", n))
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ func render(s *drawstate.State, n node, sc renderScope) string {
|
|||||||
|
|
||||||
// renderRepeat renders each iteration of t inside the name scopes rendering t, where a name bound
|
// 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 picks again.
|
||||||
func renderRepeat(s *drawstate.State, t *template, sc renderScope) string {
|
func renderRepeat(s *drawstate.State, t *template, env renderEnv) string {
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
b.Grow(t.repeat * (t.compiled.grow + len(t.separator)))
|
b.Grow(t.repeat * (t.compiled.grow + len(t.separator)))
|
||||||
own := t.ownScope()
|
own := t.ownScope()
|
||||||
@@ -99,26 +99,26 @@ func renderRepeat(s *drawstate.State, t *template, sc renderScope) string {
|
|||||||
b.WriteString(t.separator)
|
b.WriteString(t.separator)
|
||||||
}
|
}
|
||||||
if own == nil {
|
if own == nil {
|
||||||
b.WriteString(expand(s, t, sc))
|
b.WriteString(expand(s, t, env))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
mark := sc.frames.push(newPickFrame(own))
|
mark := env.frames.push(newPickFrame(own))
|
||||||
b.WriteString(expand(s, t, sc))
|
b.WriteString(expand(s, t, env))
|
||||||
sc.frames.pop(mark)
|
env.frames.pop(mark)
|
||||||
}
|
}
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// pick selects one item. Uniform choices are O(1); weighted choices are an
|
// drawItem selects one item. Uniform choices are O(1); weighted choices are an
|
||||||
// O(log n) search over precomputed cumulative weights.
|
// O(log n) search over precomputed cumulative weights.
|
||||||
func pick(s *drawstate.State, c *choice) node {
|
func drawItem(s *drawstate.State, c *choice) node {
|
||||||
if c.cum == nil {
|
if c.cum == nil {
|
||||||
return c.items[s.IntN(len(c.items))]
|
return c.items[s.IntN(len(c.items))]
|
||||||
}
|
}
|
||||||
return c.items[s.Weighted(c.cum)]
|
return c.items[s.Weighted(c.cum)]
|
||||||
}
|
}
|
||||||
|
|
||||||
func expand(s *drawstate.State, t *template, sc renderScope) string {
|
func expand(s *drawstate.State, t *template, env renderEnv) string {
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
b.Grow(t.compiled.grow)
|
b.Grow(t.compiled.grow)
|
||||||
for i := range t.compiled.ops {
|
for i := range t.compiled.ops {
|
||||||
@@ -127,13 +127,13 @@ func expand(s *drawstate.State, t *template, sc renderScope) string {
|
|||||||
case grammar.LiteralRun:
|
case grammar.LiteralRun:
|
||||||
b.WriteString(o.Lit)
|
b.WriteString(o.Lit)
|
||||||
case grammar.NameRead:
|
case grammar.NameRead:
|
||||||
b.WriteString(readField(s, t, sc, o.arms[s.IntN(len(o.arms))]).text)
|
b.WriteString(readField(s, t, env, o.arms[s.IntN(len(o.arms))]).text)
|
||||||
case grammar.BuiltinCall:
|
case grammar.BuiltinCall:
|
||||||
var operands []string
|
var operands []string
|
||||||
if len(o.operands) > 0 {
|
if len(o.operands) > 0 {
|
||||||
operands = make([]string, len(o.operands))
|
operands = make([]string, len(o.operands))
|
||||||
for j, a := range o.operands {
|
for j, a := range o.operands {
|
||||||
operands[j] = readField(s, t, sc, a).text
|
operands[j] = readField(s, t, env, a).text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b.WriteString(o.call(s, b.String(), operands)) // b.String() is the output so far
|
b.WriteString(o.call(s, b.String(), operands)) // b.String() is the output so far
|
||||||
@@ -145,48 +145,48 @@ func expand(s *drawstate.State, t *template, sc renderScope) string {
|
|||||||
// readField renders one arm of a token. A read is kept in a name's pick when it reads the name, or
|
// readField renders one arm of a token. A read is kept in a name's pick when it reads the name, or
|
||||||
// a level that a read of the name being rendered addresses; every other read draws afresh, so
|
// a level that a read of the name being rendered addresses; every other read draws afresh, so
|
||||||
// {word} {word} draws twice and {p.a} {p.b} reads two draws of p.
|
// {word} {word} draws twice and {p.a} {p.b} reads two draws of p.
|
||||||
// A fresh read clears sc.pick: were it kept, a read nested in the fresh one would key the pick's
|
// A fresh read clears env.pick: were it kept, a read nested in the fresh one would key the pick's
|
||||||
// memo under sc.pickAt and share a value with another node at that key.
|
// memo under env.pickAt and share a value with another node at that key.
|
||||||
func readField(s *drawstate.State, t *template, sc renderScope, a arm) readValue {
|
func readField(s *drawstate.State, t *template, env renderEnv, a arm) readValue {
|
||||||
switch {
|
switch {
|
||||||
case a.kind == namedRead:
|
case a.kind == namedRead:
|
||||||
return readName(s, sc, a)
|
return readName(s, env, a)
|
||||||
case sc.pick != nil && !grammar.IsRef(a.head) && sc.keeps(a):
|
case env.pick != nil && !grammar.IsRef(a.head) && env.keeps(a):
|
||||||
return readUnder(s, t, sc, a)
|
return readUnder(s, t, env, a)
|
||||||
}
|
}
|
||||||
sc.pick = nil
|
env.pick = nil
|
||||||
if !grammar.IsRef(a.head) {
|
if !grammar.IsRef(a.head) {
|
||||||
leaf, _ := drawSteps(s, t.head(a.head), a.steps, nil, nil, a.levels)
|
leaf, _ := drawSteps(s, t.startOf(a.head), a.steps, nil, nil, a.levels)
|
||||||
return renderLeaf(s, leaf, sc)
|
return renderLeaf(s, leaf, env)
|
||||||
}
|
}
|
||||||
var pins pinSet
|
var pins pinSet
|
||||||
leaf, leafPins := drawSteps(s, t.head(a.head), a.steps, &pins, nil, a.levels)
|
leaf, leafPins := drawSteps(s, t.startOf(a.head), a.steps, &pins, nil, a.levels)
|
||||||
sc, mark := sc.at(leaf, leafPins).enter(leaf, nil)
|
env, mark := env.at(leaf, leafPins).enter(leaf, nil)
|
||||||
r := renderLeaf(s, leaf, sc)
|
r := renderLeaf(s, leaf, env)
|
||||||
sc.frames.pop(mark)
|
env.frames.pop(mark)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderLeaf draws and renders what a read lands on: null on a null item, or on a column that only
|
// renderLeaf draws and renders what a read lands on: null on a null item, or on a column that only
|
||||||
// reads one reference or name whose read drew null.
|
// reads one reference or name whose read drew null.
|
||||||
func renderLeaf(s *drawstate.State, n node, sc renderScope) readValue {
|
func renderLeaf(s *drawstate.State, n node, env renderEnv) readValue {
|
||||||
n = resolveChoice(s, n)
|
n = drawThroughChoices(s, n)
|
||||||
switch leaf := n.(type) {
|
switch leaf := n.(type) {
|
||||||
case *nullItem:
|
case *nullItem:
|
||||||
return readValue{null: true}
|
return readValue{null: true}
|
||||||
case *template:
|
case *template:
|
||||||
if leaf.readsColumn != nil {
|
if leaf.readsColumn != nil {
|
||||||
return readField(s, leaf, sc, leaf.readsColumn.a)
|
return readField(s, leaf, env, leaf.readsColumn.a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return readValue{text: render(s, n, sc)}
|
return readValue{text: render(s, n, env)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveChoice resolves a choice to one variant. Nested choices unwrap too: a draw is one value,
|
// drawThroughChoices draws a choice's variant. Nested choices unwrap too: a draw is one value,
|
||||||
// not another set to pick from.
|
// not another set to draw from.
|
||||||
func resolveChoice(s *drawstate.State, n node) node {
|
func drawThroughChoices(s *drawstate.State, n node) node {
|
||||||
for c, ok := n.(*choice); ok; c, ok = n.(*choice) {
|
for c, ok := n.(*choice); ok; c, ok = n.(*choice) {
|
||||||
n = pick(s, c)
|
n = drawItem(s, c)
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -43,7 +43,7 @@ func TestGrowIsALowerBound(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for i := 0; i < 50; i++ {
|
for i := 0; i < 50; i++ {
|
||||||
var frames frameStack
|
var frames frameStack
|
||||||
if got := len(expand(f.drawState, tmpl, renderScope{frames: &frames})); got < tmpl.compiled.grow {
|
if got := len(expand(f.drawState, tmpl, renderEnv{frames: &frames})); got < tmpl.compiled.grow {
|
||||||
t.Errorf("format %q: expand emitted %d bytes, below grow %d", format, got, tmpl.compiled.grow)
|
t.Errorf("format %q: expand emitted %d bytes, below grow %d", format, got, tmpl.compiled.grow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ func TestNodeSwitchesHandleEveryKind(t *testing.T) {
|
|||||||
"prove": func(n node) { (&valueProof{}).prove(n) },
|
"prove": func(n node) { (&valueProof{}).prove(n) },
|
||||||
"render": func(n node) {
|
"render": func(n node) {
|
||||||
var frames frameStack
|
var frames frameStack
|
||||||
render(engine(1).drawState, n, renderScope{frames: &frames, row: renderedRow{tbl, 0}})
|
render(engine(1).drawState, n, renderEnv{frames: &frames, row: renderedRow{tbl, 0}})
|
||||||
},
|
},
|
||||||
"renderEdges": func(n node) { renderEdges(n) },
|
"renderEdges": func(n node) { renderEdges(n) },
|
||||||
"stepInto": func(n node) { _, _ = stepInto(n, "x") },
|
"stepInto": func(n node) { _, _ = stepInto(n, "x") },
|
||||||
|
|||||||
+35
-35
@@ -7,71 +7,71 @@ import (
|
|||||||
"github.com/larvit/fejkdata/internal/grammar"
|
"github.com/larvit/fejkdata/internal/grammar"
|
||||||
)
|
)
|
||||||
|
|
||||||
// linkSite is a template to link, with its folder and category, both empty for an inline
|
// templateSite is a template to resolve, with its folder and category, both empty for an inline
|
||||||
// template, and eachNode's label for it.
|
// template, and eachNode's label for it.
|
||||||
type linkSite struct {
|
type templateSite struct {
|
||||||
t *template
|
t *template
|
||||||
folder []string
|
folder []string
|
||||||
category, label string
|
category, label string
|
||||||
}
|
}
|
||||||
|
|
||||||
// linkCategories links every template of the categories, once all data is merged, so a reference
|
// resolveCategoryTemplates resolves every template of the categories, once all data is merged, so a
|
||||||
// sees the override-resolved tree.
|
// reference sees the override-resolved tree.
|
||||||
func linkCategories(sites []categorySite, root map[string]node) error {
|
func resolveCategoryTemplates(sites []categorySite, root map[string]node) error {
|
||||||
var ts []linkSite
|
var ts []templateSite
|
||||||
for _, s := range sites {
|
for _, s := range sites {
|
||||||
if err := eachNode(s.n, s.path, func(label string, n node) error {
|
if err := eachNode(s.n, s.path, func(label string, n node) error {
|
||||||
if t, isTemplate := n.(*template); isTemplate {
|
if t, isTemplate := n.(*template); isTemplate {
|
||||||
ts = append(ts, linkSite{t: t, folder: s.dir, category: s.path, label: label})
|
ts = append(ts, templateSite{t: t, folder: s.dir, category: s.path, label: label})
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return linkTemplates(ts, root)
|
return resolveTemplates(ts, root)
|
||||||
}
|
}
|
||||||
|
|
||||||
// linkInline links the templates of an inline node against the loaded tree.
|
// resolveInlineTemplates resolves the templates of an inline node against the loaded tree.
|
||||||
func linkInline(scope nodeScope, root map[string]node) error {
|
func resolveInlineTemplates(nodes nodeSet, root map[string]node) error {
|
||||||
var ts []linkSite
|
var ts []templateSite
|
||||||
if err := scope(func(label string, n node) error {
|
if err := nodes(func(label string, n node) error {
|
||||||
if t, isTemplate := n.(*template); isTemplate {
|
if t, isTemplate := n.(*template); isTemplate {
|
||||||
ts = append(ts, linkSite{t: t, label: label})
|
ts = append(ts, templateSite{t: t, label: label})
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return linkTemplates(ts, root)
|
return resolveTemplates(ts, root)
|
||||||
}
|
}
|
||||||
|
|
||||||
// nameTarget is what a binding resolves to in the assembled tree: the node its head names and the
|
// nameTarget is what a binding resolves to in the assembled tree: start, the node its head names, and
|
||||||
// path it reads into that node.
|
// the path it reads into that node.
|
||||||
type nameTarget struct {
|
type nameTarget struct {
|
||||||
head node
|
start node
|
||||||
tail []string
|
tail []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// linkedNames is what the link resolved about the names of a set of templates: each binding's
|
// resolvedNames is what resolveTemplates resolved about the names of a set of templates: each binding's
|
||||||
// target, the keys its reads address, and its reads.
|
// target, the keys its reads address, and its reads.
|
||||||
type linkedNames struct {
|
type resolvedNames struct {
|
||||||
targets map[*nameBinding]nameTarget
|
targets map[*nameBinding]nameTarget
|
||||||
addressed map[*nameBinding]map[pickKey]string
|
addressed map[*nameBinding]map[pickKey]string
|
||||||
uses map[*nameBinding][]nameUse
|
uses map[*nameBinding][]nameUse
|
||||||
}
|
}
|
||||||
|
|
||||||
// linkTemplates links ts in steps, each over every template before the next starts. Each step
|
// resolveTemplates resolves ts in steps, each over every template before the next starts. Each step
|
||||||
// returns what it builds. The references, the compiled format and the column read are kept on each
|
// returns what it builds. The references, the compiled format and the column read are kept on each
|
||||||
// template. The names' targets, keys and reads pass to later steps as arguments, and the bindings
|
// template. The names' targets, keys and reads pass to later steps as arguments, and the bindings
|
||||||
// take their targets and keys only once every check has passed.
|
// take their targets and keys only once every check has passed.
|
||||||
func linkTemplates(ts []linkSite, root map[string]node) error {
|
func resolveTemplates(ts []templateSite, root map[string]node) error {
|
||||||
for _, s := range ts {
|
for _, s := range ts {
|
||||||
link, err := s.t.resolveLink(s.folder, s.label, s.category, root)
|
refs, err := s.t.resolveRefs(s.folder, s.label, s.category, root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.t.link = link
|
s.t.refs = refs
|
||||||
}
|
}
|
||||||
targets := nameTargets(ts)
|
targets := nameTargets(ts)
|
||||||
for _, s := range ts {
|
for _, s := range ts {
|
||||||
@@ -81,7 +81,7 @@ func linkTemplates(ts []linkSite, root map[string]node) error {
|
|||||||
}
|
}
|
||||||
s.t.compiled = compiled
|
s.t.compiled = compiled
|
||||||
}
|
}
|
||||||
names := linkedNames{targets: targets, addressed: addressedKeys(ts, targets), uses: nameUses(ts)}
|
names := resolvedNames{targets: targets, addressed: addressedKeys(ts, targets), uses: nameUses(ts)}
|
||||||
for _, s := range ts {
|
for _, s := range ts {
|
||||||
s.t.readsColumn = columnReadOf(s.t, targets)
|
s.t.readsColumn = columnReadOf(s.t, targets)
|
||||||
}
|
}
|
||||||
@@ -101,14 +101,14 @@ func linkTemplates(ts []linkSite, root map[string]node) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// nameTargets resolves what each binding of ts binds, from its binder's link.
|
// nameTargets resolves what each binding of ts binds, from its binder's references.
|
||||||
func nameTargets(ts []linkSite) map[*nameBinding]nameTarget {
|
func nameTargets(ts []templateSite) map[*nameBinding]nameTarget {
|
||||||
targets := map[*nameBinding]nameTarget{}
|
targets := map[*nameBinding]nameTarget{}
|
||||||
for _, s := range ts {
|
for _, s := range ts {
|
||||||
for _, tok := range s.t.tokens {
|
for _, tok := range s.t.tokens {
|
||||||
if tok.Kind == grammar.NameBind {
|
if tok.Kind == grammar.NameBind {
|
||||||
a := splitArm(tok.BoundRef, s.t.link.refs)
|
a := splitArm(tok.BoundRef, s.t.refs.byName)
|
||||||
targets[s.t.nameScope.bindings[tok.Bound]] = nameTarget{head: s.t.head(a.head), tail: a.tail}
|
targets[s.t.nameScope.bindings[tok.Bound]] = nameTarget{start: s.t.startOf(a.head), tail: a.tail}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,7 +117,7 @@ func nameTargets(ts []linkSite) map[*nameBinding]nameTarget {
|
|||||||
|
|
||||||
// addressedKeys is every key the reads of each name in ts land on or pass, from the name's own
|
// addressedKeys is every key the reads of each name in ts land on or pass, from the name's own
|
||||||
// level, with the spelling of the first read reaching it.
|
// level, with the spelling of the first read reaching it.
|
||||||
func addressedKeys(ts []linkSite, targets map[*nameBinding]nameTarget) map[*nameBinding]map[pickKey]string {
|
func addressedKeys(ts []templateSite, targets map[*nameBinding]nameTarget) map[*nameBinding]map[pickKey]string {
|
||||||
keys := map[*nameBinding]map[pickKey]string{}
|
keys := map[*nameBinding]map[pickKey]string{}
|
||||||
for _, s := range ts {
|
for _, s := range ts {
|
||||||
for _, r := range namedReads(s.t) {
|
for _, r := range namedReads(s.t) {
|
||||||
@@ -136,7 +136,7 @@ func addressedKeys(ts []linkSite, targets map[*nameBinding]nameTarget) map[*name
|
|||||||
}
|
}
|
||||||
|
|
||||||
// nameUses is every read of each name in ts.
|
// nameUses is every read of each name in ts.
|
||||||
func nameUses(ts []linkSite) map[*nameBinding][]nameUse {
|
func nameUses(ts []templateSite) map[*nameBinding][]nameUse {
|
||||||
uses := map[*nameBinding][]nameUse{}
|
uses := map[*nameBinding][]nameUse{}
|
||||||
for _, s := range ts {
|
for _, s := range ts {
|
||||||
for _, r := range namedReads(s.t) {
|
for _, r := range namedReads(s.t) {
|
||||||
@@ -171,18 +171,18 @@ func columnReadOf(t *template, targets map[*nameBinding]nameTarget) *columnRead
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
a := ops[0].arms[0]
|
a := ops[0].arms[0]
|
||||||
head, category, tail := t.head(a.head), a.head[min(1, len(a.head)):], a.tail
|
start, category, tail := t.startOf(a.head), a.head[min(1, len(a.head)):], a.tail
|
||||||
switch b := a.named; {
|
switch b := a.named; {
|
||||||
case a.kind == namedRead && b.bindsField():
|
case a.kind == namedRead && b.bindsField():
|
||||||
head, category = b.binder, ""
|
start, category = b.binder, ""
|
||||||
tail = append(append([]string{splitArm(b.ref, nil).head}, targets[b].tail...), a.tail...)
|
tail = append(append([]string{splitArm(b.ref, nil).head}, targets[b].tail...), a.tail...)
|
||||||
case a.kind == namedRead:
|
case a.kind == namedRead:
|
||||||
head, category = targets[b].head, b.binder.link.refs[b.ref].head[1:]
|
start, category = targets[b].start, b.binder.refs.byName[b.ref].head[1:]
|
||||||
tail = append(targets[b].tail[:len(targets[b].tail):len(targets[b].tail)], a.tail...)
|
tail = append(targets[b].tail[:len(targets[b].tail):len(targets[b].tail)], a.tail...)
|
||||||
case !grammar.IsRef(a.head):
|
case !grammar.IsRef(a.head):
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if target, isTemplate := head.(*template); isTemplate && target.isRecord && len(tail) == 1 {
|
if target, isTemplate := start.(*template); isTemplate && target.isRecord && len(tail) == 1 {
|
||||||
return &columnRead{a: a, category: category, field: tail[0], column: target.fields[tail[0]]}
|
return &columnRead{a: a, category: category, field: tail[0], column: target.fields[tail[0]]}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
+1
-1
@@ -94,7 +94,7 @@ func readsFact(n node) string {
|
|||||||
collect(cell)
|
collect(cell)
|
||||||
}
|
}
|
||||||
case *template:
|
case *template:
|
||||||
for _, b := range n.link.refs {
|
for _, b := range n.refs.byName {
|
||||||
set[strings.TrimPrefix(b.head, "/")] = true
|
set[strings.TrimPrefix(b.head, "/")] = true
|
||||||
}
|
}
|
||||||
for _, field := range n.fields {
|
for _, field := range n.fields {
|
||||||
|
|||||||
+8
-8
@@ -64,11 +64,11 @@ func (f *Generator) loadShippedAt(segs []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// unloadedReads is every unloaded shipped category the templates of scope reference, each
|
// unloadedReads is every unloaded shipped category the templates of nodes reference, each
|
||||||
// reference read from the folder dir.
|
// reference read from the folder dir.
|
||||||
func unloadedReads(root *folder, dir []string, scope nodeScope) []unloadedCategory {
|
func unloadedReads(root *folder, dir []string, nodes nodeSet) []unloadedCategory {
|
||||||
var out []unloadedCategory
|
var out []unloadedCategory
|
||||||
_ = scope(func(_ string, n node) error {
|
_ = nodes(func(_ string, n node) error {
|
||||||
t, isTemplate := n.(*template)
|
t, isTemplate := n.(*template)
|
||||||
if !isTemplate {
|
if !isTemplate {
|
||||||
return nil
|
return nil
|
||||||
@@ -76,7 +76,7 @@ func unloadedReads(root *folder, dir []string, scope nodeScope) []unloadedCatego
|
|||||||
for _, name := range refTokens(t.tokens) {
|
for _, name := range refTokens(t.tokens) {
|
||||||
segs, err := refSegments(name, dir)
|
segs, err := refSegments(name, dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue // the link reports it
|
continue // resolveRefs reports it
|
||||||
}
|
}
|
||||||
if u, unloaded := unloadedAt(root, segs); unloaded {
|
if u, unloaded := unloadedAt(root, segs); unloaded {
|
||||||
out = append(out, u)
|
out = append(out, u)
|
||||||
@@ -88,7 +88,7 @@ func unloadedReads(root *folder, dir []string, scope nodeScope) []unloadedCatego
|
|||||||
}
|
}
|
||||||
|
|
||||||
// loadShipped loads the shipped categories wanted and, for each category it loads, every
|
// loadShipped loads the shipped categories wanted and, for each category it loads, every
|
||||||
// category it reads and its table family, then binds all it loaded as a whole load binds.
|
// category it reads and its table family, then runs all it loaded through a whole load's pipeline.
|
||||||
// TestEveryShippedCategoryLoadsAlone reaches each category, so a failure here means a stale
|
// TestEveryShippedCategoryLoadsAlone reaches each category, so a failure here means a stale
|
||||||
// index.
|
// index.
|
||||||
func loadShipped(root *folder, wanted []unloadedCategory) {
|
func loadShipped(root *folder, wanted []unloadedCategory) {
|
||||||
@@ -104,14 +104,14 @@ func loadShipped(root *folder, wanted []unloadedCategory) {
|
|||||||
panic(invariant.Broken("shipped %s: %v; after a change under data/, regenerate shippedindex.go", categoryPath(u.dir, u.name), err))
|
panic(invariant.Broken("shipped %s: %v; after a change under data/, regenerate shippedindex.go", categoryPath(u.dir, u.name), err))
|
||||||
}
|
}
|
||||||
sites = append(sites, site)
|
sites = append(sites, site)
|
||||||
queue = append(queue, unloadedReads(root, u.dir, sitesScope([]categorySite{site}))...)
|
queue = append(queue, unloadedReads(root, u.dir, siteNodes([]categorySite{site}))...)
|
||||||
queue = append(queue, u.linkedTables(e)...)
|
queue = append(queue, u.linkedTables(e)...)
|
||||||
}
|
}
|
||||||
if len(sites) == 0 {
|
if len(sites) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := categoryBinding(sites, root.children).bind(); err != nil {
|
if err := categoryPipeline(sites, root.children).run(); err != nil {
|
||||||
panic(invariant.Broken("binding shipped categories: %v; after a change under data/, regenerate shippedindex.go", err))
|
panic(invariant.Broken("loading shipped categories: %v; after a change under data/, regenerate shippedindex.go", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ func (s *structShape) compileRecord(root *folder, t reflect.Type, label string,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", label, err)
|
return fmt.Errorf("%s: %w", label, err)
|
||||||
}
|
}
|
||||||
if err := bindInline(root, n, label, true); err != nil {
|
if err := loadInline(root, n, label, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
record, err := recordOf(n)
|
record, err := recordOf(n)
|
||||||
@@ -323,7 +323,7 @@ func (k columnKind) holds(v proven.Facts) bool {
|
|||||||
func (s *structShape) fill(state *drawstate.State, v reflect.Value) {
|
func (s *structShape) fill(state *drawstate.State, v reflect.Value) {
|
||||||
if s.record != nil {
|
if s.record != nil {
|
||||||
var frames frameStack
|
var frames frameStack
|
||||||
for i, c := range renderRecord(state, s.record, renderScope{frames: &frames}).columns {
|
for i, c := range renderRecord(state, s.record, renderEnv{frames: &frames}).columns {
|
||||||
setColumn(fieldAt(v, s.fieldIndexes[i]), c)
|
setColumn(fieldAt(v, s.fieldIndexes[i]), c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,11 +79,10 @@
|
|||||||
| 89 | 0.1.0 | | **Fail `New` without `WithSeed`, naming the option, and seed the CLI from the system.** | 3 | 3 | 6 | 7 | 10.1 | 16.0 |
|
| 89 | 0.1.0 | | **Fail `New` without `WithSeed`, naming the option, and seed the CLI from the system.** | 3 | 3 | 6 | 7 | 10.1 | 16.0 |
|
||||||
| 127 | 0.1.0 | | **Give a Swedish street only postal codes whose delivery digit means street or rural delivery, dropping codes whose delivery digit is 9, or record why they stay.** | 3 | 3 | 4 | 6 | 4 | 14.4 |
|
| 127 | 0.1.0 | | **Give a Swedish street only postal codes whose delivery digit means street or rural delivery, dropping codes whose delivery digit is 9, or record why they stay.** | 3 | 3 | 4 | 6 | 4 | 14.4 |
|
||||||
| 110 | 0.1.0 | | **Refuse an empty segment in a path `Fake`, `FakeRecord` and the CLI take, as a reference does: `sv_SE.person.` reports `path has an empty segment`.** | 1 | 1 | 3 | 6 | 6 | 13.8 |
|
| 110 | 0.1.0 | | **Refuse an empty segment in a path `Fake`, `FakeRecord` and the CLI take, as a reference does: `sv_SE.person.` reports `path has an empty segment`.** | 1 | 1 | 3 | 6 | 6 | 13.8 |
|
||||||
| 120 | 0.1.0 | | **Rename the engine's overloaded nouns, so `scope`, `binding`, `link`, `pick` and `head` each name one thing.** | 2 | 4 | 1 | 8 | 3.3 | 13.4 |
|
|
||||||
| 123 | 0.1.0 | | **Name the name fences by what they refuse, and state each rule at `checkOnce`, `checkTemplateOnce` and `checkUses`, so they read without the README's Names list.** | 1 | 2 | 1 | 7 | 3.4, 3.3 | 13.1 |
|
| 123 | 0.1.0 | | **Name the name fences by what they refuse, and state each rule at `checkOnce`, `checkTemplateOnce` and `checkUses`, so they read without the README's Names list.** | 1 | 2 | 1 | 7 | 3.4, 3.3 | 13.1 |
|
||||||
| 79 | 0.1.0 | decision | **Beside a `--data-path`, load in `New` the user's categories, the shipped ones they override or read, and the shipped ones whose reads reach an overridden one; load the rest on first reach.** | 6 | 6 | 7 | 8 | 13 | 12.9 |
|
| 79 | 0.1.0 | decision | **Beside a `--data-path`, load in `New` the user's categories, the shipped ones they override or read, and the shipped ones whose reads reach an overridden one; load the rest on first reach.** | 6 | 6 | 7 | 8 | 13 | 12.9 |
|
||||||
| 121 | 0.1.0 | | **Move `renderScope`'s methods out of `pick.go` into `scope.go`, a table's route and draw steps out of `path.go` into `table.go`, and `arm`, `op` and `compileArm` out of `format.go` into a file named for the compiled read.** | 1 | 3 | 1 | 7 | 3.1 | 12.8 |
|
| 121 | 0.1.0 | | **Move `renderEnv`'s methods out of `pick.go` into `env.go`, a table's route and draw steps out of `path.go` into `table.go`, and `arm`, `op` and `compileArm` out of `format.go` into a file named for the compiled read.** | 1 | 3 | 1 | 7 | 3.1 | 12.8 |
|
||||||
| 122 | 0.1.0 | | **Rewrite `columnReadOf` so each case says which column it reads, with a named step stripping the sigil, and say on `refBinding` that its head carries the `/`.** | 2 | 2 | 1 | 7 | 3.4, 3.3 | 12.1 |
|
| 122 | 0.1.0 | | **Rewrite `columnReadOf` so each case says which column it reads, with a named step stripping the sigil, and say on `resolvedRef` that its head carries the `/`.** | 2 | 2 | 1 | 7 | 3.4, 3.3 | 12.1 |
|
||||||
| 124 | 0.1.0 | | **State the `..` step-down rule where `drawSteps` carries `climbed`, and split `route`'s draw condition into named parts.** | 2 | 2 | 1 | 6 | 3.4 | 10.3 |
|
| 124 | 0.1.0 | | **State the `..` step-down rule where `drawSteps` carries `climbed`, and split `route`'s draw condition into named parts.** | 2 | 2 | 1 | 6 | 3.4 | 10.3 |
|
||||||
| 126 | 0.1.0 | | **Name in `geo-se.py` the source of its delivery-digit rule and of its 200 floor.** | 1 | 1 | 1 | 5 | 3.4 | 9.8 |
|
| 126 | 0.1.0 | | **Name in `geo-se.py` the source of its delivery-digit rule and of its 200 floor.** | 1 | 1 | 1 | 5 | 3.4 | 9.8 |
|
||||||
| 125 | 0.1.0 | | **Delete the doc lines the panel read as restating their code.** | 1 | 2 | 1 | 5 | 3.4 | 9.5 |
|
| 125 | 0.1.0 | | **Delete the doc lines the panel read as restating their code.** | 1 | 2 | 1 | 5 | 3.4 | 9.5 |
|
||||||
@@ -115,7 +114,7 @@
|
|||||||
| 22 | 0.2.0 | | **Give `url` and `email` a path that draws only domains nobody can register, keeping the wide set as the default.** | 3 | 4 | 7 | 8 | 16 | 14.0 |
|
| 22 | 0.2.0 | | **Give `url` and `email` a path that draws only domains nobody can register, keeping the wide set as the default.** | 3 | 4 | 7 | 8 | 16 | 14.0 |
|
||||||
| 46 | 0.2.0 | | **Report the same error every load for a table with two bad options, and for a folder with two unnamed rows files.** | 2 | 2 | 3 | 7 | 6, 10 | 14.0 |
|
| 46 | 0.2.0 | | **Report the same error every load for a table with two bad options, and for a folder with two unnamed rows files.** | 2 | 2 | 3 | 7 | 6, 10 | 14.0 |
|
||||||
| 107 | 0.2.0 | decision | **Move the template engine into `internal/`, leaving the root the public API and its options.** | 6 | 9 | 3 | 8 | 3.2, 3.4 | 12.2 |
|
| 107 | 0.2.0 | decision | **Move the template engine into `internal/`, leaving the root the public API and its options.** | 6 | 9 | 3 | 8 | 3.2, 3.4 | 12.2 |
|
||||||
| 112 | 0.2.0 | | **Check a calc's field operands where its template links, beside its name operands, once item 36 reports every mistake at once.** | 2 | 2 | 1 | 6 | 3.2 | 10.3 |
|
| 112 | 0.2.0 | | **Check a calc's field operands where its template resolves, beside its name operands, once item 36 reports every mistake at once.** | 2 | 2 | 1 | 6 | 3.2 | 10.3 |
|
||||||
| 94 | 0.2.0 | | **Refuse a name whose every read passes one deeper level, naming the binding of that level: `{place as p}{p.geo.town.name} {p.geo.town.zip}` is `{place.geo.town as t}{t.name} {t.zip}`.** | 3 | 3 | 3 | 5 | 5.7 | 10.2 |
|
| 94 | 0.2.0 | | **Refuse a name whose every read passes one deeper level, naming the binding of that level: `{place as p}{p.geo.town.name} {p.geo.town.zip}` is `{place.geo.town as t}{t.name} {t.zip}`.** | 3 | 3 | 3 | 5 | 5.7 | 10.2 |
|
||||||
| 108 | 0.2.0 | defect | **Name item 26 in the decision "No builtin reads the clock, so a date is bounded by days, never by an age", as the item that ends its conflict with goal 4.** | 1 | 1 | 1 | 4 | 3.4 | 8.0 |
|
| 108 | 0.2.0 | defect | **Name item 26 in the decision "No builtin reads the clock, so a date is bounded by days, never by an age", as the item that ends its conflict with goal 4.** | 1 | 1 | 1 | 4 | 3.4 | 8.0 |
|
||||||
| 14 | 0.3.0 | | **Spell `misc.creditcard`'s digit runs `{digits(n)}`.** | 2 | 2 | 4 | 6 | 5.7 | 15.1 |
|
| 14 | 0.3.0 | | **Spell `misc.creditcard`'s digit runs `{digits(n)}`.** | 2 | 2 | 4 | 6 | 5.7 | 15.1 |
|
||||||
@@ -285,10 +284,6 @@ Goal 10.1 has the caller supply the seed, and today `New` without `WithSeed` see
|
|||||||
|
|
||||||
Today `fejkdata 'sv_SE.person.'` reports `no field ""`, while `{/sv_SE.person.}` reports `path has an empty segment`. `grammar.CheckSegments` is the check a reference, a sibling path and a step up `..` run.
|
Today `fejkdata 'sv_SE.person.'` reports `no field ""`, while `{/sv_SE.person.}` reports `path has an empty segment`. `grammar.CheckSegments` is the check a reference, a sibling path and a step up `..` run.
|
||||||
|
|
||||||
### 120. Rename the engine's overloaded nouns, so `scope`, `binding`, `link`, `pick` and `head` each name one thing.
|
|
||||||
|
|
||||||
From the panel of 2026-10-05: `nodeScope`, `nameScope` and `renderScope`; `binding`, `nameBinding` and `refBinding`; `linkTemplates` beside `linkTables` and `rows.Link`; `pick` the function beside a named pick; `head` a string on `arm` and a node on `nameTarget`.
|
|
||||||
|
|
||||||
### 123. Name the name fences by what they refuse, and state each rule at `checkOnce`, `checkTemplateOnce` and `checkUses`, so they read without the README's Names list.
|
### 123. Name the name fences by what they refuse, and state each rule at `checkOnce`, `checkTemplateOnce` and `checkUses`, so they read without the README's Names list.
|
||||||
|
|
||||||
Seven of the nine seats in the panel of 2026-10-05 needed the README's Names list open to learn what these refuse.
|
Seven of the nine seats in the panel of 2026-10-05 needed the README's Names list open to learn what these refuse.
|
||||||
@@ -299,11 +294,11 @@ Needs item 77, which makes an override fail unless it says it replaces.
|
|||||||
|
|
||||||
The decision "With only the shipped set, a category loads on the first call reaching it; beside a `--data-path`, every category loads in `New`" parses the whole shipped set on every `--data-path` run, about 45 ms today and seconds once items 61 and 78 land. Goal 6 needs only the user's categories, the shipped ones they override, the shipped ones they read, and the shipped ones whose reads reach an overridden category, so the index would carry each category's reads.
|
The decision "With only the shipped set, a category loads on the first call reaching it; beside a `--data-path`, every category loads in `New`" parses the whole shipped set on every `--data-path` run, about 45 ms today and seconds once items 61 and 78 land. Goal 6 needs only the user's categories, the shipped ones they override, the shipped ones they read, and the shipped ones whose reads reach an overridden category, so the index would carry each category's reads.
|
||||||
|
|
||||||
### 121. Move `renderScope`'s methods out of `pick.go` into `scope.go`, a table's route and draw steps out of `path.go` into `table.go`, and `arm`, `op` and `compileArm` out of `format.go` into a file named for the compiled read.
|
### 121. Move `renderEnv`'s methods out of `pick.go` into `env.go`, a table's route and draw steps out of `path.go` into `table.go`, and `arm`, `op` and `compileArm` out of `format.go` into a file named for the compiled read.
|
||||||
|
|
||||||
Both architects in the panel of 2026-10-05 ranked this first. Revise the README's Layout block with it.
|
Both architects in the panel of 2026-10-05 ranked this first. Revise the README's Layout block with it.
|
||||||
|
|
||||||
### 122. Rewrite `columnReadOf` so each case says which column it reads, with a named step stripping the sigil, and say on `refBinding` that its head carries the `/`.
|
### 122. Rewrite `columnReadOf` so each case says which column it reads, with a named step stripping the sigil, and say on `resolvedRef` that its head carries the `/`.
|
||||||
|
|
||||||
Seven of the nine seats in the panel of 2026-10-05 named it: `a.head[min(1, len(a.head)):]` and `refs[b.ref].head[1:]` strip a sigil no comment names.
|
Seven of the nine seats in the panel of 2026-10-05 named it: `a.head[min(1, len(a.head)):]` and `refs[b.ref].head[1:]` strip a sigil no comment names.
|
||||||
|
|
||||||
@@ -317,7 +312,7 @@ Seven of the nine seats in the panel of 2026-10-05 named `route` and `drawSteps`
|
|||||||
|
|
||||||
### 125. Delete the doc lines the panel read as restating their code.
|
### 125. Delete the doc lines the panel read as restating their code.
|
||||||
|
|
||||||
Named in the panel of 2026-10-05: `Fake` in `inline.go`, `Record.Columns`, `pick` in `render.go`, `compileInto`, `mergeChildren`, `tableOptions`, `randomBytes`, `frameStack.push`, `checkCalcFields`, the callers `recordOf`'s doc lists, and "Each step returns what it builds" on `linkTemplates`.
|
Named in the panel of 2026-10-05: `Fake` in `inline.go`, `Record.Columns`, `drawItem` in `render.go`, `compileInto`, `mergeChildren`, `tableOptions`, `randomBytes`, `frameStack.push`, `checkCalcFields`, the callers `recordOf`'s doc lists, and "Each step returns what it builds" on `resolveTemplates`.
|
||||||
|
|
||||||
### 58. Ship prebuilt binaries, a container image, and packages for Homebrew, Scoop and the biggest Linux package managers, so the CLI needs no Go.
|
### 58. Ship prebuilt binaries, a container image, and packages for Homebrew, Scoop and the biggest Linux package managers, so the CLI needs no Go.
|
||||||
|
|
||||||
@@ -426,9 +421,9 @@ Require the path step to reach a sibling category.
|
|||||||
|
|
||||||
The engine returns column values and the root builds `Record`. Revises the decision "The template engine stays the root package until item 107; what reads no engine type, a table's rows included, sits in `internal/`", and, if the vocabulary moves, "The vocabulary sits below `doc.go`'s package clause, not in the package doc".
|
The engine returns column values and the root builds `Record`. Revises the decision "The template engine stays the root package until item 107; what reads no engine type, a table's rows included, sits in `internal/`", and, if the vocabulary moves, "The vocabulary sits below `doc.go`'s package clause, not in the package doc".
|
||||||
|
|
||||||
### 112. Check a calc's field operands where its template links, beside its name operands, once item 36 reports every mistake at once.
|
### 112. Check a calc's field operands where its template resolves, beside its name operands, once item 36 reports every mistake at once.
|
||||||
|
|
||||||
Needs item 36. `checkCalcFields` runs at compile and `checkCalcNames` at link, two passes over one rule. Moving the field check to link before item 36 would change which of two mistakes in two category files a load reports, since every category compiles before any links.
|
Needs item 36. `checkCalcFields` runs at compile and `checkCalcNames` in `resolveTemplates`, two passes over one rule. Moving the field check there before item 36 would change which of two mistakes in two category files a load reports, since every category compiles before any resolves.
|
||||||
|
|
||||||
### 94. Refuse a name whose every read passes one deeper level, naming the binding of that level: `{place as p}{p.geo.town.name} {p.geo.town.zip}` is `{place.geo.town as t}{t.name} {t.zip}`.
|
### 94. Refuse a name whose every read passes one deeper level, naming the binding of that level: `{place as p}{p.geo.town.name} {p.geo.town.zip}` is `{place.geo.town as t}{t.name} {t.zip}`.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/larvit/fejkdata/internal/proven"
|
"github.com/larvit/fejkdata/internal/proven"
|
||||||
)
|
)
|
||||||
|
|
||||||
// valueProof proves what typed columns and their calc operands hold, each node once per scope.
|
// valueProof proves what typed columns and their calc operands hold, each node once per nodeSet.
|
||||||
type valueProof struct {
|
type valueProof struct {
|
||||||
memo map[node]proven.Facts
|
memo map[node]proven.Facts
|
||||||
columnMemo map[node]proven.Facts
|
columnMemo map[node]proven.Facts
|
||||||
|
|||||||
Reference in New Issue
Block a user