JSCC: JavaScript로 개발하는 C Compiler

코스 전체목록

닫기

dcl: C의 선언 분석 프로그램 (1)

3.3) dcl: C의 선언 분석 프로그램

이제 우리는 C의 선언이 어떠한지 이해했으므로, C의 선언을 분석하는 dcl 프로그램을 작성할 수 있다이 예제는The C Programming Language에 나온 것을 기반으로 작성하는 것이다.

이를 설명하기 전에 몇 가지 중요한 용어를 알려주고 진행하겠다.

예약어(keyword)프로그래밍 언어에서 특정한 용도로 사용되기 때문에 사용자가 임의로 사용할 수 없는 단어를 말한다. int, char와 같은 자료형과 for, if와 같은 반복문조건문을 위한 예약어 등이 이에 속한다.

식별자(identifier)개체를 식별하는 데 사용할 수 있는 이름을 의미한다변수 이름함수 이름사용자가 새롭게 정의한 자료형의 이름 등이 있다. C는 식별자라면 지켜야 할 규칙이 있는데바로 알파벳밑줄(_), 숫자만 가능하며첫 글자는 밑줄 또는 알파벳이어야 한다는 것이다.

태그(tag)구조체공용체 및 열거 형식과 같은 사용자 정의 자료형을 지칭하는 이름이다식별자와 작성하는 규칙은 같지만 식별자와는 다르다예를 들어 다음의 문장이 적법한 이유는 태그는 식별자가 아니기 때문에 식별자를 중복으로 정의하는 것이 아니기 때문이다.

struct node node;

다만 이 경우 typedef 키워드를 이용해 node를 정의했다면 이 경우는 식별자로 인정된다.

typedef struct node node;

다음은 dcl 프로젝트를 위한 개념으로이 프로그램에서만 그렇다고 납득해야 하는 부분이다.

선언문(declaration-statement)선언을 하는 문장이다형식은 다음과 같다.

declaration-statement: <형식(type)> <선언자(declarator)> ;

형식(type)선언할 대상이 자료를 보관하는 방법을 말한다. int, char 등이 여기에 속한다.

선언문은 간단하게 형식과 선언자로 나눌 수 있다.

int var; // 형식: int / 선언자: var

int *ptr; // 형식: int / 선언자: *ptr

int arr[5]; // 형식: int / 선언자: arr[5]

int fnc(); // 형식: int / 선언자: fnc()

const int MAX; // 형식: const int / 선언자: MAX

직접 선언자(direct-declarator)선언을 할 때 사용되는 이름 등 직접적으로 선언을 하는 데 사용되는 단어를 말한다.

선언자(declarator)직접 선언자의 앞에 *가 붙어해당 직접 선언자가 포인터임을 나타낸다.

다음은 선언자와 직접 선언자 간의 관계를 나타낸 것이다.

declarator: * direct-declarator … (1)

direct-declarator: <이름… (2)

(declarator) … (3)

direct-declarator() … (4)

direct-declarator[<크기>] … (5) (이때 크기는 생략 가능)

사실 이 내용만 가지고는 선언자와 직접 선언자를 이해하기 아주 어렵다예를 들어보자이 예제에서 선언자를dcl, 직접 선언자를 dirdcl이라고 간단하게 표기하겠다.

(*pfa[])()

pfa는 이름이므로 dirdcl이다. pfa가 dirdcl이므로 pfa[] 또한 dirdcl이다. (5)에 의해 dirdcl[] 또한 정의에 의해dirdcl이기 때문이다. *pfa[]는 pfa[]가 dirdcl이므로 (1)에 의해 dcl이다. (*pfa[])는 (3)에 의해, *pfa[]가 dcl이므로 dirdcl이 되고, (*pfa[])()는 dirdcl()의 꼴이므로 (4)에 의해 dirdcl이다.

여기서 완전하게 이해하지 못했다고 하더라도 일단은 진행할 수 있으니이제 dcl 프로그램을 만들어보자입력에 대해 다음과 같이 출력이 나오는 것이 목표다테스트의 편의를 위해 무한히 반복하다가입력으로 세미콜론이 처음 문자로 들어오면 종료하도록 하자.

입력

출력

int var;

int arr[];

int *ptr;

int arr2d[][];

int *ptrarr[];

int (*arrptr)[];

int fnc();

int arr_fnc()[];

int *ptrarr_fnc()[];

int (*arr_fncptr)()[];

int (*arrptr_fnc())[];

char (*(*x[])())[];

;

var: int

arr: array of int

ptr: pointer to int

arr2d: array of array of int

ptrarr: array of pointer to int

