verb-master

web application to practice verb conjugations for the korean language
git clone git://brookjeynes.dev/bjeynes/verb-master.git
Log | Files | Refs | README | LICENSE

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 }