summaryrefslogtreecommitdiff
path: root/internal/engine/engine.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-12 12:58:14 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-12 12:58:14 +0200
commit24a84671ace373ae331fa83a1ff484990f4dff0e (patch)
treea6b6e3949d7dd241f1d13e079dfb982d758c89a2 /internal/engine/engine.go
parent3b36a48b7ce5a53a9366f3b31f94311f178e2553 (diff)
downloadkrino-24a84671ace373ae331fa83a1ff484990f4dff0e.tar.gz
krino-24a84671ace373ae331fa83a1ff484990f4dff0e.zip
krino: planning — chains, placeholders, conflicts, JSON
Diffstat (limited to 'internal/engine/engine.go')
-rw-r--r--internal/engine/engine.go38
1 files changed, 38 insertions, 0 deletions
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 {