当前位置: 首页 > news >正文

数据库 搭建 网站泉州手机网站建设价格

数据库 搭建 网站,泉州手机网站建设价格,网站建设软著,制作小程序的平台续接上文。 了解过顺序结构分析器之后,接下来要展示的是列表结构分析器ListParser类的代码,如代码8-16所示: 代码8-16class ListParser extends Parser {Parser parser;TokenType terminalTag; ListParser(Parser parser, TokenType termina…

        续接上文。

        了解过顺序结构分析器之后,接下来要展示的是列表结构分析器ListParser类的代码,如代码8-16所示:

代码8-16class ListParser extends Parser {Parser parser;TokenType terminalTag;	ListParser(Parser parser, TokenType terminalTag) {super(null);this.parser = parser;this.terminalTag = terminalTag;}@Overridevoid parse(ParseContext context) {if (!context.isPreviousMatched()) {return;}List<Token> matchedTokens = new ArrayList<>();boolean isNormalEnd = true;while (true) {this.parser.parse(context);if (!context.isPreviousMatched()) {Token current = context.tokenBuffer.getCurrentToken();context.tokenBuffer.backIndex();if (current.type == terminalTag) {break;}isNormalEnd = false;break;}matchedTokens.addAll(context.matchedTokens());}if (isNormalEnd) {context.matchSuccess(matchedTokens);this.callback(matchedTokens);}}
}

        ListParser主要被用于处理列表元素。虽然都属于容器类型的分析器,但它与SequenceParser的差异还是很大的,最重点的一点便是它所包含的子分析器是无序的。仍以RULE_BLOCK产生式为例,其包含了多个NAME_MAPPING元素,并且彼此之间无任何顺序性,这正是列表结构的特性,所以我们使用列表结构分析器来对其进行解析。不过有一点要注意,即列表项的数量。当前案例使用了NAME_MAPPING*的形式,这意味着列表中的内容是可选的;而如果使用NAME_MAPPING+形式的话,则表示列表中应至少包含一个元素。虽然只有一个符号之差,但具体的实现逻辑却会有所不同。对于本案例,笔者选择对“*”类型的列表结构进行实现,因为“+”类型的只是前者的子集。

        相对于顺序结构分析器,ListParser的实现逻辑要复杂一点,主要体现在如下两个方面:

  1. 需要组合的子分析器类型固定。ListParser类中只包含了一个固定类型的子分析器,即字段parser所指向的对象,这一点与SequenceParser的实现明显不同。之所以出现这样的情况,是因为列表中的元素类型都是相同的,只使用同一个分析器进行解析也在情理之中。为方便理解,读者可以将列表想象成一个数组对象,所有元素都共享同一个类型。顺序结构则没有这样的要求,其包含的元素可以是任何继承自Parser的对象。
  2. 需指定列表的结束标识。实现列表类型的分析器时,首先需要克服的一个问题是如何获取列表中元素数量的信息,只有这样才方便对列表进行遍历。否则,您只能明确指定一个标识符来告知分析器应何时结束遍历。仍然以rules脚本块为例,在循环分析NAME_MAPPING符号的时候,如果当前输入的词法单元是end类型的话,就意味着列表内的所有元素都已经分析完毕,可终止循环;否则就会循环解析下去,直到报错或遇到end为止。ListParser类的构造函数中,我们使用参数terminalTag来标识列表项的结束元素。需要注意的是,这一实现方式仅仅是笔者个人的一种设计观点,并不具备规则性。

        通过代码8-13,您会发现笔者在实例化ListParser对象的时候,使用了NameMappingParser类型的对象作为其构造函数的参数,这样的话,我们就可以循环使用该对象对列表内的元素进行分析。子分析器NameMappingParser用于解析rules块中名称映射部分的脚本,对应于非终结符NAME_MAPPING,如代码8-17所示:

代码8-17class NameMappingParser extends SequenceParser {NameMappingParser(String targetNode) {super(targetNode,new TerminalParser(TokenType.ID),new TerminalParser(TokenType.ID),new TerminalParser(TokenType.SEMICOLON));}@Overridevoid parse(ParseContext context) {super.parse(context);if (!context.isPreviousMatched()) {return;}String fullName = context.matchedTokens().get(0).lexeme;String alias = context.matchedTokens().get(1).lexeme;context.nameContainer.add(fullName, alias);}
}

        同RuleBlockParser,类型NameMappingParser也继承自SequenceParser类。通过构造函数可知,该分析器需要处理三个类型的词法单元,和非终结符NAME_MAPPING的定义完全一致。

        学习过RuleBlockParser相关的代码之后,相信读者此时应该可以自行实现受理类型(service_type代码块)所对应的分析器了,笔者给出的结果如代码8-18所示。其中ServiceTypeBlockParser类对应于非终结符SERVICE_TYPE_BLOCK,ServiceTypeNameParser类对应于非终结符SERVICE_TYPE_NAME,逻辑比较简单,笔者不做过多说明。

代码8-18class ServiceTypeBlockParser extends SequenceParser {ServiceTypeBlockParser(String targetNode) {super(targetNode,new TerminalParser(TokenType.SERVICE_TYPES),new ListParser(new ServiceTypeNameParser("SERVICE_TYPE_NAME"), TokenType.END),new TerminalParser(TokenType.END));}
}class ServiceTypeNameParser extends SequenceParser {ServiceTypeNameParser(String targetNode) {super(targetNode,new TerminalParser(TokenType.ID),new TerminalParser(TokenType.SEMICOLON));}@Overridevoid parse(ParseContext context) {super.parse(context);if (!context.isPreviousMatched()) {return;}String name = context.matchedTokens().get(0).lexeme;context.serviceTypeContainer.add(name);}
}

        接下来要学习的是规则绑定(bind_rules)代码块所对应的子分析器。相对于前面所介绍过的分析器,其要更复杂一点,毕竟该结构本身也具备一定的复杂度。这次让我们换个思路,从最细粒度的子分析器代码开始学习。

        通过文法8-1中可知,非终结符BINDING由ALIAS_LIST构成,后者表示规则别名的列表,也就是大括号(注意:不包括大括号本身)中的内容。针对这种形式的文法,已经无法再使用ListParser分析器进行解析,因为元素之间是以逗号作为分隔的,但尾部元素后面却没有。按照ListParser的定义,列表中的每一个元素都应该有相同的结构才可以。因此,我们必需引入一个新的解析器。好消息是,针对ALIAS_LIST模式的脚本,笔者在前面内容中展示过类似的案例(即二进制字符串语法分析器),我们可以友情借鉴一下它的实现模式。

        子分析器AliasListParser用于对非终结符ALIAS_LIST进行解析,内容较多,笔者分两部分进行说明。代码8-19展示了它的基本结构和构造函数:

代码8-19class AliasListParser extends Parser {private TokenType terminalTag;private List<String> aliases = new ArrayList<>();AliasListParser(String targetNode, TokenType terminalTag) {super(targetNode);this.terminalTag = terminalTag;}
}

        aliases字段用于存储大括号中的别名信息,后续我们会使用该字段的值来构建对应的语义模型。

        代码8-20展示了AliasListParser子分析器的核心方法:

代码8-20@Override
void parse(ParseContext context) {aliases.clear();if (!context.isPreviousMatched()) {return;}aliasList(context); //代码1if (context.isPreviousMatched()) {List<Token> tokens = aliases.stream().map(e -> new Token(TokenType.ID, e)).collect(Collectors.toList());context.matchSuccess(tokens); //代码2this.callback(tokens);}
}void aliasList(ParseContext context) {id(context);Token current = context.tokenBuffer.getCurrentToken();if (current.type == this.terminalTag) {context.matchSuccess(context.matchedTokens());context.tokenBuffer.backIndex();return;}if (!context.isPreviousMatched()) {return;}Token next = context.tokenBuffer.nextToken();if (next.type == TokenType.COMMA) {aliasList(context);} else if (next.type == this.terminalTag) {context.tokenBuffer.backIndex();}
}void id(ParseContext context) {this.matched(context, TokenType.ID);if (context.isPreviousMatched()) {Token current = context.tokenBuffer.getCurrentToken();this.aliases.add(current.lexeme);}
}void matched(ParseContext context, TokenType target) {Token current = context.tokenBuffer.nextToken();if (current.type == target) {return;}String error = this.error(current.lexeme, target.name());context.matchFailed(error);
}

        parse()方法的主体逻辑主要包含如下两部分内容:

  1. “代码1”处调用aliasList()方法对大括号中的别名列表进行分析,成功的话则将列表中的内容加入到字段aliases之中。
  2. parse()方法的执行过程中如果未出现语法错误的话,“代码2”处会通过回调和调用context.matchSuccess()方法的方式,将用于构建语义模型BindingConfig的信息传递到子语法分析器的外部。

        细心的读者应该注意到了,笔者已经数次使用context对象和回调这两种方式来实现信息的外传。第一种方式比较好理解,为什么要额外增加一种方式呢?这就需要看一下context对象中用于保存已匹配的词法单元列表所对应的数据格式了。假设bind_rules代码块中的内容为“upgrade {ResNotF, ResNotE};”(只有一条配置项),子分析器AliasListParse解析成功后,会将已经匹配的token信息放到ParseContext.MatchResult对象之中,该对象经JSON序列化后将呈代码8-21所示形式:

代码8-21[{"lexeme": "ResNotF"},{"lexeme": "ResNotE"}
]

        上述数据的最大用途在于构建语义模型BindingItem。很明显,仅依靠上述信息无法完成该模型的实例化,因为缺少serviceType信息(参考代码8-5)。那么该信息去哪里了呢?这个问题需要通过代码进行解答。让我们看一下子分析器BindingParser的具体实现,其对应于非终结符BINDING,用于解析bind_rules代码块中的绑定项信息,如代码8-22所示:

代码8-22class BindingParser extends SequenceParser {private Token serviceType;private List<Token> aliases;BindingParser(String targetNode) {super(targetNode,new TerminalParser(TokenType.ID),new TerminalParser(TokenType.OPEN_BRACE),new AliasListParser("ALIAS_LIST", TokenType.CLOSE_BRACE),new TerminalParser(TokenType.CLOSE_BRACE),new TerminalParser(TokenType.SEMICOLON));parsers[0].setupCallback(this::acceptServiceType);parsers[2].setupCallback(this::acceptAliases);}
}

        通过构造函数可知,BindingParser由一系列的子分析器所构成。而当下我们最关心的serviceType信息,则是由TerminalParser来进行分析和维护的。所以,想要实例化BindingItem对象,我们必须将TerminalParser和AliasListParser的分析结果集成起来才可以。这也是为什么笔者一再强调,我们需要找到一种方式将信息从子解析器内部传到外部。使用全局变量(比如context对象)是一个不错的解决方案,除此之外也可以采用笔者这种回调的思路。

未完待续……

上一章  下一章

http://www.dtcms.com/a/617681.html

相关文章:

  • 小米电脑管家 V5.2.0.207 新版分享,镜像链接更稳定,AI自动亮度上线,分布式文件开放使用
  • 深入理解 Vue 3 中的计算属性与侦听器:联系、区别及与函数的对比
  • 2.FPGA板卡通过电脑映射连接上网
  • RTCP包之SR和RR
  • 40 token
  • 如何在 Celestia 区块链上构建验证者节点的详细手册
  • Linux权限知识点
  • MySQL: 数据库读写分离与负载均衡的实现方式及深度分析
  • 红帽企业Linux:企业级开源操作系统领航者
  • 怎么做网站开发建一个电商平台多少钱
  • 人工智能技术- 语音语言- 05 GPT-4o 自然人机对话
  • HarmonyOS实用指南:harmonyos + 华为
  • 什么是Spring Boot 应用开发?
  • uniapp实现android/IOS消息推送
  • 汽车网站开发流程html5 网站开发软件
  • HarmonyOS:harmonyos从入门到落地
  • OpenCV(二十九):高通滤波-索贝尔算子
  • 幽冥大陆(二十一)go语言智慧农业电子秤读取——东方仙盟炼气期
  • 北京网站建设需要花多少钱视觉冲击力的网站设计
  • 开发板上搭建nextcloud和minio服务
  • Dubbo监控中心全解析:构建微服务可观测性的基石
  • Rust 内存优化实战指南:从字节对齐到零拷贝
  • 【数据结构】常见时间复杂度以及空间复杂度
  • 2345中国最好的网址站非凡软件站
  • C 语言希尔排序:原理、实现与性能深度解析
  • 【期末网页设计作业】HTML+CSS+JS 电影网站设计与实现 影视主题网站(附代码)
  • react 的状态管理
  • 世界上最有趣的网站外贸稳中提质韧性强
  • 简单理解:DCDC(直流 - 直流转换器)和LDO(低压差线性稳压器)
  • 电科金仓国产数据库KingBaseES深度解析:五个一体化的技术架构与实践指南