四则运算表达式的语法分析

四则运算表达式的语法分析

         本文参考自vczh的《如何手写语法分析器》。

         之前有《语法分析器初步学习——LISP语法分析》也是参考自《如何手写语法分析器》。

         通过语法规则表达操作符的优先级。

         四则运算表达式的语法为:

       1.Term = <数字> | “(”Exp”)”

       2.Factor = Term ((“*” | “/”) Term)*

       3.Exp = Factor ((“+” | “-”) Factor)*

         根据语法写代码。这里不对四则运算表达式求值,而是将其转换为等价的Lisp表达式,然后输出。

         具体的代码如下:

// 四则运算表达式的语法分析——生成LISP表达式
#include <iostream>
#include <string>
using namespace std;

// 表达式结构体
struct Expression
{
    bool IsNumber;     // 是否是数字
    int  Number;       // 数字内容,只有当Isnumber = true的时候有效
    char Operator;     // 操作符,只有当IsNumber = false的时候有效
    Expression* Left;  // 左表达式,只有当IsNumber = false的时候有效
    Expression* Right; // 右表达式,只有当IsNumber = false的时候有效

    Expression(int aNumber)
    {
        IsNumber = true;
        Number   = aNumber;
        Operator = 0;
        Left     = 0;
        Right    = 0;
    }

    Expression(char aOperator, Expression* aLeft, Expression* aRight)
    {
        IsNumber = false;
        Number   = 0;
        Operator = aOperator;
        Left     = aLeft;
        Right    = aRight;
    }

    // 析构
    ~Expression()
    {
        if (Left != 0)
        {
            delete Left;
            Left = 0;
        }
        if (Right != 0)
        {
            delete  Right;
            Right = 0;
        }
    }

    void PrintLISP()
    {
        if (IsNumber)
        {
            cout << Number;
        }
        else
        {
            cout << "(" << Operator << " ";
            Left->PrintLISP();
            cout << " ";
            Right->PrintLISP();
            cout << ")";
        }
    }
};

struct Exception
{
    int    Start; // 错误位置
    string Error; // 错误信息

    Exception(int aStart, const string& aError)
    {
        Start = aStart;
        Error = aError;
    }
};

// 检测是否是空白符
bool IsBlank(char ch)
{
    return ch == ' ' || ch == '	';
}

// 检测Text是否是Stream从pos起始的开头
// 如果是,则将pos前移Text.size()个字符,且返回true
// 否则pos保持不变,返回false
// 对于pos后的空白符忽略
bool IsPrefix(const string& Stream, int& pos, const string& Text)
{
    int read = pos; // 设置真实游标
    
    // 过滤空格
    while (IsBlank(Stream[read]))
    {
        ++read;
    }
    if (Stream.substr(read, Text.size()) == Text)
    {
        pos = read + Text.size();
        return true;
    }
    else
    {
        return false;
    }
}

// 检测Stream从pos开头是否是数字
Expression* GetNumber(const string& Stream, int& pos)
{
    int Result = 0;
    bool GotNumber = false;

    int read = pos;

    while (IsBlank(Stream[read]))
    {
        ++read;
    }

    while (true)
    {
        char ch = Stream[read];

        if (ch >= '0' && ch <= '9')
        {
            Result = Result * 10 + ch - '0';
            GotNumber = true;
            ++read;
        }
        else
        {
            break;
        }
    }

    if (GotNumber)
    {
        pos = read;
        return new Expression(Result);
    }
    else
    {
        throw Exception(read, "此处需要表达式"); // 不仅仅是需要数字
    }
}

Expression* GetTerm(const string& Stream, int& pos);
Expression* GetFactor(const string& Stream, int& pos);
Expression* GetExp(const string& Stream, int& pos);

// 检测Stream从pos起始是否是一个Term
// 实现语法1:Term = <数字> | “(”Exp”)”
Expression* GetTerm(const string& Stream, int& pos)
{
    try
    {
        return GetNumber(Stream, pos);
    }
    catch (Exception& e)
    {
        int read = pos;
        // 检测左括号
        if (IsPrefix(Stream, read, "("))
        {
            // 检测表达式
            Expression* Result = GetExp(Stream, read);
            if (IsPrefix(Stream, read, ")"))
            {
                // 如果使用右括号结束,则返回结果
                pos = read;
                return Result;
            }
            else // 否则抛出异常
            {
                delete Result;
                Result = 0;
                throw Exception(read, "此处需要右括号");
            }
        }
        else
        {
            throw e; // 这里要求GetNumber函数中抛出的异常信息为“需要表达式”,而非“需要数字”
            // 或者:
            // throw Exception(read, "此处需要数字或左括号");
        }
    }
}

