conjugator.js (19640B)
1 // SPDX-License-Identifier: AGPL-3.0-or-later 2 // Copyright (C) 2009 Dan Bravender 3 // Modifications Copyright (c) 2026 Brook Jeynes <me@brookjeynes.dev> 4 import { Geulja, findVowelToAppend, getLead, getPadchim, getVowel, join } from "./hangul.js"; 5 import { isDIrregular, isHIrregular, isLEuIrregular, isLIrregular, isPIrregular, isSIrregular } from "./irregulars.js"; 6 import { merge_rules } from "./merge_rules.js"; 7 export class Conjugator { 8 constructor() { 9 this.tense_rules = []; 10 this.reasons = []; 11 this.tense_rules = [ 12 { name: "base", rule: (infinitive, regular) => this.base(infinitive, regular) }, 13 { name: "base2", rule: (infinitive, regular) => this.base2(infinitive, regular) }, 14 { name: "base3", rule: (infinitive, regular) => this.base3(infinitive, regular) }, 15 { name: "declarative present informal low", rule: (infinitive, regular) => this.declarativePresentInformalLow(infinitive, regular) }, 16 { name: "declarative present informal high", rule: (infinitive, regular) => this.declarativePresentInformalHigh(infinitive, regular) }, 17 { name: "declarative present formal low", rule: (infinitive, regular) => this.declarativePresentFormalLow(infinitive, regular) }, 18 { name: "declarative present formal high", rule: (infinitive, regular) => this.declarativePresentFormalHigh(infinitive, regular) }, 19 { name: "past base", rule: (infinitive, regular) => this.pastBase(infinitive, regular) }, 20 { name: "declarative past informal low", rule: (infinitive, regular) => this.declarativePastInformalLow(infinitive, regular) }, 21 { name: "declarative past informal high", rule: (infinitive, regular) => this.declarativePastInformalHigh(infinitive, regular) }, 22 { name: "declarative past formal low", rule: (infinitive, regular) => this.declarativePastFormalLow(infinitive, regular) }, 23 { name: "declarative past formal high", rule: (infinitive, regular) => this.declarativePastFormalHigh(infinitive, regular) }, 24 { name: "future base", rule: (infinitive, regular) => this.futureBase(infinitive, regular) }, 25 { name: "declarative future informal low", rule: (infinitive, regular) => this.declarativeFutureInformalLow(infinitive, regular) }, 26 { name: "declarative future informal high", rule: (infinitive, regular) => this.declarativeFutureInformalHigh(infinitive, regular) }, 27 { name: "declarative future formal low", rule: (infinitive, regular) => this.declarativeFutureFormalLow(infinitive, regular) }, 28 { name: "declarative future formal high", rule: (infinitive, regular) => this.declarativeFutureFormalHigh(infinitive, regular) }, 29 { name: "declarative future conditional informal low", rule: (infinitive, regular) => this.declarativeFutureConditionalInformalLow(infinitive, regular) }, 30 { name: "declarative future conditional informal high", rule: (infinitive, regular) => this.declarativeFutureConditionalInformalHigh(infinitive, regular) }, 31 { name: "declarative future conditional formal low", rule: (infinitive, regular) => this.declarativeFutureConditionalFormalLow(infinitive, regular) }, 32 { name: "declarative future conditional formal high", rule: (infinitive, regular) => this.declarativeFutureConditionalFormalHigh(infinitive, regular) }, 33 { name: "inquisitive present informal low", rule: (infinitive, regular) => this.inquisitivePresentInformalLow(infinitive, regular) }, 34 { name: "inquisitive present informal high", rule: (infinitive, regular) => this.inquisitivePresentInformalHigh(infinitive, regular) }, 35 { name: "inquisitive present formal low", rule: (infinitive, regular) => this.inquisitivePresentFormalLow(infinitive, regular) }, 36 { name: "inquisitive present formal high", rule: (infinitive, regular) => this.inquisitivePresentFormalHigh(infinitive, regular) }, 37 { name: "inquisitive past informal low", rule: (infinitive, regular) => this.inquisitivePastInformalLow(infinitive, regular) }, 38 { name: "inquisitive past informal high", rule: (infinitive, regular) => this.inquisitivePastInformalHigh(infinitive, regular) }, 39 { name: "inquisitive past formal low", rule: (infinitive, regular) => this.inquisitivePastFormalLow(infinitive, regular) }, 40 { name: "inquisitive past formal high", rule: (infinitive, regular) => this.inquisitivePastFormalHigh(infinitive, regular) }, 41 { name: "imperative present informal low", rule: (infinitive, regular) => this.imperativePresentInformalLow(infinitive, regular) }, 42 { name: "imperative present informal high", rule: (infinitive, regular) => this.imperativePresentInformalHigh(infinitive, regular) }, 43 { name: "imperative present formal low", rule: (infinitive, regular) => this.imperativePresentFormalLow(infinitive, regular) }, 44 { name: "imperative present formal high", rule: (infinitive, regular) => this.imperativePresentFormalHigh(infinitive, regular) }, 45 { name: "propositive present informal low", rule: (infinitive, regular) => this.propositivePresentInformalLow(infinitive, regular) }, 46 { name: "propositive present informal high", rule: (infinitive, regular) => this.propositivePresentInformalHigh(infinitive, regular) }, 47 { name: "propositive present formal low", rule: (infinitive, regular) => this.propositivePresentFormalLow(infinitive, regular) }, 48 { name: "propositive present formal high", rule: (infinitive, regular) => this.propositivePresentFormalHigh(infinitive, regular) }, 49 { name: "connective if", rule: (infinitive, regular) => this.connectiveIf(infinitive, regular) }, 50 { name: "connective and", rule: (infinitive, regular) => this.connectiveAnd(infinitive, regular) }, 51 { name: "nominal ing", rule: (infinitive, regular) => this.nominalIng(infinitive, regular) }, 52 ]; 53 } 54 perform(infinitive, regular = false) { 55 const results = []; 56 for (const tense of this.tense_rules) { 57 this.reasons = []; 58 const conjugation = tense.rule(infinitive, regular); 59 results.push({ 60 tense: tense.name, 61 conjugation: conjugation.toString(), 62 reasons: this.reasons 63 }); 64 } 65 return results; 66 } 67 dropL(x, y) { 68 if (getPadchim(x[x.length - 1]) === "ᆯ") { 69 this.reasons.push("drop ㄹ"); 70 return x.substring(0, x.length - 1) + join(getLead(x[x.length - 1]), getVowel(x[x.length - 1])) + y; 71 } 72 throw new Error("Not an L padchim"); 73 } 74 dropLAndBorrowPadchim(x, y) { 75 if (getPadchim(x[x.length - 1]) === "ᆯ") { 76 this.reasons.push(`drop ${getPadchim(x[x.length - 1])} borrow padchim`); 77 return x.substring(0, x.length - 1) + join(getLead(x[x.length - 1]), getVowel(x[x.length - 1]), getPadchim(y[0])) + y.substring(1); 78 } 79 throw new Error("Not an L padchim"); 80 } 81 merge(x, y) { 82 for (let i = 0; i < merge_rules.length; i++) { 83 const rule = merge_rules[i]; 84 const output = rule(x, y); 85 if (output) { 86 this.reasons.push(`${output[0] && output[0] || ""} (${x} + ${y} -> ${output[1]})`); 87 return output[1]; 88 } 89 } 90 throw new Error("Unable to match on rule"); 91 } 92 // eslint-disable-next-line @typescript-eslint/no-unused-vars 93 base(infinitive, _regular = false) { 94 if (infinitive.endsWith("다")) { 95 return infinitive.substring(0, infinitive.split("").length - 1); 96 } 97 return infinitive; 98 } 99 base2(infinitive, regular = false) { 100 infinitive = this.base(infinitive, regular); 101 if (infinitive === "아니") { 102 const geulja = new Geulja("아니"); 103 geulja.hidden_padchim = true; 104 return geulja; 105 } 106 if (infinitive === "뵙") 107 return "뵈"; 108 if (infinitive === "푸") 109 return "퍼"; 110 let new_infinitive = infinitive; 111 if (isHIrregular(infinitive, regular)) { 112 new_infinitive = this.merge(infinitive.substring(0, infinitive.length - 1) + join(getLead(infinitive[infinitive.length - 1]), getVowel(infinitive[infinitive.length - 1])), "이"); 113 this.reasons.push(`ㅎ irregular (${infinitive} -> ${new_infinitive})`); 114 } 115 else if (isPIrregular(infinitive, regular)) { 116 let new_vowel = ""; 117 // only some verbs get ㅗ (highly irregular) 118 if (["묻잡"].includes(infinitive.toString()) || ["돕", "곱"].includes(infinitive[infinitive.length - 1])) { 119 new_vowel = "ㅗ"; 120 } 121 else { 122 new_vowel = "ㅜ"; 123 } 124 new_infinitive = this.merge(infinitive.substring(0, infinitive.length - 1) + join(getLead(infinitive[infinitive.length - 1]), getVowel(infinitive[infinitive.length - 1])), join("ᄋ", new_vowel)); 125 this.reasons.push(`ㅂ irregular (${infinitive} -> ${new_infinitive})`); 126 } 127 else if (isDIrregular(infinitive, regular)) { 128 new_infinitive = new Geulja(infinitive.substring(0, infinitive.length - 1) + join(getLead(infinitive[infinitive.length - 1]), getVowel(infinitive[infinitive.length - 1]), "ᆯ")); 129 new_infinitive.original_padchim = "ᆮ"; 130 this.reasons.push(`ㄷ irregular (${infinitive} -> ${new_infinitive})`); 131 } 132 else if (isSIrregular(infinitive, regular)) { 133 new_infinitive = new Geulja(infinitive.substring(0, infinitive.length - 1) + join(getLead(infinitive[infinitive.length - 1]), getVowel(infinitive[infinitive.length - 1]))); 134 new_infinitive.hidden_padchim = true; 135 this.reasons.push(`ㅅ irregular (${infinitive} -> ${new_infinitive} [hidden padchim])`); 136 } 137 return new_infinitive; 138 } 139 base3(infinitive, regular = false) { 140 infinitive = this.base(infinitive, regular); 141 if (infinitive === "아니") 142 return "아니"; 143 if (infinitive == "푸") 144 return "푸"; 145 if (infinitive == "뵙") 146 return "뵈"; 147 if (isHIrregular(infinitive, regular)) { 148 return infinitive.substring(0, infinitive.length - 1) + join(getLead(infinitive[infinitive.length - 1]), getVowel(infinitive[infinitive.length - 1])); 149 } 150 else if (isPIrregular(infinitive, regular)) { 151 return infinitive.substring(0, infinitive.length - 1) + join(getLead(infinitive[infinitive.length - 1]), getVowel(infinitive[infinitive.length - 1])) + "우"; 152 } 153 else { 154 return this.base2(infinitive, regular); 155 } 156 } 157 declarativePresentInformalLow(infinitive, regular = false, further_use = false) { 158 infinitive = this.base2(infinitive, regular); 159 if (!further_use && ((infinitive[infinitive.length - 1] === "이" && !(infinitive instanceof Geulja)) || infinitive === "아니")) { 160 this.reasons.push("야 irregular"); 161 return infinitive + "야"; 162 } 163 // 르 irregular 164 if (regular && infinitive === "이르") { 165 return "일러"; 166 } 167 if (isLEuIrregular(infinitive, regular)) { 168 let new_base = infinitive.substring(0, infinitive.length - 2) + join(getLead(infinitive[infinitive.length - 2]), getVowel(infinitive[infinitive.length - 2]), "ᆯ"); 169 if (["푸르", "이르"].includes(infinitive.substring(infinitive.length - 2))) { 170 new_base = new_base + join("ᄅ", getVowel(findVowelToAppend(new_base))); 171 this.reasons.push(`irregular stem + ${infinitive} -> ${new_base}`); 172 return infinitive + "러"; 173 } 174 else if (findVowelToAppend(infinitive.substring(0, infinitive.length - 1)) === "아") { 175 new_base += "라"; 176 this.reasons.push(`르 irregular stem change [${infinitive} -> ${new_base}]`); 177 return new_base; 178 } 179 else { 180 new_base += "러"; 181 this.reasons.push(`르 irregular stem change [${infinitive} -> ${new_base}]`); 182 return new_base; 183 } 184 } 185 else if (infinitive[infinitive.length - 1] === "하") { 186 return this.merge(infinitive, "여"); 187 } 188 else if (isHIrregular(infinitive, regular)) { 189 return this.merge(infinitive, "이"); 190 } 191 return this.merge(infinitive, findVowelToAppend(infinitive)); 192 } 193 declarativePresentInformalHigh(infinitive, regular = false) { 194 infinitive = this.base2(infinitive, regular); 195 if ((infinitive[infinitive.length - 1] === "이" && !(infinitive instanceof Geulja)) || infinitive === "아니") { 196 this.reasons.push("에요 irregular"); 197 return infinitive + "에요"; 198 } 199 return this.merge(this.declarativePresentInformalLow(infinitive, regular, true), "요"); 200 } 201 declarativePresentFormalLow(infinitive, regular = false) { 202 if (isLIrregular(this.base(infinitive), regular)) { 203 return this.dropLAndBorrowPadchim(this.base(infinitive, regular), "는다"); 204 } 205 return this.merge(this.base(infinitive, regular), "는다"); 206 } 207 declarativePresentFormalHigh(infinitive, regular = false) { 208 if (isLIrregular(this.base(infinitive), regular)) { 209 return this.dropLAndBorrowPadchim(this.base(infinitive, regular), "습니다"); 210 } 211 return this.merge(this.base(infinitive, regular), "습니다"); 212 } 213 pastBase(infinitive, regular = false) { 214 const ps = this.declarativePresentInformalLow(infinitive, regular, true); 215 if (findVowelToAppend(ps) == "아") { 216 return this.merge(ps, "았"); 217 } 218 return this.merge(ps, "었"); 219 } 220 declarativePastInformalLow(infinitive, regular = false) { 221 return this.merge(this.pastBase(infinitive, regular), "어"); 222 } 223 declarativePastInformalHigh(infinitive, regular = false) { 224 return this.merge(this.declarativePastInformalLow(infinitive, regular), "요"); 225 } 226 declarativePastFormalLow(infinitive, regular = false) { 227 return this.merge(this.pastBase(infinitive, regular), "다"); 228 } 229 declarativePastFormalHigh(infinitive, regular = false) { 230 return this.merge(this.pastBase(infinitive, regular), "습니다"); 231 } 232 futureBase(infinitive, regular = false) { 233 if (isLIrregular(this.base(infinitive, regular))) { 234 return this.dropLAndBorrowPadchim(this.base3(infinitive, regular), "을"); 235 } 236 return this.merge(this.base3(infinitive, regular), "을"); 237 } 238 declarativeFutureInformalLow(infinitive, regular = false) { 239 return this.merge(this.futureBase(infinitive, regular), " 거야"); 240 } 241 declarativeFutureInformalHigh(infinitive, regular = false) { 242 return this.merge(this.futureBase(infinitive, regular), " 거예요"); 243 } 244 declarativeFutureFormalLow(infinitive, regular = false) { 245 return this.merge(this.futureBase(infinitive, regular), " 거다"); 246 } 247 declarativeFutureFormalHigh(infinitive, regular = false) { 248 return this.merge(this.futureBase(infinitive, regular), " 겁니다"); 249 } 250 declarativeFutureConditionalInformalLow(infinitive, regular = false) { 251 return this.merge(this.base(infinitive, regular), "겠어"); 252 } 253 declarativeFutureConditionalInformalHigh(infinitive, regular = false) { 254 return this.merge(this.base(infinitive, regular), "겠어요"); 255 } 256 declarativeFutureConditionalFormalLow(infinitive, regular = false) { 257 return this.merge(this.base(infinitive, regular), "겠다"); 258 } 259 declarativeFutureConditionalFormalHigh(infinitive, regular = false) { 260 return this.merge(this.base(infinitive, regular), "겠습니다"); 261 } 262 inquisitivePresentInformalLow(infinitive, regular = false) { 263 return this.merge(this.declarativePresentInformalLow(infinitive, regular), "?"); 264 } 265 inquisitivePresentInformalHigh(infinitive, regular = false) { 266 return this.merge(this.declarativePresentInformalHigh(infinitive, regular), "?"); 267 } 268 inquisitivePresentFormalLow(infinitive, regular = false) { 269 infinitive = this.base(infinitive, regular); 270 if (isLIrregular(infinitive, regular)) { 271 return this.dropL(infinitive, "니?"); 272 } 273 return this.merge(infinitive, "니?"); 274 } 275 inquisitivePresentFormalHigh(infinitive, regular = false) { 276 infinitive = this.base(infinitive, regular); 277 if (isLIrregular(infinitive, regular)) { 278 return this.dropLAndBorrowPadchim(infinitive, "습니까?"); 279 } 280 return this.merge(infinitive, "습니까?"); 281 } 282 inquisitivePastInformalLow(infinitive, regular = false) { 283 return this.declarativePastInformalLow(infinitive, regular) + "?"; 284 } 285 inquisitivePastInformalHigh(infinitive, regular = false) { 286 return this.merge(this.declarativePastInformalHigh(infinitive, regular), "?"); 287 } 288 inquisitivePastFormalLow(infinitive, regular = false) { 289 return this.merge(this.pastBase(infinitive, regular), "니?"); 290 } 291 inquisitivePastFormalHigh(infinitive, regular = false) { 292 return this.merge(this.pastBase(infinitive, regular), "습니까?"); 293 } 294 imperativePresentInformalLow(infinitive, regular = false) { 295 return this.declarativePresentInformalLow(infinitive, regular); 296 } 297 imperativePresentInformalHigh(infinitive, regular = false) { 298 if (isLIrregular(this.base(infinitive, regular))) { 299 return this.dropL(this.base3(infinitive, regular), "세요"); 300 } 301 return this.merge(this.base3(infinitive, regular), "세요"); 302 } 303 imperativePresentFormalLow(infinitive, regular = false) { 304 return this.merge(this.imperativePresentInformalLow(infinitive, regular), "라"); 305 } 306 imperativePresentFormalHigh(infinitive, regular = false) { 307 if (isLIrregular(this.base(infinitive, regular))) { 308 return this.dropL(this.base3(infinitive, regular), "십시오"); 309 } 310 return this.merge(this.base3(infinitive, regular), "십시오"); 311 } 312 propositivePresentInformalLow(infinitive, regular = false) { 313 return this.declarativePresentInformalLow(infinitive, regular); 314 } 315 propositivePresentInformalHigh(infinitive, regular = false) { 316 return this.declarativePresentInformalHigh(infinitive, regular); 317 } 318 propositivePresentFormalLow(infinitive, regular = false) { 319 return this.merge(this.base(infinitive, regular), "자"); 320 } 321 propositivePresentFormalHigh(infinitive, regular = false) { 322 infinitive = this.base(infinitive); 323 if (isLIrregular(infinitive, regular)) { 324 return this.dropLAndBorrowPadchim(this.base3(infinitive, regular), "읍시다"); 325 } 326 return this.merge(this.base3(infinitive, regular), "읍시다"); 327 } 328 connectiveIf(infinitive, regular = false) { 329 return this.merge(this.base3(infinitive, regular), "면"); 330 } 331 connectiveAnd(infinitive, regular = false) { 332 infinitive = this.base(infinitive, regular); 333 return this.merge(this.base(infinitive, regular), "고"); 334 } 335 nominalIng(infinitive, regular = false) { 336 return this.merge(this.base3(infinitive, regular), "음"); 337 } 338 }