用 Antlr 重構腳本解釋器 前言 Antlr 升級 xjson 總結

[圖片上傳失敗...(image-636c17-1659876521176)]

前言

在上一個版本實現的腳本解釋器 GScript 中實現了基本的四則運算以及 AST 的生成。

[圖片上傳失敗...(image-2b6f3b-1659876521176)]

當我準備再新增一個 % 取模的運算符時,會發現工作很繁瑣而且幾乎都是重複的;主要是兩步:

  1. 需要在詞法解析器中新增對 % 符號的支持。
  2. 在語法解析器遍歷 AST 時對 % token 實現具體邏輯。

其中的詞法解析和遍歷 AST 完全是重複工作,所以我們可否能夠簡化這兩步呢?

Antlr

Antlr 就是做幫我們解決這些問題的常用工具,利用它我們只需要編寫詞法文件,然後就可以自動生成詞法、語法解析器,並且可以生成不同語言的代碼。

下面以 GScript 的示例來看看 antlr 是如何幫我們生成詞法分析器的。

func TestGScriptVisitor_Visit_Lexer(t *testing.T) {
    expression := "(2+3) * 2"
    input := antlr.NewInputStream(expression)
    lexer := parser.NewGScriptLexer(input)
    for {
        t := lexer.NextToken()
        if t.GetTokenType() == antlr.TokenEOF {
            break
        }
        fmt.Printf("%s (%q) %d\n",
            lexer.SymbolicNames[t.GetTokenType()], t.GetText(),t.GetColumn())
    }
}
//output:
 ("(") 0
DECIMAL_LITERAL ("2") 1
PLUS ("+") 2
DECIMAL_LITERAL ("3") 3
 (")") 4
MULT ("*") 6
DECIMAL_LITERAL ("2") 8

Antlr 會自動將我們的表達式解析爲 token,遍歷 token 時還能拿到該 token 所在的代碼行數、位置等信息,在編譯期間做語法檢查非常有用。

要實現這些我們只需要編寫詞法、語法規則文件即可。

剛纔的示例所對應的詞法、語法規則如下:

expr
    : '(' expr ')'                        #NestedExpr
    | liter=literal #Liter
    | lhs=expr bop=( MULT | DIV ) rhs=expr #MultDivExpr
    | lhs=expr bop=MOD rhs=expr            #ModExpr
    | lhs=expr bop=( PLUS | SUB ) rhs=expr #PlusSubExpr
    | expr bop=(LE | GE | GT | LT ) expr # GLe
    | expr bop=(EQUAL | NOTEQUAL) expr # EqualOrNot
    ;
DECIMAL_LITERAL:    ('0' | [1-9] (Digits? | '_'+ Digits)) [lL]?;    

完整規則:https://github.com/crossoverJie/gscript/blob/main/GScript.g4

運行:

antlr -Dlanguage=Go -o parser -visitor -no-listener GScript.g4

就可以幫我們生成 Go 的代碼(默認是 Java),關於 Antlr 的詞法、文法規則以及安裝步驟請參考官網

而我們要實現具體的語法邏輯時只需要實現相關的接口,Antlr 會自動遍歷 AST(當然也可以手動控制),同時在訪問不同的 AST 節點時會回調我們自己實現的接口,這樣我們就能編寫自己的語法規則了。

以這裏的新增的取模運算爲例:

func (v *GScriptVisitor) VisitModExpr(ctx *parser.ModExprContext) interface{} {
    lhs := v.Visit(ctx.GetLhs())
    rhs := v.Visit(ctx.GetRhs())
    return lhs.(int) % rhs.(int)
}

Antlr 回調 VisitModExpr 方法時,便能獲取到 % 符號左右兩側的數據,這時只需要做相關運算即可。

基於這個模式這次新增了一個 statement,具體語法如下:

func TestGScriptVisitor_VisitIfElse8(t *testing.T) {
    expression := `
if(3!=(1+2)){
    return 1+3
} else {
    return false
}`
    input := antlr.NewInputStream(expression)
    lexer := parser.NewGScriptLexer(input)
    stream := antlr.NewCommonTokenStream(lexer, 0)
    parser := parser.NewGScriptParser(stream)
    parser.BuildParseTrees = true
    tree := parser.Prog()
    visitor := GScriptVisitor{}
    var result = visitor.Visit(tree)
    fmt.Println(expression, " result:", result)
    assert.Equal(t, result, false)
}

Antlr 還有其他各種優勢,比如可以解決:

  • 左遞歸。
  • 二義性。
  • 優先級。

等問題。

這裏也推薦在 IDE 中安裝 Antlr 的插件,這樣就可以直觀的查看 AST 語法樹,可以幫我們更好的調試代碼。

[圖片上傳失敗...(image-f05c05-1659876521176)]
[圖片上傳失敗...(image-1adeb1-1659876521176)]

升級 xjson

藉助 GScript 提供的 statementxjson 也提供了有些有意思的寫法:
[圖片上傳失敗...(image-4a7b95-1659876521176)]

因爲 xjson 的四則運算語法沒有使用 Antlr 生成,所以爲了能支持 GScript 提供的 statement 需要手寫許多詞法代碼。

[圖片上傳失敗...(image-66f2e8-1659876521176)]

這也體現了 Antlr 這類前端工具的重要性,效率提升是非常明顯的。

總結

藉助於 Antlr 後續 GScript 會繼續支持函數調用、更完善的類型系統、面向對象等特性;感興趣的朋友請持續關注。

源碼地址:
https://github.com/crossoverJie/gscript

https://github.com/crossoverJie/xjson

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章