没有合适的资源?快使用搜索试试~ 我知道了~
解析器组合子:Smalltalk语法的可执行规范设计与实现
理论计算机科学电子笔记193(2007)3-18www.elsevier.com/locate/entcs新话中的可执行语法GiladBrachaCadence DesignSystems美国摘要我们描述了一个解析器组合子库的设计和实现在新语,一个新的语言在Smalltalk家庭。使用我们的库编写的解析器与BNF非常相似;它们几乎完全没有解决方案空间(即,编程语言)工件。我们的系统允许语法被指定为一个单独的类或mixin,独立于依赖于它的工具,如解析器,语法着色器等,因此,我们的语法作为一个共享的可执行规范的各种语言处理工具。这促使我们使用术语可执行语法。 我们讨论了语言的特点,使这些令人愉快的结果,而且,相反,我们的库给静态类型系统带来的挑战关键词:解析器组合子,动态编程语言,Smalltalk,反射1引言Newspeak是一种编程语言,源自Smalltalk,目前正在开发中。我们已经在Newspeak 中开发了一个解析器组合子库,并发现Smalltalk和Newspeak具有使它们非常适合实现和使用这样一个库的属性。解析器组合子的概念在函数式编程中有很长的历史[11,13,21]。从面向对象的角度来看,基本思想是将BNF的操作符视为方法,称为组合子,这些方法对表示语法结果的对象进行操作。每个这样的对象都是一个解析器,它接受由特定产生式生成的语言。combinator调用的结果也是这样的解析器。为了使这一点具体化,让我们来看看一个相当标准的标识符规则1 电子邮件地址:gilad@cadence.com1571-0661 © 2007 Elsevier B. V.在CC BY-NC-ND许可下开放访问。doi:10.1016/j.entcs.2007.10.0044G. Bracha/理论计算机科学电子笔记193(2007)3||id=字母(字母|数字)*我们可以用新话来表达这一点:id= letter,(letter|数字)星假设letter是接受单个字母的解析器,digit是接受单个数字的解析器。子表达式letter digit调用接受字母的解析器上的方法,并带有接受数字的参数结果将是一个接受字母或数字的解析器然后我们在结果上调用方法star(信|数字)星其产生接受字母或数字的零次或多次出现的解析器。我们将这个解析器作为参数传递给方法“”,我们在letter上调用它。“它返回一个解析器,首先接受接收者的语言,然后接受其参数的语言在我们的示例中,结果接受一个字母,后面跟着零个或多个字母或数字,正如我们所期望的那样最后,我们将这个结果绑定到id。您现在应该对解析器组合子的工作方式有了基本的直觉 以下是本文其余部分的路线图第2节展示了一个完整的可执行语法的例子。然后,第3节到第6节讨论了我们框架的一些设计问题和特性。特别地,第4节展示了语法和处理之间的模块化分离。本文提出了未来改进的想法(第7节);在第7.1节中,我们分析了使用库构建的语法和解析器的类型安全性,以及需要什么机制来静态检查它们。我们讨论了相关的工作(第8节),并以第9节结束。2一个完整的示例我们现在考虑一个小文法,并说明如何在我们的框架中表示它.在此过程中,我们将突出一些有趣的设计问题。我们将使用以下语法表达式= idreturnStatement =返回表达式。这种语法依赖于终端符号id,letter和digit,定义为:G. Bracha/理论计算机科学电子笔记193(2007)35|||数字= 0 - 9字母=(a - z)|(A -Z)id = letter(letter|数字)*我们在新话中表示如下:classExampleGrammar1 = ExecutableGrammar(digit = charBetween:$0 and:$9.letter=(charBetween:$a and:$z)(charBetween:$A and:$Z). id = letter,(letter digit)star.identi fier = tokenFor:id. hat =tokenFromChar:$nums.表达式=标识符。return Statement = hat,expression.)()我 们 定 义 了 一 个类 ExampleGrammar 1 , 它 是 我 们 框 架 中 的 主 类ExecutableGram-mar的子类。我们语法中的每个产生式都有一个对应的实例变量,或slot。所有对新话中插槽的访问都是通过方法调用。只要声明了一个槽,语言就会自动为其定义访问器。任何使用槽名的行为都意味着调用它的访问器。这意味着我们可以自由地改变一个对象的表示,只要我们保持它的方法接口,就不需要改变任何代码--甚至对象自己的类中的代码也不需要这种表示独立代码的概念在Emerald [3],Trellis/Owl [18]和Self[20]中率先提出,并且在Scala [16]中也得到了支持对象的插槽在创建对象时初始化。如果槽具有显式初始化器,则计算初始化器并将槽设置为结果. 否则,slot的值最初为nil。在这里,槽被初始化为它们对应的解析器,所以每个槽定义给出了语法的产生式规则。数字和字母的规则与语法直接对应。 他们使用继承的实用程序-方法charBetween:and:,它产生一个接受给定范围内的字符的解析器。id的规则和我们之前看到的一样。然而,在这一点上,很明显,词法分析和解析之间缺乏区别是一个问题。传统上,我们依靠词法分析器来标记输入。这样,它就消除了空白(忽略使空白变得重要的语言)和注释。这是由操作符tokenFor:处理的,该操作符接受解析器p并返回一个跳过任何前导空格|6G. Bracha/理论计算机科学电子笔记193(2007)3||然后评论并接受p接受的东西。这个解析器还将开始和结束源代码索引附加到结果中,这在将解析器集成到IDE中定义一个产生这种标记化结果的生产标识符是有用的终 端 字 符 串 的 规 则 hat 与 此 类 似 , 但 依 赖 于 一 个 方 便 的 方 法tokenFromChar:它解析给定的字符并将其标记化。我们现在可以定义语法的语法元素,而无需考虑空格或注释,就像我们在传统的BNF中一样3互递归语法规则通常是相互递归的。作为一个例子,让statement = ifStatement返回Statementif语句= if表达式then语句else语句我们2016年01月01日@上午10时30分01秒if = tokenFromSymbol:#if.then = tokenFromSymbol:#then. else =tokenFromSymbol:#else.ifStatement = if,expression,then,statement,else,statement.statement = ifStatement| return语句。)()因为我们的语法嵌入在Newspeak类中,所以我们可以利用Newspeak然而,似乎有一个问题:当初始化ifStatement时,我们使用了尚未初始化的语句。颠倒这两者之间的顺序不会有帮助,因为产生式是相互递归的。在像Haskell [17]这样的懒惰语言中,这不是一个问题-这是Haskell非常擅长定义领域特定语言的一个关键原因。 然而,新话并不是一种懒惰的语言解决这个问题的一种方法是让我们的解析器组合子将闭包而不是解析器作为参数。事实上,我们框架的早期版本正是这样做的。而不是写|G. Bracha/理论计算机科学电子笔记193(2007)37return Statement = hat,表达式我们会写return Statement = hat,[表情]对于使用闭包作为解析器组合子的参数,程序员可能会错误地将解析器作为参数直接传递。事实上,这不是一个问题。我们早期的框架动态地检测到了这样的错误。尽管检测是动态的,但它发生在语法的构造过程中,而不是在实际的解析过程中。其效果基本上与静态检查相同。此外,允许组合子接受解析器或闭包是微不足道的。然而,闭包的使用降低了语法的清晰度。幸运的是,Smalltalk(以及Newspeak)中的闭包语法是非常轻量级的,但是我们仍然倾向于完全避免闭包。闭包的使用引入了一种令人不安的不对称性,其中组合子的接收者是解析器,但其参数是一个计算解析器的闭包我们的框架不依赖于闭包或惰性,而是在执行槽初始化器之前使用反射将每个生产的槽绑定到类ForwardReferenceParser的实例ForwardReferenceParser的语法分析器支持通常的解析器组合子,并且最初是未绑定的。当一个产品,如ifStatement,这是相互依赖的另一个(例如,语句),则访问另一个生产的槽。在我们的示例中,语句将返回一个For- wardReferenceParser,语法的构造将继续进行.前向引用被延迟地绑定到适当的解析器,当它们第一次被调用解析时,它们将所有后续调用转发到该解析器4语法处理通过编写一个与语法紧密对应的程序来定义解析器的能力是很有吸引力的。 然而,仅仅接受一种语言并不是那么有用。通常,需要执行某些处理(例如,以产生AST作为结果)。为了满足处理的需要,我们在解析器上引入了一个新的操作符,wrapper:。这个操作符的结果是一个解析器,它接受与接收器相同的语言。然而,它从解析中产生的结果会影响到其他的结果;在解析过程中,接收器产生的结果会传递给一个闭包,wrapper:将其作为唯一的参数。总体结果是闭包返回的结果--通常是一个抽象语法树。8G. Bracha/理论计算机科学电子笔记193(2007)3|||||classExampleGrammar3 = ExecutableGrammar(digit = charBetween:$0 and:$9.letter=(charBetween:$a and:$z)(charBetween:$A and:$Z). id =letter,(letter digit)starwrapper:[:fst:sndfst asString,(StringwithAll:snd)]。标识符= tokenFor:idken; start : v start; end : vend]。hat=tokenFromChar:$token 。 表 达 式 = 标 识符。wrapper:[:v| VariableAST新名称:v为-returnStatement = hat,expressionStatAST new expr:e; start:r start; end:e end].)()wrapper:[:r:e|返回-这一点已在上文说明。id、identi fier和returnStatement的语法生成已经通过使用wrapper:进行处理而得到增强。语法仍然清晰可辨,AST生成在单独的行上。但是,最好保持语法完全分离。因此,我们将AST生成代码移到一个子类中,在那里语法产生式访问器将被重写,如下所示public voidrun()(id =(超级用户IDwrapper:[:fst:snd |fst asString,(String withAll:snd)])2、函数A =(超级鉴别器ken; start:v start; end:vend]。)return Statement =(超级回报声明wrapper:[:v |VariableAST新名称:v为-wrapper:[:r:e ReturnStatAST new expr:e; start:r start; end:e|G. Bracha/理论计算机科学电子笔记193(2007)39end]。))例如,如果希望解析相同的10G. Bracha/理论计算机科学电子笔记193(2007)3|||语言并将其提供给不同的后端,每个后端都接受自己的AST;或者如果需要将解析器用于不同的目的,例如语法着色,但希望共享语法。我们可以用扩展语法做类似的事情。然而,在这里,我们发现单一继承给我们带来了一个问题。我们的解析器应该扩展ExampleGrammar2还是ExampleParser1?显然,我们需要扩大两者。扩展解析器的适当超类是ExampleParser1混合|>示例语法2将 类 ExampleParser1 隐 式 定 义 的 mixin [6 , 7 , 1] 应 用 于 类ExampleGrammar2。public voidrun(){if =(超级ifStatementwrapper:[:ifKw:e:thenKw:s1:elseKw:s2如果StatAST new cond:e; true分支:s1; false分支:s2;start:ifKw start; end:s2 end]))这种模块化结构是可执行语法最显著的特征,在这种结构中,语法是独立的、可重用的可执行组件5数据库解析器一些解析器组合子库支持命名解析器。这使得更容易调试语法中的错误。在报告错误时,打印出产品的名称不幸的是,以这种方式命名产品会增加混乱并模糊语法。它还迫使程序员重复规范中已经给出的信息,例如:id =letter,(letterdigit)star.ID名称:#ID。在这里,名称id已经在周围语法的上下文中被赋予了产生式。然而,解析器本身并不知道这一点,直到在上面的第二行中显式地告诉它它的名称。而我们G. Bracha/理论计算机科学电子笔记193(2007)311|||{{{--framework分别遍历语法,并根据周围上下文中用于产生的名称命名各种解析器。不需要冗余和分散注意力的命名组合符6错误处理为了在解析器组合子框架中支持良好的错误报告,必须小心。考虑语法def =标识符stmt* stmt =(ifStmtexpr)点ifStmt = if expr then stmt else stmt if expr thenstmt expr = identifier:= expr号码识别器给定输入Foov:= 3。如果为真,则s:= 5,否则s:= 7。}在解析器组合子的简单实现中,解析失败Foov:= 3。“}”应为!->如果真正的错误是没有终止if的true分支声明如果解析器失败,情况会更清楚,如下所示:Foov:= 3。如果为true,则s:= 5期待!-> 其他实际发生的情况是,在解析if语句时,由于缺少"“,ifStmt结果的两个变体(有else和没有else)解析都失败了五点以后。然后解析器使用expr产生式。expr的前两个选项失败,但if作为标识符成功解析。但是,没有“。if之后,因此生产STMT失败。此时,控制返回到stmt*,它已经成功解析了一条语句,很高兴停止尝试并成功返回,将输入回滚到第一条语句(v)之后|12G. Bracha/理论计算机科学电子笔记193(2007)3}⟨ ⟩⟨ ⟩* = 3。)现在,def的产生式查找一个“",但没有找到,并且解析在给定消息的token if处失败。为了缓解这个问题,我们的框架记录了在解析过程中检测到的最深(输入中最右边)的错误。如果解析成功,任何这样的中间失败都是无关紧要的。但是,如果解析失败,则需要报告最深的错误,而不是最新的错误。 这将是错误的抱怨失踪5、如我所愿7未来发展7.1类型检查我们已经看到的模式,即语法与其处理分离,在坚持完全静态的类型检查时不容易维护。为了探讨这些问题,我们将用一种与当前主流编程语言风格相似的假设静态类型语言重新构建一些示例。假设 ExampleParser1不是 ExampleGrammar1的子 类型 。 例如 ,在ExampleParser1中,returnStatement返回一个返回ReturnStatAST的解析器,而在ExampleGrammar1中,returnStatement方法返回一个返回集合的解析器。这些类型是不相关的。一般来说,文法中的每个产生式都有对应的方法,并且在大多数情况下,子类方法的类型不是超类中类型的子类型。在大多数静态类型的面向对象编程语言中,这种情况是非法的。尽管如此,我们的设计是正确的,在执行过程中不会导致类型错误。Example-Grammar 1中对应于结果的所有方法的使用仅依赖于这些方法返回ExecutableGram- mar的事实;它们不依赖于解析器在解析时返回的对象类型。假设我们定义了一个接口类型S,它包含了ExampleGrammar类型的所有方法,除了它们都返回了ExecutableGrammar类型。二、假设self的类型是S的一个子类型,我们可以成功地对ExampleGrammar1进行类型检查。这表明,ExampleGrammar1的子类是S的子类型,以便保持ExampleGrammar1本身的类型安全性是足够的特别地,ExampleParser 1是S的一个子类型;其中的每个产生式方法都返回一个ExecutableGrammar X,对于某个X。一种可能性是避免将ExecutableGrammar设置为泛型类型。这不可避免地导致对动态类型检查的依赖,因为2我们这里的符号类似于Java的Java类型[19]G. Bracha/理论计算机科学电子笔记193(2007)313- -- -解析将是Object类型,并且将需要转换和/或模式匹配来恢复类型信息以供进一步处理。 我们将不再进一步探讨这个方向,因为我们的兴趣在于探索如何静态地对可执行语法进行类型检查。接口AbstractGrammarextendsExecutableGrammar<?>ExecutableGrammarE>expression();ExecutableGrammarR>returnStat ( ) ; ExecutableGrammarToken> hat();}classExampleGrammar1R,E>extendsExecutableGrammarR>typeThis = AbstractGrammarR,E>;公共ExecutableGrammarhat()... ;公共ExecutableGrammar
- >expression().公共ExecutableGrammar(Token,E)>returnStat()返回int n();}classExampleParser1extendsExampleGrammar1ReturnStat,ExpressionAST>公共公共implementsAbstractGrammar
下载后可阅读完整内容,剩余1页未读,立即下载
cpongm
- 粉丝: 5
- 资源: 2万+
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
最新资源
- 前端协作项目:发布猜图游戏功能与待修复事项
- Spring框架REST服务开发实践指南
- ALU课设实现基础与高级运算功能
- 深入了解STK:C++音频信号处理综合工具套件
- 华中科技大学电信学院软件无线电实验资料汇总
- CGSN数据解析与集成验证工具集:Python和Shell脚本
- Java实现的远程视频会议系统开发教程
- Change-OEM: 用Java修改Windows OEM信息与Logo
- cmnd:文本到远程API的桥接平台开发
- 解决BIOS刷写错误28:PRR.exe的应用与效果
- 深度学习对抗攻击库:adversarial_robustness_toolbox 1.10.0
- Win7系统CP2102驱动下载与安装指南
- 深入理解Java中的函数式编程技巧
- GY-906 MLX90614ESF传感器模块温度采集应用资料
- Adversarial Robustness Toolbox 1.15.1 工具包安装教程
- GNU Radio的供应商中立SDR开发包:gr-sdr介绍
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功