C의 선언

dcl 모듈 개선 (1)

4.4) dcl 모듈 개선

변수가 있는 수식을 성공적으로 분석해냈다여기까지 따라왔다면 진심으로 스스로를 칭찬해도 좋다굳이 필자가 말하지 않더라도 변수 분석이 가능한 계산기를 만들어냈다는 사실만으로도 이미 스스로를 대견하게 느끼고 있지 않을까 넘겨짚어 본다.

이제 선언을 분석하고 획득한 식별자 정보를 식별자와 함께 Table 객체에 넣으면 결합이 끝난다그런데 식별자 정보를 Table에 넣는 것은 어렵지 않지만식별자가 어떤 정보를 가지고 있는지를 결정하는 것이 생각보다 어렵다여기서는 간단하게 식별자가 자료형과 값만 가지고 있다고 가정한다.

식별자가 저장하는 정보가 값에서 자료형과 값으로 커졌으므로식별자 객체를 표현하는 새로운 클래스가 필요하다따라서 식별자의 정보를 표현하는 IdentifierInfo 클래스를 새롭게 작성하고 Table 객체를 리팩토링 하겠다다음은 이를 구현한 것이다.

IdentifierInfo.h

#ifndef __IDENTIFIER_H__

#define __IDENTIFIER_H__

 

#include "common.h"

#include <string>

 

class IdentifierInfo {

std::string _name; // 식별자의 이름을 저장하는 변수

std::string _datatype; // 자료형을 저장하는 변수

std::string _value; // 값을 저장하는 변수

public:

IdentifierInfo();

IdentifierInfo(

const std::string &name,

const std::string &datatype,

const std::string &value = "");

 

std::string &name(); // 식별자의 이름을 반환합니다.

const std::string &name() const; // 식별자의 이름을 반환합니다.

std::string &datatype(); // 자료형을 반환합니다.

const std::string &datatype() const; // 자료형을 반환합니다.

std::string &value(); // 값을 반환합니다.

const std::string &value() const; // 값을 반환합니다.

void set_name(const std::string &name); // 식별자의 이름을 설정합니다.

void set_datatype(const std::string &datatype); // 자료형을 설정합니다.

void set_value(const std::string &value); // 값을 설정합니다.

};

 

#endif

IdentifierInfo.cpp

#include "IdentifierInfo.h"

IdentifierInfo::IdentifierInfo() {}

IdentifierInfo::IdentifierInfo(

const std::string &name,

const std::string &dtype,

const std::string &value)

: _name(name), _datatype(dtype), _value(value) {}

// 식별자의 이름을 반환합니다.

std::string &IdentifierInfo::name() { return _name; }

const std::string &IdentifierInfo::name() const { return _name; }

// 자료형을 반환합니다.

std::string &IdentifierInfo::datatype() { return _datatype; }

const std::string &IdentifierInfo::datatype() const { return _datatype; }

// 값을 반환합니다.

std::string &IdentifierInfo::value() { return _value; }

const std::string &IdentifierInfo::value() const { return _value; }

// 식별자의 이름을 설정합니다.

 

// 자료형을 설정합니다.

void IdentifierInfo::set_datatype(const std::string &dtype) { _datatype = dtype; }

// 값을 설정합니다.

void IdentifierInfo::set_value(const std::string &value) { _value = value; }

그리고 이를 이용해 Table을 리팩토링 한 결과는 다음과 같다.

Table.h

#ifndef __IDENTIFIER_TABLE_H__

#define __IDENTIFIER_TABLE_H__

 

#include "common.h"

#include <string>

#include <map>

#include "IdentifierInfo.h"

 

class Table {

static Table *_instance; // 싱글톤 객체를 가리키는 정적 필드

std::map<std::string, IdentifierInfo> _table; // 실제 식별자 표 객체

 

private: // tbl을 싱글톤 객체로 만들기 위해 생성자를 숨긴다

explicit Table();

~Table();

 

public:

// tbl 싱글톤 객체의 인스턴스를 가져옵니다.

static Table *instance();

// 식별자에 대한 접근자설정자 함수입니다.

IdentifierInfo &get(const std::string &identifier);

void set(const std::string &identifier, const IdentifierInfo &value);

};

 

#endif

tbl.cpp

#include "Table.h"

Table *Table::_instance = nullptr;

 

Table::Table() {}

Table::~Table() {}

 

Table *Table::instance() {

return (_instance ? _instance : (_instance = new Table()));

}

 

IdentifierInfo &Table::get(const std::string &identifier) {

if (_table.find(identifier) == _table.end())

throw Exception("식별자에 대한 값을 가져올 수 없습니다.");

return _table[identifier];

}

void Table::set(const std::string &identifier, const IdentifierInfo &value) {

_table[identifier] = value;

}

이제 준비가 끝났으니 dcl 모듈에서 식별자를 표에 넣을 수 있게 리팩토링 해보자다음은 rdx 모듈과 같이 벡터를 이용하여 보다 이용하기 편리하게 작성한 예제다먼저 소스 위 부분이 바뀌었다.

dcl.cpp

#include <iostream>

#include "StringBuffer.h"

#include "common.h"

 

#include <vector>

typedef std::vector<std::string> StringList;

 

#include "Table.h"

#include "IdentifierInfo.h"

 

const int MAX_INPUT_SIZ = 256;

static char input[MAX_INPUT_SIZ];

 

// 선언을 분석하고 획득한 토큰의 벡터를 반환합니다.

