自定義語言的實現——直譯器模式(五)

Liuwei-Sunny發表於2012-07-04

18.5 再談Context的作用

       在直譯器模式中,環境類Context用於儲存直譯器之外的一些全域性資訊,它通常作為引數被傳遞到所有表示式的解釋方法interpret()中,可以在Context物件中儲存和訪問表示式直譯器的狀態,向表示式直譯器提供一些全域性的、公共的資料,此外還可以在Context中增加一些所有表示式直譯器都共有的功能,減輕直譯器的職責。

       在上面的機器人控制程式例項中,我們省略了環境類角色,下面再通過一個簡單例項來說明環境類的用途:

       Sunny軟體公司開發了一套簡單的基於字元介面的格式化指令,可以根據輸入的指令在字元介面中輸出一些格式化內容,例如輸入“LOOP 2 PRINT楊過 SPACE SPACE PRINT 小龍女 BREAK END PRINT郭靖 SPACE SPACE PRINT 黃蓉”,將輸出如下結果:

楊過     小龍女

楊過     小龍女

郭靖     黃蓉

       其中關鍵詞LOOP表示“迴圈”,後面的數字表示迴圈次數;PRINT表示“列印”,後面的字串表示列印的內容;SPACE表示“空格”;BREAK表示“換行”;END表示“迴圈結束”。每一個關鍵詞對應一條命令,計算機程式將根據關鍵詞執行相應的處理操作。

       現使用直譯器模式設計並實現該格式化指令的解釋,對指令進行分析並呼叫相應的操作執行指令中每一條命令。

       Sunny軟體公司開發人員通過分析,根據該格式化指令中句子的組成,定義瞭如下文法規則:

expression ::= command* //表示式,一個表示式包含多條命令

command ::= loop | primitive //語句命令

loop ::= 'loopnumber' expression  'end' //迴圈命令,其中number為自然數

primitive ::= 'printstring'  | 'space' | 'break' //基本命令,其中string為字串

       根據以上文法規則,通過進一步分析,繪製如圖18-6所示結構圖:

18-6    格式化指令結構圖

       在圖18-6中,Context充當環境角色,Node充當抽象表示式角色,ExpressionNodeCommandNodeLoopCommandNode充當非終結符表示式角色,PrimitiveCommandNode充當終結符表示式角色。完整程式碼如下所示:

import java.util.*;

//環境類:用於儲存和操作需要解釋的語句,在本例項中每一個需要解釋的單詞可以稱為一個動作標記(Action Token)或命令
class Context {
	private StringTokenizer tokenizer; //StringTokenizer類,用於將字串分解為更小的字串標記(Token),預設情況下以空格作為分隔符
	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();
	}
}

       在本例項程式碼中,環境類Context類似一個工具類,它提供了用於處理指令的方法,如nextToken()currentToken()skipToken()等,同時它儲存了需要解釋的指令並記錄了每一次解釋的當前標記(Token),而具體的解釋過程交給表示式直譯器類來處理。我們還可以將各種直譯器類包含的公共方法移至環境類中,更好地實現這些方法的重用和擴充套件。

       針對本例項程式碼,我們編寫如下客戶端測試程式碼:

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();
	}
}

       編譯並執行程式,輸出結果如下:

楊過     小龍女

楊過     小龍女

郭靖     黃蓉

 

 

思考

預測指令“LOOP    2 LOOP 2 PRINT楊過 SPACE SPACE    PRINT 小龍女 BREAK END PRINT   郭靖 SPACE SPACE PRINT 黃蓉    BREAK END”的輸出結果。

【作者:劉偉    http://blog.csdn.net/lovelion

相關文章