Skip to content

Instantly share code, notes, and snippets.

@RanolP
Last active May 7, 2022 13:55
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RanolP/ccd8edb2a06aa03afa242f710013a76f to your computer and use it in GitHub Desktop.
Save RanolP/ccd8edb2a06aa03afa242f710013a76f to your computer and use it in GitHub Desktop.
rke(I like to call it '갇'), Replace Korean Entities
type TailId = -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27;
function tailIdOf(word: string): TailId {
if (word.length === 0) {
return -1;
}
const last = word[word.length - 1];
if (last < '가' || '힣' < last) {
return -1;
}
// 위의 if 문에서 '가' <= last이므로 last.charCodeAt(0)은 양의 정수고
// 그 값을 28로 나눈 나머지이므로 0 ~ 27 범위입니다.
// 따라서 TailId라고 단언할 수 있습니다.
return (last.charCodeAt(0) - 44032) % 28 as TailId;
}
export function rke(template: TemplateStringsArray, ...args: unknown[]): string {
let result = '';
for (let i = 0; i < template.raw.length; i++) {
const argument = i == 0 ? null : String(args[i - 1]);
if (argument !== null) {
result += argument;
}
const current = template.raw[i];
if (current.length === 0) {
continue;
}
const [head, ...splitted] = current.split('\\');
result += head;
for (const token of splitted) {
const tail = tailIdOf(result);
switch (token[0]) {
case '은':
case '는':
result += tail === -1 ? '은(는)' : tail !== 0 ? '은' : '는';
result += token.slice(1);
break;
case '이':
if (token.length > 1 && token[1] != ' ') {
result += tail === -1 ? '(이)' : tail !== 0 ? '이' : '';
result += token.slice(1);
break;
}
case '가':
result += tail === -1 ? '이(가)' : tail !== 0 ? '이' : '가';
result += token.slice(1);
break;
case '을':
case '를':
result += tail === -1 ? '을(를)' : tail !== 0 ? '을' : '를';
result += token.slice(1);
break;
case '와':
case '과':
result += tail === -1 ? '와(과)' : tail !== 0 ? '과' : '와';
result += token.slice(1);
break;
case '으':
result += tail === -1 ? '(으)' : tail !== 0 && tail !== 8 ? '으' : '';
result += token.slice(1);
break;
default:
result += token;
continue;
}
}
}
return result;
}
@RanolP
Copy link
Author

RanolP commented Sep 4, 2020

rke

Replace Korean Entities

사용법

const 대상 = '강의';
const 상황 = '언제';
const 느낌 = '노잼';
rke`${대상}\은 ${상황}\이나 ${느낌}\이다` // "강의는 언제나 노잼이다"

자세한 명세

은/는, 이/가, 을/를, 와/과, (으)*, (이)*를 처리합니다.
이/가와 (이)*는 \이로 겹치기 때문에 \이 뒤에 공백이 아닌 문자가 있을 때만 (이)*로 간주합니다.
모든 값은 String(v)를 통해 문자열화 합니다.
영문이나 숫자 등 받침 유무를 알 수 없는 문자가 앞말이라면 (으)로, (이)지만, 이(가) 등으로 표시합니다.

한계

이스케이프 문자를 사용할 경우 올바르게 처리하지 않습니다. 다음과 같이 우회해야 합니다.

rke`\n은 안되니까 ${'\n'}` // "n은 안되니까 \n"

@RanolP
Copy link
Author

RanolP commented Aug 10, 2021

아래 코드를 복사해서 콘솔에서 테스트해보세요! (압축한 버전과 타입을 제거한 버전입니다, 둘 중 하나를 쓰세요)

function tailIdOf(e){if(0===e.length)return-1;const c=e[e.length-1];return c<"가"||"힣"<c?-1:(c.charCodeAt(0)-44032)%28}function rke(e,...c){let t="";for(let n=0;n<e.raw.length;n++){const s=0==n?null:String(c[n-1]);null!==s&&(t+=s);const a=e.raw[n];if(0===a.length)continue;const[l,...r]=a.split("\\");t+=l;for(const e of r){const c=tailIdOf(t);switch(e[0]){case"은":case"는":t+=-1===c?"은(는)":0!==c?"은":"는",t+=e.slice(1);break;case"이":if(e.length>1&&" "!=e[1]){t+=-1===c?"(이)":0!==c?"이":"",t+=e.slice(1);break}case"가":t+=-1===c?"이(가)":0!==c?"이":"가",t+=e.slice(1);break;case"을":case"를":t+=-1===c?"을(를)":0!==c?"을":"를",t+=e.slice(1);break;case"와":case"과":t+=-1===c?"와(과)":0!==c?"과":"와",t+=e.slice(1);break;case"으":t+=-1===c?"(으)":0!==c&&8!==c?"으":"",t+=e.slice(1);break;default:t+=e;continue}}}return t}
function tailIdOf(word) {
  if (word.length === 0) {
    return -1;
  }
  const last = word[word.length - 1];
  if (last < '가' || '힣' < last) {
    return -1;
  }

  // 위의 if 문에서 '가' <= last이므로 last.charCodeAt(0)은 양의 정수고
  // 그 값을 28로 나눈 나머지이므로 0 ~ 27 범위입니다.
  // 따라서 TailId라고 단언할 수 있습니다.
  return (last.charCodeAt(0) - 44032) % 28;
}

function rke(template, ...args) {
  let result = '';
  for (let i = 0; i < template.raw.length; i++) {
    const argument = i == 0 ? null : String(args[i - 1]);
    if (argument !== null) {
      result += argument;
    }
    const current = template.raw[i];
    if (current.length === 0) {
      continue;
    }
    const [head, ...splitted] = current.split('\\');
    result += head;
    for (const token of splitted) {
      const tail = tailIdOf(result);
      switch (token[0]) {
        case '은':
        case '는':
          result += tail === -1 ? '은(는)' : tail !== 0 ? '은' : '는';
          result += token.slice(1);
          break;
        case '이':
          if (token.length > 1 && token[1] != ' ') {
            result += tail === -1 ? '(이)' : tail !== 0 ? '이' : '';
            result += token.slice(1);
            break;
          }
        case '가':
          result += tail === -1 ? '이(가)' : tail !== 0 ? '이' : '가';
          result += token.slice(1);
          break;
        case '을':
        case '를':
          result += tail === -1 ? '을(를)' : tail !== 0 ? '을' : '를';
          result += token.slice(1);
          break;
        case '와':
        case '과':
          result += tail === -1 ? '와(과)' : tail !== 0 ? '과' : '와';
          result += token.slice(1);
          break;
        case '으':
          result += tail === -1 ? '(으)' : tail !== 0 && tail !== 8 ? '으' : '';
          result += token.slice(1);
          break;
        default:
          result += token;
          continue;
      }
    }
  }
  return result;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment