构建 语法树 来解析 数学表达式

本文介绍了如何构建抽象语法树(AST)来解析数学表达式,通过AST解决中缀表达式的运算优先级问题。文章详细阐述了从中缀表达式到语法树的转换过程,并提供了Java代码实现,包括Parser、ASTNode和Evaluator类。最后,通过遍历语法树得出计算结果,验证了方法的有效性。
摘要由CSDN通过智能技术生成

构建 语法树 来解析 数学表达式

本文主要讲如何把一个数学表达式转化成语法树,并通过语法树来解出结果。

引入

这个主要是用来学习语法树的构建,数学表达式只是一个引子,本项目可以引申到 HTTP 请求报文解析,SQL语句的解析的诸如此类的拥有固定规则的字符串的解析。

思考

我们想想,对于 1 + 2 * 3 这个表达式,它的值是7。但是如果你拿到的是一串字符串,那么你要如何用C ++这样的语言来解析呢?首先,这是一个所谓的 “中缀” 符号。还有前缀和后缀表示法。术语“中缀”,“前缀”和“后缀”指的是与操作数相关的运算符的位置:

  • 前缀:运算符 操作数1 操作数2(例如:+ 1 2)
  • 中缀:操作数1 运算符 操作数2(例如:1 + 2)
  • 后缀:操作数1 操作数2 运算符(例如:1 2 +)

但是很明显,我们没有办法使用程序通过中序遍历做什么,因为表达式里通常包含优先级的运算,这使得中序遍历并不能提前做什么操作。

因此,我们必须借助别的工具来辅助,常用的方式有两种

  • 逆波兰表示法(RPN)
  • 抽象语法树(AST)

但是,对于数学表达式来说,两种方法都可以做到,但是对于别的句式进行解析,逆波兰表达式就显得不那么方便了。因此我们这里通过建立抽象语法树来对数学表达式进行解析。

语法定义

首先我们定义出一种递归的语法

EXP   ->   EXP + EXP | EXP - EXP | EXP * EXP | EXP / EXP |
           - EXP | ( EXP ) | number | sin( EXP ) | cos( EXP )

但是很显然,这种方式并不能体现表达式的优先级,因此我们改进语法:

EXP    ->   EXP + TERM | 
            EXP - TERM | 
            TERM
TERM   ->   TERM * FACTOR | 
            TERM / FACTOR | 
            FACTOR
FACTOR ->   ( EXP ) | - EXP | number |
            sin( EXP ) | cos( EXP )

现在这种语法是可以表示出表达式的优先级,但是还有一个问题,这种语法是一个左递归的语法,因此我们还需要对其进行改进:

EXP    ->   TERM EXP1
EXP1   ->   + TERM EXP1 | 
            - TERM EXP1 | 
            null
TERM   ->   FACTOR TERM1
TERM1  ->   * FACTOR TERM1 | 
            / FACTOR TERM1 | 
            null
FACTOR ->   ( EXP ) | - EXP | number |
            sin( EXP ) | cos( EXP )

代码实现

我们这里将使用 Java 进行编写示例,但是你可以把它翻译成任何语言的代码

Parser类定义:

public class Parser {
   
    private Token m_crtToken;
    private final String m_Text;
    private int m_Index;

    private Parser(String str) {
   ...}

    public static ASTNode parse(String expr) {
   ...}

    private ASTNode Expression() {
   ...}
    private ASTNode Expression1() {
   ...}

    private ASTNode Term() {
   ...}
    private ASTNode Term1() {
   ...}

    private ASTNode Factor() {
   ...}

    private void Match(char expected) {
   ...}
    private void SkipWhitespaces() {
   ...}

    private void GetNextToken() {
   ...}
    private double GetNumber() {
   ...}

    private boolean isSpace(char ch) {
   ...}
    private boolean isDigit(char ch) {
   ...}
}

其中

  • ASTNode 为二叉树的一个节点
  • SkipWhitespaces() 为扫描字符串时跳过所有空格的函数
  • Match(char) 为下一个字符是否与传入字符匹配
  • GetNextToken() 为获取下一个元素类型

完整代码

Parser.java

// Parser.java
package expr_parser;

public class Parser {
   

    private Token m_crtToken;
    private final String m_Text;
    private int m_Index;

    public Parser(String str) {
   
        m_Text = str + "#";
        m_Index = 0;
        m_crtToken = new Token();
    }

    public static ASTNode parse(String expr) throws ParserException {
   
        Parser parser = new Parser(expr);
        parser.GetNextToken();
        return parser.Expression();
    }

    private ASTNode Expression() throws ParserException {
   
        ASTNode t_node = Term();
        ASTNode e1_node = Expression1();

        return new ASTNode(ASTNodeType.OPERATOR_PLUS, 0, t_node, e1_node);
    }

    private ASTNode Expression1() throws ParserException {
   
        ASTNode t_node;
        ASTNode e1_node;
        switch (m_crtToken.type) {
   
            case PLUS:
                GetNextToken();
                t_node = Term();
                e1_node = Expression1();
                return new ASTNode(ASTNodeType.OPERATOR_PLUS
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值