自定义语言的实现——解释器模式(五)

18.5 再谈Context的作用

在解释器模式中,环境类Context用于存储解释器之外的一些全局信息,它通常作为参数被传递到所有表达式的解释方法interpret()中,可以在Context对象中存储和访问表达式解释器的状态,向表达式解释器提供一些全局的、公共的数据,此外还可以在Context中增加一些所有表达式解释器都共有的功能,减轻解释器的职责。

在上面的机器人控制程序实例中,我们省略了环境类角色,下面再通过一个简单实例来说明环境类的用途:

Sunny软件公司开发了一套简单的基于字符界面的格式化指令,可以根据输入的指令在字符界面中输出一些格式化内容,例如输入“LOOP 2 PRINT杨过 SPACE SPACE PRINT 小龙女 BREAK END PRINT郭靖 SPACE SPACE PRINT 黄蓉”,将输出如下结果:

  1. 杨过 小龙女
  2. 杨过 小龙女
  3. 郭靖 黄蓉

其中关键词LOOP表示“循环”,后面的数字表示循环次数;PRINT表示“打印”,后面的字符串表示打印的内容;SPACE表示“空格”;BREAK表示“换行”;END表示“循环结束”。每一个关键词对应一条命令,计算机程序将根据关键词执行相应的处理操作。

现使用解释器模式设计并实现该格式化指令的解释,对指令进行分析并调用相应的操作执行指令中每一条命令。

Sunny软件公司开发人员通过分析,根据该格式化指令中句子的组成,定义了如下文法规则:

  1. expression ::= command* //表达式,一个表达式包含多条命令
  2. command ::= loop | primitive //语句命令
  3. loop ::= 'loopnumber' expression 'end' //循环命令,其中number为自然数
  4. primitive ::= 'printstring' | 'space' | 'break' //基本命令,其中string为字符串

根据以上文法规则,通过进一步分析,绘制如图18-6所示结构图:

自定义语言的实现——解释器模式(五) - 图1

图18-6 格式化指令结构图

