한글 오토마타에 대하여...
PDA의 키보드를 만들어야 할 상황이 있어서 제작을 해보았다.



UI는 따로 처리된 DLL에서 호출하여 사용하는 방식이고 키 코드를 넘겨 받아서 한글 조합을 하고 있다.
이번에 제작한 오토마타는 유니코드(0xac00 ~ 0xd7a3 범위)내의 한글코드를 처리하고 있다.
때문에 표현 불가능한 글자가 몇개 있는 것 같다.
아니 정확하게 표현 불가능한 코드가 있다.

하지만 입력 방식이 키보드가 아닌 키 코드의 입력 방식이기 때문에 문제는 되지 않았다. 또한 PDA는 유니코드를 사용하니 일단 사용자 범주안에서는 OK 이다.!!
문제는 키보드 입력방식에 적용이 어렵다.
이부분은 보완하여 수정해봐야 겠다.
Define 한 키 코드의 입력이 아닌 char 값을 입력 받을 수 있도록 추가로 처리를 해야 할 것 같다.

소스를 올리기 전에 간단한 한글 오토마타 와 한글 형태소 분리에 대한 설명을 해야 할 것 같다.

※ 본문 내용의 오토마타 구현 과정은 핸드폰과 같이 키의 조합이 키보드와는 다른 모형을 기준으로 하고 있다. 기본 원리를 파악하면 키의 배열이나 키의 숫자가 달라진다고 하여 문제가 될 것은 없는 것 같다.


한글 형태소 분리
{
   간단하게 설명하고 끝내려 한다.자세하게 설명 되어있는 사이트는 찾아보니 생각보다 많이 있었다.
   그리고 내가 그 사이트 보다 자세하게 설명할 자신도 없다.
}

한글코드의 기본개념: http://paero3.myzip.co.kr/hangeul_code/index.html 찾아가서 보면 너무너무 설명을 잘 하고 있다.
원문의 내용을 필요한 부분만 간단 요약 하면 이렇다.

윈도우의 한글코드 : Microsoft의 통합형 한글(확장 완성형 코드:2350자)
확장 완성형 코드 = (기존 완성형 코드 영역 0xB0A1~0xC8FE) + (0x8141~0xC6FE 부분에 한글을 추가)

완성형 코드

 정의:
완성형 방식은, 완성된 글자를 일종의 그림처럼 다루는 방식이다. 따라서 그 발상의 밑바탕에서 바라봤을 때는, 한자 같은 상형문자를 다루는 방식과 근본적으로 아무 차이점이 없다. 그리고 그게 '가나다' 순서를 지키려면, 이미 글자 체계 또한 완성되었다고 본 후 코드를 배열해야 한다. 유니코드 2.0의한글 배열은, 현대 한글에서 조합 가능한 숫자 '11,172'자를 쭉 순서대로 배열해 놓았다. 즉 '2바이트 완성형' 방식이다.

 크기: 2바이트

 자주 쓰이는 한글 2350자를 골라 가나다 순으로 배치
 0xB0A1~0xC8FE: 차례대로 코드 부여
  상위 바이트 0xB0 ~ 0xC8
  하위 바이트 0xA1~ 0xFE

조합형 코드

 정의:
조합형 방식은, 한글 글자의 구성 원리인 초성-중성-종성의 세 갈래 음운을 코드 자료로 삼아 설계된다. 따라서 여기에서는 완성된 글자에 초점이 있는 게 아니라, 각 음운들에 초점이 놓인다.

 크기: 2바이트

상위 1비트: 아스키 코드와 구분하기 위해 사용
다음 5비트: 초성
다음 5비트: 중성
다음 5비트: 종성


핵심은?...

첫번째 논점은 한글글자를 초성 + 중성 + 종성 으로 분리해 내는 것이다. 그리고 반대로 한글자모(자음,모음)가 입력될 때 이미 입력되어 있던 한글자모와 조합하여 완성시키는 것이다.

조합 완성된 글자 와 한글자모 사이에는 다음과 같은 수식이 적용된다.

초성(19자)
– ㄱ(0) ㄲ(1) ㄴ(2) ㄷ(3) ㄸ ㄹ ㅁ ㅂ ㅃ ㅅ ㅆ ㅇ ㅈ ㅉ ㅊ ㅋ(15) ㅌ(16) ㅍ(17) ㅎ(18)

중성(21자)
– ㅏ(0) ㅐ(1) ㅑ(2) ㅒ(3) ㅓ ㅔ ㅕ ㅖ ㅗ ㅘ ㅙ ㅚ ㅛ ㅜ ㅝ ㅞ ㅟ ㅠ(17) ㅡ(18) ㅢ(19) ㅣ(20)