arrptr: pointer to array of int

fnc: function returning int

arr_fnc: function returning array of int

ptrarr_fnc: function returning array of pointer to int

arr_fncptr: pointer to function return- ing array of int

arrptr_fnc: function returning pointer to array of int

x: array of pointer to function return- ing pointer to array of char

다음은 필자의 dcl 구현이다먼저 main을 보자.

03_dcl_main.cpp

// 식별자로 가능한 문자인지 확인합니다.

bool is_namch(char ch) { // 식별자 문자라면 참입니다.

return is_alnum(ch) || (ch == '_');

}

bool is_fnamch(char ch) { // 첫 식별자 문자라면 참입니다.

return is_alpha(ch) || (ch == '_');

}

// 형식을 획득하여 문자열로 반환합니다.

std::string get_type(StringBuffer &buffer_input);

// 선언자를 분석하고 결과를 출력합니다.

void dcl(StringBuffer &buffer_input);

// 직접 선언자를 분석하고 결과를 출력합니다.

void dirdcl(StringBuffer &buffer_input);

int main(void) {

try {

const int MAX_INPUT_SIZ = 256;

char input[MAX_INPUT_SIZ];

while (true) {

// 입력을 받고 버퍼를 초기화한다

std::cin.getline(input, MAX_INPUT_SIZ);

if (input[0] == ';') {

break;

}

StringBuffer buffer(input);

// 형식을 획득한다

std::string type = get_type(buffer);

while (is_space(buffer.peekc())) { // 형식과 선언자 사이의 공백을

buffer.getc(); // 무시하고 포인터를 선언자 앞으로 옮긴다

}

// 선언자를 분석한다

dcl(buffer);

if (buffer.peekc() != ';') // 문장 종료 기호가 없으면 예외

throw Exception("문장 종료 기호가 없습니다.");

std::cout << type.c_str() << std::endl;

}

return 0;

}

catch (Exception &ex) {

std::cerr << ex.c_str() << std::endl;

return 1;

}

}

ptr에 대해 프로그램은 다음과 같이 진행된다.

코드

버퍼 상태

출력

StringBuffer(input)

int *ptr;

 

get_type(buffer)

*ptr;

 

while (is_space(...)) ...

*ptr;

 

dcl(buffer)

;

ptr: pointer to

if (peekc() != ';') ...

;

ptr: pointer to

cout<<type

;

ptr: pointer to int

이 정도로 main 함수는 간단하게 이해할 수 있다. get_type과 공백을 제거하는 부분은 독자 스스로도 구현할 수 있을 정도로 어렵지 않다그러면 이제 정말 중요한 dcl 함수를 살펴보자.

03_dcl_main.cpp

void dcl(StringBuffer &bin) { // 선언자를 분석하고 결과 출력

// declarator: * direct-declarator (1) *을 분석한다

int pointer_count = 0;

char ch;

while (bin.is_empty() == false) { // 버퍼에 문자가 남아있는 동안

ch = bin.getc(); // 문자를 획득하고 확인한다

if (ch == '*') { // *라면 그만큼 포인터를 출력하기 위해

++pointer_count; // 카운터를 증가시킨다

}

else { // *가 아니라면 포인터를 되돌리고 탈출한다

bin.ungetc();

break;

}

}

// declarator: * direct-declarator (2) direct-declarator를 분석한다

dirdcl(bin); // *을 모두 획득했으므로 직접 선언자를 분석한다

while (pointer_count > 0) { // 선언자의 분석이 오른쪽에서 먼저 진행되므로

std::cout << "pointer to "; // 왼쪽에서 획득한 기호를 오른쪽의 분석이

--pointer_count; // 종료된 후에 출력해야 한다

}

}

선언자의 정의 그대로 코드로 옮긴 것이다 주석도 있으니 노력하면 이해할 수 있다.

dcl의 내부를 알았으니 예를 들어보자. ptr에 대해 이 함수는 다음과 같이 진행된다.

코드

버퍼 상태

출력

dcl(StringBuffer &)

*ptr;

 

while (c == '*') ...

ptr;

 

dirdcl(bin)

;

ptr:

while (pc > 0) ...

;

ptr: pointer to

arr에 대해서는 다음과 같이 진행된다.

코드

버퍼 상태

출력

dcl(StringBuffer &)

arr[];

 

while (c == '*') ...

arr[];

 

dirdcl(bin)

;

arr: array of

while (pc > 0) ...

;

arr: array of

이제 마지막으로 dirdcl의 내부를 보자.

댓글

댓글 본문
버전 관리
한도영
현재 버전
선택 버전
graphittie 자세히 보기