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


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

             本文参考自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。

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

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

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

  • 相关阅读:
    【原创】自己动手写工具----签到器[Beta 1.0]
    都2020了,还不好好学学泛型?
    ThreadLocal = 本地线程?
    从BWM生产学习工厂模式
    你还在用BeanUtils进行对象属性拷贝?
    JDK 1.8 之 Map.merge()
    Spring Boot认证:整合Jwt
    以商品超卖为例讲解Redis分布式锁
    如何从 if-else 的参数校验中解放出来?
    分布式全局唯一ID生成策略​
  • 原文地址:https://www.cnblogs.com/unixfy/p/3329367.html
Copyright © 2020-2023  润新知