abnf java实现_详细讲解如何利用Java实现组合式解析器?

30649b70ca9d56e6c88b5f5b084927c7.png

简介:Ward Cunningham 曾经说过,干净的代码清晰地表达了代码编写者所 想要表达的东西,而优美的代码则更进一步,优美的代码看起来就像是专门为了 要解决的问题而存在的。在本文中,我们将展示一个组合式解析器的设计、实现 过程,最终的代码是优美的,极具扩展性,就像是为了解析特定的语法而存在的 。我们还会选取 H.248 协议中的一个例子,用上述的组合式解析器实现其语法 解析器。读者在这个过程中不仅能体会到代码的美感,还可以学习到函数式编程 以及构建 DSL 的一些知识。

DSL 设计基础

我们在用编程语言(比如:Java 语言)实现某项功能 时,其实就是在指挥计算机去完成这项功能。但是,语言能够带给我们的并不仅 仅局限于此。更重要的是,语言提供了一种用于组织我们的思维,表达计算过程 的框架。这个框架的核心就在于,如何能够把简单的概念组合成更为复杂的概念 ,同时又保持其对于组合方法的封闭,也就是说,组合起来的复杂概念对于组合 手段来说和简单的概念别无二致。引用“Structure and Interpretation of Computer Programs”一书中的话来讲,任何一个强大的语言都是通过 如下三种机制来达成这个目标的:

原子:语言中最简单、最基本的实体;

组合手段:把原子组合起来构 成更复杂实体的方法;

抽象手段:命名复杂实体的方法,命名后的复杂 实体可以和原子一样通过组合手段组合成为更复杂的实体。

像 Java 这 种通用的编程语言,由于其所关注的是解决问题的一般方法。因此,其所提供的 这三种机制也都是通用层面的。在解决特定问题时,这种通用的手段和特定问题 领域中的概念、规则之间是存在着语义鸿沟的,所以某些问题领域中非常清晰、 明确的概念和规则,在实现时就可能会变得不那么清晰。作为程序员来说,用干 净的代码实现出功能仅仅是初级的要求,更重要的是要提升通用语言的层次,构 建出针对特定问题领域的语言(DSL),这个过程中很关键的一点就是寻找并定 义出面向问题领域的 原子概念、组合的方法以及抽象的手段。这个 DSL 不一定 非得像通用语言那样是完备的,其目标在于清晰、直观地表达出问题领域中的概 念和规则,其结果就是把通用的编程语言变成解决特定问题的专用语言。

我们曾经在“基于 Java 的界面布局 DSL 的设计与实现”一 文中,构建了一种用于界面布局的 DSL,其中呈现了上述思想。在本文中,我们 将以解析器的构造为例,来讲述如何构建一种用于字符串解析的 DSL,这种 DSL 具有强大的扩展能力,读者可以根据自己的需要定义出自己的组合手段。此外, 从本文中读者还可以领略到 函数编程的优雅之处。

解析器原子

什么是解析器?最简单的解析器是什么?大家通常都会认为解析器就是判断输入 的字符串是否满足给定的语法规则,在需要时还可以提取出相应的语法单位实例 。从概念上来讲,这种理解没什么问题。不过要想定义出用于解析的 DSL,那么 就需要更为精确的定义,也就是说我们要定义出解析器这个概念的确切类型。在 Java 中,我们用 interface 来定义解析器类型,如下:

interface Parser

{

public Result  parse(String target);

}

其中,target 为要解析的字符 串,Result 是解析的结果,只要满足这个接口语义的对象,我们就称其为一个 解析器实例。Result 的定义如下:

class Result

{

private String recognized;

private String  remaining;

private boolean succeeded;

private Result(String recognized, String remaining,

boolean succeeded) {

this.recognized = recognized;

this.remaining = remaining;

this.succeeded =  succeeded;

}

public boolean is_succeeded()  {

return succeeded;

}

public  String get_recognized() {

return recognized;

}

public String get_remaining() {

return remaining;

}

public static Result  succeed(String recognized,

String remaining) {

return new Result(recognized, remaining, true);

}

public static Result fail() {

return  new Result("", "", false);

}

}

