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:
2026-10-05 22:21:10 +02:00
committed by Lillem4n
parent cdc82a99c6
commit cd7f0a04dc
16 changed files with 339 additions and 313 deletions
+69 -106
View File
@@ -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
}