解释器模式-自定义语言的实现

 有时,我们希望输入一串字符串,然后计算机能够按照预先定义的文法规则来对这个字符串进行解释,从而实现相应的功能。

例如,我们想实现简单的加减法接收器,只需输入一个表达式,它就能计算出表达式结果。比如输入字符串:“2+4-1+4-5”,计算机输出4。像这种用户自己定义一套文法规则来实现对这些语句的解释,即设计一个自定义语言。此时可以使用解释器模式来实现自定义语言。

1 解释器模式

1.1 文法规则

在前面所提到的加法/减法解释器中,emigrants表达式都包含了3个语言单位,可以使用如下语法规则来定义:

expression ::= value | operation

operation ::= expression ‘+’ expression | expression ‘-’ expression

value ::= an integer // 一个整数值

::=

表示定义为

|

表示或

“{” 和 “}”

表示组合

*

表示出现0次或多次

语言单位

又称为语言构造成分,每一条语句所定义的字符串,如上例中的value和operation。

终结表达式

组成元素是最基本的语言单位,不能再进行分解。

非终结表达式

组成元素仍然可以是表达式,可以进一步分解。如expression 和 operation。

图 文法规则说明

​​​​​​​1.2 抽象语法树

除了使用文法规则来定义一个语言外,还可以通过一种被称为抽象语法树(Abstract Syntax Tree, AST)的图形方式来直观的表示语言的构成。每一棵抽象语法树对应一个语言实例。“2+4-1+4-5” 的抽象语法树表示为:

图 抽象语法树示意图

图中终结符表达式作为树的叶子节点,而非终结表达式作为非叶子节点,它们可将终结符表达式以及包含终结符和非终结符的子表达式作为其子节点。通过对抽象语法树的分析,可以识别出语言中的终结符类和非终结符类。

1.3 解释器模式概述

用于描述如何使用对象语言构成一个简单的语言解释器。定义一个语言的文法,并建立一个解释器来解释语言中的句子。这里的“语言”是指使用规定格式和语法的代码。

图 解释器模式结构图

  1. AbstractExpression,抽象表达式,声明了抽象的解释操作,是所有终结符表达式和非终结符表达式的父类。
  2. TerminalExpression,终结符表达式,实现了与文法中的终结符相关联的解释操作,在句子中的每个终结符都是该类的一个实例,它们的实例可以通过非终结符表达式组成较为复杂的句子。
  3. NonterminalExpression,非终结符表达式,实现了文法中非终结符的解释操作。在非终结符表达式中可以包含终结符表达式,也可以包含非终结符表达式,因此其解释操作一般通过递归的方式来实现。
  4. Context,环境类,又称为上下文,用于存储解释器之外的一些全局信息。通常存储了需要解释的语句。

1.3.1 解释器模式实现简单的英文控制指令

需求描述:每个指令对应一个表达式,该表达式可以是简单表达式,也可以是复合表达式。两个表达式之间可以通过and连接,形成复合表达式。简单表达式组成如下表:

移动方向(direction)

上(up)、下(down)、左(left)、右(right)

移动方式(action)

移动(move)、快速移动(run)

移动距离(distance)

为一个正整数

表 简单表达式的组成

例如“up move 5”表示向上移动5个单位。“dow run 10 and left move 20”表示向下快速移动10个单位然后向左移动20个单位。

direction ::= ‘up’|’down’|’left’|’right’;

action :: = ‘move’|’run’;

distance ::= an integer; //一个正整数

expression ::= direction action distance | complexExpression

complexExpression ::= expression ‘and’ expression