종성(28자)
– 없음(0) ㄱ(1) ㄲ(2) ㄳ ㄴ ㄵ ㄶ ㄷ ㄹ ㄺ ㄻ ㄼ ㄽ ㄾ ㄿ ㅀ ㅁ ㅂ ㅄ ㅅ ㅆ ㅇ ㅈ ㅊ ㅋ ㅌ ㅍ(26) ㅎ(27)

표현범위 : 가(0xAC00) ~ 힣(D7A3)

한글글자 = 초성 * 21 * 28 + (중성 - 19) * 28 + (종성 - 40) + [각주:1]BASE_CODE(한글코드 테이블의 첫번째 문자코드)

예) 강 = ㄱ + ㅏ + ㅇ 
         = 0 * 21 * 28 + (19 - 19) * 28 + (61 - 40) + 0xAC00 = 0xAC00(44032) + 21
         = 44053
         = 0xAC15

두번째 임의로 입력되어지는 한글자모를 받아서 이전에 입력되어진 한글자모와 한글조합이 이루어 지는지의 판단과 처리 및 이후 프로세스의 정립에 있다.

위에 보여지는 키패드의 그림상 아파트를 입력하려면 다음과 같은 순서에 의해 입력이 되어진다.

아파트 = ㅇ + ㅏ + ㅂ + ㅂ + ㅏ + ㄷ + ㄷ + ㅣ + ㅣ (ㅇ) <- 초성
          = 아 + ㅂ + ㅂ + ㅏ + ㄷ + ㄷ + ㅣ + ㅣ (ㅏ) <- 조합 초성 + 중성
          = 압 + ㅂ + ㅏ + ㄷ + ㄷ + ㅣ + ㅣ (ㅂ) <- 조합 초성 + 중성 + 종성
          = 앞 + ㅏ + ㄷ + ㄷ + ㅣ + ㅣ (ㅂ) <- 종성 합성
          = 아파 + ㄷ + ㄷ + ㅣ + ㅣ (ㅏ) <- 완성문자 분해,조합 초성 + 중성
          = 아팓 + ㄷ + ㅣ + ㅣ (ㄷ) <- 조합 초성 + 중성 + 종성
          = 아팥 + ㅣ + ㅣ (ㄷ) <- 종성 합성
          = 아파티 + ㅣ (ㅣ) <- 완성문자 분해, 초성 + 중성
          = 아파트 (ㅣ) <- 중성 합성

(아파트 세글자를 찍기 위해 그렇게도 강대리는 날을 깟다 -_ㅠ)

세번째 키의 배열과 조합의 순서도 중요하다. 위의 키패드 배열을 보면 ㅂ, ㅍ 이 한쌍을 이루고 ㄱ,ㅋ 이 한쌍을 이룬다. 이것은 우리가 기존에 사용해온 핸드폰의 영향도 있을 것이다. 그런데 왜 ㅡ, ㅣ는 순서가 바뀌어 있을까?...
간단한 예로 '애벌레'를 들어보겠다.
애벌레 = ㅇ + ㅏ + ㅣ + ㅂ + ㅓ + ㄹ + ㄹ + ㅓ + ㅣ
          = 아 + ㅣ + ㅂ + ㅓ + ㄹ + ㄹ + ㅓ + ㅣ
          = 애 + ㅂ + ㅓ + ㄹ + ㄹ + ㅓ + ㅣ
          = 앱 + ㅓ + ㄹ + ㄹ + ㅓ + ㅣ
          = 애버 + ㄹ + ㄹ + ㅓ + ㅣ
          = 애벌 + ㄹ + ㅓ + ㅣ
          = 애벌러 + ㅣ
          = 애벌레

입력된 순서와 키의 입력을 보면 알겠지만 'l' 라는 음소는 조합에 많이 사용되는 문자이다. 때문에 키입력의 최초 입력에서 나와야 입력이 편하다. 만약 ㅡ 를 두번 입력해야 ㅣ 로 변경이 가능하다면 문자 하나를 찍는 것이 보통 힘든일이 아닐 것이다...
(키패드 중에 에니콜에 들어있는 키패드 배열(천지인인가...)이 가장 좋은 것 같다. LG, 모토로라,에버,에니콜 모든 핸드폰을 사용해 보았지만 그중에서도 모토로라는 정말 최악이다. 키의 배열이 한손으로 문자를 만들기가 너무 힘들다. PDA에 모아키라는 것이 있다...난 정말... 왜만들었지??)


소스코드

초기모델


