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 함수를 일부 수정하였다.