给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释文法中的句子

一、什么是解释器模式?

 解释器这个名词想必大家都不会陌生,比如编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。诸如此类的例子也有很多,比如编译器、正则表达式等等。

如正则表达式,它就是解释器模型的一种应用,解释器为正则表达式定义了一个文法,如何表示一个特定的正则表达式,以及如何解释这个正则表达式。

如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子,这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。

UML结构图如下:

解释器模式 Interpreter ——  让你拥有最终解释权!_ide

重点就是给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

  • AbstractExpression:抽象解释器
    声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点所共享。
  • TerminalExpression:终结解释器
    实现与文法中的终结符相关联的解释操作。一个句子中的每一个终结符需要该类的一个实例。
  • NonterminalExpression:非终结解释器
    对文法中的规则的解释操作。
  • Context:环境角色
    包含解释器之外的一些全局信息。
  • Client:客户端
    构建表示该语法定义的语言中一个特定的句子的抽象语法树,并调用解释操作。

代码框架如下:

public abstract class Expression {
public abstract String interpret(Context context);
}
public class TerminalExpression extends Expression {

@Override
public String interpret(Context context) {
return "终端解释器";
}
}
public class NonTerminalExpression extends Expression {

@Override
public String interpret(Context context) {
return "非终端解释器";
}
}
public class Context {
private Map<String, Expression> map;

public String getValue(String key){
return map.get(key).interpret(this);
}
}
public class Client {
public static void main(String[] args) {
Context context = new Context();
List<Expression> list = new ArrayList<>();

list.add(new TerminalExpression());
list.add(new NonTerminalExpression());
list.add(new TerminalExpression());
list.add(new TerminalExpression());

for (Expression abstractExpression : list) {
Console.log(abstractExpression.interpret(context));
}
}
}
终端解释器
非终端解释器
终端解释器
终端解释器

其中list为一个语法容器,容纳一个具体的表达式。通常Client是一个封装类,封装的结果就是传递进来一个规范语法文件,解析器分析后产生结果并返回,避免了调用者与语法分析器的耦合关系。

二、代码实例

 通过解释器模式来实现四则运算,如计算a+b的值。UML图如下:

解释器模式 Interpreter ——  让你拥有最终解释权!_ide_02

public abstract class Expression {
// 解析公式和数值,key是公式中的参数,value是具体的数值
public abstract int interpreter(HashMap<String, Integer> var);
}

 变量解析器:

@AllArgsConstructor
public class VarAnalysis extends Expression {
private String key;

@Override
public int interpreter(HashMap<String, Integer> var) {
return var.get(this.key);
}
}

 抽象运算符号解析器:

@AllArgsConstructor
public class SymbolAnalysis extends Expression {
protected Expression left;
protected Expression right;

@Override
public int interpreter(HashMap<String, Integer> var) {
return 0;
}
}

加法解析器: 

public class AddAnalysis extends SymbolAnalysis {
public AddAnalysis(Expression left, Expression right) {
super(left, right);
}

@Override
public int interpreter(HashMap<String, Integer> var) {
return super.left.interpreter(var) + super.right.interpreter(var);
}
}

 减法解析器:

public class SubAnalysis extends SymbolAnalysis {

public SubAnalysis(Expression left, Expression right) {
super(left, right);
}

public int interpreter(HashMap<String, Integer> var) {
return super.left.interpreter(var) - super.right.interpreter(var);
}
}

 解析器封装类:

public class Calculator {

//定义表达式
private Expression expression;

//构造函数传参,并解析
public Calculator(String expStr) {
//安排运算先后顺序
Stack<Expression> stack = new Stack<>();
//表达式拆分为字符数组
char[] charArray = expStr.toCharArray();

Expression left = null;
Expression right = null;
for(int i=0; i<charArray.length; i++) {
switch (charArray[i]) {
case '+': //加法
left = stack.pop();
right = new VarAnalysis(String.valueOf(charArray[++i]));
stack.push(new AddAnalysis(left, right));
break;
case '-': //减法
left = stack.pop();
right = new VarAnalysis(String.valueOf(charArray[++i]));
stack.push(new SubAnalysis(left, right));
break;
default: //公式中的变量
stack.push(new VarAnalysis(String.valueOf(charArray[i])));
break;
}
}
this.expression = stack.pop();
}

//计算
public int run(HashMap<String, Integer> var) {
return this.expression.interpreter(var);
}
}
public class Client {
public static void main(String[] args) throws IOException {
String expStr = getExpStr();
HashMap<String, Integer> var = getValue(expStr);
Calculator calculator = new Calculator(expStr);
System.out.println("运算结果:" + expStr + "=" + calculator.run(var));
}

//获得表达式
public static String getExpStr() throws IOException {
System.out.print("请输入表达式:");
return (new BufferedReader(new InputStreamReader(System.in))).readLine();
}

//获得值映射
public static HashMap<String, Integer> getValue(String expStr) throws IOException {
HashMap<String, Integer> map = new HashMap<>();

for(char ch : expStr.toCharArray()) {
if(ch != '+' && ch != '-' ) {
if(! map.containsKey(String.valueOf(ch))) {
System.out.print("请输入" + String.valueOf(ch) + "的值:");
String in = (new BufferedReader(new InputStreamReader(System.in))).readLine();
map.put(String.valueOf(ch), Integer.valueOf(in));
}
}
}
return map;
}
}
请输入表达式:a-b+c
请输入a的值:3
请输入b的值:2
请输入c的值:1
运算结果:a-b+c=2

Calculator构造函数传参,并解析封装。这里根据栈的“先进后出”来安排运算的先后顺序(主要用在乘除法,这里只写了加减法比较简单)。以加法为例,Calculator构造函数接收一个表达式,然后把表达式转换为char数组,并判断运算符号,如果是‘+’则进行加法运算,把左边的数(left变量)和右边的数(right变量)加起来即可。

例如a+b-c这个表达式,根据for循环,首先被压入栈中的是a元素生成的VarExpression对象,然后判断到加号时,把a元素的对象从栈中pop出来,与右边的数组b进行相加,而b是通过当前的数组游标下移一个单元格得来的(为了防止该元素被再次遍历,通过++i的方式跳过下一遍历)。减法运算同理。

三、适用场景

优点:

  1. 可扩展性比较好,灵活。
  2. 增加了新的解释表达式的方式。
  3. 易于实现简单文法。


缺点:

  1. 可利用场景比较少。
  2. 对于复杂的文法比较难维护。
  3. 解释器模式会引起类膨胀。
  4. 解释器模式采用递归调用方法。


使用场景: 

  1. 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
  2. 一些重复出现的问题可以用一种简单的语言来进行表达。
  3. 一个简单语法需要解释的场景。

总的来说,如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。