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