// 检测Stream从pos起始,开头是否是Factor
// 实现语法2:Factor = Term ((“*” | “/”) Term)*
Expression* GetFactor(const string& Stream, int& pos)
{
    int read = pos;
    Expression* Result = GetTerm(Stream, read);

    while (true)
    {
        char Operator = 0;
        if (IsPrefix(Stream, read, "*"))
        {
            Operator = '*';
        }
        else if (IsPrefix(Stream, read, "/"))
        {
            Operator = '/';
        }
        else
        {
            break;
        }

        if (Operator)
        {
            // 如果是乘除号,则获得下一个Term
            try
            {
                Result = new Expression(Operator, Result, GetTerm(Stream, read));
            }
            catch (Exception& e)
            {
                delete Result;
                Result = 0;
                throw e;
            }
        }
    }
    pos = read;
    return Result;
}

// 检测Stream从pos起始,开头是否是一个Exp
// 实现语法3:Exp = Factor ((“+” | “-”) Factor)*
Expression* GetExp(const string& Stream, int& pos)
{
    int read = pos;
    Expression * Result = GetFactor(Stream, read);

    while (true)
    {
        char Operator = 0;
        if (IsPrefix(Stream, read, "+"))
        {
            Operator = '+';
        }
        else if (IsPrefix(Stream, read, "-"))
        {
            Operator = '-';
        }
        else
        {
            break;
        }

        if (Operator != 0)
        {
            // 如果是加减号,则获得下一个Factor
            try
            {
                Result = new Expression(Operator, Result, GetFactor(Stream, read));
            }
            catch (Exception& e)
            {
                delete Result;
                Result = 0;
                throw e;
            }
        }
    }
    pos = read;
    return Result;
}

int main()
{
    while (true)
    {
        string Stream;
        cout << "输入一个表达式:" << endl;
        getline(cin, Stream);
        int pos = 0;
        if (IsPrefix(Stream, pos, "exit"))
        {
            break;
        }

        pos = 0;
        try
        {
            Expression* exp = GetExp(Stream, pos);

            while (pos < Stream.size() && IsBlank(Stream[pos]))
            {
                ++pos;
            }
            if (pos < Stream.size())
            {
                delete exp;
                exp = 0;
                throw Exception(pos, "发现多余的字符");
            }
            else
            {
                exp->PrintLISP();
                cout << endl << endl;
                delete exp;
                exp = 0;
            }
        }
        catch (Exception& e)
        {
            cout << "发生错误" << endl;
            cout << "错误位置:" << e.Start << endl;
            cout << "错误信息:" << e.Error << endl;
            cout << endl;
        }
    }

    return 0;
}

         上述四则运算表达式语法分析程序中的数据结构有:Expression结构体和Exception结构体。其中Expression结构体实质是一个表达式的递归二叉树,而成员函数PrintLISP通过前序遍历输出了LISP表达式。Expression当IsNumber为true时表示一个数字,当是false时表示一个表达式。

         异常结构体Exception记录了异常发生时的位置和异常信息。

         该程序中的函数有:IsBlank、IsPrefix、GetNumber、GetTerm、GetFactor、GetExp。其中主要是后面的四个Get*函数。

         GetTerm、GetFactor、GetExp分别对应了四则运算表达式的三条语法:1.Term = <数字> | “(”Exp”)”、2.Factor = Term ((“*” | “/”) Term)*、3.Exp = Factor ((“+” | “-”) Factor)*

         本程序通过对四则运算表达式的三条语法进行实现,得到了四则运算表达式的递归二叉树,进而通过对该二叉树的前序遍历得到了对应的LISP表达式。

         下面讨论一下如何不仅仅只得到LISP表达式,还得到四则运算表达式的值。首先第一种方式是:既然得到了LISP表达式了,我们可以通过之前的《语法分析器初步学习——LISP语法分析》通过对得到的LISP表达式进行分析,最终得到四则运算表达式的值。

         另一种方法是,既然得到了四则运算表达式对应的递归二叉树,那么求其值,我们可以通过后序遍历得到四则运算表达式的值,当然,需要在Expression结构体中增加一个字段用来记录以该节点为根节点的子树的值。

         既然我们的程序是对四则运算表达式进行的语法分析,在语法分析过程中,我们就可以对其进行求值,根据+、-、*、/四种运算符进行相应的求值。具体程序如下:

// 四则运算表达式的语法分析——求值
#include <iostream>
#include <string>
using namespace std;

