본문 바로가기
42/42cursus

[minishell] 2. 파싱하기(하)

by jaemjung 2022. 3. 22.

5) 규정된 문법을 바탕으로 트리구조로 파싱하기

 

이제 입력으로 들어온 문자열이 토큰으로 쪼개졌고, 각 토큰에 맞는 속성을 지정해주었으니 토큰을 순차적으로 조회하며 트리를 만들어주기만 하면 된다.

이 단계에서 그 이름 거창한 재귀하향파싱이 필요하다.

 

먼저 아래의 문장을

echo "HELLO WORLD" | cat -e >> test.txt | ls -al

이전 글에서 정의한 문법대로 쪼개보면 다음과 같은 트리가 나온다.

파이프를 구성하게되는 문장은 임의로 phrase로 명명하였다...


이를 코드를 통해 생성해야 했는데, 내가 만났던 문제는 다음과 같다.

1) 이런 트리 맨 끝의 종단 노드를 만들어 주기 위해서는 상위 노드가 먼저 생성이 되어 있어야 한다.

2) 그런데 쪼개진 토큰을 맨 처음부터 순회하며 트리를 만들어 간다고 치면, 보통의 경우에 파이프나 리다이렉션을 만나기 전에 명령어와 해당 명령어의 인자를 만나게 된다. -> 즉, 상위 노드가 생성되기 전에 하위 노드에 들어가야할 내용부터 순회하게 되는 것이다.

 

이러한 문제를 해결하기 위해 처음에는 이런 식으로 접근했다.

1) 하위노드부터 생성을 한다.

2) 토큰을 계속 읽어가며 리다이렉션 혹은 파이프 등 분기를 만나게 되면,

3) 분기 노드를 생성하고 지금까지 나왔던 노드를 자식노드로 넣어준다.

4) 트리의 head를 새로 생성된 분기 노드로 업데이트.

 

그러나 위와 같은 방식으로 코드를 작성하고 테스트를 진행하다 보니, 수많은 예외 처리 사항이 생겼으며 이를 하나하나 다 제어해줘야 한다는 문제점이 있었다. 무엇보다도 이런식으로 예외가 너무 많이 나와버리니... 예측하지 못한 입력을 만났을 때 터져버릴 것이 분명했다.

 

수 많은 구글 검색 결과들이 이런 트리 생성을 하향식으로 접근하라고 조언하고 있었고, 무엇보다도 "재귀", "하향", 파싱을 추천하고 있었다...

 

따라서 다음과 같은 방식으로 들어온 입력을 파싱해주도록 했다.

1) 일단 무조건 분기 노드를 생성하고 종단노드까지 파고들어감

2) 토큰을 읽으며 해당 종단노드가 있으면 종단노드에 값 할당, 없으면 리턴

3) 이를 재귀적으로 구성

 

이런 식으로 코드를 작성하니 코드 자체의 양도 확 줄었을 뿐 더러 별도의 예외처리 없이도 위의 트리를 생성해 줄 수 있었다.

전체적은 코드는 다음과 같은 방식으로 진행된다.

 

int	parse_command(t_tree *root, t_token *tokens)
{
	t_tree *command_node;

	command_node = make_new_node();
	root = insert_node(command_node);
	if (tokens->type == WORD)
		add_command(tokens, command_node); //리다이렉션이나 파이프를 만나기 전까지 토큰을 읽어 모두 커맨드로 저장 
	return (0);
}

int	parse_io_redir(t_tree *root, t_token *tokens)
{
	t_tree *io_redir_node;

	io_redir_node = make_new_node();
	root = insert_node(io_redir_node);
	if (tokens->type == REDIR)
		add_ioredir(tokens, io_redir_node);
	return (0);
}

int	parse_redirs(t_tree *root, t_token *tokens)
{
	t_tree *redirs_node;
    
	redirs_node = make_new_node();
	parse_io_redir(root, tokens);
	if (tokens->type == REDIR)
		parse_io_redir(root, tokens);
	return (0);
}

int	parse_phrase(t_tree *root, t_token *tokens)
{
	t_tree *phrase_node;
    
	phrase_node = make_new_node();
	root = insert_node(phrase_node);
	parse_command(phrase_node, tokens);
	if (tokens->type == REDIR)
		parse_redirs(root, tokens);
	return (0);
}

int	parse_pipeline(t_tree *root, t_token *tokens)
{
	t_tree *pipeline_node;
    
	pipeline_node = make_new_node();
	root = insert_node(pipeline_node);
	parse_phrase(pipeline_node, tokens);
	if (tokens->type == PIPE)
		parse_pipeline(root, tokens);
	return (0);
}

저 parse_pipeline 함수를 실행하면 재귀적으로 parse_command까지 내려가며 토큰들이 파싱되어 트리를 만들게 된다.

 

이제 남은 것은 파싱된 부분에 남아있는 따옴표를 벗겨주고, $(환경변수)를 치환해 주는 일.

그런데 이것도 만만치가 않은 부분이었다...

댓글