Java設計模式-17、直譯器模式-自定義語言的實現

植樹chen 發表於 2020-11-22

第17章:直譯器模式-自定義語言的實現

定義:

直譯器模式(Interpreter Pattern):定義一個語言的文法,並且建立一個直譯器來解釋該語言中的句子,這裡的“語言”是指使用規定格式和語法的程式碼。

解釋表示式,再計算

image-20201112193743845

結構:

image-20201112201641245

程式碼實現:

//抽象表示式
abstract class AbstractExpression {
    public abstract void interpret(Context ctx);
}
//終結符表示式
class TerminalExpression extends AbstractExpression {
    public void interpret(Context ctx) {
        //終結符表示式的解釋操作
    }
}
//非終結符表示式:包含兩個操作元素
class NonterminalExpression extends AbstractExpression {
    private AbstractExpression left;
    private AbstractExpression right;

    public NonterminalExpression(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }

    public void interpret(Context ctx) {
        //遞迴呼叫每一個組成部分的interpret()方法
        //在遞迴呼叫時指定組成部分的連線方式,即非終結符的功能
    }
}
//環境類,儲存全域性資訊
class Context {
    private HashMap map = new HashMap();

    public void assign(String key, String value) {
        //往環境類中設值
    }

    public String lookup(String key) {
        //獲取儲存在環境類中的值
        return null;
    }
}

應用例項:

機器人控制程式,按照指令設定進行移動

文法規則

expression ::= direction action distance | composite //表示式
composite ::= expression 'and' expression //複合表示式
direction ::= 'up' | 'down' | 'left' | 'right' //移動方向
action ::= 'move' | 'run' //移動方式
distance ::= an integer //移動距離

1 + 2 + 3 – 4 + 1

expression ::= value | operation
operation ::= expression '+' expression | expression '-' expression
value ::= an integer //一個整數值

語法樹

image-20201112201523048

使用直譯器模式實現

image-20201112202433601

image-20201112202535199

Context作用

輸入

LOOP 2 
	PRINT楊過 SPACE SPACE PRINT 小龍女
BREAK END 
	PRINT郭靖 SPACE SPACE PRINT 黃蓉

文法規則:

expression ::= command* //表示式,一個表示式包含多條命令
command ::= loop | primitive //語句命令
loop ::= 'loopnumber' expression 'end' //迴圈命令,其中number為自然數
primitive ::= 'printstring' | 'space' | 'break' //基本命令,其中string為字串

image-20201112205344914

//環境類:用於儲存和操作需要解釋的語句,在本例項中每一個需要解釋的單詞可以稱為一個動作標記(Action
class Context {
    private StringTokenizer tokenizer; //StringTokenizer類,用於將字串分解為更小
    private String currentToken; //當前字串標記

    public Context(String text) {
        tokenizer = new StringTokenizer(text); //通過傳入的指令字串建立StringTokenizer
        nextToken();
    }

    //返回下一個標記
    public String nextToken() {
        if (tokenizer.hasMoreTokens()) {
            currentToken = tokenizer.nextToken();
        } else {
            currentToken = null;
        }
        return currentToken;
    }

    //返回當前的標記
    public String currentToken() {
        return currentToken;
    }

    //跳過一個標記
    public void skipToken(String token) {
        if (!token.equals(currentToken)) {
            System.err.println("錯誤提示:" + currentToken + "解釋錯誤!");
        }
        nextToken();
    }

    //如果當前的標記是一個數字,則返回對應的數值
    public int currentNumber() {
        int number = 0;
        try {
            number = Integer.parseInt(currentToken); //將字串轉換為整數
        } catch (NumberFormatException e) {
            System.err.println("錯誤提示:" + e);
        }
        return number;
    }
}
//抽象節點類:抽象表示式
abstract class Node {
    public abstract void interpret(Context text); //宣告一個方法用於解釋語句

    public abstract void execute(); //宣告一個方法用於執行標記對應的命令
}
//表示式節點類:非終結符表示式
class ExpressionNode extends Node {
    private ArrayList<Node> list = new ArrayList<Node>(); //定義一個集合用於儲存多個標記

