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

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

例如,我们想实现简单的加减法接收器,只需输入一个表达式,它就能计算出表达式结果。比如输入字符串:“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.rhkb.cn/news/84733.html

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

相关文章

专注于创意设计,为您的小程序和网站建设带来更多的可能性

随着移动互联网的快速发展&#xff0c;越来越多的企业开始关注小程序和网站建设&#xff0c;以此来拓展业务和提升品牌形象。 在这个领域中&#xff0c;创意设计扮演着关键的角色。它不仅可以帮助企业打造独特的形象和品牌&#xff0c;还能够提高用户体验和购买决策的效率。 因…

Word转PDF在线转换如何操作?分享转换技巧

现如今&#xff0c;pdf转换器已成为大家日常办公学习必不可少的工具&#xff0c;市场上的pdf转换器主要有两种类型&#xff0c;一种是需要下载安装的&#xff0c;另一种是网页版&#xff0c;打开就可以使用的&#xff0c;今天小编给大家推荐一个非常好用的网页版pdf转换器&…

react中使用路由起手式,一些思路和细节。

一.安装并配置 我们选择使用react-router实现路由效果 yarn add react-router-dom下载后需要对Route进行引入&#xff0c;是个内置的组件。该组件是有两个属性一个是path&#xff0c;一个是component&#xff0c;path是组件对应的路由&#xff0c;component是对应的组件 二.…

