fix: bound the spelling checker's word cache
The remembered answers are keyed on user input and never evicted: a long session could grow the map without bound. Clear it at 10000 words and let it refill - a script repeats the same words, so re-checks are cheap.
This commit is contained in:
1 parent
823c9f2d0f
commit
b0bcb812b1
2 files changed
+38
No files matched your search
@@ -90,6 +90,12 @@ func (c *Checker) Add(word string) {
|
|||||||
|
|
||||||
func normalize(w string) string { return strings.ReplaceAll(w, "’", "'") }
|
func normalize(w string) string { return strings.ReplaceAll(w, "’", "'") }
|
||||||
|
|
||||||
|
// cacheLimit bounds the remembered answers, which are keyed on user input:
|
||||||
|
// a long session of typos would grow the map forever otherwise. When full,
|
||||||
|
// the cache is cleared and refilled; a script repeats the same words, so
|
||||||
|
// re-checks are cheap.
|
||||||
|
const cacheLimit = 10000
|
||||||
|
|
||||||
// Check reports whether word is correct. Words with digits ("3rd", "R2D2")
|
// Check reports whether word is correct. Words with digits ("3rd", "R2D2")
|
||||||
// and single letters are always accepted.
|
// and single letters are always accepted.
|
||||||
func (c *Checker) Check(word string) bool {
|
func (c *Checker) Check(word string) bool {
|
||||||
@@ -104,6 +110,9 @@ func (c *Checker) Check(word string) bool {
|
|||||||
}
|
}
|
||||||
ok, found := c.cache[word]
|
ok, found := c.cache[word]
|
||||||
if !found {
|
if !found {
|
||||||
|
if len(c.cache) >= cacheLimit {
|
||||||
|
clear(c.cache)
|
||||||
|
}
|
||||||
ok = c.dict.Check(word)
|
ok = c.dict.Check(word)
|
||||||
c.cache[word] = ok
|
c.cache[word] = ok
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,6 +180,35 @@ func TestLatin1(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCheckerCacheBound(t *testing.T) {
|
||||||
|
d, err := Parse([]byte("SET UTF-8\n"), []byte("1\nknown\n"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
c := NewChecker(d, nil)
|
||||||
|
// letters encodes n in bijective base 26: distinct pure-letter words.
|
||||||
|
letters := func(n int) string {
|
||||||
|
var w []rune
|
||||||
|
for {
|
||||||
|
w = append([]rune{rune('a' + n%26)}, w...)
|
||||||
|
if n < 26 {
|
||||||
|
return string(w)
|
||||||
|
}
|
||||||
|
n = n/26 - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := range 2 * cacheLimit {
|
||||||
|
c.Check(letters(i))
|
||||||
|
}
|
||||||
|
if len(c.cache) > cacheLimit {
|
||||||
|
t.Errorf("cache holds %d words, limit %d", len(c.cache), cacheLimit)
|
||||||
|
}
|
||||||
|
// The answers are unchanged by a clear.
|
||||||
|
if !c.Check("known") || c.Check("knownn") {
|
||||||
|
t.Error("wrong answers after the cache cleared")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestWords(t *testing.T) {
|
func TestWords(t *testing.T) {
|
||||||
got := Words("Élise's well-known R2D2, l’homme… 'quoted' x")
|
got := Words("Élise's well-known R2D2, l’homme… 'quoted' x")
|
||||||
want := []Word{
|
want := []Word{
|
||||||
|
|||||||
Reference in new issue
Block a user