3000幀動畫圖解MySQL為什麼需要binlog、redo log和undo log
文法規則和抽象語法樹
直譯器模式描述瞭如何為簡單的語言定義一個文法,如何在該語言中表示一個句子,以及如何解釋這些句子。在正式分析直譯器模式結構之前,先來學習如何表示一個語言的文法規則以及如何構造一棵抽象語法樹。
例如對於表示式 “1十2+3一4十1”,其中包含了3個語言單位,可以使用如下文法規則來定義:
expression ::value | operation
operation ::=expression '+' expression | expression '-' expression
value ::= an integer //一個整數值
該文法規則包含 3 條語句。第一條表示表示式的組成方式,其中 value 和 operation 是後面兩個語言單位的定義;每一條語句所定義的字串如 operation 和 value 稱為語言構造成分或語言單位;符號 "::=" 表示 “定義為” 的意思,其左邊的語言單位通過右邊來進行說明和定義,語言單位對應終結符表示式和非終結符表示式。例如,本規則中的 operation 是
非終結符表示式,它的組成元素仍然可以是表示式,可以進一步分解;而 value 是終結符表示式,它的組成元素是最基本的語言單位,不能再進行分解。
在文法規則定義中可以使用一些符號來表示不同的含義,例如使用 "|" 表示或,使用 "{" 和 "}" 表示組合,使用 "*" 表示出現 0 次或多次等,其中使用頻率最高的符號是表示或關係的 "|",例如,文法規則 "boolValue::=0|1" 表示終結符表示式 boolValue 的取值可以為 0 或者 1。
除了使用文法規則來定義一個語言外,還可以通過一種稱之為 抽象語法樹(AbstractSyntax Tree, AST) 的圖形方式來直觀地表示語言的構成,每一棵抽象語法樹對應一個語言例項,例如加法/減法表示式語言中的語句 "1+2+3-4+1",可以通過如圖所示的抽象語法樹表示:
在該抽象語法樹中,可以通過終結符表示式 value 和非終結符表示式 operation 組成複雜的語句,每個文法規則的語言例項都可以表示為一個抽象語法樹,即每一條具體的語句都可以用類似上圖所示的抽象語法樹來表示,在圖中終結符表示式類的例項作為樹的葉子節點,而非終結符表示式類的例項作為非葉子節點,它們可以將終結符表示式類的例項以及包含終結符和非終結符例項的子表示式作為其子節點。抽象語法樹描述瞭如何構成一個複雜的句子,通過對抽象語法樹的分析,可以識別出語言中的終結符類和非終結符類。
直譯器模式概述
直譯器模式是一種使用頻率相對較低但學習難度較大的設計模式,它用於描述如何使用面嚮物件語言構成一個簡單的語言直譯器。在某些情況下,為了更好地描述某些特定型別的問題,可以建立一種新的語言,這種語言擁有自己的表示式和結構,即文法規則,這些問題的例項將對應為該語言中的句子。此時,可以使用直譯器模式來設計這種新的語言。對直譯器模式的學習能夠加深對面向物件思想的理解,並且掌握程式語言中文法規則的解釋過程。
直譯器模式定義如下: 定義一個語言的文法,並且建立一個直譯器來解釋該語言中的句子,這裡的 "語言" 是指使用規定格式和語法的程式碼。直譯器模式是一種類行為型模式。
由於表示式可分為終結符表示式
從上圖中可以看出,在直譯器模式結構圖中包含以下4個角色:
- AbstractExpression(抽象表示式):在抽象表示式中聲明瞭抽象的解釋操作,它是所有終結符表示式和非終結符表示式的公共父類。
- TerminalExpression(終結符表示式):是抽象表示式的子類,它實現了與文法中的終結符相關聯的解釋操作,在句子中的每一個終結符都是該類的一個例項。通常,在一個直譯器模式中只有少數幾個終結符表示式類,它們的例項可以通過非終結符表示式組成較為複雜的句子。
- NonterminalExpression(非終結符表示式):也是抽象表示式的子類,它實現了文法中非終結符的解釋操作,由於在非終結符表示式中可以包含終結符表示式,也可以繼續包含非終結符表示式,因此其解釋操作一般通過遞迴的方式來完成。
- Context(環境類):環境類又稱為上下文類,它用於儲存直譯器之外的一些全域性資訊,通常它臨時儲存了需要解釋的語句。
在直譯器模式中,每一種終結符和非終結符都有一個具體類與之對應,正因為使用類來表示每一條文法規則,所以系統將具有較好的靈活性和可擴充套件性。對於所有的終結符和非終結符,首先需要抽象出一個公共父類,即抽象表示式類,其典型程式碼如下:
class AbstractExpression {
public:
virtual void interpret(Context ctx) = 0;
};
終結符表示式和非終結符表示式類都是抽象表示式類的子類。對於終結符表示式,其程式碼很簡單,主要是對終結符元素的處理,其典型程式碼如下:
class TerminalExpression : public AbstractExpression {
public:
void interpret(Context ctx) {
// 終結符表示式的解釋操作
}
};
對於非終結符表示式,其程式碼相對比較複雜,因為可以通過非終結符將表示式組合成更加複雜的結構,對於包含兩個操作的元素的非終結符表示式類,其典型程式碼如下:
class NoterminalExpression : public AbstractExpression {
public:
NoterminalExpression(AbstractExpression* left, AbstractExpression* right) {
_left = left;
_right = right;
}
void interpret(Context ctx) {
// 遞迴呼叫每一個組成部分的 interpret() 方法
// 在遞迴呼叫時指定組成部分的連線方式,即非終結符的功能
}
private:
AbstractExpression* _left;
AbstractExpression* _right;
};
除了上述用於表示表示式的類以外,通常在直譯器模式中還提供了一個環境類 Context,用於儲存一些全域性資訊。在 Context 中可以包含一個 std::map
或 std::vector
等型別的集合物件來儲存一系列公共資訊,例如變數名與值的對映關係(key/value)等,用於在進行具體的解釋操作時從中獲取相關資訊。其典型程式碼片段如下:
class Context {
public:
void assign(const string& key, const string& value) {
// 往環境類中設值
}
string lookup(const string& key) {
// 獲取儲存在環境類中的值
}
private:
map<string, string> hash;
};
當系統無須提供全域性公共資訊時可以省略環境類,也可根據實際情況決定是否需要環境類。
總結
直譯器模式為自定義語言的設計和實現提供了一種解決方案,它用於定義一組文法規則並通過這組文法規則來解釋語言中的句子。雖然直譯器模式的使用頻率不是特別高,但是它在正則表示式、XML 文件解釋等領域還是得到了廣泛使用。
優點
- 易於改變和擴充套件文法。由於在直譯器模式中使用類來表示語言的文法規則,因此可以通過繼承等機制來改變或擴充套件文法。
- 每一條文法規則都可以表示為一個類,因此可以方便地實現一個簡單的語言。
- 實現文法較為容易。在抽象語法樹中每一個表示式節點類的實現方式都是相似的,這些類的程式碼編寫都不會特別複雜,還可以通過一些工具自動生成節點類程式碼。
- 增加新的解釋表示式較為方便。如果使用者需要增加新的解釋表示式只需要對應增加一個新的終結符表示式或非終結符表示式類,原有表示式類程式碼無須修改,符合開閉原則。
缺點
- 對於複雜文法難以維護。在直譯器模式中,每一條規則至少需要定義一個類,因此如果一個語言包含太多文法規則,類的個數將會急劇增加,導致系統難以管理和維護,此時可以考慮使用語法分析程式等方式來取代直譯器模式。
- 執行效率較低。由於在直譯器模式中使用了大量的迴圈和遞迴呼叫,因此在解釋較為複雜的句子時其速度很慢,而且程式碼的除錯過程也比較麻煩。
適用場景
- 可以將一個需要解釋執行的語言中的句子表示為一個抽象語法樹。
- 一些重複出現的問題可以用一種簡單的語言來進行表達。
- 一個語言的文法較為簡單。
- 執行效率不是關鍵問題。
高效的直譯器通常不是通過直接解釋抽象語法樹來實現的,而是需要將它們轉換成其他形式,使用直譯器模式的執行效率並不高。