9.3.5 수식 파서 트리 구현

이번에는 수식 파서 트리를 구현하기로 해요.

class Parser
{

수식 파서 트리에서는 어휘 분석기에서 생성한 토큰 컬렉션에 있는 토큰들을 파서 트리에 매달 것입니다. 이에 토큰 컬렉션을 멤버 필드로 선언하세요.

    Tokens tokens;

노드를 Parser 클래스 내부에 구조체로 정의합시다. private 가시성 영역이므로 Parser 클래스 외부에서는 노드 구조체 형식을 사용할 수 없어 신뢰성을 높일 수 있어요.

    Tokens tokens;
    struct Node
    {
        Token *token;
        Node *lc;
        Node *rc;
        Node(Token *token)
        {
            this->token = token;
            lc = rc = 0;
        }
    };

루트를 기억할 멤버가 필요하겠죠.

    Node *root;

생성자에서는 토큰 컬렉션을 입력 인자로 받습니다.

public:
    Parser(Tokens tokens);
    ~Parser(void);  //소멸자

파서 트리에 파싱하는 메서드를 제공합시다.

    void Parsing();

후위 표기로 출력하는 메서드도 제공합시다.

    void PostOrderView()const;

계산하는 메서드를 제공합시다.

    int Calculate();

내부적으로 토큰을 파서 트리에 매다는 메서드가 필요합니다.

private:
    void Add(Token *token);

후위 표기로 출력하기 위해 후위 순회하는 메서드가 필요합니다.

    void PostOrder(Node *sr)const;

계산할 때도 후위 순회하며 계산합니다.

    int PostOrderCalculate(Node *sr);

소멸할 때 동적으로 생성한 모든 노드를 소멸해야겠죠.

    void Clear(Node *sr);
};

Parser 클래스의 소스 코드를 구현합시다. 먼저 생성자에서는 입력 인자로 받은 컬렉션을 멤버 필드에 설정하세요.

Parser::Parser(Tokens tokens)
{
    this->tokens = tokens;
}

소멸자에서는 Clear 메서드를 호출하여 모든 노드를 소멸합니다.

Parser::~Parser(void)
{
    Clear(root);
}

모든 노드를 소멸할 때 후위 순회로 소멸해야 합니다.

void Parser::Clear(Node *sr)
{
    if(sr)
    {
        Clear(sr->lc);
        Clear(sr->rc);
        delete sr;
    }
}

파싱하는 메서드를 구현합시다.

void Parser::Parsing()
{

먼저 토큰 개수를 구하세요.

    int tcnt = tokens.size();

첫 번째 토큰으로 노드를 생성하여 root에 설정하세요.

    root = new Node(tokens[0]);

반복해서 모든 토큰을 파서 트리에 추가하세요.

    for(int i = 1; i<tcnt;i++)
    {
        Add(tokens[i]);
    }
}

토큰을 파서 트리에 매다는 메서드를 구현합시다.

void Parser::Add(Token *token)
{

먼저 입력 인자로 받은 토큰을 입력 인자로 노드를 생성하세요.

    Node *now = new Node(token);

루트의 토큰을 구하세요.

    Token *st = root->token;

루트의 토큰의 우선 순위가 추가할 토큰의 우선 순위보다 크거나 같은지 판별하세요.

    if(st->MoreThanPriority(token))
    {

예를 들어 추가할 노드의 기호가 더하기나 빼기 기호일 때는 현재 루트에 있는 토큰들은 새로운 노드의 왼쪽 피연산자로 들어가야 합니다. 그리고 루트는 now로 변경해야겠죠.

        now->lc = root;
        root = now;
수식 파서 트리

다음은 root의 토큰 우선 순위가 추가할 토큰의 우선 순위보다 크거나 같을 때 링크를 조절하는 과정을 논리적으로 도식한 것입니다.

root의 토큰 우선 순위가 추가할 토큰의 우선 순위보다 작을 때는 새로 추가하는 노드가 피연산자일 때와 연산자일 때 코드가 조금 다릅니다.

    else
    {

연산자일 때는 새로운 노드의 왼쪽 자식으로 루트의 오른쪽 자식을 매달고 루트의 오른쪽 자식에 새로운 노드를 매답니다.

        if(Operand::IsOperand(token)==false)
        {
            now->lc = root->rc;
            root->rc = now;
수식 파서 트리

다음은 root의 토큰 우선 순위가 추가할 토큰의 우선 순위보다 작으면서 연산자일 때 링크를 조절하는 과정을 논리적으로 도식한 것입니다.

새로 추가할 토큰이 피연산일 때는 루트에서 오른쪽 자식이 없는 노드를 찾아서 매답니다.

        else
        {
            Node *pa = root;
            while(pa->rc)
            {
                pa = pa->rc;
            }
            pa->rc = now;
        }
    }
}
수식 파서 트리

다음은 root의 토큰 우선 순위가 추가할 토큰의 우선 순위보다 작으면서 피연산자일 때 링크를 조절하는 과정을 논리적으로 도식한 것입니다.

후위 순위로 출력하는 메서드를 구현하세요.

void Parser::PostOrderView()const
{
    PostOrder(root);
    cout<<endl;
}
void Parser::PostOrder(Node *sr)const
{
    if(sr)
    {
        PostOrder(sr->lc);
        PostOrder(sr->rc);
        sr->token->View();
    }
}

계산하여 결과를 반환하는 메서드를 구현하세요.

int Parser::Calculate()
{
    return PostOrderCalculate(root);
}
int Parser::PostOrderCalculate(Node *sr)
{

여기에서 구현할 수식 파서 트리에서 모든 노드는 자식이 둘이거나 없습니다. 연산자는 자식이 둘 있고 피연산자는 자식이 없습니다. 따라서 왼쪽 자식이 있다는 것은 연산자를 의미합니다. 먼저 왼쪽 서브 트리를 계산하세요. 그리고 오른쪽 서브 트리를 계산합시다.

    if(sr->lc)
    {
        int lvalue = PostOrderCalculate(sr->lc);
        int rvalue = PostOrderCalculate(sr->rc);

현재 토큰을 연산자 토큰으로 변환하세요.

        Operator *op = dynamic_cast<Operator *>(sr->token);

그리고 연산 후에 결과를 반환하세요.

        return op->Calculate(lvalue, rvalue);
    }

피연산자일 때는 값을 반환합니다.

    else
    {
        Operand *op = dynamic_cast<Operand *>(sr->token);
        return op->GetValue();
    }
}