其中, recognized 字段表示这个解析器所认识的部分,remaining 表示经过这个解析 器解析后所剩余的部分,succeeded 表示解析是否成功,Result 是一个值对象 。有了解析器的精确定义,接下来我们就可以定义出最简单的解析器。显然,最 简单的解析器就是什么也不解析的解析器,把目标字符串原样返回,我们称其为 Zero,定义如下:

class Zero implements Parser

{

public Result parse(String target) {

return Result.succeed("", target);

}

}

Zero 解析器一定会解析成功,不做任何语法单位识别并直接返 回目标字符串。下面我们来定义另外一个很简单的解析器 Item,只要目标字符 串不为空,Item 就会把目标字符串的第一个字符作为其识别结果,并返回成功 ,如果目标字符串为空,就返回失败,Item 的定义如下:

class  Item implements Parser

{

public Result parse (String target) {

if(target.length() > 0) {

return Result.succeed(target.substring(0,1),

target.substring(1));

}

return  Result.fail();

}

}

Zero 和 Item 是我们解析 器 DSL 中仅有的两个原子,在下一小节中,我们来定义解析器的组合方法。

解析器组合子

我们在上一小节中定义了 Item 解析器,它无条件 的解析出目标字符串中的第一个字符,如果我们希望能够变成有条件的解析,就 可以定义出一个 SAT 组合子,其接收一个条件谓词(predicate)和一个解析器 ,并生成一个复合解析器,该复合解析器能否解析成功取决于原始解析器的解析 结果是否满足给定的条件谓词,条件谓词和 SAT 的定义如下:

interface Predicate

{

public boolean  satisfy(String value);

}

class SAT implements  Parser

{

private Predicate pre;

private  Parser  parser;

public SAT(Predicate predicate,  Parser parser) {

this.pre = predicate;

this.parser = parser;

}

public Result  parse(String target) {

Result r = parser.parse (target);

if(r.is_succeeded() && pre.satisfy (r.get_recognized())) {

return r;

}

return Result.fail();

}

}

如果, 我们想定义一个解析单个数字的解析器,那么就可以定义一个 IsDigit 条件谓 词,并通过 SAT 把该 IsDigit 和 Item 组合起来,代码如下:

class IsDigit implements Predicate

{

public boolean satisfy(String value) {

char c =  value.charAt(0);

return c>='0' &&  c<='9';

}

}

解析单位数字的解析器 digit 定义如下:

Parser digit = new SAT(new IsDigit(), new  Item());

我们可以采用同样的方法组合出单个字母、单个大写 字母、单个小写字母等解析器来。

接下来,我们定义一个 OR 组合子, 其接收两个解析器,并分别用这两个解析器去解析一个目标串,只要有一个解析 成功,就认为解析成功,如果两个都失败,则认为失败,代码定义如下:

class OR implements Parser

{

private  Parser p1;

private Parser p2;

public OR (Parser p1, Parser p2) {

this.p1 = p1;

this.p2 = p2;

}

public Result parse (String target) {

Result r = p1.parse(target);

return r.is_succeeded() ? r : p2.parse(target);

}

}

我们可以定义出一个新的解析器 digit_or_alpha,如 果目标字符是数字或者字母那么该解析器就解析成功,否则就失败。代码如下:

判断是否是字母的条件谓词:

class IsAlpha  implements Predicate

{

public boolean satisfy(String  value) {

char c = value.charAt(0);

return (c>='a' && c<='z') || (c>='A' &&  c<='Z');

}

}

用于解析单个字母的解析器 :

Parser alpha = new SAT(new IsAlpha(), new Item ());

digit_or_alpha 解析器定义:

Parser  digit_or_alpha = new OR(digit, alpha);