초기 제작한 모델은 아파트를 쓰기위해서 항상 띄어쓰기를 입력하여 문자입력을 종료시켜야 되었다.
때문에 이런 문제들을 확인 후 몇가지를 수정하여 소스를 다듬었다. 초성, 중성, 종성 합성 테이블을 두어 테이블 내에서 변환을 조절하였다.

다음에 오는 소스코드는 나름 정리를 한다고 한 모습임에도 정리가 않되고 있다.
포스팅을 하면서 이것저것 웹서핑을 통하여 찾아보았다. 다행이도 어느정도는 오토마타라는 표현에 구색을 맞추지 않았나 생각이 든다. 갱장이 복잡하게 처리하고 있는 어떤 소스를 웹서핑 중에 확인하였지만 중요한 골격이 되는 알고리즘(? 그냥 단순한 프로세스 정도인가....)은 크게 다르지 않은가 라고 생각이 된다. 누군가 고수님이 이 포스팅을 확인하고 지적해 준다면 정말 감사하겠다. 어찌되었든 잘 돌아가고 있는 모습을 볼때에는 참 뿌듯할 뿐이다. 이 프로세스 유도하기 위해 빽보드에 ㄱ,ㄴ,ㄷ을 무수히 써보기만 했다. ㅋㅋ

빽보드에 삽질한 흔적 이후 많은 변형이 있었지만 골격을 잡기 위한 과정에서 중요한 정보랄까

AutomataKR.h
#pragma once

#define	KEY_CODE_SPACE		-1	// 띄어쓰기
#define	KEY_CODE_ENTER		-2	// 내려쓰기
#define	KEY_CODE_BACKSPACE	-3	// 지우기

class CAutomataKR
{
public:
	CAutomataKR(void);
	~CAutomataKR(void);

public:
	void		Clear();					// 버퍼초기화
	wchar_t	SetKeyCode(int	nKeyCode);	// 키코드 받기 (정해진 코드값을 입력 받아 조합)

	wchar_t	ingWord;		// 작성중 글자
	CString	completeText;	// 완성 문자열

private:
	enum	HAN_STATUS	 // 단어조합상태
	{
		HS_FIRST = 0,		// 초성
		HS_FIRST_V,		// 자음 + 자음 
		HS_FIRST_C,		// 모음 + 모음
		HS_MIDDLE_STATE,	// 초성 + 모음 + 모음
		HS_END,			// 초성 + 중성 + 종성
		HS_END_STATE,		// 초성 + 중성 + 자음 + 자음
		HS_END_EXCEPTION	// 초성 + 중성 + 종성(곁자음)
	};

	int		m_nStatus;			// 단어조합상태
	int		m_nPhonemez[5]; 	// 음소[초,중,종,곁자음1,곁자음2]

	wchar_t	m_completeWord;	// 완성글자

	// 변환
	//
	int		ToInitial(int nKeyCode);	// 초성으로
	int		ToFinal(int nKeyCode);	// 종성으로

	// 분해
	//
	void		DecomposeConsonant();	// 자음분해

	// 합성
	//
	BOOL	MixInitial(int nKeyCode);	// 초성합성
	BOOL	MixFinal(int nKeyCode);	// 종성합성

	BOOL	MixVowel(int * nCurCode, int nInCode);	// 모음합성

	// 조합(한글완성)
	//
	wchar_t	CombineHangle(int cho, int jung, int jong);
	wchar_t	CombineHangle(int status);
	void		CombineIngWord(int status);

	int		DownGradeIngWordStatus(wchar_t word);	//조합상태 낮추기
};

AutomataKR.cpp
코드가 길어지면 집중력이 떨어진다는 것을 다시 한번 느낌니다.
keenam님께서 덧글로 지적해 주신 부분을 수정하였습니다.
정말 감사합니다.
최근 학습의 열의가 많이 떨어져 있습니다. 어쩌면 좋을까요 ^^;;
관심 가져주시는 분들께 감사를 드립니다. 꾸벅

keenam 님이 다시 지적해주신 부분이 있었습니다.  (;ㅁ;) 눈물이 앞을 가립니다. 아~
종성이 곁자음이 아닐경우 지우기 키를 입력하였을때 무조건 종성을 삭제하는 부분을 수정하여 반영하였습니다. 그리고 변수명 및 Function 명을 좀더 의미 있는 단어로 수정 처리하였습니다.

  
#include "StdAfx.h"
#include "AutomataKR.h"

const int BASE_CODE = 0xac00;	// 기초음성(가)
const int LIMIT_MIN = 0xac00;	// 음성범위 MIN(가)
const int LIMIT_MAX = 0xd7a3;	// 음성범위 MAX

