c로 포팅을 모두 끝냈고, 기존 ruby 로 만든 오토마타에는 없던 BackSpace 기능도 집어넣었습니다.
찾기 작업을 원할하게 하기 위해 아래처럼 배열을 만들었습니다.
데이터가 적기 때문에 이 방법이 hash map 을 만드는 것보다 오히려 효율적이라 봅니다.
const uint16_t dubeolsik[] = { /* A(0x41)..Z(0x5a) */ 0, 0, 0, 0, 0x3138, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x3152, 0x3156, 0x3143, 0x3132, 0, 0x3146, 0, 0, 0x3149, 0, 0, 0, /* ignore 0x5b..0x60 */ 0, 0, 0, 0, 0, 0, /* a(0x61)..z(0x7a) */ 0x3141, 0x3160, 0x314a, 0x3147, 0x3137, 0x3139, 0x314e, 0x3157, 0x3151, 0x3153, 0x314f, 0x3163, 0x3161, 0x315c, 0x3150, 0x3154, 0x3142, 0x3131, 0x3134, 0x3145, 0x3155, 0x314d, 0x3148, 0x314c, 0x315b, 0x314b }; /* 0x3131(ㄱ)..0x314e(ㅎ) */ const int8_t l_index[] = { 0, 1, -1, 2, -1, -1, 3, 4, 5, -1, -1, -1, -1, -1, -1, -1, 6, 7, 8, -1, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 }; /* 0x314f(ㅏ)..0x3163(ㅣ) */ const int8_t v_index[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }; /* 0x3130..0x314e(ㅎ) */ const int8_t t_index[] = { 0, 1, 2, 3, 4, 5, 6, 7, -1, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, -1, 18, 19, 20, 21, 22, -1, 23, 24, 25, 26, 27 }; typedef struct { char *key; uint16_t value; } Rule; const Rule cc_rules[] = { {"rt", 0x3133}, {"sw", 0x3135}, {"sg", 0x3136}, {"fr", 0x313a}, {"fa", 0x313b}, {"fq", 0x313c}, {"ft", 0x313d}, {"fx", 0x313e}, {"fv", 0x313f}, {"fg", 0x3140}, {"qt", 0x3144}, {NULL, 0} }; const Rule vv_rules[] = { {"hk", 0x3158}, {"ho", 0x3159}, {"hl", 0x315a}, {"nj", 0x315d}, {"np", 0x315e}, {"nl", 0x315f}, {"ml", 0x3162}, {NULL, 0} }; uint16_t rule_lookup (const Rule *rule, char *key) { for (int i = 0; rule[i].key; i++) { if (rule[i].key[0] == key[0] && rule[i].key[1] == key[1]) return rule[i].value; } return 0; }지면 관계상 모든 완전한 코드를 싣기는 곤란하고, LVT 상태일 때 코드만 실겠습니다.
다음의 루비 코드는 LVT 상태일 때 동작을 기술한 코드입니다.
when :lvt if @l_index.include? dubeolsik[@c] cc = cc_rules[dubeolsik[@seq[2]] + dubeolsik[@c]] if cc puts "preedit: " + compose_hangul(dubeolsik[@seq[0]], dubeolsik[@seq[1]], cc) @seq << @c @state = :lvtt else puts "commit: " + compose_hangul(dubeolsik[@seq[0]], dubeolsik[@seq[1]], dubeolsik[@seq[2]]) puts "preedit: " + dubeolsik[@c] @seq = [] @seq << @c @state = :c end elsif @v_index.include? dubeolsik[@c] puts "commit: " + compose_hangul(dubeolsik[@seq[0]], dubeolsik[@seq[1]], nil) puts "preedit: " + compose_hangul(dubeolsik[@seq[2]], dubeolsik[@c], nil) @seq.shift 2 @seq << @c @state = :lv endc 로는 이렇게 바뀌었습니다. 코드가 대략 4배 늘었습니다. 두벌식 오토마타를 루비로는 12시간이면 만드는데, c 로 48시간 걸렸는데 다 이유가 있었습니다.
코드량이 3~4배 늘어났으니 타이핑 시간, 검토 시간, 디버깅 시간도 덩달아 3~4배 늘어났기 때문이죠.
case STATE_LVT: if (keyval_is_l (keyval)) { char cc[2]; uint16_t t; cc[0] = tian_korean_queue_peek (korean->queue, 2); cc[1] = keyval; t = rule_lookup (cc_rules, cc); if (t) { uint16_t l, v; l = dubeolsik[tian_korean_queue_peek (korean->queue, 0) - 'A']; v = dubeolsik[tian_korean_queue_peek (korean->queue, 1) - 'A']; tian_ucs_to_utf8_with_buf (compose_hangul (l, v, t), korean->preedit_string); tian_korean_queue_add (korean->queue, keyval); korean->state = STATE_LVTT; } else { char *text; uint16_t ucs, l, v, t; l = dubeolsik[tian_korean_queue_peek (korean->queue, 0) - 'A']; v = dubeolsik[tian_korean_queue_peek (korean->queue, 1) - 'A']; t = dubeolsik[tian_korean_queue_peek (korean->queue, 2) - 'A']; ucs = compose_hangul (l, v, t); text = tian_ucs_to_utf8 (ucs); tian_lingua_emit_commit (lingua, target, text); tian_ucs_to_utf8_with_buf (dubeolsik[keyval - 'A'], korean->preedit_string); tian_korean_queue_remove_all (korean->queue); tian_korean_queue_add (korean->queue, keyval); korean->state = STATE_C; free (text); } } else if (keyval_is_v (keyval)) { char *text; uint16_t ucs, l, v; l = dubeolsik[tian_korean_queue_peek (korean->queue, 0) - 'A']; v = dubeolsik[tian_korean_queue_peek (korean->queue, 1) - 'A']; ucs = compose_hangul (l, v, 0); text = tian_ucs_to_utf8 (ucs); tian_lingua_emit_commit (lingua, target, text); l = dubeolsik[tian_korean_queue_peek (korean->queue, 2) - 'A']; v = dubeolsik[keyval - 'A']; ucs = compose_hangul (l, v, 0); tian_ucs_to_utf8_with_buf (ucs, korean->preedit_string); tian_korean_queue_remove (korean->queue); tian_korean_queue_remove (korean->queue); tian_korean_queue_add (korean->queue, keyval); korean->state = STATE_LV; free (text); } else if (keyval == TIAN_KEY_BackSpace) { uint16_t ucs, l, v; tian_korean_queue_pop (korean->queue); l = dubeolsik[tian_korean_queue_peek (korean->queue, 0) - 'A']; v = dubeolsik[tian_korean_queue_peek (korean->queue, 1) - 'A']; ucs = compose_hangul (l, v, 0); tian_ucs_to_utf8_with_buf (ucs, korean->preedit_string); korean->state = STATE_LV; } break;현재, 자작 두벌식 오토마타를 이용하여 입력하고 있는데, 모두 잘 됩니다.
다 만들고 나니까, 이런 생각이 듭디다.
원형(환형) 큐로 구현하지 말고, 그냥
char c[2], l, v[2], t[2];이렇게 변수를 만들어두고 그걸 사용하는게 오버헤드가 감소하고, 코드수가 줄어들 것이라는 생각이 들었습니다.
본 두벌식 오토마타의 상태는,
typedef enum { STATE_NONE, STATE_C, STATE_V, STATE_LV, STATE_CC, STATE_VV, STATE_LVV, STATE_LVT, STATE_LVVT, STATE_LVTT, STATE_LVVTT } TianKoreanComposeState;이러한 상태가 있는데, 예를 들어 LVVTT 는 5개의 연속된 문자 상태입니다.
dnpfg 를 치면 웷 이라는 글자가 만들어지는데,
LVVTT dnpfgL은 d, V n, 두번째 V 는 p, T 는 f, 두번째 T 는 g 에 대응됩니다. 5개 문자로 웷이라는 글자가 만들어지는거고,
BackSpace 키를 누르면 LVVT (dnpf) 상태가 됩니다. dnpf 문자로 웰 이라는 글자가 만들어지는거죠.
한글 조합 공식은
uint16_t compose_hangul (uint16_t l, uint16_t v, uint16_t t) { return l_index[l - 0x3131] * 588 + v_index[v - 0x314f] * 28 + t_index[!t ? 0 : t - 0x3130] + 0xac00; }이렇게 됩니다. 유니코드 시대에는 두벌식 오토마타 만드는 것 쯤이냐 껌이죠.
이상으로 두벌식 오토마타 연재를 마치겠습니다.
감사합니다.
되게 재미나 보이네요 ㄷㄷ - dc App