Link each template in steps that each return what they build, create a name scope with its owner in one place, and settle each record's columns at load, so Generator.records goes
This commit is contained in:
@@ -148,6 +148,7 @@ func (b binding) bind() error {
|
||||
if err := checkNoCycles(b.scope); err != nil {
|
||||
return err
|
||||
}
|
||||
settleRecords(b.scope)
|
||||
if !b.typedByGo {
|
||||
if err := checkColumns(b.scope); err != nil {
|
||||
return err
|
||||
|
||||
+3
-3
@@ -67,7 +67,7 @@ func checkColumns(s nodeScope) error {
|
||||
checked[column] = true
|
||||
items, _ := columnItems(column)
|
||||
for _, it := range items {
|
||||
if r := it.link.readsColumn; r != nil && r.category != "" {
|
||||
if r := it.readsColumn; r != nil && r.category != "" {
|
||||
if err := check(r.category, r.field, r.column); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -110,8 +110,8 @@ func columnDatatype(n node) DataType {
|
||||
|
||||
// itemDatatype is the datatype a column item declares, else that of the column it reads.
|
||||
func itemDatatype(t *template) DataType {
|
||||
if t.datatype == DataTypeString && t.link.readsColumn != nil {
|
||||
return columnDatatype(t.link.readsColumn.column)
|
||||
if t.datatype == DataTypeString && t.readsColumn != nil {
|
||||
return columnDatatype(t.readsColumn.column)
|
||||
}
|
||||
return t.datatype
|
||||
}
|
||||
|
||||
+1
-2
@@ -28,12 +28,11 @@ var ErrNoData = errors.New("no data: WithoutShippedData needs at least one WithD
|
||||
// It is safe for concurrent use; a seeded sequence is reproducible only when drawn
|
||||
// from one goroutine.
|
||||
type Generator struct {
|
||||
// mu guards drawState, records, structs and root, which gains a shipped category on the
|
||||
// mu guards drawState, structs and root, which gains a shipped category on the
|
||||
// first call reaching it.
|
||||
mu sync.Mutex
|
||||
drawState *drawstate.State
|
||||
root folder // the categories as the node a path walks from, owned here so a walk allocates none
|
||||
records map[node]recordShape
|
||||
structs map[reflect.Type]structResult
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/larvit/fejkdata/internal/builtinfunc"
|
||||
"github.com/larvit/fejkdata/internal/grammar"
|
||||
"github.com/larvit/fejkdata/internal/invariant"
|
||||
)
|
||||
|
||||
// checkFunc validates a call at compile time: a known builtin, its args, and, for calc, the
|
||||
@@ -224,7 +225,7 @@ const (
|
||||
)
|
||||
|
||||
// splitArm splits one name into head and tail. refs maps a reference to what
|
||||
// linkRefs resolved it to; before linking, a reference is whole.
|
||||
// resolveLink resolved it to; before linking, a reference is whole.
|
||||
func splitArm(name string, refs map[string]refBinding) arm {
|
||||
if grammar.IsRef(name) {
|
||||
b, linked := refs[name]
|
||||
@@ -278,43 +279,75 @@ type formatOps struct {
|
||||
grow int
|
||||
}
|
||||
|
||||
func (c *formatOps) function(tok grammar.Token, refs map[string]refBinding, isName func(string) bool) {
|
||||
var operands []arm
|
||||
for _, operand := range builtinfunc.Operands(tok.Fn, tok.Args) {
|
||||
a := splitArm(operand, refs)
|
||||
if isName(a.head) {
|
||||
a.kind = namedRead
|
||||
}
|
||||
operands = append(operands, a)
|
||||
}
|
||||
c.ops = append(c.ops, op{Token: tok, call: builtinfunc.Prep(tok.Fn, tok.Args), operands: operands})
|
||||
}
|
||||
|
||||
func (c *formatOps) field(tok grammar.Token, refs map[string]refBinding, isName func(string) bool) {
|
||||
arms := make([]arm, len(tok.Arms))
|
||||
for i, name := range tok.Arms {
|
||||
arms[i] = splitArm(name, refs)
|
||||
if isName(arms[i].head) {
|
||||
arms[i].kind = namedRead
|
||||
}
|
||||
}
|
||||
c.ops = append(c.ops, op{Token: tok, arms: arms})
|
||||
}
|
||||
|
||||
// compileFormat compiles a parsed format; a {ref as n} token compiles to no op. Call
|
||||
// checkTokens first: it is what proves every token valid.
|
||||
func compileFormat(toks []grammar.Token, refs map[string]refBinding, isName func(head string) bool) formatOps {
|
||||
// compileFormat compiles t's parsed format against its link and its names' 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) {
|
||||
var c formatOps
|
||||
for _, tok := range toks {
|
||||
for _, tok := range t.tokens {
|
||||
o := op{Token: tok}
|
||||
var err error
|
||||
switch tok.Kind {
|
||||
case grammar.LiteralRun:
|
||||
c.grow += len(tok.Lit)
|
||||
c.ops = append(c.ops, op{Token: tok})
|
||||
case grammar.BuiltinCall:
|
||||
c.function(tok, refs, isName)
|
||||
o.call = builtinfunc.Prep(tok.Fn, tok.Args)
|
||||
o.operands, err = t.compileArms(builtinfunc.Operands(tok.Fn, tok.Args), targets)
|
||||
case grammar.NameRead:
|
||||
c.field(tok, refs, isName)
|
||||
o.arms, err = t.compileArms(tok.Arms, targets)
|
||||
case grammar.NameBind:
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return c, fmt.Errorf("token {%s}: %w", tok.Body, err)
|
||||
}
|
||||
c.ops = append(c.ops, o)
|
||||
}
|
||||
return c
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (t *template) compileArms(names []string, targets map[*nameBinding]nameTarget) ([]arm, error) {
|
||||
if len(names) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
arms := make([]arm, len(names))
|
||||
for i, name := range names {
|
||||
a, err := t.compileArm(name, targets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arms[i] = a
|
||||
}
|
||||
return arms, nil
|
||||
}
|
||||
|
||||
// compileArm compiles one read: a path from the head it names, or from what the name it reads
|
||||
// through binds.
|
||||
func (t *template) compileArm(name string, targets map[*nameBinding]nameTarget) (arm, error) {
|
||||
a := splitArm(name, t.link.refs)
|
||||
if !t.isName(a.head) {
|
||||
head := t.head(a.head)
|
||||
if head == nil {
|
||||
panic(invariant.Broken("{%s} reads a head nothing bound", a.spelling))
|
||||
}
|
||||
w := compilePath(head, a.tail)
|
||||
a.steps, a.leaves = w.steps, w.leaves
|
||||
return a, nil
|
||||
}
|
||||
b := t.nameScope.lookup(a.head)
|
||||
if grammar.HasSelector(a.tail) {
|
||||
return a, fmt.Errorf("a path through name %q may not select a row; read it directly, {%s.%s}, or bind the row to a name of its own", a.head, b.ref, grammar.JoinSegments(a.tail))
|
||||
}
|
||||
target := targets[b]
|
||||
full := append(target.tail[:len(target.tail):len(target.tail)], a.tail...)
|
||||
if err := checkPathResolves(target.head, full, a.head); err != nil {
|
||||
return a, err
|
||||
}
|
||||
w := compilePath(target.head, full)
|
||||
a.kind, a.named, a.steps, a.leaves = namedRead, b, w.steps, w.leaves
|
||||
a.levels = make([]string, len(full)+1)
|
||||
for i := range a.levels {
|
||||
a.levels[i] = grammar.JoinSegments(full[:i])
|
||||
}
|
||||
a.path = grammar.JoinSegments(full)
|
||||
return a, nil
|
||||
}
|
||||
|
||||
@@ -122,26 +122,16 @@ func refuseFolderRefs(scope nodeScope) error {
|
||||
})
|
||||
}
|
||||
|
||||
// linkNodeRefs binds the references in an inline node's templates against the
|
||||
// loaded tree.
|
||||
// linkNodeRefs links the templates of an inline node against the loaded tree.
|
||||
func linkNodeRefs(scope nodeScope, root map[string]node) error {
|
||||
var ts []linkSite
|
||||
if err := scope(func(label string, m node) error {
|
||||
if t, ok := m.(*template); ok {
|
||||
return linkTemplate(nil, label, "", t, root)
|
||||
ts = append(ts, linkSite{t: t, label: label})
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, pass := range linkPasses {
|
||||
if err := scope(func(label string, m node) error {
|
||||
if t, ok := m.(*template); ok {
|
||||
return pass(label, t)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return linkTemplates(ts, root)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ type nameScope struct {
|
||||
}
|
||||
|
||||
type nameBinding struct {
|
||||
// Filled by `bindNames`, from the compiled category:
|
||||
name string
|
||||
ref string // what it binds, as written: a reference, or a path into a field of binder
|
||||
body string
|
||||
@@ -27,9 +28,10 @@ type nameBinding struct {
|
||||
scope *nameScope
|
||||
index int
|
||||
binder *template
|
||||
uses []nameUse
|
||||
head node
|
||||
tail []string
|
||||
|
||||
// Filled by `linkTemplates`, from the assembled tree:
|
||||
head node
|
||||
tail []string
|
||||
// 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.
|
||||
addressed map[string]string
|
||||
@@ -61,17 +63,16 @@ func (sc *nameScope) lookup(name string) *nameBinding {
|
||||
// choice's item, a name a field spells too, a read no field or name answers, and a binding
|
||||
// read by nothing.
|
||||
func bindNames(root node) error {
|
||||
top := &nameScope{owner: root}
|
||||
scopes := []*nameScope{top}
|
||||
var scopes []*nameScope
|
||||
var gather func(n node, scope *nameScope, inChoice bool, where string) error
|
||||
gather = func(n node, scope *nameScope, inChoice bool, where string) error {
|
||||
if t, isTemplate := n.(*template); n == root || isTemplate && t.repeat > 1 {
|
||||
scope = &nameScope{up: scope, owner: n}
|
||||
scopes = append(scopes, scope)
|
||||
inChoice = false
|
||||
}
|
||||
switch t := n.(type) {
|
||||
case *template:
|
||||
if t.repeat > 1 && n != root {
|
||||
scope = &nameScope{up: scope, owner: t}
|
||||
scopes = append(scopes, scope)
|
||||
inChoice = false
|
||||
}
|
||||
t.nameScope = scope
|
||||
if err := scope.bindAll(t, inChoice, where); err != nil {
|
||||
return err
|
||||
@@ -82,7 +83,7 @@ func bindNames(root node) error {
|
||||
_, isChoice := n.(*choice)
|
||||
return eachContained(n, where, func(c node, where string) error { return gather(c, scope, inChoice || isChoice, where) })
|
||||
}
|
||||
if err := gather(root, top, false, ""); err != nil {
|
||||
if err := gather(root, nil, false, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, sc := range scopes[1:] {
|
||||
@@ -92,15 +93,11 @@ func bindNames(root node) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := resolveReads(root, "", scopes); err != nil {
|
||||
reads, err := resolveReads(root, scopes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, sc := range scopes {
|
||||
if err := sc.settle(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return checkNameUses(scopes, reads)
|
||||
}
|
||||
|
||||
// bindAll binds every name t's tokens bind.
|
||||
@@ -133,16 +130,30 @@ func (sc *nameScope) spelled() string {
|
||||
return "in one repeat"
|
||||
}
|
||||
|
||||
// settle refuses a binding read by nothing, and hands a scope binding names to its owner, which
|
||||
// renders a frame of it.
|
||||
func (sc *nameScope) settle() error {
|
||||
for _, b := range sc.order {
|
||||
if len(b.uses) == 0 {
|
||||
return fmt.Errorf("%stoken {%s}: nothing reads name %q; drop the token", b.where, b.body, b.name)
|
||||
// checkNameUses refuses a binding read by nothing, then each binding checkUses refuses.
|
||||
func checkNameUses(scopes []*nameScope, reads map[*nameBinding][]nameUse) error {
|
||||
for _, sc := range scopes {
|
||||
for _, b := range sc.order {
|
||||
if len(reads[b]) == 0 {
|
||||
return fmt.Errorf("%stoken {%s}: nothing reads name %q; drop the token", b.where, b.body, b.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if t, isTemplate := sc.owner.(*template); isTemplate && len(sc.order) > 0 {
|
||||
t.ownNameScope = sc
|
||||
for _, sc := range scopes {
|
||||
for _, b := range sc.order {
|
||||
if err := b.checkUses(reads[b]); err != nil {
|
||||
return fmt.Errorf("%stoken {%s}: %w", b.where, b.body, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ownScope is the scope t renders a frame of, nil where that scope binds no name: a category's,
|
||||
// on its root, or a repeat's, per iteration.
|
||||
func (t *template) ownScope() *nameScope {
|
||||
if sc := t.nameScope; sc != nil && sc.owner == node(t) && len(sc.order) > 0 {
|
||||
return sc
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -162,34 +173,40 @@ func eachContained(n node, where string, fn func(c node, where string) error) er
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveReads answers each unbound read under n with a name its template sees, fields before the
|
||||
// format as compile reports them, and refuses a field spelling a name. scopes are every scope of
|
||||
// the category, which a refusal searches for a name bound where the read cannot see it.
|
||||
func resolveReads(n node, where string, scopes []*nameScope) error {
|
||||
t, isTemplate := n.(*template)
|
||||
if err := eachContained(n, where, func(c node, where string) error { return resolveReads(c, where, scopes) }); err != nil {
|
||||
return err
|
||||
}
|
||||
if !isTemplate {
|
||||
// resolveReads answers each unbound read under root with a name its template sees, fields before
|
||||
// the format as compile reports them, returning every binding's reads, and refuses a field spelling
|
||||
// a name. scopes are every scope of the category, which a refusal searches for a name bound where
|
||||
// the read cannot see it.
|
||||
func resolveReads(root node, scopes []*nameScope) (map[*nameBinding][]nameUse, error) {
|
||||
reads := map[*nameBinding][]nameUse{}
|
||||
var resolve func(n node, where string) error
|
||||
resolve = func(n node, where string) error {
|
||||
t, isTemplate := n.(*template)
|
||||
if err := eachContained(n, where, resolve); err != nil {
|
||||
return err
|
||||
}
|
||||
if !isTemplate {
|
||||
return nil
|
||||
}
|
||||
for _, name := range sortedNames(t.fields) {
|
||||
if b := t.nameScope.lookup(name); b != nil {
|
||||
holder := "the root template"
|
||||
if where != "" {
|
||||
holder = strings.TrimSuffix(where, ": ")
|
||||
}
|
||||
return fmt.Errorf("%stoken {%s}: name %q is a field of %s too; options, fields and names share one namespace, so rename one", b.where, b.body, name, holder)
|
||||
}
|
||||
}
|
||||
for _, u := range t.unbound {
|
||||
b := t.nameScope.lookup(u.head)
|
||||
if b == nil {
|
||||
return unresolved(where, u, t.nameScope, scopes)
|
||||
}
|
||||
reads[b] = append(reads[b], nameUse{tail: u.tail, in: t, operand: u.operand, noRef: u.noRef, nested: t.nameScope != b.scope})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for _, name := range sortedNames(t.fields) {
|
||||
if b := t.nameScope.lookup(name); b != nil {
|
||||
holder := "the root template"
|
||||
if where != "" {
|
||||
holder = strings.TrimSuffix(where, ": ")
|
||||
}
|
||||
return fmt.Errorf("%stoken {%s}: name %q is a field of %s too; options, fields and names share one namespace, so rename one", b.where, b.body, name, holder)
|
||||
}
|
||||
}
|
||||
for _, u := range t.unbound {
|
||||
b := t.nameScope.lookup(u.head)
|
||||
if b == nil {
|
||||
return unresolved(where, u, t.nameScope, scopes)
|
||||
}
|
||||
b.uses = append(b.uses, nameUse{tail: u.tail, in: t, operand: u.operand, noRef: u.noRef, nested: t.nameScope != b.scope})
|
||||
}
|
||||
return nil
|
||||
return reads, resolve(root, "")
|
||||
}
|
||||
|
||||
// unresolved is the refusal of u, a read no field or name answers: naming the repeat binding the
|
||||
@@ -224,18 +241,6 @@ func (t *template) isName(head string) bool {
|
||||
return !grammar.IsRef(head) && t.fields[head] == nil && t.nameScope.lookup(head) != nil
|
||||
}
|
||||
|
||||
// linkBindings resolves what each of t's bindings binds to a head node and a tail path.
|
||||
func linkBindings(t *template) {
|
||||
for _, tok := range t.tokens {
|
||||
if tok.Kind != grammar.NameBind {
|
||||
continue
|
||||
}
|
||||
b := t.nameScope.bindings[tok.Bound]
|
||||
a := splitArm(b.ref, t.link.refs)
|
||||
b.head, b.tail = t.head(a.head), a.tail
|
||||
}
|
||||
}
|
||||
|
||||
// namedReads calls fn with every read of a name t's format makes, and the token holding it.
|
||||
func namedReads(t *template, fn func(o *op, a *arm) error) error {
|
||||
for i := range t.compiled.ops {
|
||||
@@ -252,45 +257,3 @@ func namedReads(t *template, fn func(o *op, a *arm) error) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// linkPasses run in order once every template is linked, each over every template before the next
|
||||
// starts: a pass reads what the one before filled in.
|
||||
var linkPasses = []func(label string, t *template) error{linkNames, linkColumnRead, checkNameReads, checkCalcNames}
|
||||
|
||||
// linkNames compiles t's reads of a name as paths from the head its binding resolved to, once every
|
||||
// template is linked, so each binder has resolved what it binds.
|
||||
func linkNames(label string, t *template) error {
|
||||
return namedReads(t, func(o *op, a *arm) error {
|
||||
if err := linkName(t, a); err != nil {
|
||||
return fmt.Errorf("%s: token {%s}: %w", label, o.Body, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func linkName(t *template, a *arm) error {
|
||||
b := t.nameScope.lookup(a.head)
|
||||
if grammar.HasSelector(a.tail) {
|
||||
return fmt.Errorf("a path through name %q may not select a row; read it directly, {%s.%s}, or bind the row to a name of its own", a.head, b.ref, grammar.JoinSegments(a.tail))
|
||||
}
|
||||
full := append(b.tail[:len(b.tail):len(b.tail)], a.tail...)
|
||||
if err := checkPathResolves(b.head, full, a.head); err != nil {
|
||||
return err
|
||||
}
|
||||
w := compilePath(b.head, full)
|
||||
a.named, a.steps, a.leaves = b, w.steps, w.leaves
|
||||
a.levels = make([]string, len(full)+1)
|
||||
for i := range a.levels {
|
||||
a.levels[i] = grammar.JoinSegments(full[:i])
|
||||
}
|
||||
a.path = grammar.JoinSegments(full)
|
||||
if b.addressed == nil {
|
||||
b.addressed = map[string]string{}
|
||||
}
|
||||
for _, key := range append(a.levels[len(b.tail):], a.path) {
|
||||
if _, seen := b.addressed[key]; !seen {
|
||||
b.addressed[key] = a.spelling
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+7
-16
@@ -8,18 +8,9 @@ import (
|
||||
"github.com/larvit/fejkdata/internal/grammar"
|
||||
)
|
||||
|
||||
// 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
|
||||
// draw of w.
|
||||
// checkNameReads 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 draw of w.
|
||||
func checkNameReads(label string, t *template) error {
|
||||
for _, tok := range t.tokens {
|
||||
if tok.Kind != grammar.NameBind {
|
||||
continue
|
||||
}
|
||||
if err := t.nameScope.bindings[tok.Bound].checkUses(); err != nil {
|
||||
return fmt.Errorf("%s: token {%s}: %w", label, tok.Body, err)
|
||||
}
|
||||
}
|
||||
return namedReads(t, func(o *op, a *arm) error {
|
||||
if a.named.bindsField() && rendersInside(compilePath(a.named.head, a.named.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, o.Body, a.named.name, a.named.ref)
|
||||
@@ -60,13 +51,13 @@ func rendersInside(nodes []node, t *template) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// checkUses refuses a binding read once at a spot the bound spelling can stand: that spelling
|
||||
// draws the same way without the name. A bound field can stand in only where the reading
|
||||
// checkUses refuses a binding whose uses read it once at a spot the bound spelling can stand: that
|
||||
// spelling draws the same way without the name. A bound field can stand in only where the reading
|
||||
// template reaches the binder through fields. Where the spelling would be a CLI argument or tag
|
||||
// of one reference alone, that entry point's own refusal then names the bare path.
|
||||
func (b *nameBinding) checkUses() error {
|
||||
r := b.uses[0]
|
||||
if len(b.uses) > 1 || r.nested {
|
||||
func (b *nameBinding) checkUses(uses []nameUse) error {
|
||||
r := uses[0]
|
||||
if len(uses) > 1 || r.nested {
|
||||
return nil
|
||||
}
|
||||
spelling := b.ref
|
||||
|
||||
@@ -47,7 +47,7 @@ func (*nullItem) isNode() {}
|
||||
// template renders a format string, substituting {tokens} from fields. A bare
|
||||
// JSON string is a template with no fields. repeat (default 1) renders that format
|
||||
// that many times and joins the results with separator (default ""), each render
|
||||
// an independent pick. Every format compiles in `linkTemplate`.
|
||||
// an independent pick. Every format compiles in `linkTemplates`.
|
||||
type template struct {
|
||||
// Filled by `compileString`, `compileTemplate` and `table.compileRowFormat`:
|
||||
format string
|
||||
@@ -61,19 +61,21 @@ type template struct {
|
||||
unbound []unboundRead // the heads its tokens read that no field holds
|
||||
|
||||
// Filled by `bindNames`, from the compiled category:
|
||||
nameScope *nameScope // where its tokens look a name up
|
||||
ownNameScope *nameScope // the scope it renders a frame of: a category's, on its root, or a repeat's, per iteration
|
||||
nameScope *nameScope // where its tokens look a name up
|
||||
|
||||
// Filled by `linkTemplate`, from the assembled tree:
|
||||
link templateLink
|
||||
compiled formatOps
|
||||
// Filled by `linkTemplates`, from the assembled tree:
|
||||
link templateLink
|
||||
compiled formatOps
|
||||
readsColumn *columnRead // set when the format is one reference or name read alone reading a record's column
|
||||
|
||||
// Filled by `settleRecords`, once the load refused every cycle:
|
||||
columns []recordColumn // a record's, where it has fields, in name order
|
||||
}
|
||||
|
||||
// templateLink is what a template resolves to in the assembled tree.
|
||||
type templateLink struct {
|
||||
refs map[string]refBinding // each reference the format reads -> what it resolves to
|
||||
refHeads map[string]node // each refBinding.head -> the category it names
|
||||
readsColumn *columnRead // set when the format is one reference or name read alone reading a record's column
|
||||
refs map[string]refBinding // each reference the format reads -> what it resolves to
|
||||
refHeads map[string]node // each refBinding.head -> the category it names
|
||||
}
|
||||
|
||||
func (*template) isNode() {}
|
||||
|
||||
@@ -124,10 +124,11 @@ 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
|
||||
// category opened one, returning the mark that closes it, or -1.
|
||||
func (sc renderScope) renderFrame(t *template) int {
|
||||
if t.ownNameScope == nil || sc.frameOf(t.ownNameScope) != nil {
|
||||
own := t.ownScope()
|
||||
if own == nil || sc.frameOf(own) != nil {
|
||||
return -1
|
||||
}
|
||||
return sc.frames.push(newPickFrame(t.ownNameScope))
|
||||
return sc.frames.push(newPickFrame(own))
|
||||
}
|
||||
|
||||
// frameOf is the frame of scope rendering since the read entering the category, nil where none is.
|
||||
|
||||
@@ -142,15 +142,15 @@ func (f *Generator) FakeRecord(path string) (*Record, error) {
|
||||
} else if len(tail) > 0 {
|
||||
return nil, fmt.Errorf("fejkdata: %s descends into %q, a field; only a category-level template is a record", path, tail[0])
|
||||
}
|
||||
shape := f.recordShapeOf(n)
|
||||
if errors.Is(shape.err, ErrNoColumns) {
|
||||
record, err := recordOf(n)
|
||||
if errors.Is(err, ErrNoColumns) {
|
||||
ns := grammar.NameSegments(segments)
|
||||
return nil, fmt.Errorf(`fejkdata: %s %w; render it as a column of one: {"format":"","%s":"{/%s}"}`, path, shape.err, ns[len(ns)-1], path)
|
||||
return nil, fmt.Errorf(`fejkdata: %s %w; render it as a column of one: {"format":"","%s":"{/%s}"}`, path, err, ns[len(ns)-1], path)
|
||||
}
|
||||
if shape.err != nil {
|
||||
return nil, fmt.Errorf("fejkdata: %s %w", path, shape.err)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fejkdata: %s %w", path, err)
|
||||
}
|
||||
return renderRecord(f.drawState, shape.template, shape.columns, sc), nil
|
||||
return renderRecord(f.drawState, record, sc), nil
|
||||
}
|
||||
|
||||
// tableRecord walks a path's tail from a table to the table whose row is the record, and that
|
||||
@@ -171,35 +171,11 @@ func tableRecord(s *drawstate.State, t *table, tail []string) (node, renderedRow
|
||||
return n, renderedRow{}, nil
|
||||
}
|
||||
|
||||
// recordShape is what recordOf settled about a node: the template to project, its
|
||||
// columns, or why it is not a record.
|
||||
type recordShape struct {
|
||||
template *template
|
||||
columns []Column
|
||||
err error
|
||||
}
|
||||
|
||||
// recordShapeOf fences a node once and remembers the answer. Callers hold the
|
||||
// generator's lock.
|
||||
func (f *Generator) recordShapeOf(n node) recordShape {
|
||||
if shape, done := f.records[n]; done {
|
||||
return shape
|
||||
}
|
||||
t, columns, err := recordOf(n)
|
||||
shape := recordShape{template: t, columns: columns, err: err}
|
||||
if f.records == nil {
|
||||
f.records = map[node]recordShape{}
|
||||
}
|
||||
f.records[n] = shape
|
||||
return shape
|
||||
}
|
||||
|
||||
// RecordTemplate is an inline record compiled, referenced and validated once,
|
||||
// ready to render many times with [RecordTemplate.Fake].
|
||||
type RecordTemplate struct {
|
||||
g *Generator
|
||||
template *template
|
||||
columns []Column
|
||||
}
|
||||
|
||||
// Fake renders the record, one pick of each name across its columns.
|
||||
@@ -207,7 +183,7 @@ func (t *RecordTemplate) Fake() *Record {
|
||||
t.g.mu.Lock()
|
||||
defer t.g.mu.Unlock()
|
||||
var frames frameStack
|
||||
return renderRecord(t.g.drawState, t.template, t.columns, renderScope{frames: &frames})
|
||||
return renderRecord(t.g.drawState, t.template, renderScope{frames: &frames})
|
||||
}
|
||||
|
||||
// NewRecordTemplate compiles an inline record — a JSON object with a format and
|
||||
@@ -217,11 +193,11 @@ func (f *Generator) NewRecordTemplate(input string) (*RecordTemplate, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tm, columns, err := recordOf(t.n)
|
||||
record, err := recordOf(t.n)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fejkdata: an inline record %w", err)
|
||||
}
|
||||
return &RecordTemplate{g: f, template: tm, columns: columns}, nil
|
||||
return &RecordTemplate{g: f, template: record}, nil
|
||||
}
|
||||
|
||||
// FakeRecordTemplate compiles and renders an inline record in one call.
|
||||
@@ -237,63 +213,87 @@ func (f *Generator) FakeRecordTemplate(input string) (*Record, error) {
|
||||
// the record to write instead.
|
||||
var ErrNoColumns = errors.New("has no fields, so no columns")
|
||||
|
||||
// recordOf is the fence both record entry points pass. The columns come back with
|
||||
// the template, fixed for every draw the caller goes on to make.
|
||||
func recordOf(n node) (*template, []Column, error) {
|
||||
// recordOf is the fence both record entry points pass: the record template n is, whose columns
|
||||
// its link fixed for every draw.
|
||||
func recordOf(n node) (*template, error) {
|
||||
if tb, isTable := n.(*table); isTable {
|
||||
n = tb.formatTemplate
|
||||
}
|
||||
t, ok := n.(*template)
|
||||
if !ok {
|
||||
return nil, nil, errors.New("names a choice, not a template; a record is a template whose fields are its columns")
|
||||
return nil, errors.New("names a choice, not a template; a record is a template whose fields are its columns")
|
||||
}
|
||||
names := sortedNames(t.fields)
|
||||
if len(names) == 0 {
|
||||
return nil, nil, ErrNoColumns
|
||||
if len(t.fields) == 0 {
|
||||
return nil, ErrNoColumns
|
||||
}
|
||||
if !t.isRecord {
|
||||
return nil, nil, fmt.Errorf("carries repeat %d, which composes its format into one string; a record projects columns instead — drop the repeat and render the record again for more rows", t.repeat)
|
||||
return nil, fmt.Errorf("carries repeat %d, which composes its format into one string; a record projects columns instead — drop the repeat and render the record again for more rows", t.repeat)
|
||||
}
|
||||
columns := make([]Column, len(names))
|
||||
for i, name := range names {
|
||||
columns[i] = Column{Name: name, DataType: columnDatatype(t.fields[name])}
|
||||
}
|
||||
return t, columns, nil
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// renderRecord draws each column once, in the name order recordOf fixed, as one render, so the
|
||||
// columns read one pick of each name; a table's columns read sc's row.
|
||||
func renderRecord(s *drawstate.State, t *template, columns []Column, sc renderScope) *Record {
|
||||
// recordColumn is one column of a record template: what it writes, the field drawing it, and the
|
||||
// name the template binds to that whole field, whose pick the column renders so the row agrees
|
||||
// with the columns reading the name.
|
||||
type recordColumn struct {
|
||||
Column
|
||||
field node
|
||||
whole *nameBinding
|
||||
}
|
||||
|
||||
// settleRecords fixes the columns of every record in scope, whose datatypes a cycle would walk
|
||||
// forever, so bind refuses the scope's cycles first.
|
||||
func settleRecords(scope nodeScope) {
|
||||
_ = scope(func(_ string, n node) error {
|
||||
if t, isTemplate := n.(*template); isTemplate {
|
||||
t.columns = recordColumns(t)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// recordColumns is t's columns in name order where t is a record with fields, else nil.
|
||||
func recordColumns(t *template) []recordColumn {
|
||||
if !t.isRecord || len(t.fields) == 0 {
|
||||
return nil
|
||||
}
|
||||
whole := map[node]*nameBinding{}
|
||||
for _, tok := range t.tokens {
|
||||
if tok.Kind != grammar.NameBind {
|
||||
continue
|
||||
}
|
||||
if b := t.nameScope.bindings[tok.Bound]; b.bindsField() && len(b.tail) == 0 && whole[b.head] == nil {
|
||||
whole[b.head] = b
|
||||
}
|
||||
}
|
||||
names := sortedNames(t.fields)
|
||||
columns := make([]recordColumn, len(names))
|
||||
for i, name := range names {
|
||||
field := t.fields[name]
|
||||
columns[i] = recordColumn{Column: Column{Name: name, DataType: columnDatatype(field)}, field: field, whole: whole[field]}
|
||||
}
|
||||
return columns
|
||||
}
|
||||
|
||||
// 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.
|
||||
func renderRecord(s *drawstate.State, t *template, sc renderScope) *Record {
|
||||
if mark := sc.renderFrame(t); mark >= 0 {
|
||||
defer sc.frames.pop(mark)
|
||||
}
|
||||
r := &Record{columns: append([]Column(nil), columns...)}
|
||||
for i := range r.columns {
|
||||
field := t.fields[r.columns[i].Name]
|
||||
r := &Record{columns: make([]Column, len(t.columns))}
|
||||
for i, c := range t.columns {
|
||||
var column readValue
|
||||
if b := bindingOfWhole(t, field); b != nil {
|
||||
column = readName(s, sc, arm{kind: namedRead, named: b, levels: wholeLevels})
|
||||
if c.whole != nil {
|
||||
column = readName(s, sc, arm{kind: namedRead, named: c.whole, levels: wholeLevels})
|
||||
} else {
|
||||
column = renderLeaf(s, field, sc)
|
||||
column = renderLeaf(s, c.field, sc)
|
||||
}
|
||||
r.columns[i] = c.Column
|
||||
r.columns[i].Value, r.columns[i].Null = column.text, column.null
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// wholeLevels is the levels of a read of a whole name, as linkName compiles {n}.
|
||||
// wholeLevels is the levels of a read of a whole name, as compileArm compiles {n}.
|
||||
var wholeLevels = []string{""}
|
||||
|
||||
// bindingOfWhole is the name t binds to the whole of field, whose pick the field's column renders
|
||||
// so the row agrees with the columns reading the name; nil where none does.
|
||||
func bindingOfWhole(t *template, field node) *nameBinding {
|
||||
if t.ownNameScope == nil {
|
||||
return nil
|
||||
}
|
||||
for _, b := range t.ownNameScope.order {
|
||||
if b.bindsField() && b.binder == t && len(b.tail) == 0 && b.head == field {
|
||||
return b
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+89
-43
@@ -2,10 +2,10 @@ package fejkdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/larvit/fejkdata/internal/grammar"
|
||||
"github.com/larvit/fejkdata/internal/invariant"
|
||||
)
|
||||
|
||||
// refBinding is what a reference resolves to: the head its category is held
|
||||
@@ -35,59 +35,106 @@ func refSegments(name string, folder []string) ([]string, error) {
|
||||
return append(append([]string{}, base...), segs...), nil
|
||||
}
|
||||
|
||||
// linkRefs binds every reference and compiles every format, once all data is merged,
|
||||
// so a reference sees the override-resolved tree. A reference's head binds into
|
||||
// refHeads under its root path and its tail reads like a sibling path.
|
||||
// linkRefs links every template of the categories, once all data is merged, so a reference sees
|
||||
// the override-resolved tree.
|
||||
func linkRefs(sites []categorySite, root map[string]node) error {
|
||||
var ts []linkSite
|
||||
if err := eachTemplate(sites, func(s categorySite, label string, t *template) error {
|
||||
return linkTemplate(s.dir, label, s.path, t, root)
|
||||
ts = append(ts, linkSite{t: t, folder: s.dir, category: s.path, label: label})
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, pass := range linkPasses {
|
||||
if err := eachTemplate(sites, func(_ categorySite, label string, t *template) error { return pass(label, t) }); err != nil {
|
||||
return linkTemplates(ts, root)
|
||||
}
|
||||
|
||||
// linkSite is a template to link, with the folder and category it sits in, "" for an inline
|
||||
// template, and eachNode's label for it.
|
||||
type linkSite struct {
|
||||
t *template
|
||||
folder []string
|
||||
category, label string
|
||||
}
|
||||
|
||||
// nameTarget is what a binding resolves to in the assembled tree: the node its head names and the
|
||||
// path it reads into that node.
|
||||
type nameTarget struct {
|
||||
head node
|
||||
tail []string
|
||||
}
|
||||
|
||||
// linkTemplates links ts in steps, each over every template before the next starts, each
|
||||
// returning what it builds: the references, the names' targets, the compiled formats, the keys
|
||||
// each name's reads address, and the column each format reads.
|
||||
func linkTemplates(ts []linkSite, root map[string]node) error {
|
||||
for _, s := range ts {
|
||||
link, err := s.t.resolveLink(s.folder, s.label, s.category, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.t.link = link
|
||||
}
|
||||
targets := nameTargets(ts)
|
||||
for _, s := range ts {
|
||||
compiled, err := compileFormat(s.t, targets)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", s.label, err)
|
||||
}
|
||||
s.t.compiled = compiled
|
||||
}
|
||||
addressed := addressedKeys(ts)
|
||||
for b, target := range targets {
|
||||
b.head, b.tail, b.addressed = target.head, target.tail, addressed[b]
|
||||
}
|
||||
for _, s := range ts {
|
||||
s.t.readsColumn = columnReadOf(s.t)
|
||||
}
|
||||
for _, check := range []func(label string, t *template) error{checkNameReads, checkCalcNames} {
|
||||
for _, s := range ts {
|
||||
if err := check(s.label, s.t); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// linkTemplate resolves t's references in category, "" for an inline template, and
|
||||
// compiles its format against them.
|
||||
func linkTemplate(folder []string, label, category string, t *template, root map[string]node) error {
|
||||
link, err := t.resolveLink(folder, label, category, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.link, t.compiled = link, compileFormat(t.tokens, link.refs, t.isName)
|
||||
linkBindings(t)
|
||||
compileArms(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
func compileArms(t *template) {
|
||||
for i := range t.compiled.ops {
|
||||
o := &t.compiled.ops[i]
|
||||
for j := range o.operands {
|
||||
if o.operands[j].kind != namedRead {
|
||||
compileArm(t, &o.operands[j])
|
||||
}
|
||||
}
|
||||
for j := range o.arms {
|
||||
if o.arms[j].kind != namedRead {
|
||||
compileArm(t, &o.arms[j])
|
||||
// nameTargets resolves what each binding of ts binds, from its binder's link.
|
||||
func nameTargets(ts []linkSite) map[*nameBinding]nameTarget {
|
||||
targets := map[*nameBinding]nameTarget{}
|
||||
for _, s := range ts {
|
||||
for _, tok := range s.t.tokens {
|
||||
if tok.Kind == grammar.NameBind {
|
||||
a := splitArm(tok.BoundRef, s.t.link.refs)
|
||||
targets[s.t.nameScope.bindings[tok.Bound]] = nameTarget{head: s.t.head(a.head), tail: a.tail}
|
||||
}
|
||||
}
|
||||
}
|
||||
return targets
|
||||
}
|
||||
|
||||
func compileArm(t *template, a *arm) {
|
||||
head := t.head(a.head)
|
||||
if head == nil {
|
||||
panic(invariant.Broken("{%s} reads a head nothing bound", a.spelling))
|
||||
// addressedKeys is every key the reads of each name in ts land on or pass, from the name, with the
|
||||
// spelling of the first read reaching it.
|
||||
func addressedKeys(ts []linkSite) map[*nameBinding]map[string]string {
|
||||
keys := map[*nameBinding]map[string]string{}
|
||||
for _, s := range ts {
|
||||
for _, o := range s.t.compiled.ops {
|
||||
for _, a := range slices.Concat(o.arms, o.operands) {
|
||||
if a.kind != namedRead {
|
||||
continue
|
||||
}
|
||||
if keys[a.named] == nil {
|
||||
keys[a.named] = map[string]string{}
|
||||
}
|
||||
for _, key := range append(a.levels[len(a.levels)-len(a.tail)-1:], a.path) {
|
||||
if _, seen := keys[a.named][key]; !seen {
|
||||
keys[a.named][key] = a.spelling
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
w := compilePath(head, a.tail)
|
||||
a.steps, a.leaves = w.steps, w.leaves
|
||||
return keys
|
||||
}
|
||||
|
||||
// resolveLink binds every reference t reads, refusing one to t's own category:
|
||||
@@ -132,9 +179,9 @@ type columnRead struct {
|
||||
column node
|
||||
}
|
||||
|
||||
// linkColumnRead sets the record's column t's format reads, where the format only reads one
|
||||
// reference or name, once the names are linked.
|
||||
func linkColumnRead(_ string, t *template) error {
|
||||
// columnReadOf is the record's column t's format reads, where the format only reads one reference
|
||||
// or name, and nil where it does not.
|
||||
func columnReadOf(t *template) *columnRead {
|
||||
ops := t.compiled.ops
|
||||
if t.repeat != 1 || len(ops) != 1 || ops[0].Kind != grammar.NameRead || len(ops[0].arms) != 1 {
|
||||
return nil
|
||||
@@ -152,9 +199,8 @@ func linkColumnRead(_ string, t *template) error {
|
||||
case !grammar.IsRef(a.head):
|
||||
return nil
|
||||
}
|
||||
target, isTemplate := head.(*template)
|
||||
if isTemplate && target.isRecord && len(tail) == 1 {
|
||||
t.link.readsColumn = &columnRead{a: a, category: category, field: tail[0], column: target.fields[tail[0]]}
|
||||
if target, isTemplate := head.(*template); isTemplate && target.isRecord && len(tail) == 1 {
|
||||
return &columnRead{a: a, category: category, field: tail[0], column: target.fields[tail[0]]}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -93,15 +93,16 @@ func render(s *drawstate.State, n node, sc renderScope) string {
|
||||
func renderRepeat(s *drawstate.State, t *template, sc renderScope) string {
|
||||
var b strings.Builder
|
||||
b.Grow(t.repeat * (t.compiled.grow + len(t.separator)))
|
||||
own := t.ownScope()
|
||||
for i := 0; i < t.repeat; i++ {
|
||||
if i > 0 {
|
||||
b.WriteString(t.separator)
|
||||
}
|
||||
if t.ownNameScope == nil {
|
||||
if own == nil {
|
||||
b.WriteString(expand(s, t, sc))
|
||||
continue
|
||||
}
|
||||
mark := sc.frames.push(newPickFrame(t.ownNameScope))
|
||||
mark := sc.frames.push(newPickFrame(own))
|
||||
b.WriteString(expand(s, t, sc))
|
||||
sc.frames.pop(mark)
|
||||
}
|
||||
@@ -172,8 +173,8 @@ func renderLeaf(s *drawstate.State, n node, sc renderScope) readValue {
|
||||
case *nullItem:
|
||||
return readValue{null: true}
|
||||
case *template:
|
||||
if leaf.link.readsColumn != nil {
|
||||
return readField(s, leaf, sc, leaf.link.readsColumn.a)
|
||||
if leaf.readsColumn != nil {
|
||||
return readField(s, leaf, sc, leaf.readsColumn.a)
|
||||
}
|
||||
}
|
||||
return readValue{text: render(s, n, sc)}
|
||||
|
||||
+2
-2
@@ -45,8 +45,8 @@ func shippedShape(f *Generator) string {
|
||||
}
|
||||
case *template:
|
||||
facts[prefix] = "\tformat " + strconv.Quote(n.format) + readsFact(n)
|
||||
if _, columns, err := recordOf(n); err == nil {
|
||||
for _, c := range columns {
|
||||
if record, err := recordOf(n); err == nil {
|
||||
for _, c := range record.columns {
|
||||
fact := "\t" + c.DataType.String()
|
||||
if _, nullable := columnItems(n.fields[c.Name]); nullable {
|
||||
fact += " null"
|
||||
|
||||
@@ -70,7 +70,6 @@ func (f *Generator) structShapeOf(t reflect.Type) (*structShape, error) {
|
||||
// index path each column fills, and the struct fields carrying tags of their own.
|
||||
type structShape struct {
|
||||
record *template
|
||||
columns []Column
|
||||
fieldIndexes [][]int
|
||||
nested []nestedStruct
|
||||
}
|
||||
@@ -266,20 +265,20 @@ func (s *structShape) compileRecord(root *folder, t reflect.Type, label string,
|
||||
if err := bindInline(root, n, label, true); err != nil {
|
||||
return err
|
||||
}
|
||||
record, columns, err := recordOf(n)
|
||||
record, err := recordOf(n)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", label, err)
|
||||
}
|
||||
proof := &valueProof{}
|
||||
s.fieldIndexes = make([][]int, len(columns))
|
||||
for i, c := range columns {
|
||||
s.fieldIndexes = make([][]int, len(record.columns))
|
||||
for i, c := range record.columns {
|
||||
sf, _ := t.FieldByName(c.Name)
|
||||
if err := proof.checkField(label+"."+c.Name, sf.Type, record.fields[c.Name]); err != nil {
|
||||
return err
|
||||
}
|
||||
s.fieldIndexes[i] = sf.Index
|
||||
}
|
||||
s.record, s.columns = record, columns
|
||||
s.record = record
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -324,7 +323,7 @@ func (k columnKind) holds(v proven.Facts) bool {
|
||||
func (s *structShape) fill(state *drawstate.State, v reflect.Value) {
|
||||
if s.record != nil {
|
||||
var frames frameStack
|
||||
for i, c := range renderRecord(state, s.record, s.columns, renderScope{frames: &frames}).columns {
|
||||
for i, c := range renderRecord(state, s.record, renderScope{frames: &frames}).columns {
|
||||
setColumn(fieldAt(v, s.fieldIndexes[i]), c)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ Until a panel scores 7.0 or above, only items 104 to 106 and the exceptions in `
|
||||
|
||||
### 91. Accept a path through a name as a `calc` operand, so `{/misc.coordinate as c}{c}: {calc(c.lat * 60)}` computes from the coordinate `{c}` prints.
|
||||
|
||||
A calc operand parses as a bare identifier, so `{calc(c.lat * 60)}` fails with `unexpected ".lat * 60"`, and a second binding of `/misc.coordinate.lat` would draw another coordinate. Resolve the path through `linkName`'s leaves, as `operandNodes` does for a bare name.
|
||||
A calc operand parses as a bare identifier, so `{calc(c.lat * 60)}` fails with `unexpected ".lat * 60"`, and a second binding of `/misc.coordinate.lat` would draw another coordinate. Resolve the path through `compileArm`'s leaves, as `operandNodes` does for a bare name.
|
||||
|
||||
### 89. Fail `New` without `WithSeed`, naming the option, and seed the CLI from the system.
|
||||
|
||||
|
||||
+3
-3
@@ -24,7 +24,7 @@ func (p *valueProof) checkDatatype(label string, n node) error {
|
||||
if !ok || t.datatype == DataTypeString {
|
||||
return nil
|
||||
}
|
||||
if r := t.link.readsColumn; r != nil && columnDatatype(r.column) == t.datatype {
|
||||
if r := t.readsColumn; r != nil && columnDatatype(r.column) == t.datatype {
|
||||
return fmt.Errorf(`%s: %s takes datatype %s from the column it reads; drop "datatype"`, label, t.format, t.datatype)
|
||||
}
|
||||
if reason := p.proveColumnItem(t).Not[t.datatype]; reason != "" {
|
||||
@@ -66,10 +66,10 @@ func (p *valueProof) checkField(label string, ft reflect.Type, column node) erro
|
||||
|
||||
// proveColumnItem proves a column item: what it renders, or, when it is a column it reads, that column.
|
||||
func (p *valueProof) proveColumnItem(t *template) proven.Facts {
|
||||
if t.link.readsColumn == nil {
|
||||
if t.readsColumn == nil {
|
||||
return p.prove(t)
|
||||
}
|
||||
return p.proveColumn(t.link.readsColumn.column)
|
||||
return p.proveColumn(t.readsColumn.column)
|
||||
}
|
||||
|
||||
// proveColumn proves a column over what its items draw, a null item marking it null rather than
|
||||
|
||||
Reference in New Issue
Block a user