이번에는 수식 파서 트리(Numeric Parser Tree)를 이용한 계산기에서 사용할 토큰을 구현합시다.
먼저 토큰 클래스를 정의합시다.
class Token {
수식 파서 트리를 만들 때 사용할 우선 순위를 멤버 필드로 선언하세요.
int priority;
토큰 정보를 보여주는 부분은 피연산자와 연산자에 따라 다르므로 순수 가상 메서드(추상 메서드)예요.
public: virtual void View()const=0;
파서 트리에서 토큰을 매달 때 우선 순위를 비교하는 메서드를 제공합시다.
bool MoreThanPriority(Token *token);
파생 클래스에서 자신의 우선 순위를 결정할 수 있게 설정자를 추가하세요.
protected: void SetPriority(int priority); };
이제 토큰 소스 파일을 구현합시다. 여기에서는 토큰을 벡터에 보관할 거예요.
#include <vector> using namespace std;
Token *형식이 템플릿 인자인 vector를 Tokens 형식으로 지정합시다.
typedef vector<Token *> Tokens;
토큰 컬렉션에 보관한 토큰들을 순회할 수 있는 반복자도 타입 재지정하세요.
typedef Tokens::iterator TIter; typedef Tokens::const_iterator CTIter;
토큰의 우선 순위를 비교한 결과를 반환하는 메서드를 정의하세요.
bool Token::MoreThanPriority(Token *token) { return priority>=token->priority; }
우선 순위 설정자도 정의하세요.
void Token::SetPriority(int priority) { this->priority = priority; }
이번에는 연산자 토큰을 Operator 이름의 클래스로 정의합시다.
class Operator : public Token {
연산 기호를 기억하고 있어야겠죠.
char ch;
생성할 때 연산 기호를 입력 인자로 받습니다.
public: Operator(char ch);
자신의 정보를 출력하는 View 메서드를 재정의(override) 해야죠.
void View()const;
두 개의 피연산자의 값을 받아 연산하는 메서드를 추가하세요.
int Calculate(int lvalue, int rvalue)const;
정적 메서드로 특정 문자가 연산자가 맞는지 판별하는 메서드를 제공하세요.
static bool IsOperator(char ch);
토큰이 Operator인지 판별하는 메서드를 제공하세요.
static bool IsOperator(const Token *token); };
Operator 소스 코드를 구현합시다. 먼저 생성자를 구현하세요.
Operator::Operator(char ch) {
입력 인자로 받은 연산 기호를 멤버 필드에 설정하세요.
this->ch = ch;
여기에서는 더하기와 빼기는 우선 순위를 1로 곱하기와 나누기는 2로 설정하기로 해요.
if((ch=='+')||(ch=='-')) { SetPriority(1); } else { SetPriority(2); } }
정보를 출력하는 메서드에서는 연산 기호를 출력합니다.
void Operator::View()const { cout<<ch<<" "; }
계산하는 메서드를 구현합시다.
int Operator::Calculate(int lvalue, int rvalue)const {
연산 기호에 따라 알맞은 연산을 수행한 결과를 반환하세요.
switch(ch) { case '+': return lvalue + rvalue; case '-': return lvalue - rvalue; case '*': return lvalue * rvalue;
나누기 연산에서는 오른쪽 피연산자 값이 0이 아닐 때만 나눈 값을 반환하세요.
case '/': if(rvalue) { return lvalue / rvalue; }
오른쪽 피연산자가 0이면 나누기 제로 오류가 있음을 출력하세요.
cout<<"divide zero error"<<endl; return 0; }
만약 다른 연산 기호라면 프로그램에 버그가 있는 것입니다.
throw "연산자 기호에 문제가 있습니다."; }
문자가 사칙 연산 기호인지 판별하는 메서드를 구현하세요. 여기에서는 사칙 연산만 연산자로 제공할 거예요.
bool Operator::IsOperator(char ch) { return (ch=='+')||(ch=='-')||(ch=='*')||(ch=='/'); }
토큰이 연산자인지 판별하는 메서드를 구현하세요. 전달받은 token이 Operator 형식인지 판별하세요. dynamic_cast 결과가 0이 아니면 연산자입니다.
bool Operator::IsOperator(const Token *token) { return dynamic_cast<const Operator *>(token)!=0; }
피연산자 토큰은 Operand 이름의 클래스로 정의합시다.
class Operand: public Token {
피연산자 값을 멤버 필드로 선언하세요.
int value;
생성자에서는 피연산자의 값을 입력 인자로 받습니다.
public: Operand(int value);
자신의 정보를 출력하는 View 메서드를 재정의해 주어야 합니다.
void View()const;
피연산자 값의 접근자 메서드를 제공하세요.
int GetValue()const;
문자가 숫자 문자인지 판별하는 정적 메서드를 제공하세요.
static bool IsDigit(char ch);
문자열을 정수로 변환하는 정적 메서드를 제공하세요. 변환후 에 다음 토큰의 위치를 판단하기 위해 인덱스를 참조 형식으로 받습니다.
static int ConvertStrToInt(const char *str,int &index);
토큰이 피연산자인지 판별하는 정적 메서드를 제공하세요.
static bool IsOperand(const Token *token); };
Operand 소스 코드를 작성합시다. 먼저 생성자를 정의하기로 해요.
Operand::Operand(int value) {
입력 인자로 전달받은 피연산자 값을 멤버 필드 value에 설정하세요.
this->value = value;
우선 순위는 제일 높은 3으로 설정하세요.
SetPriority(3); }
자신의 정보를 출력하는 메서드를 구현하세요.
void Operand::View()const { cout<<value<<" "; }
피연산자 값의 접근자 메서드를 구현하세요.
int Operand::GetValue()const { return value; }
문자가 정수 문자인지 판별하는 메서드를 구현하세요. 정수 문자는 ‘0’보다 값이 크거나 같고 ‘9’보다 작거나 같습니다. C언어 표준 라이브러리 함수 isdigit을 사용할 수도 있겠죠.
bool Operand::IsDigit(char ch) { return (ch>='0')&&(ch<='9'); }
문자열을 정수로 변환하는 메서드를 구현하세요. C언어 표준 라이브러리 함수 atoi를 사용할 수도 있어요.
int Operand::ConvertStrToInt(const char *str,int &index) {
초기 값을 0으로 설정합니다.
int value = 0;
정수 문자가 아닌 문자가 나올 때까지 반복합니다.
while(IsDigit(str[index])) {
현재 값에 10을 곱한 후에 문자 – ‘0’을 하세요. str[i]가 ‘8’일 때 str[i] – ‘0’은 8입니다.
value = value * 10 + str[index] - '0';
인덱스를 1 증가하세요.
index++; }
변환한 값을 반환하세요.
return value; }
토큰이 피연산자인지 판별하는 메서드를 구현하세요. 전달받은 token이 Operand 형식인지 판별하세요. dynamic_cast 결과가 0이 아니면 연산자입니다.
bool Operand::IsOperand(const Token *token) { return dynamic_cast<const Operand *>(token)!=0; }
여기에서는 정수 문자인지 판별하거나 문자열을 정수로 변환하는 메서드를 직접 구현하였습니다. 물론 표준 라이브러리 함수를 사용할 수도 있겠죠.