// 음성 테이블
wchar_t SOUND_TABLE[68] =
{
	/* 초성 19자 0 ~ 18 */
	_T('ㄱ'), _T('ㄲ'), _T('ㄴ'), _T('ㄷ'), _T('ㄸ'),
	_T('ㄹ'), _T('ㅁ'), _T('ㅂ'), _T('ㅃ'), _T('ㅅ'),
	_T('ㅆ'), _T('ㅇ'), _T('ㅈ'), _T('ㅉ'), _T('ㅊ'),
	_T('ㅋ'), _T('ㅌ'), _T('ㅍ'), _T('ㅎ'),
	/* 중성 21자 19 ~ 39 */
	_T('ㅏ'), _T('ㅐ'), _T('ㅑ'), _T('ㅒ'), _T('ㅓ'),
	_T('ㅔ'), _T('ㅕ'), _T('ㅖ'), _T('ㅗ'), _T('ㅘ'),
	_T('ㅙ'), _T('ㅚ'), _T('ㅛ'), _T('ㅜ'), _T('ㅝ'),
	_T('ㅞ'), _T('ㅟ'), _T('ㅠ'), _T('ㅡ'), _T('ㅢ'),
	_T('ㅣ'),
	/* 종성 28자 40 ~ 67 */
	_T(' '), _T('ㄱ'), _T('ㄲ'), _T('ㄳ'), _T('ㄴ'),
	_T('ㄵ'), _T('ㄶ'), _T('ㄷ'), _T('ㄹ'), _T('ㄺ'), 
	_T('ㄻ'), _T('ㄼ'), _T('ㄽ'), _T('ㄾ'), _T('ㄿ'),
	_T('ㅀ'), _T('ㅁ'), _T('ㅂ'), _T('ㅄ'), _T('ㅅ'),
	_T('ㅆ'), _T('ㅇ'), _T('ㅈ'), _T('ㅊ'), _T('ㅋ'),
	_T('ㅌ'), _T('ㅍ'), _T('ㅎ')
};

// 초성 합성 테이블
int MIXED_CHO_CONSON[14][3] =
{
	{ 0, 0,15}, // ㄱ,ㄱ,ㅋ
	{15, 0, 1}, // ㅋ,ㄱ,ㄲ
	{ 1, 0, 0}, // ㄲ,ㄱ,ㄱ

	{ 3, 3,16}, // ㄷ,ㄷ,ㅌ
	{16, 3, 4}, // ㅌ,ㄷ,ㄸ
	{ 4, 3, 3}, // ㄸ,ㄷ,ㄷ

	{ 7, 7,17}, // ㅂ,ㅂ,ㅍ
	{17, 7, 8}, // ㅍ,ㅂ,ㅃ
	{ 8, 7, 7}, // ㅃ,ㅂ,ㅂ

	{ 9, 9,10}, // ㅅ,ㅅ,ㅆ
	{10, 9, 9}, // ㅆ,ㅅ,ㅅ

	{12,12,14}, // ㅈ,ㅈ,ㅊ
	{14,12,13}, // ㅊ,ㅈ,ㅉ
	{13,12,12}  // ㅉ,ㅈ,ㅈ
};

// 초성,중성 모음 합성 테이블
int MIXED_VOWEL[21][3] = 
{
	{19,19,21},	// ㅏ,ㅏ,ㅑ
	{21,19,19},	// ㅑ,ㅏ,ㅏ

	{19,39,20},	// ㅏ,ㅣ,ㅐ
	{21,39,22},	// ㅑ,ㅣ,ㅒ

	{23,23,25},	// ㅓ,ㅓ,ㅕ
	{25,23,23},	// ㅕ,ㅓ,ㅓ

	{23,39,24},	// ㅓ,ㅣ,ㅔ
	{25,39,26},	// ㅕ,ㅣ,ㅖ

	{27,27,31},	// ㅗ,ㅗ,ㅛ
	{31,27,27},	// ㅛ,ㅗ,ㅗ

	{27,19,28},	// ㅗ,ㅏ,ㅘ
	{28,39,29},	// ㅘ,ㅣ,ㅙ

	{27,39,30},	// ㅗ,ㅣ,ㅚ

	{32,32,36},	// ㅜ,ㅜ,ㅠ
	{36,32,32},	// ㅠ,ㅜ,ㅜ

	{32,23,33},	// ㅜ,ㅓ,ㅝ
	{33,39,34},	// ㅝ,ㅣ,ㅞ

	{32,39,35},	// ㅜ,ㅣ,ㅟ

	{39,39,37},	// ㅣ,ㅣ,ㅡ
	{37,39,38},	// ㅡ,ㅣ,ㅢ
	{38,39,39}	// ㅢ,ㅣ,ㅣ
};

