diff --git a/builtins_test.go b/builtins_test.go index 5c30158..eb4d5fd 100644 --- a/builtins_test.go +++ b/builtins_test.go @@ -188,12 +188,12 @@ func ibanValid(s string) bool { return rem == 1 } -// TestRegistryShapes pins the builtin contract compileOps relies on: every entry +// TestRegistryShapes pins the builtin contract compileFormat relies on: every entry // supplies prep, and args parsed at compile only behind a check. func TestRegistryShapes(t *testing.T) { for name, b := range builtins { if b.prep == nil { - t.Errorf("builtin %q has no prep: compileOps would call a nil func", name) + t.Errorf("builtin %q has no prep: compileFormat would call a nil func", name) } if b.arity != 0 && b.checkArgs == nil { t.Errorf("builtin %q parses args in prep with no check", name) @@ -203,7 +203,7 @@ func TestRegistryShapes(t *testing.T) { // TestArgGuardsPanic pins the guards that report a builtin arg its check should // have rejected. No data reaches them — checkFunc runs a builtin's check before -// compileOps ever calls prep — so they are exercised directly. +// compileFormat ever calls prep — so they are exercised directly. func TestArgGuardsPanic(t *testing.T) { for name, call := range map[string]func(){ "atoi on an unvalidated arg": func() { atoi("nope") }, diff --git a/format.go b/format.go index 825f52b..2d3d4e3 100644 --- a/format.go +++ b/format.go @@ -441,19 +441,9 @@ func (c *formatOps) field(tok formatToken, refs map[string]refBinding) { c.ops = append(c.ops, op{formatToken: tok, arms: arms}) } -// compileFormat compiles a format into ops, and applies the fences that need the -// compiled reads. -func compileFormat(toks []formatToken, refs map[string]refBinding) (formatOps, error) { - c := compileOps(toks, refs) - if err := checkNoOverlap(c); err != nil { - return c, err - } - return c, checkNoRepeatedRead(c) -} - -// compileOps compiles a parsed format. Call checkTokens first: it is what proves +// compileFormat compiles a parsed format. Call checkTokens first: it is what proves // every token valid. -func compileOps(toks []formatToken, refs map[string]refBinding) formatOps { +func compileFormat(toks []formatToken, refs map[string]refBinding) formatOps { var c formatOps for _, tok := range toks { switch tok.kind { diff --git a/holdfence.go b/holdfence.go index fe19439..ad1ae91 100644 --- a/holdfence.go +++ b/holdfence.go @@ -15,10 +15,23 @@ func heldCheck(path string, n node) error { if !ok || len(t.compiled.held) == 0 { return nil } + if err := checkHeld(t); err != nil { + return fmt.Errorf("%s: %w", t.site.label(path), err) + } + return nil +} + +func checkHeld(t *template) error { + if err := checkNoOverlap(t.compiled); err != nil { + return err + } + if err := checkNoRepeatedRead(t.compiled); err != nil { + return err + } readers := pathHeadReaders(t.compiled) for _, name := range heldNames(t) { if err := checkNameHeld(t, name, readers); err != nil { - return fmt.Errorf("%s: %w", path, err) + return err } } return nil diff --git a/node.go b/node.go index 0725b7d..b36ba47 100644 --- a/node.go +++ b/node.go @@ -73,6 +73,14 @@ func (s tableSite) isCell() bool { return s.table != nil && s.row != formatRow } +// label names the template at path in an error, a cell by its line in the rows file. +func (s tableSite) label(path string) string { + if s.isCell() { + return fmt.Sprintf("%s, line %d", path, s.row+2) + } + return path +} + // 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 diff --git a/reference.go b/reference.go index f2ac8cb..93c58c5 100644 --- a/reference.go +++ b/reference.go @@ -81,18 +81,11 @@ func linkRefs(root map[string]node) error { // linkTemplate resolves t's references in category, "" for an inline template, and // compiles its format against them. func linkTemplate(folder []string, path, category string, t *template, root map[string]node) error { - if t.site.isCell() { - path = fmt.Sprintf("%s, line %d", path, t.site.row+2) - } - link, err := t.resolveLink(folder, path, category, root) + link, err := t.resolveLink(folder, t.site.label(path), category, root) if err != nil { return err } - compiled, err := compileFormat(t.tokens, link.refs) - if err != nil { - return fmt.Errorf("%s: %w", path, err) - } - t.link, t.compiled = link, compiled + t.link, t.compiled = link, compileFormat(t.tokens, link.refs) compileArms(t) return nil }