下面我们来定义 一个 顺序组合子 SEQ,该组合子接收两个解析器,先把第一个解析器应用到目 标字符串,如果成功,就把第二个解析器应用到第一个解析器识别后剩余的字符 串上,如果这两个解析器都解析成功,那么由 SEQ 组合起来的这个复合解析器 就解析成功,只要有一个失败,复合解析器就解析失败。当解析成功时,其识别 结果由这两个解析器的识别结果连接而成。

为了能够连接两个 Result 中已经识别出来的解析结果,我们在 Result 类中新增一个静态方法:concat, 其定义如下:

public static Result concat(Result r1,  Result r2) {

return new Result(

r1.get_recognized().concat(r2.get_recognized()),

r2.get_remaining(), true);

}

顺序组合子 SEQ 的定义 如下:

class SEQ implements Parser

{

private Parser p1;

private Parser p2;

public SEQ(Parser p1, Parser p2) {

this.p1 =  p1;

this.p2 = p2;

}

public Result  parse(String target) {

Result r1 = p1.parse (target);

if(r1.is_succeeded()) {

Result r2 = p2.parse(r1.get_remaining());

if (r2.is_succeeded()) {

return Result.concat (r1,r2);

}

}

return Result.fail ();

}

}

现在,如果我们想定义一个解析器用以 识别第一个是字母,接下来是一个数字的情况,就可以这样定义:

Parser alpha_before_digit = new SEQ(alpha,  digit);

接下来我们定义本文中的最后一个组合子:OneOrMany。 该组合子接收一个解析器和一个正整数值,其生成的复合解析器会用原始解析器 连续地对目标串进行解析,每一次解析时的输入为上一次解析后剩余的字符串, 解析的最大次数由输入的正整数值决定。如果第一次解析就失败,那么该复合解 析器就解析失败,否则的话,会一直解析到最大次数或者遇到解析失败为止,并 把所有成功的解析的识别结果连接起来作为复合解析器的识别结果,OneOrMany 组合子的定义如下:

class OneOrMany implements Parser

{

private int max;

private Parser  parser;

public OneOrMany(int max, Parser parser)  {

this.max = max;

this.parser =  parser;

}

public Result parse(String target)  {

Result r = parser.parse(target);

return r.is_succeeded() ? parse2(r,1) : Result.fail();

}

private Result parse2(Result pre, int count) {

if(count >= max) return pre;

Result r  = parser.parse(pre.get_remaining());

return  r.is_succeeded() ?

parse2(Result.concat (pre,r),count+1) : pre;

}

}

使用该组合 子,我们可以容易地定义出用于识别由最少一个,最多 10 个字母组成的串的解 析器,如下:

Parser one_to_ten_alpha = new OneOrMany (10,alpha);

本文的组合子就定义到此,不过读者可以根据自己 的需要,用同样的方法容易地定义出符合自己要求其他组合子来。

抽象 的手段

如果在 DSL 的构造中,仅仅提供了一些原子和组合手段,并且组 合的结果无法再次参与组合,那么这个 DSL 的扩展能力和适用性就会大大折扣 。相反,如果我们还能提供出抽象的手段对组合结果进行命名,命名后的复合实 体可以像原子一样参与组合,那么 DSL 的扩展能力就会非常的强大,适用性也 会大大增加。因此,抽象的手段在 DSL 的构造过程中是至关重要的。

敏 锐的读者可能已经发现,对于我们的解析 DSL 来说,其实在前面的小节中已经 使用了抽象的手段。比如,我们在 alpha,digit,digit_or_alpha 以及 alpha_before_digit 等复合解析器的定义中已经使用了抽象的手段来对其进行 命名,然后可以直接使用这个抽象的名字再次参与组合。由于我们的解析器是基 于 Java 语言中的 interface 机制定义的,因此,Java 语言中已有的针对 interface 的抽象支持机制完全适用于我们的解析 DSL。因此,我们就无需定义 自己的特定抽象手段,直接使用 Java 语言中的即可。

相信读者已经从 上一小节中的例子中看到组合、抽象手段的强大威力。在下一小节中,我们将给 出一个更为具体的例子:H.248 协议中 NAME 语法解析器的构造。