// 종성 합성 테이블
int MIXED_JONG_CONSON[22][3] = 
{
	{41,41,64}, // ㄱ,ㄱ,ㅋ
	{64,41,42}, // ㅋ,ㄱ,ㄲ
	{42,41,41}, // ㄲ,ㄱ,ㄱ

	{41,59,43}, // ㄱ,ㅅ,ㄳ

	{44,62,45}, // ㄴ,ㅈ,ㄵ
	{44,67,46}, // ㄴ,ㅎ,ㄶ

	{47,47,65}, // ㄷ,ㄷ,ㅌ
	{65,47,47}, // ㅌ,ㄷ,ㄷ

	{48,41,49}, // ㄹ,ㄱ,ㄺ
	{48,56,50}, // ㄹ,ㅁ,ㄻ

	{48,57,51}, // ㄹ,ㅂ,ㄼ
	{51,57,54}, // ㄼ,ㅂ,ㄿ

	{48,59,52}, // ㄹ,ㅅ,ㄽ
	{48,47,53}, // ㄹ,ㄷ,ㄾ	
	{48,67,55}, // ㄹ,ㅎ,ㅀ

	{57,57,66}, // ㅂ,ㅂ,ㅍ
	{66,57,57}, // ㅍ,ㅂ,ㅂ

	{57,59,58}, // ㅂ,ㅅ,ㅄ

	{59,59,60}, // ㅅ,ㅅ,ㅆ
	{60,59,59}, // ㅆ,ㅅ,ㅅ

	{62,62,63}, // ㅈ,ㅈ,ㅊ
	{63,62,62}  // ㅊ,ㅈ,ㅈ
};

// 종성 분해 테이블
int DIVIDE_JONG_CONSON[13][3] = 
{
	{41,41,42}, // ㄱ,ㄱ,ㄲ
	{41,59,43}, // ㄱ,ㅅ,ㄳ
	{44,62,45}, // ㄴ,ㅈ,ㄵ
	{44,67,46}, // ㄴ,ㅎ,ㄶ
	{48,41,49}, // ㄹ,ㄱ,ㄺ
	{48,56,50}, // ㄹ,ㅁ,ㄻ
	{48,57,51}, // ㄹ,ㅂ,ㄼ
	{51,66,54}, // ㄹ,ㅍ,ㄿ
	{48,59,52}, // ㄹ,ㅅ,ㄽ
	{48,65,53}, // ㄹ,ㅌ,ㄾ	
	{48,67,55}, // ㄹ,ㅎ,ㅀ
	{57,59,58}, // ㅂ,ㅅ,ㅄ
	{59,59,60}  // ㅅ,ㅅ,ㅆ
};

CAutomataKR::CAutomataKR(void)
{
	Clear();
}

CAutomataKR::~CAutomataKR(void)
{
}

//
// 버퍼 초기화
void CAutomataKR::Clear()
{
	m_nStatus		= HS_FIRST;
	completeText	= _T("");
	ingWord			= NULL;
	m_completeWord	= NULL;
}

