merge_rules.js (3484B)
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 { getLead, getPadchim, getVowel, hasHiddenPadchim, join, matchGeulja } from "./hangul.js"; 5 /** 6 * Helper function for defining common contractions between a character without 7 * a padchim and a character that starts with 'ᄋ', e.g. ㅐ + ㅕ -> ㅐ when 8 * applied to 해 + 였 yields 했. 9 * 10 * @param {string} vowel1 11 * @param {string} vowel2 12 * @param {string} new_vowel 13 * @returns {Rule} The created rule. 14 */ 15 function vowelContraction(vowel1, vowel2, new_vowel) { 16 function rule(x, y) { 17 if (matchGeulja(x[x.length - 1], undefined, vowel1, null) && matchGeulja(y[0], "ᄋ", vowel2, undefined)) { 18 return [`vowel contraction [${vowel1} + ${vowel2} -> ${new_vowel}]`, x.substring(0, x.length - 1) + join(getLead(x[x.length - 1]), new_vowel, getPadchim(y[0])) + y.substring(1)]; 19 } 20 return null; 21 } 22 return rule; 23 } 24 /** 25 * Helper function for defining merges where a character will take the padchim 26 * of a merged character if the first character doesn't already have a padchim, 27 * .e.g. 습 -> 가 + 습니다 -> 갑니다. 28 */ 29 function noPadchimRule(geuljas) { 30 function rule(x, y) { 31 if (!getPadchim(x[x.length - 1]) && geuljas.includes(y[0])) { 32 return ["borrow padchim", x.substring(0, x.length - 1) + join(getLead(x[x.length - 1]), getVowel(x[x.length - 1]), getPadchim(y[0])) + y.substring(1)]; 33 } 34 return null; 35 } 36 return rule; 37 } 38 export const merge_rules = [ 39 noPadchimRule(["을", "습", "읍", "는", "음"]), 40 (x, y) => { 41 if (getPadchim(x[x.length - 1]) === "ᆯ" && y.startsWith("음")) { 42 return ["ㄹ + ㅁ -> ᆱ", x.slice(0, x.length - 1) + join(getLead(x[x.length - 1]), getVowel(x[x.length - 1]), "ᆱ")]; 43 } 44 return null; 45 }, 46 vowelContraction("ㅐ", "ㅓ", "ㅐ"), 47 vowelContraction("ㅡ", "ㅓ", "ㅓ"), 48 vowelContraction("ㅜ", "ㅓ", "ㅝ"), 49 vowelContraction("ㅗ", "ㅏ", "ㅘ"), 50 vowelContraction("ㅚ", "ㅓ", "ㅙ"), 51 vowelContraction("ㅙ", "ㅓ", "ㅙ"), 52 vowelContraction("ㅘ", "ㅓ", "ㅘ"), 53 vowelContraction("ㅝ", "ㅓ", "ㅝ"), 54 vowelContraction("ㅏ", "ㅏ", "ㅏ"), 55 vowelContraction("ㅡ", "ㅏ", "ㅏ"), 56 vowelContraction("ㅣ", "ㅓ", "ㅕ"), 57 vowelContraction("ㅓ", "ㅓ", "ㅓ"), 58 vowelContraction("ㅓ", "ㅣ", "ㅐ"), 59 vowelContraction("ㅏ", "ㅣ", "ㅐ"), 60 vowelContraction("ㅑ", "ㅣ", "ㅒ"), 61 vowelContraction("ㅒ", "ㅓ", "ㅒ"), 62 vowelContraction("ㅔ", "ㅓ", "ㅔ"), 63 vowelContraction("ㅕ", "ㅓ", "ㅕ"), 64 vowelContraction("ㅏ", "ㅕ", "ㅐ"), 65 vowelContraction("ㅖ", "ㅓ", "ㅖ"), 66 vowelContraction("ㅞ", "ㅓ", "ㅞ"), 67 // Rule: Don't append 으 to ㄹ irregulars. 68 (x, y) => { 69 if (getPadchim(x[x.length - 1]) === "ᆯ" && y.startsWith("면")) { 70 return ["join", x.toString() + y.toString()]; 71 } 72 return null; 73 }, 74 // Rule: 으 insertion. 75 (x, y) => { 76 const geuljas = ["면", "세", "십"]; 77 if ((hasHiddenPadchim(x[x.length - 1]) || getPadchim(x[x.length - 1])) && geuljas.includes(y[0])) { 78 return ["padchim + consonant -> insert 으", x + "으" + y]; 79 } 80 return null; 81 }, 82 // Default rule: Just append the contents. 83 (x, y) => ["join", x.toString() + y.toString()], 84 ];