設計模式:解釋器(Interpreter)模式
一、前言
這是我們23個設計模式中最后一個設計模式了,大家或許也沒想到吧,竟然是編譯原理上的編譯器,這樣說可能不對,因為編譯器分為幾個部分組成呢,比如詞法分析器、語法分析器、語義分析器、中間代碼優化器以及最終的最終代碼生成器。而這個解釋器其實就是完成了對語法的解析,將一個個的詞組解釋成了一個個語法范疇,之后拿來使用而已。
為什么會有這個解釋器模式呢,我想這是從編譯原理中受到啟發的,使用了這樣的一個解釋器可以在Java語言之上在定義一層語言,這種語言通過Java編寫的解釋器可以放到Java環境中去執行,這樣如果用戶的需求發生變化,比如打算做其他事情的時候,只用在自己定義的新的語言上進行修改,對於Java編寫的代碼不需要進行任何的修改就能在Java環境中運行,這是非常有用的。這就好像,雖然不管怎么編譯,最終由中間代碼生成最終代碼(機器碼)是依賴於相應的機器的。但是編譯器卻能理解高級語言和低級語言,無論高級語言的程序是怎么樣編寫的,編譯器的代碼是不用修改的,而解釋器模式就是想做一個建立在Java和我們自定義語言之間的編譯器。
二、代碼
本程序使用自頂向下文法來解析源程序:
首先是文法的定義:
1 <program> -> program <Command List> 2 3 <Command List> -> <Command>* end 4 5 <Command> -> <Repeat Command> | <Primitive Command> 6 7 <Repeat Command> -> repeat <number> <Command List> 8 9 <Primitive Command> -> go | right | left
由此可以生成一顆語法樹。
然后使用自頂向下文法生成這樣的語法樹,自頂向下文法從根節點開始,不斷的向下解析,遇到一個語法范疇就嘗試着自己的定義去解析,直至解析到相應的程序,這里要注意二義性問題,不能嘗試兩種解析方式都能對源程序解析成功;在實現的時候將一個語法范疇定義為一個類,然后不斷地遞歸的去解析,直至到了葉子節點,將所有的單詞解析完畢。
Node抽象類:
1 package zyr.dp.interpreter; 2 3 public abstract class Node { 4 public abstract void parse(Context context) throws ParseException; 5 }
ProgramNode:起始節點 <program> -> program <Command List>
1 package zyr.dp.interpreter; 2 3 public class ProgramNode extends Node { 4 5 private Node commandListNode; 6 public void parse(Context context) throws ParseException { 7 context.skipToken("program"); 8 commandListNode=new CommandListNode(); 9 commandListNode.parse(context); 10 } 11 public String toString(){ 12 return "[program "+commandListNode+"]"; 13 } 14 15 }
CommandListNode類: <Command List> -> <Command>* end
1 package zyr.dp.interpreter; 2 3 import java.util.ArrayList; 4 5 public class CommandListNode extends Node { 6 7 private ArrayList list=new ArrayList(); 8 9 public void parse(Context context) throws ParseException { 10 while(true){ 11 if(context.getCurrentToken()==null){ 12 throw new ParseException("錯誤!!!"+"當前字符為空"); 13 }else if(context.getCurrentToken().equals("end")){ 14 context.skipToken("end"); 15 break; 16 }else{ 17 Node commandNode=new CommandNode(); 18 commandNode.parse(context); 19 list.add(commandNode); 20 } 21 } 22 } 23 24 public String toString(){ 25 return list.toString(); 26 } 27 28 }
CommandNode類: <Command> -> <Repeat Command> | <Primitive Command>
1 package zyr.dp.interpreter; 2 3 public class CommandNode extends Node { 4 5 private Node node; 6 public void parse(Context context) throws ParseException { 7 if(context.getCurrentToken().equals("repeat")){ 8 node = new RepeatCommandNode(); 9 node.parse(context); 10 }else{ 11 node = new PrimitiveCommandNode(); 12 node.parse(context); 13 } 14 } 15 16 public String toString(){ 17 return node.toString(); 18 } 19 20 }
RepeatCommandNode 類:<Repeat Command> -> repeat <number> <Command List>
1 package zyr.dp.interpreter; 2 3 public class RepeatCommandNode extends Node { 4 5 private int number; 6 private Node commandListNode; 7 public void parse(Context context) throws ParseException { 8 context.skipToken("repeat"); 9 number=context.currentNumber(); 10 context.nextToken(); 11 commandListNode=new CommandListNode(); 12 commandListNode.parse(context); 13 } 14 public String toString(){ 15 return "[repeat "+number+" "+commandListNode+"]"; 16 } 17 18 }
PrimitiveCommandNode類:<Primitive Command> -> go | right | left
1 package zyr.dp.interpreter; 2 3 public class PrimitiveCommandNode extends Node { 4 5 String name; 6 public void parse(Context context) throws ParseException { 7 name=context.getCurrentToken(); 8 context.skipToken(name); 9 if(!name.equals("go") && !name.equals("left") && !name.equals("right") ){ 10 throw new ParseException("錯誤!!!非法字符:"+name); 11 } 12 } 13 14 public String toString(){ 15 return name; 16 } 17 }
ParseException類:
1 package zyr.dp.interpreter; 2 3 public class ParseException extends Exception { 4 5 private static final long serialVersionUID = 3996163326179443976L; 6 7 public ParseException(String word){ 8 super(word); 9 } 10 11 }
Context 類,承載了詞法分析的職責,為上面的語法樹提供單詞,遍歷程序,當然沒考慮到程序的注釋等處理信息。
1 package zyr.dp.interpreter; 2 3 import java.util.StringTokenizer; 4 5 public class Context { 6 7 private StringTokenizer tokenizer ; 8 private String currentToken; 9 public Context(String token){ 10 tokenizer=new StringTokenizer(token); 11 nextToken(); 12 } 13 public String nextToken() { 14 if(tokenizer.hasMoreTokens()){ 15 currentToken=tokenizer.nextToken(); 16 }else{ 17 currentToken=null; 18 } 19 return currentToken; 20 } 21 public String getCurrentToken(){ 22 return currentToken; 23 } 24 public void skipToken(String token) throws ParseException{ 25 if(!token.equals(currentToken)){ 26 throw new ParseException("錯誤!!!"+"期待"+currentToken+"但是卻得到"+token); 27 } 28 nextToken(); 29 } 30 public int currentNumber() throws ParseException{ 31 int num=0; 32 try{ 33 num=Integer.parseInt(currentToken); 34 }catch(NumberFormatException e){ 35 throw new ParseException(e.toString()); 36 } 37 return num; 38 } 39 40 }
Main類,讀取用戶編寫的程序並且執行詞法分析和語法分析。這里的詞法分析就是簡單的遍歷程序,語法分析采用的自頂向下的語法分析,對於上下文無關文法可以檢測到語法錯誤,並且能生成語法范疇,但是這些語法范疇是我們能看到的,不是及其最終可以拿來去處理的,真正要編寫編譯系統,最好使用,自下而上的算符優先文法等方式來分析。
1 package zyr.dp.text; 2 3 import java.io.BufferedReader; 4 import java.io.FileNotFoundException; 5 import java.io.FileReader; 6 import java.io.IOException; 7 8 import zyr.dp.interpreter.*; 9 10 public class Main { 11 12 public static void main(String[] args) { 13 14 try { 15 BufferedReader reader = new BufferedReader(new FileReader("program.txt")); 16 String line=null; 17 while((line=reader.readLine())!=null){ 18 System.out.println("源程序為:"+line); 19 System.out.println("自頂向下解析為:"); 20 Node node=new ProgramNode(); 21 node.parse(new Context(line)); 22 System.out.println(node); 23 } 24 } catch (FileNotFoundException e) { 25 e.printStackTrace(); 26 } catch (IOException e) { 27 e.printStackTrace(); 28 } catch (ParseException e) { 29 e.printStackTrace(); 30 } 31 32 } 33 34 }
運行結果:
源程序:
在這里我專門寫錯了一個源程序:
1 program end 2 program go end 3 program go right go right go right go right go right go right end 4 program repeat 4 go right end end 5 program repeat 4 repeat 3 go right end go right end end 6 program repeat 4 go right end
可以看到編譯器檢測到了語法錯誤,對於語法正確的,也形式化的生成了自己的分析結果,使用[ ]括起來的就是語法范疇了,形成層次遞歸嵌套結構。
三、總結
最后的設計模式是解釋器模式,在Java這種高級語言之上再次定義一種語言的編譯器,然后在不改動這個編譯器的條件下,也就是不改變Java代碼就能夠隨意的書寫更高級的代碼,然后執行。在這種模式之下java程序都不用修改,只用修改上面的文本文件就可以了,非常的方便,適合於結構已經固定,但是可以隨意修改功能的場合。