//
// 키코드 입력 및 조합 (정해진 int 코드값을 입력 받아 한글조합)
wchar_t CAutomataKR::SetKeyCode(int nKeyCode)
{
	m_completeWord = NULL;

	// 특수키 입력
	//
	if(nKeyCode < 0)
	{
		m_nStatus = HS_FIRST;

		if(nKeyCode == KEY_CODE_SPACE) // 띄어쓰기
		{
			if(ingWord != NULL)
				completeText += ingWord;
			else
				completeText += _T(' ');

			ingWord = NULL;
		}
		else if(nKeyCode == KEY_CODE_ENTER) // 내려쓰기
		{
			if(ingWord != NULL)
				completeText += ingWord;

			completeText += _T("\r\n");

			ingWord = NULL;
		}
		else if(nKeyCode == KEY_CODE_BACKSPACE) // 지우기
		{
			if(ingWord == NULL)
			{
				if(completeText.GetLength() > 0)
				{
					if(completeText.Right(1) == _T("\n"))
						completeText = completeText.Left(completeText.GetLength() -2);
					else
						completeText = completeText.Left(completeText.GetLength() -1);
				}
			}
			else
			{
				m_nStatus = DownGradeIngWordStatus(ingWord);
			}
		}

		return m_completeWord;
	}

	switch(m_nStatus)
	{
	case HS_FIRST:
		// 초성
		//
		m_nPhonemez[0]	= nKeyCode;
		ingWord			= SOUND_TABLE[m_nPhonemez[0]];
		m_nStatus		= nKeyCode > 18 ? HS_FIRST_C : HS_FIRST_V;
		break;

	case HS_FIRST_C:
		// 모음 + 모음
		//
		if(nKeyCode > 18)	// 모음
		{
			if(MixVowel(&m_nPhonemez[0], nKeyCode) == FALSE)
			{
				m_completeWord = SOUND_TABLE[m_nPhonemez[0]];
				m_nPhonemez[0] = nKeyCode;
			}
		}
		else				// 자음
		{
			m_completeWord	= SOUND_TABLE[m_nPhonemez[0]];
			m_nPhonemez[0]	= nKeyCode;
			m_nStatus		= HS_FIRST_V;
		}
		break;

	case HS_FIRST_V:
		// 자음 + 자음
		//
		if(nKeyCode > 18)	// 모음
		{
			m_nPhonemez[1]	= nKeyCode;
			m_nStatus		= HS_MIDDLE_STATE;
		}
		else				// 자음
		{
			if(!MixInitial(nKeyCode))
			{
				m_completeWord	= SOUND_TABLE[m_nPhonemez[0]];
				m_nPhonemez[0]	= nKeyCode;
				m_nStatus		= HS_FIRST_V;
			}
		}
		break;

	case HS_MIDDLE_STATE:
		// 초성 + 모음 + 모음
		//
		if(nKeyCode > 18)
		{
			if(MixVowel(&m_nPhonemez[1], nKeyCode) == FALSE)
			{
				m_completeWord	= CombineHangle(1);
				m_nPhonemez[0]	= nKeyCode;
				m_nStatus		= HS_FIRST_C;
			}
		}
		else
		{
			int jungCode = ToFinal(nKeyCode);

			if(jungCode > 0)
			{
				m_nPhonemez[2]	= jungCode;
				m_nStatus		= HS_END_STATE;
			}
			else
			{
				m_completeWord	= CombineHangle(1);
				m_nPhonemez[0]	= nKeyCode;
				m_nStatus		= HS_FIRST;
			}
		}
		break;

	case HS_END:
		// 초성 + 중성 + 종성
		//
		if(nKeyCode > 18)  
		{
			m_completeWord	= CombineHangle(1);
			m_nPhonemez[0]	= nKeyCode;
			m_nStatus		= HS_FIRST;
		}
		else
		{
			int jungCode = ToFinal(nKeyCode);
			if(jungCode > 0)
			{
				m_nPhonemez[2]	= jungCode;
				m_nStatus		= HS_END_STATE;
			}
			else
			{
				m_completeWord	= CombineHangle(1);
				m_nPhonemez[0]	= nKeyCode;
				m_nStatus		= HS_FIRST;
			}  
		}
		break;

	case HS_END_STATE:
		// 초성 + 중성 + 자음(종) + 자음(종)
		//
		if(nKeyCode > 18)
		{
			m_completeWord = CombineHangle(1);

			m_nPhonemez[0]	= ToInitial(m_nPhonemez[2]);
			m_nPhonemez[1]	= nKeyCode;
			m_nStatus		= HS_MIDDLE_STATE;
		}
		else
		{
			int jungCode = ToFinal(nKeyCode);
			if(jungCode > 0)
			{
				m_nStatus = HS_END_EXCEPTION;

				if(!MixFinal(jungCode))
				{
					m_completeWord	= CombineHangle(2);
					m_nPhonemez[0]	= nKeyCode;
					m_nStatus		= HS_FIRST_V;
				}
			}
			else
			{
				m_completeWord	= CombineHangle(2);
				m_nPhonemez[0]	= nKeyCode;
				m_nStatus		= HS_FIRST_V;
			}  
		}
		break;

	case HS_END_EXCEPTION:
		// 초성 + 중성 + 종성(곁자음)
		//
		if(nKeyCode > 18)  
		{
			DecomposeConsonant();
			m_nPhonemez[1]	= nKeyCode;
			m_nStatus		= HS_MIDDLE_STATE;
		}
		else
		{
			int jungCode = ToFinal(nKeyCode);
			if(jungCode > 0)
			{
				m_nStatus = HS_END_EXCEPTION;

				if(!MixFinal(jungCode))
				{
					m_completeWord	= CombineHangle(2);
					m_nPhonemez[0]	= nKeyCode;
					m_nStatus		= HS_FIRST_V;
				}
			}
			else
			{
				m_completeWord	= CombineHangle(2);
				m_nPhonemez[0]	= nKeyCode;
				m_nStatus		= HS_FIRST_V; 
			}
		}
		break;
	}

	// 현재 보이는 글자상태
	CombineIngWord(m_nStatus);

	// 완성 문자열 만들기
	if(m_completeWord != NULL) 
		completeText += TCHAR(m_completeWord);

	return m_completeWord;
}