// 简单的英语控制指令,例如 left move 10
public interface AbstractExpression {void interpret();}public class ActionExpression implements AbstractExpression{private final String action;public ActionExpression(String action) {this.action = action;}@Overridepublic void interpret() {if (action == null) {throw new RuntimeException("解析失败:行为值为空");}String tempStr = action.toLowerCase();switch (tempStr) {case "move":System.out.print("移动");break;case "run":System.out.print("快速移动");break;default:throw new RuntimeException("解析失败:行为值不合法");}}}public class ComplexExpression implements AbstractExpression{private AbstractExpression leftExpress;private AbstractExpression rightExpress;public ComplexExpression(AbstractExpression leftExpress, AbstractExpression rightExpress) {this.leftExpress = leftExpress;this.rightExpress = rightExpress;}@Overridepublic void interpret() {leftExpress.interpret();System.out.print(" 然后 ");rightExpress.interpret();}}public class DirectionExpression implements AbstractExpression{private final String direction;public DirectionExpression(String direction) {this.direction = direction;}@Overridepublic void interpret() {if (direction == null) {throw new RuntimeException("解析失败:方向值为空");}String tempStr = direction.toLowerCase();switch (tempStr) {case "up":System.out.print("向上");break;case "down":System.out.print("向下");break;case "left":System.out.print("向左");break;case "right":System.out.print("向右");break;default: throw new RuntimeException("解析失败:方向值不合法");}}}public class DistanceExpression implements AbstractExpression{private final String value;public DistanceExpression(String value) {this.value = value;}@Overridepublic void interpret() {if (value == null) {throw new RuntimeException("解析失败:距离为空");}System.out.print(Integer.valueOf(value) + "个单位");}}public class SimpleExpression implements AbstractExpression{private final AbstractExpression direction;private final AbstractExpression action;private final AbstractExpression distance;public SimpleExpression(AbstractExpression direction, AbstractExpression action, AbstractExpression distance) {this.direction = direction;this.action = action;this.distance = distance;}@Overridepublic void interpret() {direction.interpret();action.interpret();distance.interpret();}}public class Client {private static final String[] expressArr = {"left move 12 and down run 5","down run 3","right run 1 and left move 12 and left run 4 and down move 12"};public static void main(String[] args) {for (String str : expressArr) {String[] strArr = str.split(" ");Stack<AbstractExpression> expressionStack = new Stack<>();for (int i = 0; i < strArr.length; i++) {if ("and".equals(strArr[i])) {AbstractExpression leftExp = expressionStack.pop();AbstractExpression rightExp = buildSimpleExp(strArr, i + 1);expressionStack.push(new ComplexExpression(leftExp,rightExp));i += 3;} else {expressionStack.push(buildSimpleExp(strArr,i));i += 2;}}expressionStack.pop().interpret();System.out.println();}
//        运行结果:
//        向左移动12个单位 然后 向下快速移动5个单位
//        向下快速移动3个单位
//        向右快速移动1个单位 然后 向左移动12个单位 然后 向左快速移动4个单位 然后 向下移动12个单位}private static AbstractExpression buildSimpleExp(String[] strArr, int start) {AbstractExpression directionExp = new DirectionExpression(strArr[start]);AbstractExpression actionExp = new ActionExpression(strArr[start + 1]);AbstractExpression distance = new DistanceExpression(strArr[start + 2]);return new SimpleExpression(directionExp,actionExp,distance);}}

1.3.2 简单的英语控制指令 为啥要用解释器模式?

 其实,上面这个需求在不用解释器模式的情况下也可以实现,而且只需在一个类就能完成。

public class Client2 {private static final String[] expressArr = {"left move 12 and down run 5","down run 3","right run 1 and left move 12 and left run 4 and down move 12"};public static void main(String[] args) {System.out.println("不用解释器模式实现需求:");System.out.println("--------------");for (String str : expressArr) {StringBuilder sb = new StringBuilder();String[] strArr = str.split(" ");for (String s : strArr) {switch (s) {case "and":sb.append(" 然后 ");break;case "up":sb.append("向上");break;case "down":sb.append("向下");break;case "left":sb.append("向左");break;case "right":sb.append("向右");break;case "move":sb.append("移动");break;case "run":sb.append("快速移动");break;default:sb.append(s).append("个单位");}}System.out.println(sb);}
//        运行结果:
//        不用解释器模式实现需求:
//        --------------
//        向左移动12个单位 然后 向下快速移动5个单位
//        向下快速移动3个单位
//        向右快速移动1个单位 然后 向左移动12个单位 然后 向左快速移动4个单位 然后 向下移动12个单位}}

不用解释器模式实现的代码量更少、类的数量也更少而且运行速度也更快。所以为什么要用解释器模式呢?

