sweep: name the repeated length/window thresholds in eval checks (V-581)

Five near-duplicate magic-number checks (< 3 runes for a suffix to be
grammar, +/-3 word windows around a self-reference marker, < 5 runes
for a plural verb ending) get named constants with the reasoning
beside them: minInflectedRunes, selfRefWindow, minPluralVerbRunes.
Also dedupes a doubled sentence in the checkAddress comment block that
said the same thing about time-word stoplisting twice. No check logic
changed; word lists and check firing behaviour are untouched.
This commit is contained in:
2026-08-06 02:14:02 +04:00
parent e488ee2285
commit 8c774abe5b
+30 -17
View File
@@ -126,6 +126,17 @@ func checkLength(body string) Result {
// correct, "я напомнил" is not. Both directions matter, which is why this is a
// windowed scan around "я" and not a bare search for masculine endings.
// minInflectedRunes — the shortest a word can be and still carry one of the
// verb/adjective endings this file matches (masculine -л, feminine -ла, …).
// Below this length a suffix match would be coincidence, not grammar.
const minInflectedRunes = 3
// selfRefWindow — how many words past a self-reference marker ("я"/"ты", …)
// still count as belonging to that reference before the sentence has moved
// on. Same window on both the forward scan after "я" and the backward scans
// in governedByYou and hersNotHis.
const selfRefWindow = 3
var wordRE = regexp.MustCompile(`[\p{Cyrillic}]+|[,.;:!?…—-]`)
// secondPerson — pronouns that end the self-reference window. Everything after
@@ -154,7 +165,7 @@ var masculinePredicative = map[string]bool{
// ends in -л AND is a form of a verb (Vikunja #526). Every noun the list held is
// correctly not a verb, and so are the ones it had not got round to.
func masculinePast(w string) bool {
if len([]rune(w)) < 3 {
if len([]rune(w)) < minInflectedRunes {
return false
}
if !strings.HasSuffix(w, "л") && !strings.HasSuffix(w, "лся") {
@@ -172,7 +183,7 @@ func checkFeminine(body string) Result {
// Scan the next few words. Stop at punctuation or at a second-person
// pronoun: past that point the sentence is about him and masculine is
// correct.
for j := i + 1; j < len(words) && j <= i+3; j++ {
for j := i + 1; j < len(words) && j <= i+selfRefWindow; j++ {
nw := words[j]
if len(nw) == 1 && !unicode.Is(unicode.Cyrillic, []rune(nw)[0]) {
break
@@ -259,7 +270,7 @@ var notFeminineVerb = map[string]bool{
// femininePast reports whether a word looks like a feminine past-tense verb:
// "отдыхала", "поела", "выспалась".
func femininePast(w string) bool {
if len([]rune(w)) < 3 || notFeminineVerb[w] {
if len([]rune(w)) < minInflectedRunes || notFeminineVerb[w] {
return false
}
return strings.HasSuffix(w, "ла") || strings.HasSuffix(w, "лась")
@@ -268,7 +279,7 @@ func femininePast(w string) bool {
// looksFeminineNoun — a crude guard against "зарядка была": a word right before
// the verb that ends in "а"/"я" and is not itself a verb is probably the subject.
func looksFeminineNoun(w string) bool {
if femininePast(w) || len([]rune(w)) < 3 {
if femininePast(w) || len([]rune(w)) < minInflectedRunes {
return false
}
return strings.HasSuffix(w, "а") || strings.HasSuffix(w, "я")
@@ -307,7 +318,7 @@ func checkHisGender(body string) Result {
// hersNotHis — the verb is Maven's own if "я" comes shortly before it, or if the
// thing she did was done to him ("напомнила тебе", "проверила за тебя").
func hersNotHis(words []string, i int) bool {
for j := i - 1; j >= 0 && j >= i-3; j-- {
for j := i - 1; j >= 0 && j >= i-selfRefWindow; j-- {
if words[j] == "я" {
return true
}
@@ -368,12 +379,10 @@ func prevWord(words []string, i int) string {
// after the pronoun and is flagged wrongly.
// - any noun earlier in the message counts as an antecedent, even when it is
// not one ("после обеда он не ел", "выпей воды, он не пил" — both missed).
// Verbs and time words no longer count, which covers the usual nudge, but a
// plain noun before the pronoun still blinds it. The
// common time words are stoplisted so the usual nudge opening does not
// blind it, but a message with any other noun in front still slips through.
// This is the check's real hole; widening it further would start flagging
// legitimate third-party messages, so it stops here.
// Verbs and the common time words are stoplisted so the usual nudge opening
// does not blind it, but a message with any other noun in front still
// slips through. This is the check's real hole; widening it further would
// start flagging legitimate third-party messages, so it stops here.
// - a message that opens with "ты" and only later slips into "он" is missed,
// because "ты" itself is skipped but the words around it are not.
// - formal address outside these endings (short adjectives, "вашими" style
@@ -400,10 +409,14 @@ var prepositions = map[string]bool{
"под": true, "про": true, "без": true, "для": true, "через": true,
}
// minPluralVerbRunes — the -ите/-ете/-йте/-ьте endings pluralVerb matches are
// three runes on their own, so anything shorter can't carry a stem plus one.
const minPluralVerbRunes = 5
// pluralVerb reports whether a word looks like a plural/formal verb form:
// "приходите", "выпейте", "забудьте", "хотите".
func pluralVerb(w string) bool {
if len([]rune(w)) < 5 {
if len([]rune(w)) < minPluralVerbRunes {
return false
}
return strings.HasSuffix(w, "ите") || strings.HasSuffix(w, "ете") ||
@@ -443,7 +456,7 @@ var notAnAntecedent = map[string]bool{
// slipped through with "попробуй" taken for the person being talked about).
func looksVerb(w string) bool {
r := []rune(w)
if len(r) < 3 {
if len(r) < minInflectedRunes {
return false
}
for _, suf := range []string{
@@ -681,11 +694,11 @@ func checkEllipsis(body string) Result {
}
// governedByYou reports whether "ты" stands close enough in front of the verb
// at index i to be its subject. Three words, the same window checkFeminine's
// first pass uses after "я", and it stops at a first-person pronoun so "ты
// просил, я напомнил" still trips.
// at index i to be its subject. selfRefWindow words, the same window
// checkFeminine's first pass uses after "я", and it stops at a first-person
// pronoun so "ты просил, я напомнил" still trips.
func governedByYou(words []string, i int) bool {
for j := i - 1; j >= 0 && j >= i-3; j-- {
for j := i - 1; j >= 0 && j >= i-selfRefWindow; j-- {
switch words[j] {
case "ты":
return true