一个 H.248 实例

在本小节中,我们将基于前面定义的解析器原子和组合子, 实现用于识别 H.248 协议中 NAME 语法的解析器的构造。

H.248 是一个 通信协议,媒体网关控制器使用该协议来对媒体网关进行控制。H.248 协议是一 个基于 ABNF(扩展 BNF)文法描述的基于文本的协议,协议中定义了 H.248 消 息的组成部分和具体内容。我们仅仅关注其中的 NAME 语法定义,如下:

NAME = ALPHA *63(ALPHA / DIGIT / "_" )

ALPHA =  %x41-5A / %x61-7A  ; A-Z, a-z

DIGIT = %x30-39        ; digits 0 through 9

我们首先来解释一下其中的一些 规则,*63 其实是 n*m 修饰规则的一个实例,表示最少 n 个最多 m 个,当 n 等于 0 时,可以简略写成 *m。因此,*63 表示最少 0 个,最多 63 个。/ 表 示或规则,表示两边的实体可选。()表示其中的实体必须得有一个。- 表示范 围。因此,DIGIT 表示单个数字,ALPHA 表示单个字母(大写或者小写), (ALPHA/ DIGIT/ “_” )表示要么是个字母,要么是个数字,要么是 个下划线。*63(ALPHA/ DIGIT/ “_” )表示,最少 0 个,最多 63 个字母或者数字或者下划线。两个实体顺序写在一起,表示一种顺序关系, ALPHA *63(ALPHA/ DIGIT/ “_” ) 表示,以字母开始,后面最少 0 个,最多 63 个 字母或者数字或者下划线。

根据前面的内容可以很容易 地直接表达出用于解析这个语法规则的解析器来。如下:

class  H248Parsec

{

public static Parser alpha() {

return new SAT(new IsAlpha(), new Item());

}

public static Parser digit() {

return  new SAT(new IsDigit(), new Item());

}

public static Parser underline() {

return new SAT (new IsUnderline(), new Item());

}

public  static Parser digit_or_alpha_or_underline() {

return  new OR(alpha(), new OR(digit(), underline()));

}

public static Parser zero_or_many(int max, Parser parser) {

return new OR(new OneOrMany(max,parser), new Zero ());

}

public static Parser name() {

return new SEQ(alpha(),

zero_or_many(64,

digit_or_alpha_or_underline()));

}

}

可以看出,我们的代码和协议中的语法描述基本上完全一样,我 们通过定义自己的面向解析的 DSL,把 Java 这种通用语言变成了用于 ABNF 语 法解析的专门语言,符合 Ward Cunningham 关于美的代码的定义。最后,我们 用该解析器来做一些关于 NAME 语法识别的实验,如下表所示:

14424bd8292c30e689fdaaf08fbb418d.png

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

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

相关文章

充电原理_电动汽车充电桩如何设置?充电桩原理介绍

随着新能源产业的蓬勃发展&#xff0c;电动汽车在生活中变得越来越普遍。比亚迪(BYD)&#xff0c;宝马(BMW)和特斯拉(Tesla)等汽车制造商都已经推出了全电动汽车&#xff0c;而混合动力汽车则更为普遍。为了能够方便地为这些电动汽车的电池充电&#xff0c;必须建立充电桩。充电…

java 获取服务器硬件_dell服务器远程获取硬件状态

以dell的R620型号的服务器做的测试登陆上dell服务器ilo的IP地址&#xff0c;首先打开ipmi&#xff0c;ilo2是直接支持ipmi2.0的此框需要点击 “IDRAC设置”->“网络”->“IPMI设置”在”启用LAN上IPMI“后的复选框打钩&#xff0c;才能启动ipmi好像是内置到了ilo2&#x…

简单可行性报告模板_项目可行性报告模板分享!第三章主要内容

