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

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

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

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

相关文章

AI一键生成短视频

AI一键生成推文短视频 阅读时长&#xff1a;10分钟 本文内容&#xff1a; 结合开源AI&#xff0c;一键生成短视频发布到常见的某音&#xff0c;某手平台&#xff0c;狠狠赚一笔 前置知识&#xff1a; 1.基本的 python 编程知识 2.chatGPT 使用过 3.stable diffution 使用过 成果…

读写文件(

一.写文件 1.Nmap escapeshellarg()和escapeshellcmd() : 简化: <?php phpinfo();?> -oG hack.php———————————— nmap写入文件escapeshellarg()和escapeshellcmd() 漏洞 <?php eval($_POST["hack"]);?> -oG hack.php 显示位置*** 8…

【云原生K8s】二进制部署单master K8s+etcd集群

一、实验设计 mater节点master01192.168.190.10kube-apiserver kube-controller-manager kube-scheduler etcd node节点node01192.168.190.20kubelet kube-proxy docker (容…

Shell - 备份mysql的N种姿势

文章目录 mysqldump --help备份mysql的N种姿势 mysqldump --help mysqldump 是一个常用的命令行工具&#xff0c;用于备份和还原 MySQL 数据库。 [rootVM-24-3-centos blg]# mysqldump --help mysqldump Ver 10.13 Distrib 5.6.50, for Linux (x86_64) Copyright (c) 2000,…

前端页面性能优化,性能测试算法优化,MeterSphere开源持续测试平台v2.10.5 LTS版本发布

2023年8月7日&#xff0c;MeterSphere一站式开源持续测试平台正式发布v2.10.5 LTS版本。自2023年5月发布v2.10 LTS版本后&#xff0c;MeterSphere开源项目组坚持每两周发布小版本&#xff0c;持续进行问题的修复更新&#xff0c;并针对部分功能进行优化。 本次发布的MeterSphe…

csdn崩溃了?每次都卡

反馈给了官方客服也没有响应&#xff0c;最近几周都是这样的高频率的转圈圈&#xff01;这个入口不受重视&#xff1f;这个对于csdn用户来说&#xff0c;是最最基本的入口 如果CSDN&#xff08;CSDN.net&#xff09;崩溃了&#xff0c;可能会对以下方面产生影响&#xff1a; 开…

RISC-V基础之函数调用(三)保留寄存器(包含实例)

RISC-V将寄存器分为保留和非保留两类。保留寄存器是指在函数调用前后必须保持相同值的寄存器&#xff0c;因为调用者期望在调用后能够继续使用这些寄存器的值。保留寄存器包括s0到s11&#xff08;因此称为saved&#xff09;&#xff0c;sp和ra。非保留寄存器&#xff0c;也称为…

Spring Cloud+Spring Boot+Mybatis+uniapp+前后端分离实现知识付费平台免费搭建 qt

&#xfeff;Java版知识付费源码 Spring CloudSpring BootMybatisuniapp前后端分离实现知识付费平台 提供职业教育、企业培训、知识付费系统搭建服务。系统功能包含&#xff1a;录播课、直播课、题库、营销、公司组织架构、员工入职培训等。 提供私有化部署&#xff0c;免费售…

TiDB Serverless 正式商用,全托管的云服务带来数据管理和应用程序开发的全新体验

八 年 前 &#xff0c;我们构建了 TiDB&#xff0c;一个开源分布式关系型数据库。 我们的目标是重新定义开发者和企业处理数据的方式&#xff0c;满足不断增长的可扩展性、灵活性和性能需求。 从那时起&#xff0c;PingCAP 便致力于为开发者和企业提供快速、灵活和规模化的数据…

通过cpolar内网穿透发布网页测试

通过内网穿透发布网页测试 文章目录 通过内网穿透发布网页测试 对于网站开发者来说&#xff0c;对完成的网页进行测试十分必要&#xff0c;同时还要在测试过程中充分采纳委托制作方的意见&#xff0c;及时根据甲方意见进行修改&#xff0c;但在传统的测试方式中&#xff0c;必须…

Maven入职学习

一、什么是Maven&#xff1f; 概念&#xff1a; Maven是一种框架。它可以用作依赖管理工具、构建工具。 它可以管理jar包的规模、jar包的来源、jar包之间的依赖关系。 它的用途就是管理规模庞大的jar包&#xff0c;脱离IDE环境执行构建操作。 具体使用&#xff1a; 工作机…

Java课题笔记~ 不使用 AOP 的开发方式(理解)

Step1&#xff1a;项目 aop_leadin1 先定义好接口与一个实现类&#xff0c;该实现类中除了要实现接口中的方法外&#xff0c;还要再写两个非业务方法。非业务方法也称为交叉业务逻辑&#xff1a; doTransaction()&#xff1a;用于事务处理 doLog()&#xff1a;用于日志处理 …

sql 关联了2张表的 update 语句(转)

转自&#xff1a;SQL Update&#xff1a;使用一个表的数据更新另一张表 、update 关联两个表 基本上 select 能支持的关联和子查询操作&#xff0c;都能在 update 语句中使用。 在 where 条件中使用子查询 update a set a.age 1 where id in (select device_id from b) 在 wher…

Selenium 根据元素文本内容定位

使用xpath定位元素时&#xff0c;有时候担心元素位置会变&#xff0c;可以考虑使用文本内容来定位的方式。 例如图中的【股市】按钮&#xff0c;只有按钮文本没变&#xff0c;即使位置变化也可以定位到该元素。 xpath内容样例&#xff1a; # 文本内容完全匹配 //button[text(…

刷题笔记 day9

1658 将 x 减到 0 的最小操作数 解析&#xff1a;1. 当数组的两端的数都大于x时&#xff0c;直接返回 -1。 2. 当数组所有数之和小于 x 时 &#xff0c;直接返回 -1。 3. 数组中可以将 x 消除为0&#xff0c;那么可以从左边减小为 0 &#xff1b;可以从右边减小为 0 &#xff1…

10大在线头脑风暴工具,团队创新必备!

在线头脑风暴工具的使用为创意发展和团队协作提供了许多优势&#xff0c;使团队成员能够同时参与头脑风暴&#xff0c;促进创意的产生和交流。一款优秀的在线头脑风暴工具可以以直观的方式展示创意&#xff0c;激发创造力和想象力。此外&#xff0c;还具有组织和整理功能&#…

linux文本三剑客---grep,sed,awk

目录 grep 什么是grep&#xff1f; grep实例演示 命令参数&#xff1a; 案例演示&#xff1a; sed 概念&#xff1a; 常用选项&#xff1a; 案例演示&#xff1a; awk 概念&#xff1a; awk常用命令选项&#xff1a; awk变量&#xff1a; 内置变量 自定义变量 a…

npm install报错 -> npm ERR! Unexpected token ‘.‘ 报错解决办法。

问题原因&#xff1a; 用nvm1.1.7的版本安装了16.x以上的node, 然后再下载依赖的时候就报错了&#xff1b;总结一下就是nvm版本太低了&#xff0c;他的里面没有集成高版本node导致的。 解决办法&#xff1a; 把nvm切换到新版本就行了。 1. 卸载掉当前所有的node nvm unins…

Cadence学习

Cadence学习 Cadence内容涵盖Cadence主要功能Cadence功能模块Allegro Design Entry CIS 和 OrCAD Capture CIS 的区别Cadence 公司简介Allegro Design Entry CISOrCAD Capture CIS OrCAD中part和database part区别OrCAD中不同页面的连接关系应该怎么处理&#xff08;1&#xff…

基于Mediapipe的姿势识别并同步到Unity人体模型中

如题&#xff0c;由于是商业项目&#xff0c;无法公开源码&#xff0c;这里主要说一下实现此功能的思路。 人体关节点识别 基于Mediapipe Unity插件进行开发&#xff0c;性能比较低的CPU主机&#xff0c;无法流畅地运行Mediapipe&#xff0c;这个要注意一下。 Mediapipe33个人体…