《Language Implementation Patterns》之访问重写语法树
每個編程的人都學(xué)習(xí)過樹遍歷算法,但是AST的遍歷并不是開始想象的那么簡單。有幾個因素會影響遍歷算法:1)是否擁有節(jié)點的源碼;2)是否子節(jié)點的訪問方式是統(tǒng)一的;3)ast是homogeneous或heterogeneous;4)遍歷的過程中是否需要修改ast;5)以何種順序呢遍歷。這一章會討論常用的四個ast遍歷模式。
- Pattern 12, Embedded Heterogeneous Tree Walker, AST的node類包含了對應(yīng)的訪問方法,后者執(zhí)行嵌入的操作,并訪問所有的子節(jié)點。這種模式將訪問邏輯遍布所有的節(jié)點類,比較簡單直接,但是缺乏靈活性;
- Pattern 13, External Tree Visitor, 一個獨立于AST存在的Visitor類,很靈活,但是手動編寫很復(fù)雜;
- Pattern 14, Tree Grammar, 通過一個語法來描述AST的結(jié)構(gòu),就像用語法來描述語言一樣,這樣可以通過工具來生成Visitor代碼。
- Pattern 15, Tree Pattern Matcher, 該模式不通過語法描述整個AST,而是針對某些我們關(guān)注的subtree。與前面的模式不一樣的是,該模式不關(guān)注如何訪問整個AST,只關(guān)注尋找符合條件的子樹
AST訪問順序
我們說“訪問“一顆樹,意思是我對樹中的節(jié)點執(zhí)行某些操作,因此訪問節(jié)點的順序是非常重要的,這直接影響了執(zhí)行操作的順序。與一般的樹結(jié)構(gòu)一樣,存在前序、中序、后序3種遍歷順序。
對AST訪問來說,情況稍微復(fù)雜一點,我們采用一種叫做depth-first search的算法,如果算法到達(dá)一個節(jié)點t,表示我們discover該節(jié)點,等到對t的訪問、處理結(jié)束,表示我們finished該節(jié)點。
對某種的的訪問機制來說,discover節(jié)點的順序是固定的,但是會產(chǎn)生不同的遍歷效果,取決與將相關(guān)操作放在walk()方法的哪個位置。
以表達(dá)式1+2+3為例,節(jié)點的訪問順序如下:
左側(cè)的圖描述了節(jié)點的discover和finish順序,右側(cè)圖種的星指出了操作可能執(zhí)行的時機。如果所有的操作發(fā)生在discover的時候,那么相當(dāng)于前序遍歷;如果所有的操作發(fā)生在兩個子節(jié)點之間,相當(dāng)與中序遍歷;如果所有的操作發(fā)生在finish的時候,相當(dāng)于后序遍歷。
Pattern 12 Embedded Heterogeneous Tree Walker
每中節(jié)點類型增加一個訪問方法,遞歸調(diào)用
public void walk() {?preorder-action? left.walk(); ?inorder-action? right.walk();?postorder-action? }Pattern 13, External Tree Visitor
<b》將上面嵌入式的訪問代碼,抽取出來放入一個獨立的類里面
第一種實現(xiàn)適合heterogeneous tree,依賴傳統(tǒng)的double-dispatcher設(shè)計模式,每個節(jié)點類型添加一個方法來dispatch自身的訪問到合適的visotor方法。
節(jié)點子類的visit方法實現(xiàn)基本是一樣的:public void visit(VecMathVisitor visitor) { visitor.visit(this); }。
Visitor的實現(xiàn)如下:
visitor的節(jié)點的visit方法也是遞歸形式:
public void visit(AssignNode n) {n.id.visit(this); System.out.print("=" ); n.value.visit(this); System.out.println(); }第二種方式通過node的token類型來分別執(zhí)行訪問操作。
public class Visitor {public void print(ExprNode n) {switch ( n.token.type ) { // switch on token typecase Token.PLUS : print((AddNode)n); break;case Token.INT : print((IntNode)n); break; default : ?error-unhandled-node-type?} public void print(AddNode n) {print(n.left); // walk left child System.out.print("+"); // print operatorprint(n.right); // walk right child}public void print(IntNode n) {...} }這個模式依據(jù)節(jié)點類型來執(zhí)行不同的訪問操作,只要節(jié)點能夠提供type信息即可。
Pattern 14,Tree Grammar
描述AST節(jié)點樹結(jié)構(gòu)的語法,在前面的Parser語法里面也有涉及,通過Tree Grammar來生成的visitor,與Pattern 13具備的能力一致,更加緊湊。
下面是Tree Grammar的一個片段:
里面嵌入了操作代碼,可以控制這些代碼的插入位置來達(dá)到PreOrder,InOrder,PostOrder的效果。
通過Tree Grammar來訪問AST的過程,類似通過語言Grammar來解析語句,因此如果能先把AST轉(zhuǎn)換成線性結(jié)構(gòu),就可以使用傳統(tǒng)的Parser模式來生成訪問代碼;
在前面的章節(jié)說過如何通過文本來表示樹結(jié)構(gòu),表達(dá)式1+2可以表示為(+ 1 2),將括號替換成特殊的token DOWN和UP,得到序列 + DOWN 1 2 UP。DOWN和UP模擬了tree訪問的移動操作。
對上面的Tree Grammar,生成的訪問代碼類似:
因此Tree Grammar,不是基于Node類型來執(zhí)行操作,而是基于某種子樹模式來執(zhí)行操作。
Tree Grammar同時定義了AST有效的結(jié)構(gòu),運行基于Tree Grammar的visitor,可以在運行時檢查AST的合法性。
Pattern 15, Tree Pattern Matcher
該模式用于掃描AST,當(dāng)遇到感興趣的子樹模式的時候,執(zhí)行操作或樹重寫。這種書重寫操作叫做”項重寫“(term rewriting)。
Pattern Matcher就好像文本匹配&改寫工具:awk、sed、perl等;Tree Grammar需要所有子樹對應(yīng)的Grammar,而該模式只需要為關(guān)注的子樹模式指定Grammar,因而并不會發(fā)現(xiàn)ast的所有節(jié)點。
下面先看一個通過項重寫來簡化向量乘法的例子,我們想把向量乘法4*[0,5*0,3]簡化為[40,450,43],進(jìn)一步簡化”乘0"運算,得到[0,0,4*3]。
向量乘法的Grammar為:^('*' INT ^(VEC .+)),其中“.”表示任意的節(jié)點類型,轉(zhuǎn)換的規(guī)則定義如下:
“e”是引入的變量,通過e+=.成為包含向量元素的list.
簡化“乘零”運算的規(guī)則如下:
{$a.int==0}?是語法謂詞,用來控制該匹配選項。
剩下的事情,就是指定上述規(guī)則的運用時機:
ANTLR采用depth-first搜尋,當(dāng)dicovery一個node時候,執(zhí)行topdown;finish一個node的時候執(zhí)行bottomup。
有時候"項重寫”需要對AST多次執(zhí)行Tree Pattern Matcher;比如將操作3+3重寫為3<<1,需要盡力3+3=》3*2》3<<1。
轉(zhuǎn)載于:https://www.cnblogs.com/longhuihu/p/4002264.html
與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的《Language Implementation Patterns》之访问重写语法树的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C# 对Outlook联系人的增、删、查
- 下一篇: java.util.Date和java.