feat: update typecheck script for incremental compilation and add JosaUtil for Korean postposition handling

This commit is contained in:
2025-12-29 12:50:45 +00:00
parent cd43445e4b
commit 30de11797b
5 changed files with 143 additions and 5 deletions
+132
View File
@@ -0,0 +1,132 @@
export type JosaKey =
| '은'
| '이'
| '과'
| '이나'
| '을'
| '으로'
| '이라'
| '이랑';
const DEFAULT_POSTPOSITION: Record<JosaKey, string> = {
: '는',
: '가',
: '와',
: '나',
: '를',
: '로',
: '라',
: '랑',
};
const buildMapPostPosition = (): Record<string, JosaKey> => {
const map: Record<string, JosaKey> = {};
for (const [key, value] of Object.entries(DEFAULT_POSTPOSITION)) {
const k = key as JosaKey;
map[key] = k;
map[value] = k;
map[`(${key})${value}`] = k;
}
return map;
};
const MAP_POSTPOSITION = buildMapPostPosition();
const REG_INVALID_CHAR = /[^a-zA-Z0-9---\s]+/gu;
const REG_TARGET_CHAR = /^[\s\S]*?(\S*)\s*$/u;
const KO_START_CODE = 0xac00;
const KO_FINISH_CODE = 0xd7a3;
const JONGSUNG_RIEUL = 8;
const getLastChar = (text: string): string => {
const cleaned = text
.replace(REG_INVALID_CHAR, ' ')
.replace(REG_TARGET_CHAR, '$1')
.trim();
if (!cleaned) {
return '';
}
const chars = Array.from(cleaned);
return chars[chars.length - 1] ?? '';
};
const getDigitJongsung = (
digit: number
): { has: boolean; rieul: boolean } => {
switch (digit) {
case 0:
case 3:
case 6:
return { has: true, rieul: false };
case 1:
case 7:
case 8:
return { has: true, rieul: true };
default:
return { has: false, rieul: false };
}
};
const hasJongsung = (text: string, isRo: boolean): boolean => {
const lastChar = getLastChar(text);
if (!lastChar) {
return false;
}
const code = lastChar.codePointAt(0);
if (code === undefined) {
return false;
}
if (code >= KO_START_CODE && code <= KO_FINISH_CODE) {
const jongsung = (code - KO_START_CODE) % 28;
if (jongsung === 0) {
return false;
}
if (isRo && jongsung === JONGSUNG_RIEUL) {
return false;
}
return true;
}
if (lastChar >= 'ㄱ' && lastChar <= 'ㅎ') {
if (isRo && lastChar === 'ㄹ') {
return false;
}
return true;
}
if (lastChar >= '0' && lastChar <= '9') {
const { has, rieul } = getDigitJongsung(Number(lastChar));
if (isRo && rieul) {
return false;
}
return has;
}
const lower = lastChar.toLowerCase();
const isVowel = ['a', 'e', 'i', 'o', 'u', 'y'].includes(lower);
return !isVowel;
};
export class JosaUtil {
static pick(text: string | null | undefined, wJongsung: string, woJongsung = ''): string {
const normalizedText = text ?? '';
let withJongsung = wJongsung;
let withoutJongsung = woJongsung;
if (!withoutJongsung) {
const mapped = MAP_POSTPOSITION[wJongsung];
if (!mapped) {
throw new Error('올바르지 않은 조사 지정');
}
withJongsung = mapped;
withoutJongsung = DEFAULT_POSTPOSITION[mapped];
}
const isRo = withJongsung === '으로';
return hasJongsung(normalizedText, isRo)
? withJongsung
: withoutJongsung;
}
static put(text: string, wJongsung: string, woJongsung = ''): string {
return text + JosaUtil.pick(text, wJongsung, woJongsung);
}
}