9.3.2 프로토 타이핑

이제 수식 파서 트리(Numeric Parser Tree)를 이용한 계산기를 만들어 보아요. 먼저 프로토 타이핑을 합시다.

수식 파서 트리를 이용한 계산기 구조

계산기는 어휘를 분석하는 어휘 분석기(Lexer)와 구문 분석기(SynAnalyzer)와 수식 파서 트리(Parser)를 이용할 거예요. 그리고 어휘 분석기에서는 토큰(Token)을 생성하는데 토큰의 종류는 연산자(Operator)와 피 연산자(Operand)가 있습니다.

진입점에서는 6가지 테스트 케이스에 사용할 수식 표현을 수식 계산기를 통해 테스트를 합시다. 테스트 케이스에 사용할 수식 표현에는 올바른 수식 표현과 수식에 사용할 수 없는 어휘를 사용한 표현과 구문을 잘못 사용한 표현을 적절하게 정하세요.

“2+3-5*5+6/2″는 올바른 수식이며 후위 표기로 2 3 + 5 5 * – 6 2 / +이며 연산 결과는 -17입니다.

“23*5/2+4*6″은 올바른 수식이며 후위 표기로 23 5 * 2 / 4 6 * + 이며 연산 결과는 81입니다.

“2+4*5#6″은 올바른 수식이 아닙니다. 어휘 분석기에서 오류를 찾을 수 있어야겠죠.

“2+3*5+”는 올바른 수식이 아닙니다. 구문 분석기에서 오류를 찾을 수 있어야겠죠.

“3+36+-7″는 올바른 수식이 아닙니다. 구문 분석기에서 오류를 찾을 수 있어야겠죠.

“45+3*5-2+5/2*7″은 올바른 수식이며 후위 표기로 45 3 5 * + 2 – 5 2 / 7 * + 이며 연산 결과는 72입니다.

int main()
{
    char *tc_exprs[6]=
    {
        "2+3-5*5+6/2",
        "23*5/2+4*6",
        "2+4*5#6",
        "2+3*5+",
        "3+36+-7",
        "45+3*5-2+5/2*7"
    };

테스트 케이스에 사용할 수식 표현을 하나씩 전달하면서 수식 계산기를 테스트 합시다.

    for(int i = 0; i<6; i++)
    {

수식 계산기는 수식 표현을 입력 인자로 받아 생성합니다.

        Calculator *cal  = new Calculator(tc_exprs[i]);

그리고 수식 계산기를 수행하게 합시다.

        cal->Run();

테스트를 수행한 후에 계산기는 소멸해야겠죠.

        delete cal;
    }
    return 0;
}

수식 계산기를 표현할 Calculator 클래스를 정의합시다.

class Calculator
{

멤버 필드로 입력 수식과 어휘 분석기에 의해 생성한 토큰 컬렉션이 필요하죠. 토큰 컬렉션은 Token *를 템플릿 인자인 벡터를 이용합시다. 나중에 Token 클래스를 정의할 때 Tokens 형식은 정의하기로 해요.

    char *expr;
    Tokens tokens;

제공할 메서드는 생성자, 소멸자, 계산기 수행 메서드입니다. 생성자는 입력 인자로 수식 표현을 받습니다.

public:
    Calculator(const char *expr);
    ~Calculator(void);
    void Run();
};

생성자를 정의합시다.

Calculator::Calculator(const char *expr)
{

먼저 입력 인자로 받은 수식의 문자열 길이를 더하세요. 널 문자까지 복사해야 하므로 +1을 합니다.

    int len_p1 = strlen(expr)+1;

문자열을 복사할 메모리를 동적으로 할당합니다.

    this->expr = new char[len_p1];

문자열을 복사하세요. strcpy_s 함수는 strcpy 함수의 안전한 버전입니다.

    strcpy_s(this->expr,len_p1,expr);
}

소멸자에서는 수식 표현을 저장했던 메모리를 해제하세요.

Calculator::~Calculator(void)
{
    delete[] expr;
}

계산기를 가동하면 먼저 수식을 출력합시다.

void Calculator::Run()
{
    cout<<expr<<"을 계산합니다. ..."<<endl;
    Lexer lexer;

먼저 어휘 분석기로 토큰을 생성합니다.

    if(lexer.MakeToken(expr))
    {

어휘 분석기에서 생성한 토큰 컬렉션을 얻어옵니다.

        tokens = lexer.GetTokens();

구문 분석기를 통해 올바른 표현인지 확인합시다. 여기에서 구문 분석기는 올바른 표현인지 확인하는 것 말고는 특별한 작업이나 유지해야 할 상태가 없어서 정적 클래스로 정의하기로 해요.

        if(SynAnalyzer::Analyze(tokens))
        {

구문 분석을 성공하면 파서를 통해 파싱하세요.

            Parser parser(tokens);
            parser.Parsing();

파싱 후에 후위 표기로 출력합시다.

            parser.PostOrderView();

그리고 계산한 후에 결과를 출력하세요.

            cout<<expr<<"="<<parser.Calculate()<<endl;
        }

만약 구문 분석을 실패하면 메시지를 출력합니다.

        else
        {
            cout<<"수식에 사용한 표현이 문법에 맞지 않습니다."<<endl;
        }
    }

토큰 생성 과정에서 문제가 있을 때도 메시지를 출력하세요.

    else
    {
        cout<<"사용할 수 없는 기호가 있습니다."<<endl;
    }
    cout<<endl;
}

작성한 코드는 모든 구현을 구현 후에 다시 한 번 코드를 보여드릴게요