struct Exception
{
    int    Start; // 错误位置
    string Error; // 错误信息

    Exception(int aStart, const string& aError)
    {
        Start = aStart;
        Error = aError;
    }
};

// 表达式结构体
struct Expression
{
    bool IsNumber;     // 是否是数字
    int  Number;       // 数字内容,只有当Isnumber = true的时候有效
    char Operator;     // 操作符,只有当IsNumber = false的时候有效
    Expression* Left;  // 左表达式,只有当IsNumber = false的时候有效
    Expression* Right; // 右表达式,只有当IsNumber = false的时候有效

    int  Value;        // 以该节点为根节点的子树的值

    Expression(int aNumber)
    {
        IsNumber = true;
        Number   = aNumber;
        Operator = 0;
        Left     = 0;
        Right    = 0;

        Value    = aNumber;
    }

    // 在建立表达式的同时,求值
    Expression(char aOperator, Expression* aLeft, Expression* aRight)
    {
        IsNumber = false;
        Number   = 0;
        Operator = aOperator;
        Left     = aLeft;
        Right    = aRight;

        switch (aOperator)
        {
        case '+':
            Value = aLeft->Value + aRight->Value;
            break;

        case '-':
            Value = aLeft->Value - aRight->Value;
            break;

        case '*':
            Value = aLeft->Value * aRight->Value;
            break;

        case '/':
            if (aRight->Value == 0)
            {
                throw Exception(-1, "除数为0"); // 这里没有发生错误时的位置信息
            }
            Value = aLeft->Value / aRight->Value;
            break;

        default:
            break;
        }
    }

    // 析构
    ~Expression()
    {
        if (Left != 0)
        {
            delete Left;
            Left = 0;
        }
        if (Right != 0)
        {
            delete  Right;
            Right = 0;
        }
    }

    void PrintLISP()
    {
        if (IsNumber)
        {
            cout << Number;
        }
        else
        {
            cout << "(" << Operator << " ";
            Left->PrintLISP();
            cout << " ";
            Right->PrintLISP();
            cout << ")";
        }
    }

    // 通过后续遍历四则运算表达式对应的递归二叉树,得到该表达式的值
    int GetValue()
    {
        if (IsNumber)
        {
            return Value;
        }

        int left = Left->GetValue();
        int right = Right->GetValue();
        switch (Operator)
        {
        case '+':
            return left + right;
            break;

        case '-':
            return left - right;
            break;

        case '*':
            return left * right;
            break;

        case '/':
            if (right == 0)
            {
                throw Exception(-1, "除数为0"); // 这里没有发生错误时的位置信息
            }
            return left / right;
            break;

        default:
            break;
        }
    }
};

// 检测是否是空白符
bool IsBlank(char ch)
{
    return ch == ' ' || ch == '	';
}

// 检测Text是否是Stream从pos起始的开头
// 如果是,则将pos前移Text.size()个字符,且返回true
// 否则pos保持不变,返回false
// 对于pos后的空白符忽略
bool IsPrefix(const string& Stream, int& pos, const string& Text)
{
    int read = pos; // 设置真实游标
    
    // 过滤空格
    while (IsBlank(Stream[read]))
    {
        ++read;
    }
    if (Stream.substr(read, Text.size()) == Text)
    {
        pos = read + Text.size();
        return true;
    }
    else
    {
        return false;
    }
}

// 检测Stream从pos开头是否是数字
Expression* GetNumber(const string& Stream, int& pos)
{
    int Result = 0;
    bool GotNumber = false;

    int read = pos;

    while (IsBlank(Stream[read]))
    {
        ++read;
    }

    while (true)
    {
        char ch = Stream[read];

        if (ch >= '0' && ch <= '9')
        {
            Result = Result * 10 + ch - '0';
            GotNumber = true;
            ++read;
        }
        else
        {
            break;
        }
    }

    if (GotNumber)
    {
        pos = read;

        Expression* ret = new Expression(Result);
        ret->Value = Result;
        return ret;
    }
    else
    {
        throw Exception(read, "此处需要表达式"); // 不仅仅是需要数字
    }
}

Expression* GetTerm(const string& Stream, int& pos);
Expression* GetFactor(const string& Stream, int& pos);
Expression* GetExp(const string& Stream, int& pos);