std::vector<IdentifierInfo> get_dcl_info(const char *decl);

 

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

std::string get_type(StringBuffer &buf_in);

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

void dcl(StringBuffer &buf_in, StringList &vec_out);

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

void dirdcl(StringBuffer &buf_in, StringList &vec_out);

dcl, dirdcl 함수의 내부도 vout을 이용하게끔 바뀌었다.

dcl.cpp

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

// 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)

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

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

vout.push_back("*"); // 왼쪽에서 획득한 기호를 오른쪽의 분석이

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

}

}

void dirdcl(StringBuffer &bin, StringList &vout) { // 직접 선언자를 분석하고 결과 출력

char ch = bin.peekc();

if (is_fnamch(ch)) { // direct-declarator: 이름 (2)

std::string identifier = "";

while (bin.is_empty() == false) {

ch = bin.getc();

if (is_namch(ch) == false) {

bin.ungetc();

break;

}

identifier += ch;

}

if (identifier.empty()) // 식별자에 추가된 문자가 없다면 예외

throw Exception("올바른 식별자 이름이 아닙니다.");

vout.push_back(identifier);

}

else if (ch == '(') { // direct-declarator: (declarator) (3)

bin.getc(); // ( 문자를 해석해서 진입했으므로 다음으로 넘긴다

dcl(bin, vout);

if (bin.peekc() != ')') // 닫는 괄호가 없으면 예외

throw Exception("닫는 괄호가 없습니다.");

bin.getc(); // ) 괄호 검사를 진행했으므로 다음으로 넘긴다

}

// direct-declarator: direct-declarator() (4)

// direct-declarator: direct-declarator[] (5)

while (bin.is_empty() == false) {

ch = bin.peekc();

if (ch == '(') { // 함수 기호 획득

bin.getc(); // ( 괄호를 해석해서 진입했으므로 넘긴다

if (bin.peekc() != ')') // 닫는 괄호가 없으면 예외

throw Exception("잘못된 함수 기호입니다.");

bin.getc(); // ) 괄호를 해석했으므로 다음으로 넘긴다

vout.push_back("()");

}

else if (ch == '[') { // 배열 기호 획득

bin.getc(); // [ 괄호를 해석해서 진입했으므로 넘긴다

if (bin.peekc() != ']') // 닫는 괄호가 없으면 예외

throw Exception("잘못된 배열 기호입니다.");

bin.getc(); // ] 괄호를 해석했으므로 다음으로 넘긴다

vout.push_back("[]");

}

else { // 이외의 경우 반복문을 탈출한다

break;

}

}

}

선언을 문자열로 입력하면 이를 분석하여 IdentifierInfo의 벡터로 반환하는 get_dcl_info 함수가 추가되었다.

dcl.cpp

std::vector<IdentifierInfo> get_dcl_info(const char *decl) {

std::vector<IdentifierInfo> identifiers;

StringList tokens;

StringBuffer bin(decl);

std::string type = get_type(bin);

 

while (bin.is_empty() == false) {

tokens.clear();

bin.trim();

dcl(bin, tokens);

if (bin.peekc() == ',') {

bin.getc();

std::string identifier = tokens[0];

std::string datatype;

for (int i = 1, len = tokens.size(); i < len; ++i) {

datatype += tokens[i];

}

datatype += type;

IdentifierInfo info(identifier, datatype);

identifiers.push_back(info);

}

else if (bin.peekc() == ';') {

break;

}

else {

throw Exception("unknown character");

}

}

std::string identifier = tokens[0];

std::string datatype;

for (int i = 1, len = tokens.size(); i < len; ++i) {

datatype += tokens[i];

}

datatype += type;

IdentifierInfo info(identifier, datatype);

identifiers.push_back(info);

return identifiers;

}

마지막으로 이를 테스트할 수 있게 main 함수를 작성하였다.

dcl.cpp

int main_dcl(void) {

try {

while (true) {

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

std::vector<IdentifierInfo> decl_list = get_dcl_info(input);

for (int i = 0, len = decl_list.size(); i < len; ++i) {

const IdentifierInfo &info = decl_list[i];

Table::instance()->set(info.name(), info);

}

 

for (int i = 0, len = decl_list.size(); i < len; ++i) {

const std::string &identifier = decl_list[i].name();

const IdentifierInfo &info = Table::instance()->get(identifier);

std::cout << info.name() << ": " << info.datatype() << std::endl;

}

}

return 0;

}

catch (Exception &ex) {

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

return 1;

}

}

드디어 dcl 모듈의 리팩토링이 모두 끝났다남은 일은 rdx 모듈의 main 함수가 수행했던 과정에서 선언을 넣고 값을 정의하는 것이다마지막으로 두 모듈을 합치기 위해 해야 하는 일을 정리하겠다.

프로그램은 실행 시에 다음 화면을 띄운다.

Usage:

- decl <declaration>

- movl <identifier> <value>

- calc <expression>

- exit

>

- decl 명령 이후에는 선언이 온다이전과 달리 변수는 선언해야 사용할 수 있다.

- movl 명령은 식별자의 값을 설정한다선언되지 않은 변수에 값을 대입할 수 없다.

- calc 명령 이후에는 식이 온다선언되지 않은 변수를 발견하면 경고 메시지를 출력하고 종료한다.

필자가 구현한 것을 보이기 전에이전 소스에서 수정된 사항을 먼저 보이겠다.

rdx 모듈은 main_rdx 함수를 삭제하고 calculate_postfix 함수를 일부 수정하였다.

댓글

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