From fcec9c4a2a46e5fc22049147513aa9f34fc9285c Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Wed, 7 Oct 2026 02:09:52 +0200 Subject: [PATCH] Rename grammar's NameRead to PathRead and CheckName to CheckIdentifier, so a name in the engine is only a bound name --- README.md | 2 +- arm.go | 2 +- format.go | 8 +++---- internal/datafiles/datafiles.go | 4 ++-- internal/grammar/format.go | 6 +++--- internal/grammar/format_test.go | 2 +- internal/grammar/{name.go => identifier.go} | 24 ++++++++++----------- internal/grammar/identifier_test.go | 16 ++++++++++++++ internal/grammar/name_test.go | 16 -------------- internal/grammar/path.go | 4 ++-- internal/grammar/path_test.go | 6 +++--- internal/grammar/template.go | 4 ++-- internal/rows/rows.go | 2 +- node.go | 2 +- record.go | 2 +- render.go | 2 +- resolve.go | 2 +- struct.go | 2 +- table.go | 2 +- valueproof.go | 2 +- 20 files changed, 55 insertions(+), 55 deletions(-) rename internal/grammar/{name.go => identifier.go} (54%) create mode 100644 internal/grammar/identifier_test.go delete mode 100644 internal/grammar/name_test.go diff --git a/README.md b/README.md index 9e7b08d..a2972fa 100644 --- a/README.md +++ b/README.md @@ -989,7 +989,7 @@ internal/builtinfunc/ the {name()} functions: their checks and draws, checksums, internal/datafiles/ the walk of a data tree: its folders, its category files and the rows files beside them internal/datatype/ the datatype a record column holds; the root's DataType is an alias of it internal/drawstate/ the seeded randomness and the {seq()} counters a generator draws through -internal/grammar/ how the template language is written: format tokens, paths and selectors, reference sigils, names, calc syntax, and whether an argument is a template or a path +internal/grammar/ how the template language is written: format tokens, paths and selectors, reference sigils, identifiers, calc syntax, and whether an argument is a template or a path internal/invariant/ the one phrase every package panics with when an invariant breaks internal/proven/ what a proof knows of a value, and the bounds a calc takes from its operands internal/rows/ a table's rows: the TSV, the options proved over it, the links between tables, row selection and draws, and the pin set one path or one named pick fixes diff --git a/arm.go b/arm.go index 0ccffdf..6970178 100644 --- a/arm.go +++ b/arm.go @@ -77,7 +77,7 @@ func checkSegments(a arm) error { // or a builtin already prepared with its args. type op struct { grammar.Token - arms []arm // grammar.NameRead: the '|' alternatives, split into head and tail once + arms []arm // grammar.PathRead: the '|' alternatives, split into head and tail once call builtinfunc.Call // operands are the fields the builtin reads, in the order its operands func // fixed; expand reads them before the call. nil for a builtin that reads none. diff --git a/format.go b/format.go index f0b4946..f311791 100644 --- a/format.go +++ b/format.go @@ -60,7 +60,7 @@ func checkToken(t grammar.Token, fields map[string]node) ([]unboundRead, error) return checkReads(t, fields, true) case grammar.NameBind: return nil, checkBind(t, fields) - case grammar.NameRead: + case grammar.PathRead: unbound, err := checkReads(t, fields, false) if err != nil { return nil, err @@ -113,7 +113,7 @@ func checkBind(t grammar.Token, fields map[string]node) error { if isOption(name) { return fmt.Errorf("token {%s}: %q is an option and can never be a name; rename it", t.Body, name) } - if err := grammar.CheckName(name); err != nil { + if err := grammar.CheckIdentifier(name); err != nil { return fmt.Errorf("token {%s}: name %w", t.Body, err) } return nil @@ -167,7 +167,7 @@ func checkArm(name string, fields map[string]node, wholeToken bool) (missing boo // hintableRef reports whether {/name} is a reference the grammar accepts, so the // hint never names a spelling that fails too. func hintableRef(name string) bool { - return grammar.CheckPathNames(name) == nil + return grammar.CheckPathIdentifiers(name) == nil } // tokenReads lists the names t reads: a read's arms, or the operands a call's builtin reads. @@ -214,7 +214,7 @@ func compileFormat(t *template, targets map[*nameBinding]nameTarget) (formatOps, case grammar.BuiltinCall: o.call = builtinfunc.Prep(tok.Fn, tok.Args) o.operands, err = t.compileArms(builtinfunc.Operands(tok.Fn, tok.Args), targets) - case grammar.NameRead: + case grammar.PathRead: o.arms, err = t.compileArms(tok.Arms, targets) case grammar.NameBind: continue diff --git a/internal/datafiles/datafiles.go b/internal/datafiles/datafiles.go index 846e38d..b260fa6 100644 --- a/internal/datafiles/datafiles.go +++ b/internal/datafiles/datafiles.go @@ -115,7 +115,7 @@ func (s Source) walkFolder(dir []string, compile func(Category) error) (bool, er if err != nil || !holds { return false, err } - if err := grammar.CheckName(dir[len(dir)-1]); err != nil { + if err := grammar.CheckIdentifier(dir[len(dir)-1]); err != nil { return false, fmt.Errorf("%s: folder %w", s.labelled(s.dirPath(dir)), err) } return true, nil @@ -140,7 +140,7 @@ func (s Source) compileFile(dir []string, file string, rows *rowsFiles, compile } full := path.Join(rows.dir, file) name := strings.TrimSuffix(file, ".json") - if err := grammar.CheckName(name); err != nil { + if err := grammar.CheckIdentifier(name); err != nil { return false, fmt.Errorf("%s: category %w", s.labelled(full), err) } b, err := fs.ReadFile(s.fsys, full) diff --git a/internal/grammar/format.go b/internal/grammar/format.go index 0d7dd90..cee6da0 100644 --- a/internal/grammar/format.go +++ b/internal/grammar/format.go @@ -66,7 +66,7 @@ type Token struct { Body string // the braces' content, as written Fn string // BuiltinCall Args []string // BuiltinCall - Arms []string // NameRead: the '|' alternatives + Arms []string // PathRead: the '|' alternatives // NameBind: the reference it binds, and the name. BoundRef, Bound string } @@ -77,7 +77,7 @@ const ( BuiltinCall TokenKind = iota + 1 LiteralRun NameBind - NameRead + PathRead ) const AsWord = " as " @@ -104,7 +104,7 @@ func ParseFormat(format string) ([]Token, error) { return nil } if indexOutside(u.body, '(') < 0 { - toks = append(toks, Token{Kind: NameRead, Body: u.body, Arms: splitOutside(u.body, '|')}) + toks = append(toks, Token{Kind: PathRead, Body: u.body, Arms: splitOutside(u.body, '|')}) return nil } name, args, ok := FuncCall(u.body) diff --git a/internal/grammar/format_test.go b/internal/grammar/format_test.go index d7b0221..6e5ff14 100644 --- a/internal/grammar/format_test.go +++ b/internal/grammar/format_test.go @@ -27,7 +27,7 @@ func TestParseFormatReadsEachTokenKind(t *testing.T) { } want := []Token{ {Kind: LiteralRun, Lit: "a{"}, - {Kind: NameRead, Body: "x|/y.z[a.b]", Arms: []string{"x", "/y.z[a.b]"}}, + {Kind: PathRead, Body: "x|/y.z[a.b]", Arms: []string{"x", "/y.z[a.b]"}}, {Kind: NameBind, Body: "/p as q", BoundRef: "/p", Bound: "q"}, {Kind: BuiltinCall, Body: "f(1, n)", Fn: "f", Args: []string{"1", "n"}}, } diff --git a/internal/grammar/name.go b/internal/grammar/identifier.go similarity index 54% rename from internal/grammar/name.go rename to internal/grammar/identifier.go index eee53a2..48cfb53 100644 --- a/internal/grammar/name.go +++ b/internal/grammar/identifier.go @@ -5,18 +5,18 @@ import ( "strings" ) -// reservedInName is what a category, folder or field name may not contain: a dot +// reservedInIdentifier is what an identifier may not contain: a dot // separates the segments of a path, '|' the arms of a token, '(' opens a function // call, braces delimit the token, '/' starts a reference, and brackets and a quote -// open a JSON value. A name carrying one is rejected where it is authored rather +// open a JSON value. An identifier carrying one is rejected where it is authored rather // than where it would be unreachable. -const reservedInName = ".|({}/[]\"" +const reservedInIdentifier = ".|({}/[]\"" -var reservedList = strings.Join(strings.Split(reservedInName, ""), " ") +var reservedList = strings.Join(strings.Split(reservedInIdentifier, ""), " ") -// CheckName rejects a name the dot path, {token} and JSON grammars cannot spell, or a struct -// tag cannot read. -func CheckName(name string) error { +// CheckIdentifier rejects an identifier, the name of a folder, category, field or column, that the +// dot path, {token} and JSON grammars cannot spell, or a struct tag cannot read. +func CheckIdentifier(name string) error { if name == "" { return fmt.Errorf("%q is empty, which is not a path segment, so List never offers it", name) } @@ -26,21 +26,21 @@ func CheckName(name string) error { if strings.Contains(name, AsWord) { return fmt.Errorf("%q contains %q, which a token reads as binding a name; rename it", name, AsWord) } - if i := strings.IndexAny(name, reservedInName); i >= 0 { + if i := strings.IndexAny(name, reservedInIdentifier); i >= 0 { return fmt.Errorf("%q contains %q; a name may not use %s, which the dot path, {token} and JSON grammars reserve", name, name[i:i+1], reservedList) } return nil } -// CheckPathNames rejects a dotted path with a segment no name may be. -func CheckPathNames(path string) error { +// CheckPathIdentifiers rejects a dotted path with a segment no identifier may be. +func CheckPathIdentifiers(path string) error { segs, err := SplitPath(path) if err != nil { return err } - for _, seg := range NameSegments(segs) { - if err := CheckName(seg); err != nil { + for _, seg := range IdentifierSegments(segs) { + if err := CheckIdentifier(seg); err != nil { return fmt.Errorf("path %w", err) } } diff --git a/internal/grammar/identifier_test.go b/internal/grammar/identifier_test.go new file mode 100644 index 0000000..64e9465 --- /dev/null +++ b/internal/grammar/identifier_test.go @@ -0,0 +1,16 @@ +package grammar + +import "testing" + +func TestCheckIdentifierRefusesWhatAGrammarReserves(t *testing.T) { + for _, name := range []string{"", "-", "a as b", "a.b", "a|b", "a(b", "a{b", "a}b", "a/b", "a[b", "a]b", `a"b`} { + if CheckIdentifier(name) == nil { + t.Errorf("CheckIdentifier(%q) = nil, want an error", name) + } + } + for _, name := range []string{"a", "postal-code", "é", "a_b"} { + if err := CheckIdentifier(name); err != nil { + t.Errorf("CheckIdentifier(%q) = %v, want nil", name, err) + } + } +} diff --git a/internal/grammar/name_test.go b/internal/grammar/name_test.go deleted file mode 100644 index 5b648ff..0000000 --- a/internal/grammar/name_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package grammar - -import "testing" - -func TestCheckNameRefusesWhatAGrammarReserves(t *testing.T) { - for _, name := range []string{"", "-", "a as b", "a.b", "a|b", "a(b", "a{b", "a}b", "a/b", "a[b", "a]b", `a"b`} { - if CheckName(name) == nil { - t.Errorf("CheckName(%q) = nil, want an error", name) - } - } - for _, name := range []string{"a", "postal-code", "é", "a_b"} { - if err := CheckName(name); err != nil { - t.Errorf("CheckName(%q) = %v, want nil", name, err) - } - } -} diff --git a/internal/grammar/path.go b/internal/grammar/path.go index e68eedc..9dadf38 100644 --- a/internal/grammar/path.go +++ b/internal/grammar/path.go @@ -150,8 +150,8 @@ func UnspellableInSelector(s string) string { return "" } -// NameSegments is the segments of a path that are names, its selectors and ".." left out. -func NameSegments(segs []string) []string { +// IdentifierSegments is the segments of a path that are identifiers, its selectors and ".." left out. +func IdentifierSegments(segs []string) []string { out := segs[:0:0] for _, s := range segs { if !IsSelector(s) && s != ".." { diff --git a/internal/grammar/path_test.go b/internal/grammar/path_test.go index 47194a1..39c9f7c 100644 --- a/internal/grammar/path_test.go +++ b/internal/grammar/path_test.go @@ -33,9 +33,9 @@ func TestSplitPathRefusesAStraySelectorBracket(t *testing.T) { } } -func TestNameSegmentsDropSelectorsAndStepsUp(t *testing.T) { - if got := NameSegments([]string{"a", "[b]", "..", "c"}); !reflect.DeepEqual(got, []string{"a", "c"}) { - t.Errorf("NameSegments = %q, want [a c]", got) +func TestIdentifierSegmentsDropSelectorsAndStepsUp(t *testing.T) { + if got := IdentifierSegments([]string{"a", "[b]", "..", "c"}); !reflect.DeepEqual(got, []string{"a", "c"}) { + t.Errorf("IdentifierSegments = %q, want [a c]", got) } } diff --git a/internal/grammar/template.go b/internal/grammar/template.go index d8af71c..444230d 100644 --- a/internal/grammar/template.go +++ b/internal/grammar/template.go @@ -34,7 +34,7 @@ func isJSONStart(arg string) bool { // pathAdvice refuses a spelling of path, naming path to write, or why no name can spell it. func pathAdvice(path, refusal string) error { - if err := CheckPathNames(path); err != nil { + if err := CheckPathIdentifiers(path); err != nil { return err } return fmt.Errorf("%s; write %s", refusal, path) @@ -71,7 +71,7 @@ func loneReference(arg string) (string, bool) { // loneRef is the reference a format of one reference token and nothing else reads. func loneRef(toks []Token) (string, bool) { - if len(toks) != 1 || toks[0].Kind != NameRead || len(toks[0].Arms) != 1 || !IsRef(toks[0].Arms[0]) { + if len(toks) != 1 || toks[0].Kind != PathRead || len(toks[0].Arms) != 1 || !IsRef(toks[0].Arms[0]) { return "", false } return toks[0].Arms[0], true diff --git a/internal/rows/rows.go b/internal/rows/rows.go index eb3281f..0b94edf 100644 --- a/internal/rows/rows.go +++ b/internal/rows/rows.go @@ -94,7 +94,7 @@ func (t *Table[O]) parseRows(data string) error { t.header = strings.Split(header, "\t") t.col = make(map[string]int, len(t.header)) for i, name := range t.header { - if err := grammar.CheckName(name); err != nil { + if err := grammar.CheckIdentifier(name); err != nil { return fmt.Errorf("column %w", err) } if _, dup := t.col[name]; dup { diff --git a/node.go b/node.go index 567aa1d..9cf6976 100644 --- a/node.go +++ b/node.go @@ -381,7 +381,7 @@ func compileFields(m map[string]any, pos position) (map[string]node, error) { if isOption(k) { continue } - if err := grammar.CheckName(k); err != nil { + if err := grammar.CheckIdentifier(k); err != nil { return nil, fmt.Errorf("field %w", err) } n, err := compileAt(m[k], pos) diff --git a/record.go b/record.go index 7639d80..46b1935 100644 --- a/record.go +++ b/record.go @@ -145,7 +145,7 @@ func (f *Generator) FakeRecord(path string) (*Record, error) { } record, err := recordOf(n) if errors.Is(err, ErrNoColumns) { - ns := grammar.NameSegments(segments) + ns := grammar.IdentifierSegments(segments) 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 err != nil { diff --git a/render.go b/render.go index 3c3f58c..1cccd5b 100644 --- a/render.go +++ b/render.go @@ -123,7 +123,7 @@ func expand(s *drawstate.State, t *template, env renderEnv) string { switch o.Kind { case grammar.LiteralRun: b.WriteString(o.Lit) - case grammar.NameRead: + case grammar.PathRead: b.WriteString(readField(s, t, env, o.arms[s.IntN(len(o.arms))]).text) case grammar.BuiltinCall: var operands []string diff --git a/resolve.go b/resolve.go index f4c467d..f89daf7 100644 --- a/resolve.go +++ b/resolve.go @@ -105,7 +105,7 @@ type columnRead struct { // or name, and nil where it does not. func columnReadOf(t *template, targets map[*nameBinding]nameTarget) *columnRead { ops := t.compiled.ops - if t.repeat != 1 || len(ops) != 1 || ops[0].Kind != grammar.NameRead || len(ops[0].arms) != 1 { + if t.repeat != 1 || len(ops) != 1 || ops[0].Kind != grammar.PathRead || len(ops[0].arms) != 1 { return nil } a := ops[0].arms[0] diff --git a/struct.go b/struct.go index a67f82a..6b5e3d5 100644 --- a/struct.go +++ b/struct.go @@ -231,7 +231,7 @@ func tagValue(sf reflect.StructField, tag string) (any, error) { case inline: return inputValue(tag) } - if err := grammar.CheckPathNames(tag); err != nil { + if err := grammar.CheckPathIdentifiers(tag); err != nil { return nil, err } return "{/" + tag + "}", nil diff --git a/table.go b/table.go index fe6beb2..0012740 100644 --- a/table.go +++ b/table.go @@ -167,7 +167,7 @@ func (t *table) compileRowFormat(format string) error { return err } for _, tok := range toks { - if tok.Kind != grammar.NameRead { + if tok.Kind != grammar.PathRead { continue } for _, name := range tok.Arms { diff --git a/valueproof.go b/valueproof.go index 0d41e52..2621741 100644 --- a/valueproof.go +++ b/valueproof.go @@ -167,7 +167,7 @@ func (p *valueProof) proveTemplate(t *template) proven.Facts { o := t.compiled.ops[0] body, name, args := o.Body, o.Fn, o.Args switch { - case o.Kind == grammar.NameRead: + case o.Kind == grammar.PathRead: var leaves []node for _, a := range o.arms { leaves = append(leaves, a.leaves...)