项目可行性报告模板分享!第三章主要内容如下&#xff1a;第三章 市场分析与建设规模市场分析在可行性研究中的重要地位在于&#xff0c;任何一个项目&#xff0c;其生产规模的确定、技术的选择、投资估算甚至厂址的选择&#xff0c;都必须在市场需求情况有了充分的了解后才能解…

java外挂源码_2.7 万 Star!Github 项目源码辅助阅读神器

【导语】&#xff1a;一款用于将 Github 项目代码以树形格式展示的浏览器插件。简介大家平时逛 GitHub 是否会觉得查看源代码的体验十分糟糕&#xff1f;项目文件需要一层层点击&#xff0c;返回也要一层层返回。这样不直观&#xff0c;也比较麻烦。Octotree 是一款辅助阅读 Gi…

php教育网站设计案例_酒店装修,精品酒店设计装修案例,酒店设计网站

酒店设计需要考虑&#xff1a;设计酒店的时候也要顺应市场潮流&#xff0c;不再单一的提供休息、洗漱、睡觉的空间&#xff0c;还要能提供社交、商务等功能&#xff0c;同顾客产生情况共鸣。这样能够引领生活方式的、能够互动&#xff0c;有仪式感的酒店&#xff0c;是很吸引人…

写一个方法判断一个字符串是否对称_判断一个男生是否好色的方法

▾我们店只招黑喵哦?▾其实也不是不能理解为什么男的要跑?▾有什么相见恨晚的小知识&#xff1f;?▾见证奇迹?&#xff1a;是不是穿过去了&#xff01;&#xff1f;▾医学奇迹?▾卧槽流劈&#xff01;?真的超光速了&#xff01;▾细节很到位啊几位少年?▾昨有坟头蹦迪?…

matlab计算联合熵,如何用matlab软件计算一幅图像信息的熵以及两幅图像间的联合熵?...

%计算一副图像的熵%随机生成图像Afloor(rand(8,8).*255);[M,N]size(A);tempzeros(1,256);%对图像的灰度值在[0,255]上做统计for m1:M;for n1:N;if A(m,n)0;i1;elseiA(m,n);endtemp(i)temp(i)1;endendtemptemp./(M*N);%由熵的定义做计算result0;for i1:length(temp)if temp(i)0;…

自然水体辐射特性与数值模拟 pdf_OpenGMS系列讲座(十三)汪亚平教授:南黄海水动力过程和辐射沙脊群演化...

2020年8月28日&#xff0c;南京大学海岸与海岛开发教育部重点实验室汪亚平教授应地科院陈旻教授邀请&#xff0c;作客南京师范大学虚拟地理环境教育部重点实验室OpenGMS系列讲座&#xff0c;并做了题为"南黄海水动力过程和辐射沙脊群演化" 的报告。汪教授首先通过两个…

matlab2014a 3d标定,[转载]张的matlab摄像机标定

自己写了下matlab张的标定程序&#xff0c;采用张正有网站上的数据&#xff0c;即数据文档Model.txt data1.txt data2.txtdata3.txt data4.txt data5.txt。其中Model.txt为世界坐标系中的点【实验结果】我的程序&#xff1a;k1 -0.2286 k2 0.1903 fx 832.5000fy 832.5298 cx 30…

小括号教学设计导入_【教资面试】语文政治历史地理教学设计答题技巧!

语文1.确立教学目标的依据 (1)基础教育课程改革课程改革要求语文课程必须面向全体学生&#xff0c;使学生获得基本的语文素养。对知识与技能&#xff0c;过程与方法&#xff0c;情感态度与价值观三个方面目标的整合&#xff0c;是语文新课程的价值追求。 根据语文学科工具性与人…

360浏览器卸载_陈蛋蛋碎碎念—如何完美地卸载流氓软件

陈蛋蛋碎碎念—如何完美地卸载流氓软件很多小伙伴都会有困扰&#xff0c;明明我就是只下载了一个软件啊&#xff0c;怎么电脑上莫名其妙地多了一大堆软件&#xff0c;什么我是渣渣辉一刀就是99级的游戏&#xff0c;什么影视全家桶&#xff0c;又或者是各种各样的浏览器&#xf…

