From 0b0370936a2798187b0cdc7b6760e420df0246ce Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sat, 3 Oct 2026 12:14:58 +0200 Subject: [PATCH] Refuse a name read once only when read whole, and name every way out in the scope and draw-group refusals --- CHANGELOG.md | 2 +- README.md | 4 ++-- format.go | 3 +++ name.go | 29 +++++++++++++++-------------- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56596d4..d7bc4b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ replacement, and each removed path, column or flag. `repeat` binds picks again on every iteration. Refused at `New`: a name bound twice outside any `repeat`, twice in one `repeat`, or both inside a `repeat` and outside it; a name that is a field or an option; a binding of anything but a reference; a binding in a - choice's item; a binding nothing reads, or one read once, which the reference spells; a + choice's item; a binding nothing reads, or one read once whole, which the reference spells; a path through a name that selects a row; a read of a name rendering a field twice where a path through the name reads it; reads of a name in two draw groups, or inside a `repeat`, where what it names reads a reference path; and a binding in a table. A field, column, diff --git a/README.md b/README.md index 049ba02..3af9d2b 100644 --- a/README.md +++ b/README.md @@ -731,8 +731,8 @@ Rejected at load, each error naming what to write instead: - a name that is a field or an option; - a binding of anything but a reference; - a binding in a choice's item, which every other item would leave unbound; -- a binding nothing reads, or read once outside a `repeat`, which the reference spells: - `{/word as w}{w}` is `{/word}`, and `{/word as w}{w.x}` is `{/word.x}`; +- a binding nothing reads, or read once whole outside a `repeat`, which the reference + spells: `{/word as w}{w}` is `{/word}`; - a path through a name that selects a row; - a read of a name rendering a field twice, where a path through the name reads it; - reads of a name in two draw groups, or inside a `repeat`, where what it names reads a diff --git a/format.go b/format.go index c175ce0..e457a5c 100644 --- a/format.go +++ b/format.go @@ -297,6 +297,9 @@ func checkBind(t formatToken) error { if _, _, err := refShape(ref); err != nil { return fmt.Errorf("token {%s}: %w", t.body, err) } + if name == "" { + return fmt.Errorf("token {%s}: a binding names nothing; write {%s as n}", t.body, ref) + } if isOption(name) { return fmt.Errorf("token {%s}: %q is an option and can never be a name", t.body, name) } diff --git a/name.go b/name.go index e07d119..fcba65e 100644 --- a/name.go +++ b/name.go @@ -138,11 +138,8 @@ func (sc *nameScope) settle() error { if len(b.uses) == 0 { return fmt.Errorf("%stoken {%s}: nothing reads name %q; drop the token", b.where, b.body, b.name) } - if r := b.uses[0]; len(b.uses) == 1 && !r.nested { + if r := b.uses[0]; len(b.uses) == 1 && r.tail == "" && !r.nested { spelling := b.ref - if r.tail != "" { - spelling += "." + r.tail - } if !r.operand { spelling = "{" + spelling + "}" } @@ -203,9 +200,18 @@ func resolveReads(n node, where, group string, scopes []*nameScope) error { return nil } -// unresolved is the refusal of u, a read no field or name answers: naming the names the read sees, -// else the repeat binding the name where the read cannot see it. +// unresolved is the refusal of u, a read no field or name answers: naming the repeat binding the +// name where the read cannot see it, else the names the read sees. func unresolved(where string, u unboundRead, seen *nameScope, scopes []*nameScope) error { + for _, sc := range scopes { + if b, ok := sc.bindings[u.head]; ok { + at := strings.TrimSuffix(b.where, ": ") + if at == "" { + at = "the root template" + } + return fmt.Errorf("%s%w; name %q is bound inside the repeat at %s, which a read outside the repeat cannot see; bind it outside the repeat to read one pick on every line", where, u.err, u.head, at) + } + } var visible []string for sc := seen; sc != nil; sc = sc.up { for _, b := range sc.order { @@ -216,11 +222,6 @@ func unresolved(where string, u unboundRead, seen *nameScope, scopes []*nameScop sort.Strings(visible) return fmt.Errorf("%stoken {%s}: no field or name %q; the names bound here are %v", where, u.body, u.head, visible) } - for _, sc := range scopes { - if b, ok := sc.bindings[u.head]; ok { - return fmt.Errorf("%s%w; name %q is bound inside the repeat at %s, which a read outside the repeat cannot see; bind it outside the repeat to read one pick on every line", where, u.err, u.head, strings.TrimSuffix(b.where, ": ")) - } - } return fmt.Errorf("%s%w", where, u.err) } @@ -272,7 +273,7 @@ func linkNames(path string, t *template) error { func linkName(t *template, a *arm) error { b := t.nameScope.lookup(a.head) if hasSelector(a.tail) { - return fmt.Errorf("a path through name %q selects no row; read it directly, {%s.%s}, or bind the row to a name of its own", a.head, b.ref, joinSegments(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, 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 { @@ -327,10 +328,10 @@ func (b *nameBinding) checkUses() error { } for _, u := range b.uses { if u.nested { - return fmt.Errorf("name %q is read inside a repeat, and what it names reads a reference path, which each iteration draws apart; bind the name inside the repeat", b.name) + return fmt.Errorf("name %q is read inside a repeat, and what it names reads a reference path, which each iteration draws apart; bind the name inside the repeat, or bind a second name there", b.name) } if u.group != b.uses[0].group { - return fmt.Errorf("name %q is read in two draw groups, %s and %s, and what it names reads a reference path, which each draw group draws apart; read the name in one draw group", b.name, groupSpelling(b.uses[0].group), groupSpelling(u.group)) + return fmt.Errorf("name %q is read in two draw groups, %s and %s, and what it names reads a reference path, which each draw group draws apart; read the name in one draw group, or bind a name in each", b.name, groupSpelling(b.uses[0].group), groupSpelling(u.group)) } } return nil