解释器模式——解析语法,分离实现

解释器模式用于定义特定语法的解析规则,通过终结符解释器和非终结符解释器实现。文章通过一个计算后缀表达式的例子,展示了如何创建数字、加法和乘法的解释器,并利用栈进行表达式解析和计算。此模式常用于解析简单语言或表达式,如计算器应用。
摘要由CSDN通过智能技术生成

  解释器模式主要用户解析某一种特性的语法,在日常的开发中使用较少。其目的是定义一个语言的文法,并且建立一个解释器来解释该语言中的句子,这里的 “语言” 是指使用规定格式和语法的代码。在实现思路上,解释器将统一的解释逻辑分成了两种类型的实现,一种是终结符号解释器,一种是非终结符号解释器,注意,这里说的是两类,不是两个,在同一类的解释器中,可以实现多个解释器实现类,具体要看需要解析的语法规则,在使用的时候可以联系组合模式,在嵌套的过程中,解析语法规则,达到能够复用的逻辑。

解释器模式结构图

解释器模式结构图

  • AbstractExpression:定义解释器的接口,约定解释器的解释操作。
  • TerminalExpression:终结符解释器,用来实现语法规则中和终结符相关的操作,里面不再包含其他的解释器。如果用组合模式来构建语法树,就相当于组合模式中的叶子节点,可以有多种终结符解释器。
  • NonterminalExpression:非终结符解释器,用来实现语法规则中非终结符相关的操作,通常一个解释器对应一个语法规则,可以包含其他的解释器。如果用组合模式来构建抽象的语法树,就相当于组合模式中的组合对象。同样的,也可以有多种非终结符解释器。
  • Context:上下文,一般是传递解释器需要的数据或者公共的功能。

用解释器计算后缀表达式示例

  后缀表达式是计算器实现中一种算法规则,关于其特点可以自行百度一下,将一个原始的计算表达式转化为后缀表达式后再进行求解,可以简化计算的算法。我们这里就用一个简单的示例来演示一个后缀表达式的解析过程,为了将注意力放在解释器本身的逻辑上,我们只实现加法和乘法的计算,计算顺序也是按照从左到右,不再考虑优先级问题。示例代码如下:

1. 定义一个统一的解释器接口,interpret()方法用来实现执行返回结果

public interface Expression {
    /**
     * 解释对象:
     * @return :
     */
    int interpret();

}

2. 实现对于数字的解释器,数字解释器其实就是返回数字本身,是一种终结符解释器,不会持有其他的解释器

/**
 * 数字非终结表达式
 */
public class NumberNonTerminalExpression implements Expression {
    private int number;

    public NumberNonTerminalExpression(int number) {
        this.number = number;
    }

    public NumberNonTerminalExpression(String number) {
        this.number = Integer.parseInt(number);
    }

    @Override
    public int interpret() {
        return this.number;
    }

}

3. 实现加法和乘法两种非终结符解释器

/**
 * 加法解释器
 */
public class AddTerminalExpression implements Expression {

    private Expression firstExpression;

    private Expression secondExpression;

    public AddTerminalExpression(Expression firstExpression, Expression secondExpression) {
        this.firstExpression = firstExpression;
        this.secondExpression = secondExpression;
    }

    @Override
    public int interpret() {
        return this.firstExpression.interpret() + this.secondExpression.interpret();
    }
}
/**
 * 乘法解释器
 */
public class MultiTerminalExpression implements Expression {

    private Expression firstExpression;

    private Expression secondExpression;

    public MultiTerminalExpression(Expression firstExpression, Expression secondExpression) {
        this.firstExpression = firstExpression;
        this.secondExpression = secondExpression;
    }

    @Override
    public int interpret() {
        return this.firstExpression.interpret() * this.secondExpression.interpret();
    }

}

4. 实现一个表达式解析类,用来实现对输入的后缀表达式的解析并转成解释器的表达式

/**
 * 表达式解析类
 */
public class ExpressionParser {
    /**
     * 存放数字非终结表达式的栈
     */
    private final Stack<Expression> numberStack = new Stack<>();

    /**
     * 解析并计算
     *
     * @param inputStr :输入表达式
     */
    public void parseAndCalculate(String inputStr) {
        System.out.println("input expression = " + inputStr);
        String[] strs = inputStr.split(" ");
        if (strs.length == 0) {
            throw new IllegalArgumentException("inputStr is empty!");
        }
        for (String str : strs) {
            str = str.trim();
            if (isNumber(str)) {
                //是数字直接入栈
                numberStack.push(new NumberNonTerminalExpression(str));
                continue;
            }
            // 是表达式就计算
            if (isSupportOperator(str)) {
                calculateExpression(str);
                continue;
            }
            // 否则抛出不支持异常
            throw new IllegalArgumentException("not support symbol:" + str);
        }
        int result = numberStack.pop().interpret();
        System.out.println("result = " + result);
    }

    private void calculateExpression(String str) {
        if (numberStack.isEmpty() || numberStack.size() < 2) {
            throw new IllegalArgumentException("input expression is error.");
        }
        Expression secondExpression = numberStack.pop();
        Expression firstExpression = numberStack.pop();
        Expression terminalExpression = getTerminalExpression(firstExpression, secondExpression, str);
        numberStack.push(new NumberNonTerminalExpression(terminalExpression.interpret()));
    }

    private Expression getTerminalExpression(Expression firstExpression, Expression secondExpression, String symbol) {
        if ("+".equals(symbol)) {
            return new AddTerminalExpression(firstExpression, secondExpression);
        }
        if ("*".equals(symbol)) {
            return new MultiTerminalExpression(firstExpression, secondExpression);
        }
        throw new IllegalArgumentException("not support symbol:" + symbol);
    }

    /**
     * 判断是否是支持的表达式
     *
     * @param symbol :
     * @return :
     */
    private boolean isSupportOperator(String symbol) {
        return "+".equals(symbol) || "*".equals(symbol);
    }

    /**
     * 判断是否是数字表达式
     *
     * @param inputStr :
     * @return :
     */
    private boolean isNumber(String inputStr) {
        try {
            Integer.parseInt(inputStr);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

}

5. 编写一个测试方法

public class Client {

    @Test
    public void testInterpreter() {
        String inputExpression = "6 100 11 + *";
        ExpressionParser parser = new ExpressionParser();
        parser.parseAndCalculate(inputExpression);
    }
}

测试结果如下:

input expression = 6 100 11 + *
result = 666

6. 示例结构图

示例结构图

总结

  解释器模式的重点思路是要分析清楚在需要解析的语法中哪些是终结符解释器,哪些是非终结符解释器,只有将这两类解释器分析清楚,才能用解释器模式写出解释逻辑。在实践中,我们可以用是否包含关系来简单区分,一般情况下,终结符解释器是会包含或者依赖非终结符解释器的,就好比是上面的示例中,加法和乘法解释器,是要依赖前后两个数字解释器来实现逻辑的,而数字解释器就不需要依赖其他解释器,所以加法和乘法解释器是非终结符解释器,数字是终结符解释器。


后记
  个人总结,欢迎转载、评论、批评指正

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值