1. 程式人生 > >構建 語法樹 來解析 數學表示式

構建 語法樹 來解析 數學表示式

構建 語法樹 來解析 數學表示式

本文主要講如何把一個數學表示式轉化成語法樹,並通過語法樹來解出結果。

引入

這個主要是用來學習語法樹的構建,數學表示式只是一個引子,本專案可以引申到 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, 0, t_node, e1_node);
            case MINUS:
                GetNextToken();
                t_node = Term();
                e1_node = Expression1();
                return new ASTNode(ASTNodeType.OPERATOR_MINUS, 0, t_node, e1_node);
            default:
                return new ASTNode(ASTNodeType.NUMBER_VALUE, 0, null, null);
        }
    }

    private ASTNode Term() throws ParserException {
        ASTNode f_node = Factor();
//        GetNextToken();
        ASTNode t1_node = Term1();
        return new ASTNode(ASTNodeType.OPERATOR_MUL, 0, f_node, t1_node);
    }

    private ASTNode Term1() throws ParserException {
        ASTNode t_node;
        ASTNode e1_node;
        switch (m_crtToken.type) {
            case MUL:
                GetNextToken();
                t_node = Factor();
                e1_node = Term1();
                return new ASTNode(ASTNodeType.OPERATOR_MUL, 0, t_node, e1_node);
            case DIV:
                GetNextToken();
                t_node = Factor();
                e1_node = Term1();
                return new ASTNode(ASTNodeType.OPERATOR_DIV, 0, t_node, e1_node);
            default:
                return new ASTNode(ASTNodeType.NUMBER_VALUE, 1, null, null);
        }
    }

    private ASTNode Factor() throws ParserException {
        ASTNode node;
        switch (m_crtToken.type) {
            case OPEN_PARENTHESIS:
                GetNextToken();
                node = Expression();
                Match(')');
                return node;
            case MINUS:
                GetNextToken();
                node = Factor();
                return new ASTNode(ASTNodeType.UNARY_MINUS, 0, node, null);
            case NUMBER:
                double number = m_crtToken.value;
                GetNextToken();
                return new ASTNode(ASTNodeType.NUMBER_VALUE, number, null, null);
            case SIN:
                GetNextToken();
                node = Expression();
                Match(')');
                return new ASTNode(ASTNodeType.OPERATOR_SIN, 0, node, null);
            case COS:
                GetNextToken();
                node = Expression();
                Match(')');
                return new ASTNode(ASTNodeType.OPERATOR_COS, 0, node, null);
            default:
                String err_msg = "Unexpected token '" + m_Text.charAt(m_Index) + "' at position " + m_Index;
                throw new ParserException(err_msg, m_Index);
        }
    }

    private void Match(char expected) throws ParserException {
        if (m_Text.charAt(m_Index - 1) == expected)
            GetNextToken();
        else {
            String err_msg = "Unexpected token '" + m_Text.charAt(m_Index) + "' at position " + m_Index;
            throw new ParserException(err_msg, m_Index);
        }
    }

    private void SkipWhitespaces() {
        while (isSpace(m_Text.charAt(m_Index))) m_Index++;
    }

    private void GetNextToken() throws ParserException {
        // Ignore white spaces
        SkipWhitespaces();
        m_crtToken.value = 0;
        m_crtToken.symbol = 0;

        // Test for the end of test
        if (m_Text.charAt(m_Index) == '#') {
            m_crtToken.type = TokenType.EOT;
            return;
        }

        if (isDigit(m_Text.charAt(m_Index))) {
            m_crtToken.type = TokenType.NUMBER;
            m_crtToken.value = GetNumber();
            return;
        }

        m_crtToken.type = TokenType.ERROR;

        switch (m_Text.charAt(m_Index)) {
            case '+': m_crtToken.type = TokenType.PLUS; break;
            case '-': m_crtToken.type = TokenType.MINUS; break;
            case '*': m_crtToken.type = TokenType.MUL; break;
            case '/': m_crtToken.type = TokenType.DIV; break;
            case '(': m_crtToken.type = TokenType.OPEN_PARENTHESIS; break;
            case ')': m_crtToken.type = TokenType.CLOSE_PARENTHESIS; break;
            case 's':
                if (m_Text.substring(m_Index, m_Index + 4).equals("sin(")) {
                    m_crtToken.type = TokenType.SIN;
                    m_Index += 3;
                }
                break;
            case 'c':
                if (m_Text.substring(m_Index, m_Index + 4).equals("cos(")) {
                    m_crtToken.type = TokenType.COS;
                    m_Index += 3;
                }
                break;
        }

        if (m_crtToken.type != TokenType.ERROR) {
            m_crtToken.symbol = m_Text.charAt(m_Index);
            m_Index++;
        } else {
            String err_msg = "Unexpected token '" + m_Text.charAt(m_Index) + "' at position " + m_Index;
            throw new ParserException(err_msg, m_Index);
        }
    }

    private double GetNumber() throws ParserException {
        SkipWhitespaces();

        int index = m_Index;
        while (isDigit(m_Text.charAt(m_Index))) m_Index++;
        if (m_Text.charAt(m_Index) == '.') m_Index++;
        while (isDigit(m_Text.charAt(m_Index))) m_Index++;

        if (m_Index - index == 0)
            throw new ParserException("Number expected but not found!", m_Index);

        String buffer = m_Text.substring(index, m_Index);

        return Double.valueOf(buffer);
    }

    private boolean isSpace(char ch) {
        return ch == ' ';
    }

    private boolean isDigit(char ch) {
        return ch >= '0' && ch <= '9';
    }

}