在图18-6中,Context充当环境角色,Node充当抽象表达式角色,ExpressionNode、CommandNode和LoopCommandNode充当非终结符表达式角色,PrimitiveCommandNode充当终结符表达式角色。完整代码如下所示:

  1. import java.util.*;
  2. //环境类:用于存储和操作需要解释的语句,在本实例中每一个需要解释的单词可以称为一个动作标记(Action Token)或命令
  3. class Context {
  4. private StringTokenizer tokenizer; //StringTokenizer类,用于将字符串分解为更小的字符串标记(Token),默认情况下以空格作为分隔符
  5. private String currentToken; //当前字符串标记
  6. public Context(String text) {
  7. tokenizer = new StringTokenizer(text); //通过传入的指令字符串创建StringTokenizer对象
  8. nextToken();
  9. }
  10. //返回下一个标记
  11. public String nextToken() {
  12. if (tokenizer.hasMoreTokens()) {
  13. currentToken = tokenizer.nextToken();
  14. }
  15. else {
  16. currentToken = null;
  17. }
  18. return currentToken;
  19. }
  20. //返回当前的标记
  21. public String currentToken() {
  22. return currentToken;
  23. }
  24. //跳过一个标记
  25. public void skipToken(String token) {
  26. if (!token.equals(currentToken)) {
  27. System.err.println("错误提示:" + currentToken + "解释错误!");
  28. }
  29. nextToken();
  30. }
  31. //如果当前的标记是一个数字,则返回对应的数值
  32. public int currentNumber() {
  33. int number = 0;
  34. try{
  35. number = Integer.parseInt(currentToken); //将字符串转换为整数
  36. }
  37. catch(NumberFormatException e) {
  38. System.err.println("错误提示:" + e);
  39. }
  40. return number;
  41. }
  42. }
  43. //抽象节点类:抽象表达式
  44. abstract class Node {
  45. public abstract void interpret(Context text); //声明一个方法用于解释语句
  46. public abstract void execute(); //声明一个方法用于执行标记对应的命令
  47. }
  48. //表达式节点类:非终结符表达式
  49. class ExpressionNode extends Node {
  50. private ArrayList<Node> list = new ArrayList<Node>(); //定义一个集合用于存储多条命令
  51. public void interpret(Context context) {
  52. //循环处理Context中的标记
  53. while (true){
  54. //如果已经没有任何标记,则退出解释
  55. if (context.currentToken() == null) {
  56. break;
  57. }
  58. //如果标记为END,则不解释END并结束本次解释过程,可以继续之后的解释
  59. else if (context.currentToken().equals("END")) {
  60. context.skipToken("END");
  61. break;
  62. }
  63. //如果为其他标记,则解释标记并将其加入命令集合
  64. else {
  65. Node commandNode = new CommandNode();
  66. commandNode.interpret(context);
  67. list.add(commandNode);
  68. }
  69. }
  70. }
  71. //循环执行命令集合中的每一条命令
  72. public void execute() {
  73. Iterator iterator = list.iterator();
  74. while (iterator.hasNext()){
  75. ((Node)iterator.next()).execute();
  76. }
  77. }
  78. }
  79. //语句命令节点类:非终结符表达式
  80. class CommandNode extends Node {
  81. private Node node;
  82. public void interpret(Context context) {
  83. //处理LOOP循环命令
  84. if (context.currentToken().equals("LOOP")) {
  85. node = new LoopCommandNode();
  86. node.interpret(context);
  87. }
  88. //处理其他基本命令
  89. else {
  90. node = new PrimitiveCommandNode();
  91. node.interpret(context);
  92. }
  93. }
  94. public void execute() {
  95. node.execute();
  96. }
  97. }
  98. //循环命令节点类:非终结符表达式
  99. class LoopCommandNode extends Node {
  100. private int number; //循环次数
  101. private Node commandNode; //循环语句中的表达式
  102. //解释循环命令
  103. public void interpret(Context context) {
  104. context.skipToken("LOOP");
  105. number = context.currentNumber();
  106. context.nextToken();
  107. commandNode = new ExpressionNode(); //循环语句中的表达式
  108. commandNode.interpret(context);
  109. }
  110. public void execute() {
  111. for (int i=0;i<number;i++)
  112. commandNode.execute();
  113. }
  114. }
  115. //基本命令节点类:终结符表达式
  116. class PrimitiveCommandNode extends Node {
  117. private String name;
  118. private String text;
  119. //解释基本命令
  120. public void interpret(Context context) {
  121. name = context.currentToken();
  122. context.skipToken(name);
  123. if (!name.equals("PRINT") && !name.equals("BREAK") && !name.equals ("SPACE")){
  124. System.err.println("非法命令!");
  125. }
  126. if (name.equals("PRINT")){
  127. text = context.currentToken();
  128. context.nextToken();
  129. }
  130. }
  131. public void execute(){
  132. if (name.equals("PRINT"))
  133. System.out.print(text);
  134. else if (name.equals("SPACE"))
  135. System.out.print(" ");
  136. else if (name.equals("BREAK"))
  137. System.out.println();
  138. }
  139. }

在本实例代码中,环境类Context类似一个工具类,它提供了用于处理指令的方法,如nextToken()、currentToken()、skipToken()等,同时它存储了需要解释的指令并记录了每一次解释的当前标记(Token),而具体的解释过程交给表达式解释器类来处理。我们还可以将各种解释器类包含的公共方法移至环境类中,更好地实现这些方法的重用和扩展。

针对本实例代码,我们编写如下客户端测试代码:

  1. class Client{
  2. public static void main(String[] args){
  3. String text = "LOOP 2 PRINT 杨过 SPACE SPACE PRINT 小龙女 BREAK END PRINT 郭靖 SPACE SPACE PRINT 黄蓉";
  4. Context context = new Context(text);
  5. Node node = new ExpressionNode();
  6. node.interpret(context);
  7. node.execute();
  8. }
  9. }

编译并运行程序,输出结果如下:

  1. 杨过 小龙女
  2. 杨过 小龙女
  3. 郭靖 黄蓉

思考

预测指令“LOOP 2 LOOP 2 PRINT杨过 SPACE SPACE PRINT 小龙女 BREAK END PRINT 郭靖 SPACE SPACE PRINT 黄蓉 BREAK END”的输出结果。