// 检测Stream从pos起始是否是一个Term
// 实现语法1:Term = <数字> | “(”Exp”)”
Expression* GetTerm(const string& Stream, int& pos)
{
    try
    {
        return GetNumber(Stream, pos);
    }
    catch (Exception& e)
    {
        int read = pos;
        // 检测左括号
        if (IsPrefix(Stream, read, "("))
        {
            // 检测表达式
            Expression* Result = GetExp(Stream, read);
            if (IsPrefix(Stream, read, ")"))
            {
                // 如果使用右括号结束,则返回结果
                pos = read;
                return Result;
            }
            else // 否则抛出异常
            {
                delete Result;
                Result = 0;
                throw Exception(read, "此处需要右括号");
            }
        }
        else
        {
            throw e; // 这里要求GetNumber函数中抛出的异常信息为“需要表达式”,而非“需要数字”
            // 或者:
            // throw Exception(read, "此处需要数字或左括号");
        }
    }
}

// 检测Stream从pos起始,开头是否是Factor
// 实现语法2:Factor = Term ((“*” | “/”) Term)*
Expression* GetFactor(const string& Stream, int& pos)
{
    int read = pos;
    Expression* Result = GetTerm(Stream, read);

    while (true)
    {
        char Operator = 0;
        if (IsPrefix(Stream, read, "*"))
        {
            Operator = '*';
        }
        else if (IsPrefix(Stream, read, "/"))
        {
            Operator = '/';
        }
        else
        {
            break;
        }

        if (Operator)
        {
            // 如果是乘除号,则获得下一个Term
            try
            {
                Result = new Expression(Operator, Result, GetTerm(Stream, read));
            }
            catch (Exception& e)
            {
                delete Result;
                Result = 0;
                throw e;
            }
        }
    }
    pos = read;
    return Result;
}

// 检测Stream从pos起始,开头是否是一个Exp
// 实现语法3:Exp = Factor ((“+” | “-”) Factor)*
Expression* GetExp(const string& Stream, int& pos)
{
    int read = pos;
    Expression * Result = GetFactor(Stream, read);

    while (true)
    {
        char Operator = 0;
        if (IsPrefix(Stream, read, "+"))
        {
            Operator = '+';
        }
        else if (IsPrefix(Stream, read, "-"))
        {
            Operator = '-';
        }
        else
        {
            break;
        }

        if (Operator != 0)
        {
            // 如果是加减号,则获得下一个Factor
            try
            {
                Result = new Expression(Operator, Result, GetFactor(Stream, read));
            }
            catch (Exception& e)
            {
                delete Result;
                Result = 0;
                throw e;
            }
        }
    }
    pos = read;
    return Result;
}

int main()
{
    while (true)
    {
        string Stream;
        cout << "输入一个表达式:" << endl;
        getline(cin, Stream);
        int pos = 0;
        if (IsPrefix(Stream, pos, "exit"))
        {
            break;
        }

        pos = 0;
        try
        {
            Expression* exp = GetExp(Stream, pos);

            while (pos < Stream.size() && IsBlank(Stream[pos]))
            {
                ++pos;
            }
            if (pos < Stream.size())
            {
                delete exp;
                exp = 0;
                throw Exception(pos, "发现多余的字符");
            }
            else
            {
                exp->PrintLISP();
                cout << endl << exp->Value;
                cout << endl << exp->GetValue();
                cout << endl << endl;
                delete exp;
                exp = 0;
            }
        }
        catch (Exception& e)
        {
            cout << "发生错误" << endl;
            cout << "错误位置:" << e.Start << endl;
            cout << "错误信息:" << e.Error << endl;
            cout << endl;
        }
    }

    return 0;
}

         之前我们讨论了三种求值的方法:通过对LISP表达式的计算、对二叉树后序遍历、在分析表达式的同时进行求值。这三种方法存在先后顺序,其中通过LISP求值是最后的,对二叉树后续遍历是处于中间,在分析表达式的同时进行求值是最早的。

         上述程序中我们对第二、三种方法进行了实现,其中通过对Expression构造函数的改造实现了第三种方法,通过对Expression结构体增加了GetValue成员函数实现了第二种方法。

         由于这两种方法都是在Expression结构体中完成的,所以对于除法除数为0时的异常处理,是无法记录错误发生的位置信息的,这里我们标注为-1。

         以上是对四则运算表达式进行语法分析的相关内容,语法分析的方法是最基本的递归向下方法。

         另外,通过对四则运算表达式的语法分析,我们得到了其对应的递归二叉树,进而根据该二叉树得到表达式对应的前缀、中缀、后缀表达式变得很简单了。这不失为一种统一的将四则运算表达式在前缀、中缀、后缀等三种形式之间相互转换的一种好方法。

         语法分析相关的其他内容有待进一步学习和实践,另推荐《构造正则表达式引擎》等。

原文地址:https://www.cnblogs.com/unixfy/p/3329367.html