유한 상태 머신으로 구현한 ft_print

joonpark·2021년 6월 29일
0

42seoul

목록 보기
1/3

유한 상태 머신은 % 뒤에 오는 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

0개의 댓글