// 초성으로 변환
int CAutomataKR::ToInitial(int nKeyCode)
{
	switch(nKeyCode)
	{
	case 41: return 0;	// ㄱ
	case 42: return 1;	// ㄲ
	case 44: return 2;	// ㄴ
	case 47: return 3;	// ㄷ
	case 48: return 5;	// ㄹ
	case 56: return 6;	// ㅁ
	case 57: return 7;	// ㅂ
	case 59: return 9;	// ㅅ
	case 60: return 10;	// ㅆ
	case 61: return 11;	// ㅇ
	case 62: return 12;	// ㅈ
	case 63: return 14;	// ㅊ
	case 64: return 15;	// ㅋ
	case 65: return 16;	// ㅌ
	case 66: return 17;	// ㅍ
	case 67: return 18;	// ㅎ
	}
	return -1;
}

// 종성으로 변환
int CAutomataKR::ToFinal(int nKeyCode)
{
	switch(nKeyCode)
	{
	case 0: return 41;	// ㄱ
	case 1: return 42;	// ㄲ
	case 2: return 44;	// ㄴ
	case 3: return 47;	// ㄷ
	case 5: return 48;	// ㄹ
	case 6: return 56;	// ㅁ
	case 7: return 57;	// ㅂ
	case 9: return 59;	// ㅅ
	case 10: return 60;	// ㅆ
	case 11: return 61;	// ㅇ
	case 12: return 62;	// ㅈ
	case 14: return 63;	// ㅊ
	case 15: return 64;	// ㅋ
	case 16: return 65;	// ㅌ
	case 17: return 66;	// ㅍ
	case 18: return 67;	// ㅎ
	}
	return -1;
}

// 자음분해
void CAutomataKR::DecomposeConsonant()
{
	int i = 0;
	if(m_nPhonemez[3] > 40 || m_nPhonemez[4] > 40)
	{
		do
		{
			if(DIVIDE_JONG_CONSON[i][2] == m_nPhonemez[2])
			{
				m_nPhonemez[3] = DIVIDE_JONG_CONSON[i][0];
				m_nPhonemez[4] = DIVIDE_JONG_CONSON[i][1];

				m_completeWord = CombineHangle(3);
				m_nPhonemez[0]	 = ToInitial(m_nPhonemez[4]);
				return;
			}
		}
		while(++i< 13);
	}

	m_completeWord = CombineHangle(1);
	m_nPhonemez[0]	 = ToInitial(m_nPhonemez[2]);
}

// 초성합성
BOOL CAutomataKR::MixInitial(int nKeyCode)
{
	if(nKeyCode >= 19)
		return FALSE;

	int i = 0;
	do
	{
		if(MIXED_CHO_CONSON[i][0] == m_nPhonemez[0] && MIXED_CHO_CONSON[i][1] == nKeyCode)
		{
			m_nPhonemez[0] = MIXED_CHO_CONSON[i][2];
			return TRUE;
		}
	}
	while(++i < 14);

	return FALSE;
}

// 종성합성
BOOL CAutomataKR::MixFinal(int nKeyCode)
{
	if(nKeyCode <= 40) return FALSE;

	int i = 0;
	do
	{
		if(MIXED_JONG_CONSON[i][0] == m_nPhonemez[2] && MIXED_JONG_CONSON[i][1] == nKeyCode)
		{
			m_nPhonemez[3] = m_nPhonemez[2];
			m_nPhonemez[4] = nKeyCode;
			m_nPhonemez[2] = MIXED_JONG_CONSON[i][2];

			return TRUE;
		}
	}
	while(++i < 22);

	return FALSE;
}

// 모음합성
BOOL CAutomataKR::MixVowel(int * currentCode, int inputCode)
{
	int i = 0;
	do
	{
		if(MIXED_VOWEL[i][0] == * currentCode && MIXED_VOWEL[i][1] == inputCode)
		{
			* currentCode = MIXED_VOWEL[i][2];
			return TRUE;
		}
	}
	while(++i< 21);

	return FALSE;
}

// 한글조합
wchar_t CAutomataKR::CombineHangle(int cho, int jung, int jong)
{
	// 초성 * 21 * 28 + (중성 - 19) * 28 + (종성 - 40) + BASE_CODE;
	return BASE_CODE - 572 + jong + cho * 588 + jung * 28;
}