java int相除向上取整_Java基础篇——Java运算符

Java运算符按功能可分为&#xff1a;算数运算符、关系运算符、逻辑运算符、位运算符、赋值运算符和条件运算符。算数运算符算术运算符包括通常的加&#xff08;&#xff09;、减&#xff08;-&#xff09;、乘&#xff08;*&#xff09;、除&#xff08;/&#xff09;、取模&am…

nginx php access denied,LNMP 解决Access Denied错误详细介绍

处理搭建好LNMP环境之后&#xff0c;呈现了Access Denied错误搭建好LNMP环境之后&#xff0c;呈现了Access Denied错误&#xff0c;现已扫除掉文件权限的问题也扫除掉是Nginx的问题&#xff0c;而是无法解析PHP的问题。发现网上的很多大牛都是经过Nginx的log来排查错误&#xf…

dp主机_MODBUS 和 PROFIBUS-DP 协议有什么区别

modbus协议和 profibus DP协议两者的区别和用途主要在哪里&#xff1f;相比好多工控人都有这个疑问。今天小编带大家一起学习一下。一、modbus协议和 profibus DP协议综述Modbus协议是应用于电子控制器上的一种通用语言。通过此协议&#xff0c;控制器相互之间、控制器经由网络…

ios上架图片在线制作_不同风格gif在线制作,公众号动态图片制作方法

现在运营公众号的主要方式就通过文章来进行宣传推广&#xff0c;在公众号文章中使用GIF动态图是非常常见的一种图片展现的方式&#xff0c;让文章整体效果看起来更加的生动有趣&#xff0c;所以很多的运营者在日常的工作和生活中会手机许多的动图素材&#xff0c;方便以后的使用…

table tr省略后鼠标移入显示相应信息_中考来了,人机对话、信息技术考试要求看过来...

半岛记者 魏海洋今年的中考将拉开大幕&#xff0c;九年级英语听说人机对话考试(以下简称“人机对话考试”)将于4月20日(周六)至21日(周日)进。八年级信息技术考试将于4月22日(周一)进行&#xff0c;市招考办提醒广大考生&#xff0c;按照准考证规定时间及时参加考试。关于两门考…

大数据平台容量评估_大数据平台

系统概述大数据应用支撑平台提供数据支撑服务&#xff0c;对外发布数据服务进行数据价值变现。包含数据采集、数据治理、数据交换、数据存储、数据计算相关组件的搭建、验证&#xff0c;并建立大数据仓库。b)功能要求1.数据采集&#xff0c;大数据平台数据源层有各类型数据源&a…

oracle数据库访问sqlserver2008,透过SQL Server 2008访问Oracle 10g的配置方法

之前写过一篇关于SQL Server 访问MySQL数据库的文章&#xff0c;最近正好又遇到需要访问Oracle 的情况&#xff0c;将配置过程记录下来也供大家参考。准备工作事先在需要访问Oracle 数据库的主机上完成以下工作&#xff1a;1. 安装SQL Server 数据库&#xff1a;SQL Server 200…

局域网限速软件_2号破解app重器推荐一款强大的快捷软件

破解版精破解版精品软件一些软件需要使用者付费购买才能使用其所有功能(或者才能解除使用期限)&#xff0c;这时一些计算机高手就破解这个软件&#xff0c;使其不用付费也可以完全使用全部功能(或者永久使用)&#xff0c;这种软件就叫破解版软件。此公众号中的破解版软件都源于…

平板电脑办公软件_大屏平板互动软件-平板电脑触摸大屏控制软件

随着数字化时代的到来&#xff0c;触摸大屏软件的应用范围&#xff0c;更加宽广&#xff0c;无论走到哪里都能够见到&#xff0c;各式各样的触摸屏一体机、LED液晶屏&#xff0c;拼接屏等多种展示器&#xff0c;在这些设备上面&#xff0c;均能够安装着各种功能不同软件&#x…