    public void interpret(Context context) {
        //迴圈處理Context中的標記
        while (true) {
            //如果已經沒有任何標記,則退出解釋
            if (context.currentToken() == null) {
                break;
            }
            //如果標記為END,則不解釋END並結束本次解釋過程,可以繼續之後的解釋
            else if (context.currentToken().equals("END")) {
                context.skipToken("END");
                break;
            }
            //如果為其他標記,則解釋標記並將其加入命令集合
            else {
                Node commandNode = new CommandNode();
                commandNode.interpret(context);
                list.add(commandNode);
            }
        }
    }

    //迴圈執行命令集合中的每一條命令
    public void execute() {
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            ((Node) iterator.next()).execute();
        }
    }
}
//語句命令節點類:非終結符表示式
class CommandNode extends Node {
    private Node node;

    public void interpret(Context context) {
        //處理LOOP迴圈命令
        if (context.currentToken().equals("LOOP")) {
            node = new LoopCommandNode();
            node.interpret(context);
        }
        //處理其他基本命令
        else {
            node = new PrimitiveCommandNode();
            node.interpret(context);
        }
    }

    public void execute() {
        node.execute();
    }
}
//迴圈命令節點類:非終結符表示式
class LoopCommandNode extends Node {
    private int number; //迴圈次數
    private Node commandNode; //迴圈語句中的表示式

    //解釋迴圈命令
    public void interpret(Context context) {
        context.skipToken("LOOP");
        number = context.currentNumber();
        context.nextToken();
        commandNode = new ExpressionNode(); //迴圈語句中的表示式
        commandNode.interpret(context);
    }

    public void execute() {
        for (int i = 0; i < number; i++)
            commandNode.execute();
    }
}
//基本命令節點類:終結符表示式
class PrimitiveCommandNode extends Node {
    private String name;
    private String text;

    //解釋基本命令
    public void interpret(Context context) {
        name = context.currentToken();
        context.skipToken(name);
        if (!name.equals("PRINT") && !name.equals("BREAK") && !name.equals("SPACE")) {
            System.err.println("非法命令!");
        }
        if (name.equals("PRINT")) {
            text = context.currentToken();
            context.nextToken();
        }
    }

    public void execute() {
        if (name.equals("PRINT"))
            System.out.print(text);
        else if (name.equals("SPACE"))
            System.out.print(" ");
        else if (name.equals("BREAK"))
            System.out.println();
    }
}
class Client {
    public static void main(String[] args) {
        String text = "LOOP 2 PRINT 楊過 SPACE SPACE PRINT 小龍女 BREAK END PRINT 郭靖 SPACE SPACE PRINT 黃蓉";
        Context context = new Context(text);
        Node node = new ExpressionNode();
        node.interpret(context);
        node.execute();
    }
}

image-20201112211825221

優點:

  1. 易於改變和擴充套件文法。由於在直譯器模式中使用類來表示語言的文法規則,因此可以通過繼承等機制來改變或擴充套件文法。
  2. 每一條文法規則都可以表示為一個類,因此可以方便地實現一個簡單的語言。
  3. 實現文法較為容易。在抽象語法樹中每一個表示式節點類的實現方式都是相似的,這些類的程式碼編寫都不會特別複雜,還可以通過一些工具自動生成節點類程式碼。
  4. 增加新的解釋表示式較為方便。如果使用者需要增加新的解釋表示式只需要對應增加一個新的終結符表示式或非終結符表示式類,原有表示式類程式碼無須修改,符合“開閉原則”。

缺點:

  1. 對於複雜文法難以維護。在直譯器模式中,每一條規則至少需要定義一個類,因此如果一個語言包含太多文法規則,類的個數將會急劇增加,導致系統難以管理和維護,此時可以考慮使用語法分析程式等方式來取代直譯器模式。
  2. 執行效率較低。由於在直譯器模式中使用了大量的迴圈和遞迴呼叫,因此在解釋較為複雜的句子時其速度很慢,而且程式碼的除錯過程也比較麻煩。

適用場景:

  1. 可以將一個需要解釋執行的語言中的句子表示為一個抽象語法樹。
  2. 一些重複出現的問題可以用一種簡單的語言來進行表達。
  3. 一個語言的文法較為簡單。
  4. 執行效率不是關鍵問題。