9.3.3 토큰 구현

이번에는 수식 파서 트리(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;
}

여기에서는 정수 문자인지 판별하거나 문자열을 정수로 변환하는 메서드를 직접 구현하였습니다. 물론 표준 라이브러리 함수를 사용할 수도 있겠죠.