인터프리터 패턴(Interpreter Pattern)#
- 인터프리터 패턴은 간단한 언어의 문법을 정의하고 해석하는 패턴입니다.
- 데이터를 주고/받을 때 데이터의 패턴(문법)을 객체화하여 언어를 해석합니다.
- 언어의 패턴을 다루는 예제는 매우 많습니다.
- 간단한 후휘 표기법 문법을 객체화하여 살펴보겠습니다.
- 후위 표기법:
1 + 1
을 1 1 +
와 같은 형태로 작성하는 방법, 연산자가 피연산자들 뒤에 위치합니다.
인터프리터 패턴 구조#
- 인터프리터 패턴이 문장의 어휘를 해석하고 처리하기 위해 다음과 같은 5가지 구성 요소가 필요합니다.
- Context
- Abstract Expression
- 문장을 해석하기 위한 해석자 인터페이스 클래스
- Terminal Expression(Concrete Expression)
- Non-Terminal Expression(Concrete Expression)
- 문장의 비종료를 나타내는 해석자 클래스 구현체
- Client
인터프리터 패턴: Context 클래스#
- Context 클래스는 문장을 저장하고 관리하는 클래스입니다.
- 예제를 위해
{{ 1 1 + }}
와 같은 문자열을 후위 표기법 문법으로 해석하겠습니다. - 문자열의 구성은 다음과 같습니다.
{{
: 문자열의 시작을 의미합니다.}}
: 문자열의 종료를 의미합니다.
: 문자열 내부 정보는 공백으로 구분합니다.
- Context 클래스는 문자열의 정보를 저장하고 관리하는 클래스로 토큰화가 필요합니다.
- 토큰화 코드가 들어가있어 다소 복잡합니다.
class Context
{
public:
Context(std::string data)
{
m_idx = 0;
// Tokenize
std::stringstream ss(data);
const char delimiter = ' '; // space
std::string token;
while (std::getline(ss, token, delimiter))
{
m_token.push_back(token);
}
// print
printf("==Print Token List==\n");
for (int i = 0; i < m_token.size(); i++)
{
printf("[%d] -> %s\n", i, m_token[i]);
}
printf("==End Token List==\n");
}
bool isStart()
{
if (m_token[m_idx] == "{{")
{
m_idx++;
return true;
}
else
{
return false;
}
}
std::string next()
{
if (m_idx < m_token.size())
{
std::string token = m_token[m_idx];
m_idx++;
return token;
}
else
{
return "";
}
}
protected:
std::vector<std::string> m_token;
int m_idx;
};
인터프리터 패턴: Abstract Expression 인터페이스 클래스#
- 문장의 해석을 위한 인터페이스 클래스입니다.
- 필요한 기능은 하위 클래스에서 구현합니다.
class Expression
{
public:
virtual std::string interpret() = 0;
};
인터프리터 패턴: Terminal Expression 클래스 구현#
Terminal
클래스는 문법 해석의 마지막으로, 문자 또는 숫자로 치환되지 않는 최종적인 기호입니다.- 여기서는 단순한 문자열을 입력받고, 반환하는 형태로 구현하였습니다.
// Terminal (Concrete Expression)
class Terminal :
public Expression
{
public:
Terminal(std::string n)
{
m_n = n;
}
std::string interpret()
{
return m_n;
}
private:
std::string m_n;
};
인터프리터 패턴: Non-Terminal Expression 클래스 구현#
- 문자열 해석을 위해 전개되는 표현을 담당합니다.
- 문자열을 숫자로 치환하고, 더하기 연산을 수행한 다음 이를 다시 문자열로 반환하는 코드가 내부적으로 존재합니다.
- 하나의 클래스에서 역할이 두 개로 구분되기 때문에 분리하여 작성할까 하다가 패턴의 의미를 강조하기 위해 하나로 구성했습니다.
// Non-Terminal (Concrete Expression)
class Add :
public Expression
{
public:
Add(std::string left, std::string right)
{
m_left = std::make_shared<Terminal>(left);
m_right = std::make_shared<Terminal>(right);
}
std::string interpret() override
{
int left = std::atoi(m_left->interpret().c_str());
int right = std::atoi(m_right->interpret().c_str());
return std::to_string(left + right);
}
private:
std::shared_ptr<Expression> m_left;
std::shared_ptr<Expression> m_right;
};
인터프리터 패턴: 실행 코드#
- 인터프리터 패턴은 다른 패턴에 비해 실행 코드(Client)가 다소 복잡합니다.
- 생성한 문법에 따라 클라이언트의 구성이 달라지기 때문입니다.
- 예제를 위해 간단한 문법 규칙을 사용하였지만, 문법 규칙이 많아지면 패턴이 복잡해집니다.
int main(const int argc, const char* argv[])
{
std::shared_ptr<Context> context = std::make_shared<Context>("{{ 1 1 + }}");
std::stack<std::string> stack;
// Client
while (true)
{
std::string token = context->next();
if (token.empty())
{
break;
}
if (token == "}}")
{
std::cout << "End!" << std::endl;
}
else if (std::atoi(token.c_str()))
{
std::cout << "Stack Push" << std::endl;
stack.push(token);
}
else if (token == "+")
{
std::cout << "+ Operator" << std::endl;
std::string left = stack.top();
stack.pop();
std::string right = stack.top();
stack.pop();
std::shared_ptr<Add> add = std::make_shared<Add>(left, right);
std::string value = add->interpret();
std::cout << "Result: " << value << std::endl;
stack.push(value);
}
}
std::cout << "Final Result: " << stack.top() << std::endl;
stack.pop();
return 0;
}
==Print Token List==
[0] -> {{
[1] -> 1
[2] -> 1
[3] -> +
[4] -> }}
==End Token List==
Stack Push
Stack Push
+ Operator
Result: 2
End!
Final Result: 2