UG NX二次开发(C#)-CAM自定义铣加工的出口环境

文章目录 1、前言2、自定义铣削加工操作3、出错原因4、解决方案4.1 MILL_USER的用户参数4.2 采用自定义铣削的方式生成自定义的dll4.2 配置加工的出口环境4.3 调用dll5、结论1、前言 作为一款大型的CAD/CAM软件, UG NX为我们提供了丰富的加工模板,通过加工模板能直接用于生成…

Spring Initailizr--快速入门--SpringBoot的选择

&#x1f600;前言 本篇博文是关于IDEA使用Spring Initializer快速创建Spring Boot项目的说明&#xff0c;希望能够帮助到您&#x1f60a; &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可…

Python-OpenCV中的图像处理-图像平滑

Python-OpenCV中的图像处理-图像平滑 图像平滑平均滤波高斯模糊中值模糊双边滤波 图像平滑 使用低通滤波器可以达到图像模糊的目的。这对与去除噪音很有帮助。其实就是去除图像中的高频成分&#xff08;比如&#xff1a;噪音&#xff0c;边界&#xff09;。所以边界也会被模糊…

【Linux】TCP协议——传输层

目录 TCP协议 谈谈可靠性 TCP协议格式 序号与确认序号 窗口大小 六个标志位 确认应答机制&#xff08;ACK&#xff09; 超时重传机制 连接管理机制 三次握手 四次挥手 流量控制 滑动窗口 拥塞控制 延迟应答 捎带应答 面向字节流 粘包问题 TCP异常情况 TC…

【已解决】mac端 sourceTree 解决remote: HTTP Basic: Access denied报错

又是在一次使用sourcetree拉取或者提交代码时候&#xff0c;遇到了sourcetree报错&#xff1b; 排查了一会&#xff0c;比如查看了SSH keys是否有问题、是否与sourcetree账户状态有问题等等&#xff0c;最终才发现并解决问题 原因&#xff1a; 因为之前公司要求企业gitlab中…

Linux/centos上如何配置管理samba服务器?

Linux/centos上如何配置管理samba服务器&#xff1f; 1 samba服务相关知识1.1 SMB协议1.2 samba工作原理1.2.1 相关进程1.2.2 samba工作流程1.2.3 samba功能 2 samba服务器安装2.1 利用光驱安装2.2 利用光盘映射文件 3 启动与停止samba服务4 配置samba服务器4.1 samba主配置文件…

数据结构—图的遍历

6.3图的遍历 遍历定义&#xff1a; ​ 从已给的连通图中某一顶点出发&#xff0c;沿着一些边访问遍历图中所有的顶点&#xff0c;且使每个顶点仅被访问一次&#xff0c;就叫作图的遍历&#xff0c;它是图的基本运算。 遍历实质&#xff1a;找每个顶点的邻接点的过程。 图的…

ElasticSearch:项目实战(2)

ElasticSearch: 项目实战 (1) 需求&#xff1a; 新增文章审核通过后同步数据到es索引库 1、文章服务中添加消息发送方法 在service层文章新增成功后&#xff0c;将数据通过kafka消息同步发送到搜索服务 Autowiredprivate KafkaTemplate<String,String> kafkaTemplate;/…

Linux系统调试课:Linux Kernel Printk

🚀返回专栏总目录 文章目录 0、printk 说明1、printk 日志等级设置2、屏蔽等级日志控制机制3、printk打印常用方式4、printk打印格式0、printk 说明 在开发Linux device Driver或者跟踪调试内核行为的时候经常要通过Log API来trace整个过程,Kernel API printk()是整个Kern…

学习C语言第三天 :关系操作符、逻辑操作符

1.关系操作符 C语言用于比较的表达式&#xff0c;称为“关系表达式”里面使用的运算符就称(relationalexpression)&#xff0c;为“关系运算符” (relationaloperator) &#xff0c;主要有下面6个。 > 大于运算符 < 小于运算符 > 大于等于运算符 < 小于等…

Docker安装Hadoop分布式集群

一、准备环境 docker search hadoop docker pull sequenceiq/hadoop-docker docker images二、Hadoop集群搭建 1. 运行hadoop102容器 docker run --name hadoop102 -d -h hadoop102 -p 9870:9870 -p 19888:19888 -v /opt/data/hadoop:/opt/data/hadoop sequenceiq/hadoop-do…

二叉树题目:根据二叉树创建字符串

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;根据二叉树创建字符串 出处&#xff1a;606. 根据二叉树创建字符串 难度 3 级 题目描述 要求 给你二叉树的根结…

【广州华锐视点】AR电力职业技能培训系统让技能学习更“智慧”

随着科技的发展&#xff0c;教育方式也在不断地进步和创新。其中&#xff0c;增强现实(AR)技术的出现&#xff0c;为教育领域带来了全新的可能。AR电力职业技能培训系统就是这种创新教学方法的完美实践&#xff0c;它将虚拟与现实相结合&#xff0c;为学生提供了一个沉浸式的学…

Token 失效退出至登录页面

1. 在登录页面&#xff0c;调用登录的接口后&#xff0c;直接写上当前时间&#xff0c;保存在本地 代码&#xff1a; // 点击登录login(form) {this.$refs[form].validate((valid) > {if (valid) {this.$API.Login(this.form).then((res) > {// console.log(res, "1…

AIRIOT出席IOTE生态行·北京物联网应用交流大会

8月8日&#xff0c;由物联传媒、IOTE物联展、AIoT库、AIoT星图研究院联合主办的IOTE生态行北京物联网应用交流大会圆满结束&#xff0c;超300位业界同行同台交流。 航天科技控股集团股份有限公司受邀参会&#xff0c;旗下AIRIOT物联网平台产品负责人段丽娜发表演讲&#xff0c;…

JVM 性能优化思路

点击下方关注我&#xff0c;然后右上角点击...“设为星标”&#xff0c;就能第一时间收到更新推送啦~~~ 一般在系统出现问题的时候&#xff0c;我们会考虑对 JVM 进行性能优化。优化思路就是根据问题的情况&#xff0c;结合工具进行问题排查&#xff0c;针对排查出来的可能问题…

uniapp软键盘谈起遮住输入框和头部被顶起的问题解决

推荐&#xff1a; pages.json中配置如下可解决头部被顶起和表单被遮住的问题。 { "path": "pages/debug/protocol/tagWord", "style": { "app-plus": { "soft…