From 24a84671ace373ae331fa83a1ff484990f4dff0e Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Sat, 12 Sep 2026 12:58:14 +0200 Subject: krino: planning — chains, placeholders, conflicts, JSON MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/engine/engine.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) (limited to 'internal/engine/engine.go') diff --git a/internal/engine/engine.go b/internal/engine/engine.go index eec9a60..ea61e8c 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -6,6 +6,7 @@ package engine import ( + "fmt" "os" "time" @@ -13,6 +14,7 @@ import ( "krino/internal/config" "krino/internal/extract" "krino/internal/ignore" + "krino/internal/plan" ) // Engine holds a loaded, compiled configuration: everything a front end @@ -84,6 +86,10 @@ func Load(mainFile string, names ...string) (*Engine, []*config.Diag) { errs = append(errs, cerrs...) continue } + if diag := checkCaptures(d.File, r, c); diag != nil { + errs = append(errs, diag) + continue + } dir.Rules = append(dir.Rules, &Rule{Name: r.Name, Conf: r, Settings: rs, Cond: c}) } dir.ContentVariants = contentVariants(dir.Rules) @@ -102,6 +108,38 @@ func Load(mainFile string, names ...string) (*Engine, []*config.Diag) { }, nil } +// checkCaptures validates a compiled rule's actions against the capture +// groups its own name tests can supply (spec 7.3): a rule using {N} needs a +// name test at all, and every name test in it needs at least N groups. It +// reports only the first offending action, so one config mistake yields one +// diagnostic. +func checkCaptures(file string, r *config.Rule, c *cond.Cond) *config.Diag { + groups := c.NameGroups() + for _, a := range r.Actions { + n, err := plan.MaxIndex(a.Arg) + if err != nil || n == 0 { + continue + } + if len(groups) == 0 { + return &config.Diag{File: file, Pos: a.Pos, Msg: fmt.Sprintf("rule %q: {%d} needs a name test to capture from", r.Name, n)} + } + for _, g := range groups { + if g < n { + return &config.Diag{File: file, Pos: a.Pos, Msg: fmt.Sprintf("rule %q: {%d} but a name test has only %s", r.Name, n, captureGroups(g))} + } + } + } + return nil +} + +// captureGroups renders a capture-group count with correct singular/plural. +func captureGroups(n int) string { + if n == 1 { + return "1 capture group" + } + return fmt.Sprintf("%d capture groups", n) +} + // dedupeNames returns names with every repeat after its first occurrence // removed, order preserved. func dedupeNames(names []string) []string { -- cgit v1.3