유한 상태 머신은 % 뒤에 오는 printf 의 옵션들을 파싱하기 위해 사용됐다.
정말 간단하기 때문에 살짝 혀끝만 댄다는 느낌으로 봐주세요 ㅎㅎ
예제 몇 개를 보자
ft_printf("%13.2d\n", 27);
ft_printf("%0.*d\n", 3, 5);
위 코드들은 정상적으로 작동한다.
ft_printf("%0\0d\n", 13);
ft_printf("%7*.*d\n", 4, 5, 18);
반면 위의 코드들은 잘못된 코드다.
어떻게 잘못된 코드인 걸 알까??
if, else if 문을 사용하면 가능할거다.
대신 코드가 길어지고 더러워질거다.
또 다른 대안은 상태 머신을 그리고 구현하는 것이다.
처음 이름을 들었을 때는 뭔 말인지 몰라서 쫄았고, 좀 찾아봤을 때는 너무 나가서 NFA->DFA 변환까지 찾아봤지만 NFA까지 건들일 필요가 없었다.
유한 상태 머신에서 내가 궁금한 거는 "어떤 상태(A)에서 어떤 입력(B)가 들어왔을 때 A가 어떤 상태로 변하지?"기 때문에 ft_printf의 옵션 파싱에서 생기는 상태에 대해서만 그림을 그려주면 됐다.
먼저 유한 상태 머신으로 처리하려는 부분은 ft_print("%<- 이 부분 ->서식지정자", ); %부터 서식지정자 이전까지다.
printf()는 "%[플래그(flag)][폭(width)][.정밀도][크기(length)]서식 문자(specifier)"
이런 모양이지만 여기서는 플래그, 폭, 정밀도, 크기를 합쳐서 옵션이라고 하겠다.
자, 그럼 이제 상태가 뭐가 있는지 보자.
여기서 각 상태는 다음 입력으로 어떤 값을 읽을 수 있는 상태를 말한다.
예를 들어 2번 상태는 다음 입력으로 1 ~ 9에 해당하는 정수, *, '0', '-' 중 하나를 읽을 수 있는 상태다.
위 사진에서 각 상태에서 입력이 들어왔을 때 어떤 상태로 변하는지를 확인할 수 있다. 만약 해당 상태에서 올 수 없는 입력이 온다면 에러로 처리하면 된다.
이런 식으로 0번 상태부터 8번 상태까지 왔을 경우만 모든 옵션이 정상적으로 왔다고 가정할 수 있다.
만약 if, else if 를 썼다면 상태 하나에 올 수 있는 입력을 다 if문으로 처리해줘야 했을 거다.
이걸 표로 정리하면 아래와 같다.
현재 구현한 상태 머신은 옵션이 옳냐 옳지 않냐만 확인할 수 있는 수준이다.
만약 상태 머신에서 서식 지정자까지 확인하고 싶다면 할 수는 있다. 하지만 모든 서식 지정자마다 받을 수 있는 옵션이 다르기 때문에 상태 머신이 엄청 커지고 복잡해질 거다.
int get_token(char input)
{
if (input == '%')
return (TK_PERCENTAGE);
else if (input >= '1' && input <= '9')
return (TK_ONE_NINE);
else if (input == '\0')
return (TK_NULL);
else if (input == '.')
return (TK_DOT);
else if (input == '*')
return (TK_ASTERISK);
else if (input == '-')
return (TK_HYPEN);
else if (input == '0')
return (TK_ZERO);
else if (input == 'c' || input == 'd' || input == 's' ||
input == 'x' || input == 'X' || input == 'u' ||
input == 'i' || input == 'p')
return (TK_SPECIFIER);
else
return (TK_ELSE);
return (-1);
}
int get_state(int state, int input)
{
int arr[81] = { 2, 1, 10, 1, 1, 1, 1, 1, 1,
2, 1, 10, 1, 1, 1, 1, 1, 1,
8, 3, -1, 5, 4, 2, 2, 8, -1,
8, 3, -1, 5, -1, -1, 3, 8, -1,
8, -1, -1, 5, -1, -1, -1, 8, -1,
8, 6, -1, -1, 7, -1, 6, 8, -1,
8, 6, -1, -1, -1, -1, 6, 8, -1,
8, -1, -1, -1, -1, -1, -1, 8, -1
};
return (arr[(state * 9) + input]);
}
while (*str)
{
token = get_token(*str);
state = get_state(state, token);
if (state == 1)
write(1, str, 1);
if (state == 2)
{
if (*str == '-')
{
opt->hypen = 1;
opt->padc = ' ';
}
else if (*str == '0' && opt->hypen != 1)
opt->padc = '0';
}
if (state == 3)
{
opt->width *= 10;
opt->width += (*str - '0');
}
if (state == 4)
{
opt->width = va_arg(ap, int);
if (opt->width < 0 && !opt->hypen)
{
opt->hypen = 1;
opt->padc = ' ';
}
if (opt->width < 0)
opt->width = -opt->width;
}
if (state == 5)
{
opt->dot = 1;
opt->length = 0;
}
if (state == 6)
{
opt->length *= 10;
opt->length += (*str - '0');
}
if (state == 7)
opt->length = va_arg(ap, int);
if (state == 8 || state == 10) // 현재 % 에 대한 것들은 다 읽은 거니까 종료해야지
break ;
if (state == -1)
return (-1);
str++;
}
구현한 코드 링크: https://gist.github.com/42joonpark/edbd927103e4c56ec76515c3e78edc81