summaryrefslogtreecommitdiff
path: root/internal/cond
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 15:16:55 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 15:16:55 +0200
commit0b5d0eb92c5be2f0ddb2fa73990f31e5654e57fe (patch)
tree358e331945b8206ed4a72703e3aedfe8ea7cdcdb /internal/cond
parent1d3f2d1e4c59867024470d3444e12698b7ebb22e (diff)
downloadkrino-0b5d0eb92c5be2f0ddb2fa73990f31e5654e57fe.tar.gz
krino-0b5d0eb92c5be2f0ddb2fa73990f31e5654e57fe.zip
krino: 0.0.5 — keyword cache, t and d in reviewv0.0.5
Diffstat (limited to 'internal/cond')
-rw-r--r--internal/cond/compile.go1
-rw-r--r--internal/cond/compile_test.go21
-rw-r--r--internal/cond/eval.go17
-rw-r--r--internal/cond/eval_test.go12
-rw-r--r--internal/cond/types.go24
5 files changed, 66 insertions, 9 deletions
diff --git a/internal/cond/compile.go b/internal/cond/compile.go
index 590d61a..94118ac 100644
--- a/internal/cond/compile.go
+++ b/internal/cond/compile.go
@@ -266,6 +266,7 @@ func (c *compiler) compileContent(n *sexp.Node) *node {
continue
}
keywords = append(keywords, keyword{norm: normed, src: a.Text})
+ c.cond.Keywords = append(c.cond.Keywords, Keyword{Opt: c.opt, Norm: normed})
}
if bad {
return nil
diff --git a/internal/cond/compile_test.go b/internal/cond/compile_test.go
index 2a07ad6..8432365 100644
--- a/internal/cond/compile_test.go
+++ b/internal/cond/compile_test.go
@@ -165,3 +165,24 @@ func TestGroupsMatchSpec(t *testing.T) {
}
}
}
+
+// TestCompileCollectsKeywords: every content keyword is recorded as
+// compared, normalised under the compile options, and its key names those
+// options.
+func TestCompileCollectsKeywords(t *testing.T) {
+ opt := Options{IgnoreCase: true, Fold: true}
+ c, errs := Compile("d.conf", nodes(t, `(or (content "Spółka" "x") (not (content "NIP")))`), opt)
+ if len(errs) > 0 {
+ t.Fatal(errs)
+ }
+ want := []Keyword{{Opt: opt, Norm: "spolka"}, {Opt: opt, Norm: "x"}, {Opt: opt, Norm: "nip"}}
+ if !reflect.DeepEqual(c.Keywords, want) {
+ t.Errorf("Keywords = %+v, want %+v", c.Keywords, want)
+ }
+ if got := want[0].Key(); got != "if:spolka" {
+ t.Errorf("Key = %q", got)
+ }
+ if got := KeywordKey(Options{}, "NIP"); got != "sn:NIP" {
+ t.Errorf("strict, unfolded key = %q", got)
+ }
+}
diff --git a/internal/cond/eval.go b/internal/cond/eval.go
index e3093af..3cec110 100644
--- a/internal/cond/eval.go
+++ b/internal/cond/eval.go
@@ -19,7 +19,10 @@ type Facts interface {
Size() int64
ModTime() time.Time
Now() time.Time
- Content(ignoreCase, fold bool) (string, error) // normalised with norm.Text
+ // ContentContains reports the index of the first of keywords, each
+ // normalised under opt with norm.Text, that the file's text contains, or
+ // -1 when it contains none.
+ ContentContains(opt Options, keywords []string) (int, error)
Duplicate(dirs []string) (original string, ok bool, err error)
Matched() bool // an earlier rule matched this file
}
@@ -167,14 +170,16 @@ func (c *Cond) evalLeaf(n *node, f Facts) (ok bool, reason, warn string, caps []
return false, "", "", nil
case kContent:
- text, err := f.Content(c.opt.IgnoreCase, c.opt.Fold)
+ norms := make([]string, len(n.keywords))
+ for i, kw := range n.keywords {
+ norms[i] = kw.norm
+ }
+ i, err := f.ContentContains(c.opt, norms)
if err != nil {
return false, "", "content unreadable: " + err.Error(), nil
}
- for _, kw := range n.keywords {
- if strings.Contains(text, kw.norm) {
- return true, `content "` + kw.src + `"`, "", nil
- }
+ if i >= 0 {
+ return true, `content "` + n.keywords[i].src + `"`, "", nil
}
return false, "", "", nil
diff --git a/internal/cond/eval_test.go b/internal/cond/eval_test.go
index 3607978..8891de7 100644
--- a/internal/cond/eval_test.go
+++ b/internal/cond/eval_test.go
@@ -37,12 +37,18 @@ func (f *fake) Size() int64 { return f.size }
func (f *fake) ModTime() time.Time { return now.Add(-f.age) }
func (f *fake) Now() time.Time { return now }
func (f *fake) Matched() bool { return f.matched }
-func (f *fake) Content(ic, fold bool) (string, error) {
+func (f *fake) ContentContains(opt Options, keywords []string) (int, error) {
f.contentCalls++
if f.rawErr != nil {
- return "", f.rawErr
+ return -1, f.rawErr
}
- return norm.Text(f.raw, ic, fold), nil
+ text := norm.Text(f.raw, opt.IgnoreCase, opt.Fold)
+ for i, kw := range keywords {
+ if strings.Contains(text, kw) {
+ return i, nil
+ }
+ }
+ return -1, nil
}
func (f *fake) Duplicate(dirs []string) (string, bool, error) { return f.dupOrig, f.dupOK, nil }
diff --git a/internal/cond/types.go b/internal/cond/types.go
index 9455f63..28a4fc9 100644
--- a/internal/cond/types.go
+++ b/internal/cond/types.go
@@ -24,6 +24,30 @@ type Cond struct {
opt Options // the case/fold settings conditions were compiled with; Task 8 needs them again at eval time
UsesContent bool // some content test exists
DupDirs [][]string // the raw directory arguments of each duplicate test, in order
+ Keywords []Keyword // every content keyword, as compiled, in order
+}
+
+// Keyword is one content keyword as a content test compares it: normalised
+// under the options it was compiled with.
+type Keyword struct {
+ Opt Options
+ Norm string
+}
+
+// Key is the keyword's identity across runs, for the keyword cache: its
+// options and its normalised text.
+func (k Keyword) Key() string { return KeywordKey(k.Opt, k.Norm) }
+
+// KeywordKey is Keyword.Key for opt and an already normalised keyword.
+func KeywordKey(opt Options, norm string) string {
+ b := []byte("sn:")
+ if opt.IgnoreCase {
+ b[0] = 'i'
+ }
+ if opt.Fold {
+ b[1] = 'f'
+ }
+ return string(b) + norm
}
// kind is what a compiled node tests, or how it combines its children.