抽象語法樹的執(zhí)行

前言:在上一篇博客中,我們已經(jīng)實現(xiàn)了一個計算式的抽象語法樹。這一篇博客主要完成計算式的抽象語法樹的執(zhí)行,達到實現(xiàn)一個計算器的目的

0X00 原理討論

如何執(zhí)行一個計算式的抽象樹?

1+2*3

其實并不難,我們看上面的圖片。只看 + 的那個節(jié)點。左樹只有一個節(jié)點值是 1,右樹是是另外一顆子樹,所以我們要先得到右樹的值,才能計算整個樹的值。

這是什么?先得到所有子樹的值在計算當前節(jié)點的值

這就是簡單的后序遍歷?。?/p>

由于我們已經(jīng)得到了一整顆抽象語法樹,所以實現(xiàn)起來并不是很復(fù)雜。只要根據(jù)表達式,計算子樹就行。

而我們的表達式也只有四種:

  • Unary
  • Binary
  • Group
  • Literal

0X01 代碼實現(xiàn)

我們先從簡單的開始寫:

  • Literal

這個直接返回表達式的值就好了:

    @Override
    public Object visitLiteralExpr(Expr.Literal expr) {
        return expr.value;
    }
  • Group

Group 也比較簡單,直接返回他的 expression 的值就好了

    @Override
    public Object visitGroupingExpr(Expr.Grouping expr) {
        // 執(zhí)行表達式
        return evaluate(expr.expression);
    }

如何得到表達式的值呢?重新 accept 這個表達式:

    // 執(zhí)行表達式
    private Object evaluate(Expr expr) {
        return expr.accept(this);
    }

因為 accept 這個表達式就能重新調(diào)用它的訪問者的 visit 函數(shù)得到這個表達式的值。

  • Unary
    @Override
    public Object visitUnaryExpr(Expr.Unary expr) {
        Token operator = expr.operator;
        Object right = evaluate(expr.right);

        switch (operator.type) {
        case MINUS:
            checkNumberOperands(operator, right);
            return -(double) right;
        case BANG:
            return !isTruthy(right);
        default:
            // 不會執(zhí)行到這里
            break;
        }
        // 不會執(zhí)行到這里
        return null;
    }

解釋一下怎么獲得一元運算的值,首先計算右值,再根據(jù)運算符的 type 計算一元運算的值。

當 operator.type == MINUS 時由于 right 值可能不是數(shù)字,所以要檢查,如果出錯就要扔出「運行時錯誤」

  • Binary

最復(fù)雜的來了,由于二元運算的 operator.type 的值有很多。所以要列出所有的情況:

    @Override
    public Object visitBinaryExpr(Expr.Binary expr) {
        // 計算左右值
        Object left = evaluate(expr.left);
        Object right = evaluate(expr.right);

        switch (expr.operator.type) {
        case GREATER:
            checkNumberOperands(expr.operator, left, right);
            return (double) left > (double) right;
        case GREATER_EQUAL:
            checkNumberOperands(expr.operator, left, right);
            return (double) left >= (double) right;
        case LESS:
            checkNumberOperands(expr.operator, left, right);
            return (double) left < (double) right;
        case LESS_EQUAL:
            checkNumberOperands(expr.operator, left, right);
            return (double) left <= (double) right;
        case MINUS:
            checkNumberOperands(expr.operator, left, right);
            return (double) left - (double) right;
        case SLASH:
            checkNumberOperands(expr.operator, left, right);
            if ((double) right == 0) {
                throw new RuntimeError(expr.operator, "divide zero");
            }
            return (double) left / (double) right;
        case STAR:
            checkNumberOperands(expr.operator, left, right);
            return (double) left * (double) right;
        case BANG_EQUAL:
            return !isEqual(left, right);
        case EQUAL_EQUAL:
            return isEqual(left, right);
        case PLUS:
            if (left instanceof Double && right instanceof Double) {
                return (double) left + (double) right;
            }

            if (left instanceof String && right instanceof String) {
                return (String) left + (String) right;
            }
            throw new RuntimeError(expr.operator, "Operands must be two numbers or two strings.");
        default:
            break;
        }

        // Unreachable.

        return null;

    }

跟之前一樣,要檢查左右值。

但是對于加法來說,我們對 + 號進行了重載,導(dǎo)致我們可以實現(xiàn) string 的加法。

還有如果是 == 或者 != 有一個函數(shù):isEqual() 來判斷兩者是否相等,具體如何判斷,看我后面分析。

對于真假的判定

在我們的語言中我們規(guī)定:

null、數(shù)字 0、關(guān)鍵字 False 為假,其他全部為真

所以:

    private boolean isTruthy(Object obj) {
        /**
         * 什么時候是假:null、數(shù)字 0、語言本身記錄真假 除此之外全為真
         */
        if (obj == null) {
            return false;
        }
        // 由于 obj 是數(shù)字是全部由 Double 代替
        if (obj instanceof Double) {
            double value = (double) obj;
            return (value != 0);
        }
        if (obj instanceof Boolean)
            return (boolean) obj;
        return true;
    }

判斷相等

由于我們是在 Java 之上封裝的語言,所以可以依靠 Java 對象中的 equal 函數(shù)判讀對象是否相等:

    private boolean isEqual(Object a, Object b) {
        /**
         * 當兩者都為 null 的時候相等 調(diào)用 object equal 會調(diào)用子類重寫的方法 equal
         */
        if (a == null && b == null) {
            return true;
        }

        if (a == null) {
            return false;
        }

        return a.equals(b);
    }

運行中出錯

除了檢查靜態(tài)語言中的語法,我們還要動態(tài)的檢查語法,比如有些操作數(shù)只能是數(shù)字,不能是字符串、通過計算計算算出要除以 0 等等。

所以我們要在必要的時候檢查錯誤。

而且不能讓 Java 報錯,讓語言使用者看出來。

因此我們定義了:

class RuntimeError extends RuntimeException {
    final Token token;

    RuntimeError(Token token, String message) {
        super(message);
        this.token = token;
    }
}

用來處理「運行中出錯」。

。。。

就這樣我們實現(xiàn)了一個計算器。。。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容