enum TokenType {
    ERROR,
    PLUS,
    MINUS,
    MUL,
    DIV,
    SIN,
    COS,
    EOT,
    OPEN_PARENTHESIS,
    CLOSE_PARENTHESIS,
    NUMBER
}

class Token {
    TokenType type;
    double value;
    char symbol;

    Token() {
        type = TokenType.ERROR;
        value = 0;
    }
}

ASTNode.java

// ASTNode.java
package expr_parser;

public class ASTNode {
    private ASTNodeType type;
    private double value;
    private ASTNode leftChild;
    private ASTNode rightChild;

    public ASTNode() {
        type = ASTNodeType.UNDEFINED;
        value = 0;
        leftChild = null;
        rightChild = null;
    }

    public ASTNode(ASTNodeType type, double value, ASTNode leftChild, ASTNode rightChild) {
        this.type = type;
        this.value = value;
        this.leftChild = leftChild;
        this.rightChild = rightChild;
    }

    @Override
    public String toString() {
        String str = "--------------------------------------------\n";
        switch (type) {
            case NUMBER_VALUE:
                str += "node_type: NUMBER_VALUE\n";
                str += "value: " + value + "\n";
                break;
            case OPERATOR_PLUS:
                str += "node_type: OPERATOR_PLUS\n";
                break;
            case OPERATOR_MINUS:
                str += "node_type: OPERATOR_MINUS\n";
                break;
            case OPERATOR_MUL:
                str += "node_type: OPERATOR_MUL\n";
                break;
            case OPERATOR_DIV:
                str += "node_type: OPERATOR_DIV\n";
                break;
            case OPERATOR_SIN:
                str += "node_type: OPERATOR_SIN\n";
                break;
            case OPERATOR_COS:
                str += "node_type: OPERATOR_COS\n";
                break;
            default:
                str += "ERROR!!!!!!!!!!!!!!\n";
                break;
        }
        if (leftChild != null) {
            str += "left_child";
            str += leftChild.toString();
        } else
            str += "left_child is null\n";

        if (rightChild != null) {
            str += "right_child";
            str += rightChild.toString();
        } else
            str += "right_child is null\n";

        str += "--------------------------------------------\n";
        return str;
    }

    public ASTNodeType getType() {
        return type;
    }

    public void setType(ASTNodeType type) {
        this.type = type;
    }

    public double getValue() {
        return value;
    }

    public void setValue(double value) {
        this.value = value;
    }

    public ASTNode getLeftChild() {
        return leftChild;
    }

    public void setLeftChild(ASTNode leftChild) {
        this.leftChild = leftChild;
    }

    public ASTNode getRightChild() {
        return rightChild;
    }

    public void setRightChild(ASTNode rightChild) {
        this.rightChild = rightChild;
    }
}

ASTNodeType.java

// ASTNodeType.java
package expr_parser;