wchar_t CAutomataKR::CombineHangle(int status)
{
	switch(status)
	{
	//초성 + 중성
	case 1: return CombineHangle(m_nPhonemez[0], m_nPhonemez[1], 40);

	//초성 + 중성 + 종성
	case 2: return CombineHangle(m_nPhonemez[0], m_nPhonemez[1], m_nPhonemez[2]);
	
	//초성 + 중성 + 곁자음01
	case 3: return CombineHangle(m_nPhonemez[0], m_nPhonemez[1], m_nPhonemez[3]);
	}
	return ' ';
}

void CAutomataKR::CombineIngWord(int status)
{
	switch(m_nStatus)
	{
	case HS_FIRST:
	case HS_FIRST_V:
	case HS_FIRST_C:
		ingWord = SOUND_TABLE[m_nPhonemez[0]];
		break;

	case HS_MIDDLE_STATE:
	case HS_END:
		ingWord = CombineHangle(1);
		break;

	case HS_END_STATE:
	case HS_END_EXCEPTION:
		ingWord = CombineHangle(2);
		break;
	}
}

int CAutomataKR::DownGradeIngWordStatus(wchar_t word)
{
	int iWord = word;

	//초성만 있는 경우
	//
	if(iWord < LIMIT_MIN || iWord > LIMIT_MAX)
	{
		ingWord = NULL;

		return HS_FIRST;
	}

	//문자코드 체계
	//iWord = firstWord * (21*28)
	//		+ middleWord * 28
	//		+ lastWord * 27
	//		+ BASE_CODE;
	//
	int totalWord	= iWord - BASE_CODE;
	int iFirstWord	= totalWord / 28 / 21;	//초성
	int iMiddleWord = totalWord / 28 % 21;	//중성
	int iLastWord	= totalWord % 28;		//종성

	m_nPhonemez[0] = iFirstWord; //초성저장

	if(iLastWord == 0)	//종성이 없는 경우
	{
		ingWord = SOUND_TABLE[m_nPhonemez[0]];

		return HS_FIRST_V;
	}

	m_nPhonemez[1] = iMiddleWord + 19; //중성저장

	iLastWord += 40;

	for(int i = 0; i < 13; i++)
	{
		if(iLastWord == DIVIDE_JONG_CONSON[i][2])
		{
			ingWord = CombineHangle(3);
			m_nPhonemez[2] = DIVIDE_JONG_CONSON[i][0]; // 종성저장
			return HS_END_EXCEPTION;
		}
	}

	ingWord = CombineHangle(1);

	return HS_MIDDLE_STATE;
}
    





히스토리)
2009.01.
 - 초본 완성
2009.05.
 - 버그 수정 후 블로그 등록
2009.09.30
 - 모음합성시 발생하는 버그 < keenam님 제보
2009.11.11.
 - 종성이 곁자음이 아닐 경우 지우기 키를 입력시 종성이 삭제되는 문제 < keenam님 제보
 - 변수명 과 함수명 정리, 코딩 스타일(띄어쓰기, 내려쓰기, 주석) 정리
2009.12.28.
 - 광고좀 넣어봤습니다. (굽신굽신)
2010.02.11.
 - 오타버그 수정(Line 137 의 {48,57,54}// ㄹ,ㅍ,ㄿ 의 코드 수정
2010.11.15.
 - 테스트 소스 링크 추가

블로그에 올리기 까지의 히스토리는 정확하게 기억이 나지 않습니다.
이렇게까지 관심이 높아질줄 몰랐습니다.
그리고 이렇게 보완이 될지 몰랐습니다.
앞으로는 히스토리 관리를 해줘야 할것 같습니다.

프로젝트 진행중에 버그를 하나 더 잡을수 있었네요 흑...



최근 짬이 나는 시간을 빌려 테스트 소스를 만들어보았습니다.
급하게 만들다 보니 큰 변화가 없어서 아쉽지만 그래도 테스트 소스를 만들었다는 것에 의미를 두고자 합니다.
테스트 주도개발 TDD 라는 것이 있습니다. 애자일이다 익스트림이다 하는 바람은 제 주변에서 거세게 불어본적이 없어 잘 모르겠지만 한번쯤 내 입에 맞는 애자일을 아니 , 나만을위한 프로그래밍 개발법을 세워봐야 할것 같네요. 어째든 포스트 링크 추가합니다.
2010/11/12 - 한글 오토마타 만들기 테스트 소스

Trackback : 0 Comment : 74