  1. 文法规则让需求更加清晰。上面分析的5个表达式完整的表示了这个需求,通过拆解表达式的形式,由复合语句到不可拆分表达式。
  2. 扩展及修改方便。不用解释器的情况下条件判断语句太多了,如果修改或者增加某个含义,则在修改代码时将会遇到很大的困难。而解释器模式把各种语句以类的形式来表示,只需要修改相应类即可。

1.3.3 Context的作用

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

需求描述:实现一套简单的基于字符串界面的格式化指令,可以根据输入的指令在字符串界面中输出一些格式化内容。

例如:”LOOP 2 PRINT 今天发工资啦 SPACE SPACE PRINT 亲爱的老婆  BREAK END PRINT 走 SPACE SPACE PRINT 下馆子去!” 将输出如下结果:

今天发工资啦  亲爱的老婆

今天发工资啦  亲爱的老婆

走  下馆子去!

BREAK

换行

SPACE

空格

END

循环结束

PRINT

打印,后面字符串表示打印的内容

LOOP

循环,后面的数字表示循环次数

  表 关键字表示含义

每个关键字对应一条命令,程序根据关键字执行相应的处理操作。

primitive ::= ‘PRINT string’ | ‘BREAK’ | ‘SPACE’; // 基本命令,string 为字符串

command ::= loop | primitive; // 语句命令

expression ::= command *; //  表达式,一个表达式包含多条命令

loop ::= LOOP number expression ‘END’; // j循环命令,number 为自然数

public class Context {private final String[] commandStrArr;private int currentPos = 0;public Context(String str) {commandStrArr = str.split(" ");}public String getCurrentCommand() {return commandStrArr[currentPos];}public void skip() { ++currentPos;}public void interpreter(List<AbstractCommand> commandList) {while (true) {if (currentPos >= commandStrArr.length) {break;}if ("END".equals(commandStrArr[currentPos])) {skip();break;}AbstractCommand command = new ConcreteCommand();command.interpreter(this);commandList.add(command);}}}public interface AbstractCommand {void interpreter(Context context);void execute();}public class ConcreteCommand implements AbstractCommand{private AbstractCommand command;@Overridepublic void interpreter(Context context) {if ("LOOP".equals(context.getCurrentCommand())) {command = new LoopCommand();} else {command = new PrimitiveCommand();}command.interpreter(context);}@Overridepublic void execute() {command.execute();}
}public class ExpressionCommand implements AbstractCommand{private final List<AbstractCommand> commandList = new ArrayList<>();@Overridepublic void interpreter(Context context) {context.interpreter(commandList);}@Overridepublic void execute() {for (AbstractCommand command : commandList)command.execute();}}public class LoopCommand implements AbstractCommand{private Integer number;private AbstractCommand command;@Overridepublic void interpreter(Context context) {context.skip();String value = context.getCurrentCommand();context.skip();number = Integer.valueOf(value);command = new ExpressionCommand();command.interpreter(context);}@Overridepublic void execute() {for (int i = 0; i < number; i++) {command.execute();}}}public class PrimitiveCommand implements AbstractCommand {private String value;@Overridepublic void interpreter(Context context) {switch (context.getCurrentCommand()) {case "PRINT":context.skip();value = context.getCurrentCommand();break;case "BREAK":value = "\n";break;case "SPACE":value = " ";break;}context.skip();}@Overridepublic void execute() {System.out.print(value);}}public class Client {private final static String commandStr = "LOOP 2 LOOP 2 PRINT 今天发工资啦 SPACE SPACE PRINT 亲爱的老婆 BREAK END PRINT 走 SPACE SPACE PRINT 下馆子去 BREAK END";public static void main(String[] args) {Context context = new Context(commandStr);AbstractCommand command = new ExpressionCommand();command.interpreter(context);command.execute();
//        运行结果:
//        今天发工资啦  亲爱的老婆
//        今天发工资啦  亲爱的老婆
//        走  下馆子去
//        今天发工资啦  亲爱的老婆
//        今天发工资啦  亲爱的老婆
//        走  下馆子去}}

在这里,Context的作用是存储一些公共变量及实现公有方法。

1.3.4 解释器模式实现简单加减法

需求描述:实现简单的加减法接收器,只需输入一个表达式,它就能计算出表达式结果。比如输入字符串:“2+4-1+4-5”,计算机输出4。

number ::= a integer;// 一个整数

operation ::= expression ‘+|-’ expression

expression ::= number | operation

public class Context {private final String[] expressionArr;private Integer currentPos = 0;public Context(String str) {expressionArr = str.split(" ");}public String getCurrentExp() {return getExp(0);}public String getNextExp() {return getExp(1);}public String getPreExp() {return getExp(-1);}private String getExp(int num) {return currentPos + num >= expressionArr.length || currentPos + num < 0 ? null : expressionArr[currentPos + num];}public void skip(int num) {currentPos += num;}public void interpreter(AbstractExpression preExpression) {}}public interface AbstractExpression {void interpreter(Context context);Integer execute();}public class ConcreteExpression implements AbstractExpression{private AbstractExpression expression;@Overridepublic void interpreter(Context context) {if (context.getNextExp() != null) {expression = new OperationExpression();} else {expression = new NumberExpression();}expression.interpreter(context);}@Overridepublic Integer execute() {return expression.execute();}}public class NumberExpression implements AbstractExpression{private Integer number;@Overridepublic void interpreter(Context context) {number = Integer.valueOf(context.getCurrentExp());String type = context.getPreExp();if ("-".equals(type)) {number = -number;}}@Overridepublic Integer execute() {return number;}
}public class OperationExpression implements AbstractExpression{private AbstractExpression leftExpression;private AbstractExpression rightExpression;@Overridepublic void interpreter(Context context) {leftExpression = new NumberExpression();leftExpression.interpreter(context);context.skip(2);rightExpression = new ConcreteExpression();rightExpression.interpreter(context);}@Overridepublic Integer execute() {return leftExpression.execute() + rightExpression.execute();}}/* 实现简单的加减法接收器,只需输入一个表达式,
它就能计算出表达式结果。比如输入字符串:
“2 + 4 - 1 + 4 - 5”,计算机输出4。 */
public class Client {private static final String STR = "23 - 5 + 22 - 3 - 33 - 22 + 1";public static void main(String[] args) {Context context = new Context(STR);AbstractExpression expression = new ConcreteExpression();expression.interpreter(context);System.out.println(expression.execute());
//        运行结果:
//        17}}

2 优缺点

优点:

  1. 易于改变和扩展文法,使用类来表示文法规则,因此可以通过继承等机制来改变或扩展文法。
  2. 实现文法较为容易。增加新的解释表达式较为方便,只需增加相关表达式类即可,原有表达式类无须修改,符合开闭原则。

缺点:

  1. 对于复杂文法难以维护。如果一种语言包含太多文法规则,类的数量将会急剧增加,导致系统难以管理和维护,可以考虑使用语法分析程序等方式来取代解释器模式。
  2. 执行效率较低,使用了大量循环和递归调用,而且代码调试过程也比较麻烦。

3 适用场景

  1. 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
  2. 执行效率不是关键,一个语言的文法较为简单。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/57158.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

性能测试jmeter连接数据库jdbc(sql server举例)

一、下载第三方工具包驱动数据库 1. 因为JMeter本身没有提供链接数据库的功能&#xff0c;所以我们需要借助第三方的工具包来实现。 &#xff08;有这个jar包之后&#xff0c;jmeter可以发起jdbc请求&#xff0c;没有这个jar包&#xff0c;也有jdbc取样器&#xff0c;但不能发起…

Metashape和PhotoScan中文版软件下载安装地址

Metashape的点云生成功能 Metashape具有强大的点云生成功能&#xff0c;可以将图像转换为精确的三维点云数据。点云数据是进行三维建模和地形分析的重要基础。 在使用Metashape时&#xff0c;用户可以通过使用图像对齐功能生成点云数据。软件根据对齐后的图像生成稠密的点云&a…

SpringBoot系列---【三种启动传参方式的区别】

三种启动传参方式的区别 1.三种方式分别是什么? idea中经常看到下面三种启动传参方式 优先级 Program arguments > VM options > Environment variable > 系统默认值 2.参数说明 2.1、VM options VM options其实就是我们在程序中需要的运行时环境变量&#xff0c;它需…

从C语言到C++_30(哈希)闭散列和开散列(哈希桶)的实现

目录 1. 哈希结构 1.1 哈希的概念 1.2 哈希冲突(碰撞) 1.3 哈希函数 2. 闭散列/开散列解决哈希冲突 2.1 闭散列概念和代码 2.1.1 闭散列线性探测&#xff08;实现&#xff09; 闭散列线性探测完整代码 2.1.2 闭散列二次探测&#xff08;了解&#xff09; 2.2 开散列(…

2023 RISC-V中国峰会 安全相关议题汇总

安全相关议题 1、The practical use cases of the RISC-V IOPMP 2、构建安全可信、稳定可靠的RISC-V安全体系 3、Enhancing RISC-V Debug Security with hardware-based isolation 4、Closing a side door: Designing high-performance RISC-V core resilient to branch pr…

kretprobe 和 fexit

kretprobe 孬&#xff0c;跟朋友简单讨论了相关主题&#xff0c;发现 fexit 高尚。 fexit 的把戏在 2020 年中那段走火入魔的时间玩过不少&#xff0c;没想到就是 fexit 的标准&#xff0c;看来多数人觉得正确的思路它就是正确的。 kretprobe 每次调用函数都要执行复杂的带锁…

企业如何搭建矩阵内容,才能真正实现目的?

当下&#xff0c;新媒体矩阵营销已成为众多企业的营销选择之一&#xff0c;各企业可以通过新媒体矩阵实现扩大品牌声量、维持用户关系、提高销售业绩等不同的目的。 而不同目的的矩阵&#xff0c;它的内容运营模式会稍有差别&#xff0c;评价体系也会大不相同。 企业在运营某类…

b站视频标题的获取(xpath、jsonpath的一个简单应用)

目录 1.目的2.代码的演示 注&#xff1a;该篇文章为本人原创&#xff0c;由于本人学习有限&#xff0c;若有错误或者笔误或者有问题&#xff0c;欢迎大家进行批评指正&#xff0c;谢谢。 1.目的 在b站大学上&#xff0c;为了更好的写笔记&#xff0c;本人根据学到的Python(即Py…

yum源配置

1. YUM简介 YUM&#xff08;yellowdong updater modified&#xff09;是一个功能完善、易于使用的软件维护工具&#xff0c;它可以根据用户的要求分析出所需软件包及其相关的依赖关系&#xff0c;然后自动从服务器&#xff08;YUM软件仓库&#xff09;下载软件包并安装到Linux操…

Nacos单点部署

文章目录 一、Nacos概述二、部署Nacos&#xff08;1&#xff09;容器部署 三、注册服务四、整合外部的Mysql&#xff08;单节点&#xff09; 一、Nacos概述 Nacos是什么&#xff1f; Nacos的全称是Dynamic Naming and Configuration Service&#xff0c;是阿里巴巴推出来的一个…

银河麒麟QT连接DM8数据库

1. 安装达梦8 官网下载, 按照官方文档进行安装即可. 2. 安装unixodbc 1> 下载odbc安装包 unixODBC-2.3.7pre.tar.gz 2> 解压 tar -xvf unixODBC-2.3.7pre.tar.gz3> 编译 ./configure -prefix /usr/local make && make install4> 查找配置 odbcinst -j5…

Kubectl 详解

目录 陈述式资源管理方法&#xff1a;项目的生命周期&#xff1a;创建-->发布-->更新-->回滚-->删除声明式管理方法&#xff1a; 陈述式资源管理方法&#xff1a; kubernetes 集群管理集群资源的唯一入口是通过相应的方法调用 apiserver 的接口kubectl 是官方的CL…