public enum ASTNodeType {
    UNDEFINED,
    OPERATOR_PLUS,
    OPERATOR_MINUS,
    OPERATOR_MUL,
    OPERATOR_DIV,
    OPERATOR_SIN,
    OPERATOR_COS,
    NUMBER_VALUE,
    UNARY_MINUS
}

遍歷語法樹 得出結果

通過呼叫 Parser.parse(String) 後可以得到一個型別為 ASTNode 的一個二叉樹,通過後序遍歷把左右孩子節點的值進行當前結點所表示的操作符表示的操作運算並返回到上一級。通過一次遞迴後即可得出結果。

程式碼實現

Evaluator.java

// Evaluator.java
package expr_parser;

public class Evaluator {
    public static double evaluate(ASTNode ast) throws EvaluatorException {
        if (null == ast)
            throw new EvaluatorException("Incorrect abstract syntax tree");
        switch (ast.getType()) {
            case NUMBER_VALUE:
                return ast.getValue();
            case UNARY_MINUS:
                return -Evaluator.evaluate(ast.getLeftChild());
            case OPERATOR_SIN:
                double temp = Evaluator.evaluate(ast.getLeftChild());
                return Math.sin(temp);
            case OPERATOR_COS:
                return Math.cos
            
           

相關推薦

構建 語法 解析 數學表示式

構建 語法樹 來解析 數學表示式 本文主要講如何把一個數學表示式轉化成語法樹,並通過語法樹來解出結果。 引入 這個主要是用來學習語法樹的構建,數學表示式只是一個引子,本專案可以引申到 HTTP 請求報文解析,SQL語句的解析的諸如此類的擁有固定規則的字串的解析。 思考 我

hibernate中antlr對於hql生成抽象語法原始碼解析

Hibernate版本5.1.11FInal 以一句update語句作為例子。 update com.tydhot.eninty.User set userName=:userName where userId=:userId 上面這句hql經過antlr的語法解析

開發者的進階之路:用語法實現預編譯

能夠 項目 未來 部分 領域 編譯器 提高 cfb ima 如何在保證安全性的前提下,提升開發過程的效率,是每個開發者都在不斷探索的問題。借助語法樹,開發者能夠更好地展現和修改源程序代碼,優化開發環節,提高安全系數,還能進一步實現安卓預編譯。 本文基於個推高級研發工程師李健

Atitti. 語法AST、字尾表示式、DAG、三地址程式碼

Atitti. 語法樹AST、字尾表示式、DAG、三地址程式碼 抽象語法樹的觀點認為任何複雜的語句巢狀情況都可以藉助於樹的形式加以描述。確實,不得不承認應用抽象語法樹可以使語句翻譯變得相對容易,它很好地描述了語句、表示式之間的聯絡。不過,由於Neo Pascal並不會

正則表示式引擎的構建——基於編譯原理DFA(龍書第三章)——2 構造抽象語法

簡要介紹     構造抽象語法樹是構造基於DFA的正則表示式引擎的第一步。目前在我實現的這個正則表示式的雛形中,正則表示式的運算子有3種,表示選擇的|運算子,表示星號運算的*運算子,表示連線的運算子cat(在實際正則表示式中被省去)。 例如對於正則表示式a*b|c,在a*

[WebKit核心] JavaScript引擎深度解析--基礎篇(一)位元組碼生成及語法構建詳情分析

      看到HorkeyChen寫的文章《[WebKit] JavaScriptCore解析--基礎篇(三)從指令碼程式碼到JIT編譯的程式碼實現》,寫的很好,深受啟發。想補充一些Horkey沒有寫到的細節比如位元組碼是如何生成的等等,為此成文。       JSC對

[WebKit核心] JavaScriptCore深度解析--基礎篇(一)位元組碼生成及語法構建詳情分析

     看到HorkeyChen寫的文章《[WebKit] JavaScriptCore解析--基礎篇(三)從指令碼程式碼到JIT編譯的程式碼實現》,寫的很好,深受啟發。想補充一些Horkey沒有寫到的細節比如位元組碼是如何生成的等等,為此成文。       JS

淘寶數據庫OceanBase SQL編譯器部分 源代碼閱讀--解析SQL語法

git itemtype 工具 銷毀 cin bsp 年輕 you any OceanBase是阿裏巴巴集團自主研發的可擴展的關系型數據庫,實現了跨行跨表的事務,支持數千億條記錄、數百TB數據上的SQL操作。在阿裏巴巴集團下,OceanBase數據庫支持了多個重

Oracle中有關數學表示式語法

Oracle中有關數學表示式的語法 三角函式SIN               ASIN      

利用java正則表示式解析並獲取指定的字串

Java的正則表示式不僅可以用來匹配驗證字串是否符合標準型別,還可以用來解析字串,獲取自己想要得到的資料。在java.util.regex包下提供了系列的類來對字串進行匹配。來看一下下面的例子: public static String parse (String s)

【龍書筆記】用Python實現一個簡單數學表示式從中綴到字尾語法的翻譯器(採用遞迴下降分析法)

上篇筆記介紹了語法分析相關的一些基礎概念,本篇筆記根據龍書第2.5節的內容實現一個針對簡單表示式的字尾式語法翻譯器Demo。 備註:原書中的demo是java例項,我給出的將是邏輯一致的Python版本的實現。在簡單字尾翻譯器程式碼實現之前,還需要介紹幾個基本概念。1. 自

只要十步,你就可以應用表示式優化動態呼叫

表示式樹是 .net 中一系列非常好用的型別。在一些場景中使用表示式樹可以獲得更好的效能和更佳的擴充套件性。本篇我們將通過構建一個 “模型驗證器” 來理解和應用表示式樹在構建動態呼叫方面的優勢。 Newbe.Claptrap 是一個用於輕鬆應對併發問題的分散式開發框架。如果您是首次閱讀本系列文章。建議可以先

Logstash筆記(二)-----grok插件的正則表達式解析日誌

linux grok (一)簡介: 豐富的過濾器插件的存在是 logstash 威力如此強大的重要因素。名為過濾器,其實提供的不單單是過濾的功能,它們擴展了進入過濾器的原始數據,進行復雜的邏輯處理,甚至可以無中生有的添加新的 logstash 事件到後續的流程中去! Grok 是 L

Logstash語法常用案例解析(一)

logstash摘要簡述logstash的常用插件,以及簡單的使用案例一:基礎運行建議使用supervisor來管理ELK中的各個組件,方便同一管理安裝 https://www.aolens.cn/?p=809 有講解提供一個常用的配置:[program:logstash] command=/opt

R語言專題,如何使用party包構建決策

r語言下面將在iris數據集上,演示如何使用party包中的函數ctree來建立一棵決策樹。iris數據集中的Sepal.Length、Sepal.Width、Petal.Length和Petal.Width,都將用來預測鳶尾花的種類。party包中的函數ctree用來建立決策樹,函數predict用來對新數

語法

樹狀表 其中 而已 operator href %20 style state col 何為語法樹 什麽是語法樹? 你是否曾想過,這個世界存在這麽多語言的意義。 假如現在你面前有一個物體,它是一個不規則的圓體,整個身體通紅,頭部還有一根細長稍微彎曲偏右呈棕色的圓柱體。

Xamarin調用JSON.net解析JSON

bsp blank ebo sof .html www label nts 測試 https://www.cnblogs.com/zjoch/p/4458516.html 再來我們要怎麽解析JSON格示呢?在.net 中,我們很孰悉的JSON.ne

PMD 編譯 語法分析 詞法分析 抽象語法

edit get 編譯 test if語句 final 代碼掃描 pic blog 編譯原理 163 課堂 http://mooc.study.163.com/learn/-1000002001?tid=1000003000#/learn/content?type=deta

C++根據傳入的函數指針解析需要的參數

func include and cout 結束 get sin UNC 實參 C++可以根據傳入的函數指針,獲取自己需要的參數類型,然後根據參數源中獲取需要的參數,這裏我用tuple作為演示,不過,只要可以根據序號,或者順序方式等獲取實參,都可以使用類似的方式實現: 先給

使用flex進行網易雲音樂界面構建和布局解析

tel ace fixed size 易雲 spl .com -h recommend 1.為什麽要用flex進行webapp布局 第一,float布局 需要清除浮動,很麻煩。 第二,絕對定位需要考慮位置和空間占位 第三,元素垂直水平居中問題。 2.網易雲音樂首頁分析3.啥