tdk-api-wrapper 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -14
- package/dist/{chunk-5TYJDVHK.mjs → chunk-7KJHYRJZ.mjs} +426 -15
- package/dist/cli.js +559 -19
- package/dist/cli.mjs +180 -5
- package/dist/index.d.mts +119 -1
- package/dist/index.d.ts +119 -1
- package/dist/index.js +430 -16
- package/dist/index.mjs +9 -3
- package/package.json +2 -2
- package/src/cli.ts +185 -4
- package/src/index.ts +1 -1
- package/src/morphology.ts +115 -5
- package/src/tdk.ts +428 -12
- package/src/types.ts +38 -0
- package/test/grammar.test.js +52 -0
- package/test/morphology.test.js +25 -1
- package/test/proofread.test.js +60 -0
- package/test/tools.test.js +51 -0
package/src/cli.ts
CHANGED
|
@@ -4,6 +4,17 @@ import { TDK } from "./tdk";
|
|
|
4
4
|
const rawArgs = process.argv.slice(2);
|
|
5
5
|
const jsonMode = rawArgs.includes("--json");
|
|
6
6
|
const args = rawArgs.filter((a) => a !== "--json");
|
|
7
|
+
|
|
8
|
+
const isColor = !jsonMode && Boolean(process.stdout.isTTY);
|
|
9
|
+
const c = {
|
|
10
|
+
bold: (s: string) => (isColor ? `\x1b[1m${s}\x1b[0m` : s),
|
|
11
|
+
dim: (s: string) => (isColor ? `\x1b[2m${s}\x1b[0m` : s),
|
|
12
|
+
green: (s: string) => (isColor ? `\x1b[32m${s}\x1b[0m` : s),
|
|
13
|
+
yellow: (s: string) => (isColor ? `\x1b[33m${s}\x1b[0m` : s),
|
|
14
|
+
cyan: (s: string) => (isColor ? `\x1b[36m${s}\x1b[0m` : s),
|
|
15
|
+
red: (s: string) => (isColor ? `\x1b[31m${s}\x1b[0m` : s),
|
|
16
|
+
};
|
|
17
|
+
|
|
7
18
|
const KNOWN_COMMANDS = new Set([
|
|
8
19
|
"ara",
|
|
9
20
|
"anlam",
|
|
@@ -11,6 +22,7 @@ const KNOWN_COMMANDS = new Set([
|
|
|
11
22
|
"ornek",
|
|
12
23
|
"hece",
|
|
13
24
|
"uyum",
|
|
25
|
+
"kucukuyum",
|
|
14
26
|
"yazim",
|
|
15
27
|
"kok",
|
|
16
28
|
"stem",
|
|
@@ -25,6 +37,14 @@ const KNOWN_COMMANDS = new Set([
|
|
|
25
37
|
"karsilastir",
|
|
26
38
|
"analiz",
|
|
27
39
|
"oneri",
|
|
40
|
+
"bulmaca",
|
|
41
|
+
"pattern",
|
|
42
|
+
"anagram",
|
|
43
|
+
"kafiye",
|
|
44
|
+
"rhyme",
|
|
45
|
+
"denetle",
|
|
46
|
+
"proofread",
|
|
47
|
+
"repl",
|
|
28
48
|
"kubbealti",
|
|
29
49
|
"nisanyan",
|
|
30
50
|
"viki",
|
|
@@ -50,18 +70,96 @@ function printError(message: string) {
|
|
|
50
70
|
if (jsonMode) {
|
|
51
71
|
console.log(JSON.stringify({ error: message }));
|
|
52
72
|
} else {
|
|
53
|
-
console.log(`Hata: ${message}`);
|
|
73
|
+
console.log(c.red(`Hata: ${message}`));
|
|
54
74
|
}
|
|
55
75
|
}
|
|
56
76
|
|
|
77
|
+
async function startRepl() {
|
|
78
|
+
const readline = await import("node:readline");
|
|
79
|
+
const rl = readline.createInterface({
|
|
80
|
+
input: process.stdin,
|
|
81
|
+
output: process.stdout,
|
|
82
|
+
prompt: c.cyan("tdk> "),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
console.log(c.bold("TDK İnteraktif Sözlük Kabuğu (Çıkmak için 'exit' veya Ctrl+C)"));
|
|
86
|
+
console.log(c.dim("Komutlar: ara <kelime>, hece <kelime>, bulmaca <desen>, denetle <metin> veya doğrudan kelime"));
|
|
87
|
+
rl.prompt();
|
|
88
|
+
|
|
89
|
+
rl.on("line", async (line) => {
|
|
90
|
+
const trimmed = line.trim();
|
|
91
|
+
if (!trimmed) {
|
|
92
|
+
rl.prompt();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (trimmed === "exit" || trimmed === "quit" || trimmed === ".exit") {
|
|
96
|
+
rl.close();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const parts = trimmed.split(/\s+/);
|
|
101
|
+
let subCmd = parts[0].toLowerCase();
|
|
102
|
+
let subArg = parts.slice(1).join(" ");
|
|
103
|
+
if (!KNOWN_COMMANDS.has(subCmd)) {
|
|
104
|
+
subArg = trimmed;
|
|
105
|
+
subCmd = "anlam";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
if (subCmd === "ara" || subCmd === "anlam") {
|
|
110
|
+
const meanings = await TDK.getMeanings(subArg);
|
|
111
|
+
if (meanings.length === 0) console.log(c.dim("Sonuç bulunamadı."));
|
|
112
|
+
else meanings.forEach((m, i) => console.log(`${i + 1}. ${c.green(m)}`));
|
|
113
|
+
} else if (subCmd === "koken") {
|
|
114
|
+
const origin = await TDK.getOrigin(subArg);
|
|
115
|
+
console.log(`Köken: ${c.cyan(origin || "Bilinmiyor")}`);
|
|
116
|
+
} else if (subCmd === "hece") {
|
|
117
|
+
const s = TDK.syllabicate(subArg);
|
|
118
|
+
console.log(`Heceler: ${c.yellow(s.join("-"))}`);
|
|
119
|
+
} else if (subCmd === "uyum") {
|
|
120
|
+
const h = TDK.checkVowelHarmony(subArg);
|
|
121
|
+
console.log(`Büyük Ünlü Uyumu: ${h ? c.green("Uyar") : c.red("Uymaz")}`);
|
|
122
|
+
} else if (subCmd === "kucukuyum") {
|
|
123
|
+
const h = TDK.checkLabialHarmony(subArg);
|
|
124
|
+
console.log(`Küçük Ünlü Uyumu: ${h ? c.green("Uyar") : c.red("Uymaz")}`);
|
|
125
|
+
} else if (subCmd === "bulmaca" || subCmd === "pattern") {
|
|
126
|
+
const matches = await TDK.patternSearch(subArg);
|
|
127
|
+
console.log(matches.slice(0, 15).join(", "));
|
|
128
|
+
} else if (subCmd === "denetle" || subCmd === "proofread") {
|
|
129
|
+
const res = await TDK.proofread(subArg);
|
|
130
|
+
if (res.isCorrect) console.log(c.green("✓ Sorun bulunamadı."));
|
|
131
|
+
else res.issues.forEach((iss) => console.log(`- ${c.yellow(iss.word)}: ${iss.message}${iss.suggestion ? " -> " + c.green(iss.suggestion) : ""}`));
|
|
132
|
+
} else {
|
|
133
|
+
console.log(c.dim("Örnek komutlar: 'ara kalem', 'hece elektrik', 'bulmaca k_l_m', 'denetle Bugün evdeyim'"));
|
|
134
|
+
}
|
|
135
|
+
} catch (e: any) {
|
|
136
|
+
console.log(c.red(`Hata: ${e?.message || e}`));
|
|
137
|
+
}
|
|
138
|
+
rl.prompt();
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
57
142
|
async function run() {
|
|
58
|
-
if (!command
|
|
143
|
+
if (!command) {
|
|
144
|
+
if (process.stdin.isTTY) {
|
|
145
|
+
await startRepl();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
59
148
|
console.log("Kullanım: tdk [komut] <kelime> [--json]");
|
|
60
149
|
console.log(
|
|
61
|
-
"Komutlar: ara, anlam, koken, ornek, hece, uyum, yazim, kok, deyim, gunun, rastgele, esanlam, karsit, yabanci, kurallar, kural, karsilastir, analiz, oneri, kubbealti, nisanyan, viki"
|
|
150
|
+
"Komutlar: ara, anlam, koken, ornek, hece, uyum, kucukuyum, yazim, kok, deyim, gunun, rastgele, esanlam, karsit, yabanci, kurallar, kural, karsilastir, analiz, oneri, bulmaca, anagram, kafiye, denetle, repl, kubbealti, nisanyan, viki"
|
|
62
151
|
);
|
|
63
152
|
console.log("Not: Komut belirtilmezse doğrudan kelime anlamı aranır (örn: tdk selam)");
|
|
64
|
-
process.exit(
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (command === "--help" || command === "-h") {
|
|
157
|
+
console.log("Kullanım: tdk [komut] <kelime> [--json]");
|
|
158
|
+
console.log(
|
|
159
|
+
"Komutlar: ara, anlam, koken, ornek, hece, uyum, kucukuyum, yazim, kok, deyim, gunun, rastgele, esanlam, karsit, yabanci, kurallar, kural, karsilastir, analiz, oneri, bulmaca, anagram, kafiye, denetle, repl, kubbealti, nisanyan, viki"
|
|
160
|
+
);
|
|
161
|
+
console.log("Not: Komut belirtilmezse doğrudan kelime anlamı aranır (örn: tdk selam)");
|
|
162
|
+
process.exit(0);
|
|
65
163
|
}
|
|
66
164
|
|
|
67
165
|
TDK.enableCache(false);
|
|
@@ -121,6 +219,16 @@ async function run() {
|
|
|
121
219
|
break;
|
|
122
220
|
}
|
|
123
221
|
|
|
222
|
+
case "kucukuyum":
|
|
223
|
+
case "labial": {
|
|
224
|
+
if (!word) throw new Error("Kelime belirtmelisiniz.");
|
|
225
|
+
const isHarmony = TDK.checkLabialHarmony(word);
|
|
226
|
+
printResult({ word, labialHarmony: isHarmony }, () =>
|
|
227
|
+
console.log(`Küçük Ünlü Uyumu: ${isHarmony ? "Uyar" : "Uymaz"}`)
|
|
228
|
+
);
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
|
|
124
232
|
case "yazim": {
|
|
125
233
|
if (!word) throw new Error("Kelime belirtmelisiniz.");
|
|
126
234
|
const spellResult = await TDK.checkSpelling(word);
|
|
@@ -299,6 +407,79 @@ async function run() {
|
|
|
299
407
|
break;
|
|
300
408
|
}
|
|
301
409
|
|
|
410
|
+
case "bulmaca":
|
|
411
|
+
case "pattern": {
|
|
412
|
+
if (!word) throw new Error("Desen belirtmelisiniz (örn: k_l_m).");
|
|
413
|
+
const matches = await TDK.patternSearch(word);
|
|
414
|
+
printResult(matches, () => {
|
|
415
|
+
if (matches.length === 0) {
|
|
416
|
+
console.log("Eşleşen kelime bulunamadı.");
|
|
417
|
+
} else {
|
|
418
|
+
console.log(c.bold(`Bulunan Kelimeler (${matches.length}):`));
|
|
419
|
+
matches.forEach((m, i) => console.log(`${i + 1}. ${c.cyan(m)}`));
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
case "anagram": {
|
|
426
|
+
if (!word) throw new Error("Harfler belirtmelisiniz.");
|
|
427
|
+
const anagrams = await TDK.findAnagrams(word);
|
|
428
|
+
printResult(anagrams, () => {
|
|
429
|
+
if (anagrams.length === 0) {
|
|
430
|
+
console.log("Anagram veya bu harflerle türetilebilecek kelime bulunamadı.");
|
|
431
|
+
} else {
|
|
432
|
+
const clean = word.trim().toLocaleLowerCase("tr-TR").replace(/[^a-zçğıöşüâîû]/gi, "");
|
|
433
|
+
const hasExact = anagrams.some((a) => a.length === clean.length);
|
|
434
|
+
const title = hasExact
|
|
435
|
+
? `Anagramlar (${anagrams.length}):`
|
|
436
|
+
: `Birebir anagram bulunamadı. Bu harflerle türetilen kelimeler (${anagrams.length}):`;
|
|
437
|
+
console.log(c.bold(title));
|
|
438
|
+
anagrams.forEach((a, i) => console.log(`${i + 1}. ${c.green(a)} ${c.dim(`(${a.length} harf)`)}`));
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
case "kafiye":
|
|
445
|
+
case "rhyme": {
|
|
446
|
+
if (!word) throw new Error("Kelime belirtmelisiniz.");
|
|
447
|
+
const rhymes = await TDK.findRhymes(word);
|
|
448
|
+
printResult(rhymes, () => {
|
|
449
|
+
if (rhymes.length === 0) {
|
|
450
|
+
console.log("Kafiye bulunamadı.");
|
|
451
|
+
} else {
|
|
452
|
+
console.log(c.bold(`Kafiyeli Kelimeler (${rhymes.length}):`));
|
|
453
|
+
rhymes.forEach((r, i) => console.log(`${i + 1}. ${c.yellow(r)}`));
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
case "denetle":
|
|
460
|
+
case "proofread": {
|
|
461
|
+
if (!word) throw new Error("Metin belirtmelisiniz.");
|
|
462
|
+
const result = await TDK.proofread(word);
|
|
463
|
+
printResult(result, () => {
|
|
464
|
+
if (result.isCorrect) {
|
|
465
|
+
console.log(c.green("✓ Metinde imla veya bağlaç hatası tespit edilmedi."));
|
|
466
|
+
} else {
|
|
467
|
+
console.log(c.bold(c.red(`Metinde ${result.issues.length} olası sorun tespit edildi:`)));
|
|
468
|
+
result.issues.forEach((issue, i) => {
|
|
469
|
+
const label = c.yellow(`[${issue.type}]`);
|
|
470
|
+
const sug = issue.suggestion ? c.green(` -> Öneri: ${issue.suggestion}`) : "";
|
|
471
|
+
console.log(`${i + 1}. ${label} "${c.bold(issue.word)}": ${issue.message}${sug}`);
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
case "repl": {
|
|
479
|
+
await startRepl();
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
|
|
302
483
|
case "kubbealti": {
|
|
303
484
|
if (!word) throw new Error("Kelime belirtmelisiniz.");
|
|
304
485
|
const meanings = await TDK.getKubbealtiMeanings(word);
|
package/src/index.ts
CHANGED
package/src/morphology.ts
CHANGED
|
@@ -67,7 +67,8 @@ export const TURKISH_SUFFIXES: readonly string[] = [
|
|
|
67
67
|
"miş", "mış", "muş", "müş", "dim", "dım", "dum", "düm", "tim", "tım", "tum", "tüm",
|
|
68
68
|
"din", "dın", "dun", "dün", "tin", "tın", "tun", "tün", "dik", "dık", "duk", "dük",
|
|
69
69
|
"tik", "tık", "tuk", "tük", "ydi", "ydı", "ydu", "ydü", "yim", "yım", "yum", "yüm",
|
|
70
|
-
"sin", "sın", "sun", "sün", "
|
|
70
|
+
"sin", "sın", "sun", "sün", "sen", "san", "sem", "sam", "sek", "sak",
|
|
71
|
+
"siz", "sız", "suz", "süz", "lik", "lık", "luk", "lük",
|
|
71
72
|
"ici", "ıcı", "ucu", "ücü", "gen", "gan", "ken", "kan",
|
|
72
73
|
"len", "lan", "leş", "laş", "mek", "mak", "yor",
|
|
73
74
|
// 2-letter suffixes
|
|
@@ -126,6 +127,61 @@ export function restoreVowelDrop(stem: string): string[] {
|
|
|
126
127
|
return [];
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Reverses Turkish consonant gemination (ünsüz türemesi / ikizleşmesi):
|
|
132
|
+
* In words of Arabic/foreign origin, when receiving a vowel-initial suffix, the final consonant doubles:
|
|
133
|
+
* e.g. hak->hakkı, his->hissi, sır->sırrı, af->affı, ret->reddi, tıp->tıbbı, zam->zammı, hat->hattı.
|
|
134
|
+
* Restores the single consonant form and checks consonant softening on the result (e.g. redd -> red -> ret).
|
|
135
|
+
*/
|
|
136
|
+
export function restoreGemination(stem: string): string[] {
|
|
137
|
+
if (stem.length < 3) return [];
|
|
138
|
+
const c1 = stem[stem.length - 2];
|
|
139
|
+
const c2 = stem[stem.length - 1];
|
|
140
|
+
if (c1 === c2 && !isVowel(c1)) {
|
|
141
|
+
const single = stem.slice(0, -1);
|
|
142
|
+
const hardened = restoreConsonantSoftening(single);
|
|
143
|
+
return [single, ...hardened];
|
|
144
|
+
}
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Reverses Turkish vowel narrowing (ünlü daralması):
|
|
150
|
+
* Verbs ending in wide vowels 'a' or 'e' narrow to 'ı', 'i', 'u', 'ü' before the continuous tense suffix -yor:
|
|
151
|
+
* e.g. başla-yor -> başlıyor, bekle-yor -> bekliyor, özle-yor -> özlüyor, anla-yor -> anlıyor.
|
|
152
|
+
* Also handles irregular monosyllabic verbs: de-yor -> diyor, ye-yor -> yiyor.
|
|
153
|
+
*/
|
|
154
|
+
export function restoreVowelNarrowing(stem: string): string[] {
|
|
155
|
+
if (stem.length < 2) return [];
|
|
156
|
+
|
|
157
|
+
// Irregular monosyllabic verbs
|
|
158
|
+
if (stem === "di") return ["de"];
|
|
159
|
+
if (stem === "yi") return ["ye"];
|
|
160
|
+
|
|
161
|
+
const lastChar = stem[stem.length - 1];
|
|
162
|
+
const isLastNarrow = "ıiuü".includes(lastChar);
|
|
163
|
+
|
|
164
|
+
// Case 1: stem ends with narrow vowel (e.g. başlı, bekli, özlü, kutlu)
|
|
165
|
+
if (isLastNarrow) {
|
|
166
|
+
const vowelsInBase = stem.slice(0, -1).split("").filter(isVowel);
|
|
167
|
+
const lastVowel = vowelsInBase.length > 0 ? vowelsInBase[vowelsInBase.length - 1] : lastChar;
|
|
168
|
+
const widened = "aıou".includes(lastVowel) ? "a" : "e";
|
|
169
|
+
return [stem.slice(0, -1) + widened];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Case 2: stem ends with consonant (e.g. başlıyor stripped by -ıyor -> stem: başl)
|
|
173
|
+
if (!isVowel(lastChar)) {
|
|
174
|
+
const vowelsInBase = stem.split("").filter(isVowel);
|
|
175
|
+
if (vowelsInBase.length > 0) {
|
|
176
|
+
const lastVowel = vowelsInBase[vowelsInBase.length - 1];
|
|
177
|
+
const widened = "aıou".includes(lastVowel) ? "a" : "e";
|
|
178
|
+
return [stem + widened];
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
|
|
129
185
|
/**
|
|
130
186
|
* Restores verb infinitive headword form (-mek / -mak):
|
|
131
187
|
* Since TDK registers verbs in their infinitive form (e.g. okumak, gelmek, yazmak),
|
|
@@ -186,11 +242,55 @@ export function getStemCandidates(
|
|
|
186
242
|
|
|
187
243
|
const hardened = restoreConsonantSoftening(stem);
|
|
188
244
|
const vowelDropped = restoreVowelDrop(stem);
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
245
|
+
const geminated = restoreGemination(stem);
|
|
246
|
+
|
|
247
|
+
// Vowel narrowing (ünlü daralması) in Turkish strictly occurs with continuous tense (-yor)
|
|
248
|
+
// or with the monosyllabic verbs de-/ye- before buffer 'y' (diye, yiyen).
|
|
249
|
+
// Restricting narrowing to these suffixes prevents false-positive stems on other suffixes.
|
|
250
|
+
const isNarrowingSuffix =
|
|
251
|
+
suffix.startsWith("yor") ||
|
|
252
|
+
suffix.includes("iyor") ||
|
|
253
|
+
suffix.includes("ıyor") ||
|
|
254
|
+
suffix.includes("uyor") ||
|
|
255
|
+
suffix.includes("üyor");
|
|
256
|
+
|
|
257
|
+
const isDeYeBuffer = (stem === "di" || stem === "yi") && suffix.startsWith("y");
|
|
258
|
+
const narrowed = isNarrowingSuffix || isDeYeBuffer ? restoreVowelNarrowing(stem) : [];
|
|
259
|
+
|
|
260
|
+
// Suffix indicator for verbs: -yor, -ecek, -miş, -di, etc.
|
|
261
|
+
const isVerbSuffix =
|
|
262
|
+
isNarrowingSuffix ||
|
|
263
|
+
suffix.includes("ecek") ||
|
|
264
|
+
suffix.includes("acak") ||
|
|
265
|
+
suffix.includes("miş") ||
|
|
266
|
+
suffix.includes("mış") ||
|
|
267
|
+
suffix.includes("müş") ||
|
|
268
|
+
suffix.includes("muş") ||
|
|
269
|
+
suffix.includes("mek") ||
|
|
270
|
+
suffix.includes("mak") ||
|
|
271
|
+
suffix.includes("erek") ||
|
|
272
|
+
suffix.includes("arak") ||
|
|
273
|
+
suffix.includes("dik") ||
|
|
274
|
+
suffix.includes("dık") ||
|
|
275
|
+
suffix.includes("duk") ||
|
|
276
|
+
suffix.includes("dük") ||
|
|
277
|
+
suffix.includes("tik") ||
|
|
278
|
+
suffix.includes("tık") ||
|
|
279
|
+
suffix.includes("tuk") ||
|
|
280
|
+
suffix.includes("tük") ||
|
|
281
|
+
suffix.includes("sen") ||
|
|
282
|
+
suffix.includes("san") ||
|
|
283
|
+
suffix.includes("sem") ||
|
|
284
|
+
suffix.includes("sam") ||
|
|
285
|
+
suffix.includes("sek") ||
|
|
286
|
+
suffix.includes("sak");
|
|
287
|
+
|
|
288
|
+
// Infinitives apply to direct stems, hardened stems, and widened stems (e.g. başlı -> başla -> başlamak)
|
|
289
|
+
const verbalBases = [stem, ...hardened, ...narrowed];
|
|
192
290
|
const infinitives = verbalBases.flatMap((v) => restoreInfinitive(v));
|
|
193
|
-
|
|
291
|
+
|
|
292
|
+
// Base candidates
|
|
293
|
+
const variants = [stem, ...hardened, ...vowelDropped, ...geminated, ...narrowed];
|
|
194
294
|
|
|
195
295
|
for (const variant of variants) {
|
|
196
296
|
if (!seen.has(variant) && variant !== normalized) {
|
|
@@ -199,6 +299,16 @@ export function getStemCandidates(
|
|
|
199
299
|
candidatesWithWeight.push({ candidate: variant, baseLength: stem.length });
|
|
200
300
|
}
|
|
201
301
|
}
|
|
302
|
+
|
|
303
|
+
// Push infinitives with high priority if a verbal suffix matched, preventing noun false-positives
|
|
304
|
+
for (const inf of infinitives) {
|
|
305
|
+
if (!seen.has(inf) && inf !== normalized) {
|
|
306
|
+
seen.add(inf);
|
|
307
|
+
nextFrontier.push(inf);
|
|
308
|
+
const weight = isVerbSuffix ? stem.length + 5 : stem.length;
|
|
309
|
+
candidatesWithWeight.push({ candidate: inf, baseLength: weight });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
202
312
|
}
|
|
203
313
|
}
|
|
204
314
|
}
|