向PEG语法添加动作

如果您可以根据规则添加(某些)语义,则语法会更好。 特别是,对于我正在开发的Python分析器,我需要从每个替代方法返回AST节点,因为我想坚持使用CPython中的当前AST实现。



许多语法使用一种约定,该约定允许您向规则添加操作-通常是{curly方括号}中的一块代码。 更确切地说,它们与替代方案联系在一起。 该块中的代码用与编译器其余部分相同的语言编写,例如,用C语言编写,并辅以某种引用元素的能力。 在原始的pgen Python中,我没有添加此功能,但是对于一个新项目,我想实现它。


这是我作为本系列文章的一部分开发的简化解析器生成器的处理方法。


操作的语法通常是这样的:


 rule: item item item { action 1 } | item item { action 2 } 

由于这会使语法更加冗长,因此解析器生成器通常允许使用多行规则,例如:


 rule: item item item { action 1 } | item item { action 2} 

这使解析器稍微复杂一点,但是对于可读性来说很重要,因此我将支持这样的记录。


永恒的问题是何时执行此块。 在Yacc / Bison中,由于令牌列表中没有回滚,因此在解析器识别出规则后立即执行此操作。 仅对每个动作执行一次就意味着可能存在全局副作用(例如更新符号表或其他编译器数据结构)。


在无限制返回令牌列表的PEG解析器中,我们有几种选择:


  • 在分析完所有内容之前,请勿执行任何操作。 我不会考虑这一点,因为我想在解析期间构建一个AST。
  • 只要识别出它的替代方法,就执行。 要求它们的代码是幂等的(即,无论执行多少次,效果都相同)。 这意味着可以执行该动作,但是其结果最终可能会被丢弃。
  • 缓存结果并仅在首次识别出该位置的操作时才执行该操作。

我选择了第三个选项-无论如何,我们使用packrat算法缓存方法的结果,因此我们也可以缓存结果。


至于{curlies}中的内容,按照惯例,它使用C代码,并在$基础上达成协议,以指代公认替代方案中的元素(例如, $1指代第一个元素),并分配$$以指示操作结果。 听起来很陈旧(我有使用Algol-60中的函数赋值来表示返回值的记忆),所以我将其变得更Pythonic:在方括号内,您需要放置一个表达式,其结果将是操作的结果,并且与元素的链接将是提供元素文本的简单名称。 例如,这是一个可以加减数字的简单计算器:


 start: expr NEWLINE { expr } expr: expr '+' term { expr + term } | expr '-' term { expr - term } | term { term } term: NUMBER { float(number.string) } 

让我们以100 + 50 - 38 - 70为例执行它。 他将计算答案,因为 他通过计算((100 + 50) - 38) - 70 (当然是42 ((100 + 50) - 38) - 70来识别零件。


一个小细节:在term操作中term变量number包含TokenInfo对象,因此您需要使用其.string属性以字符串形式获取令牌。


当替代方案有多次出现且规则名称相同时,我们该怎么办? 解析器生成器为每个事件赋予唯一的名称,并添加1等。 对于同一替代方案中的后续事件。 例如:


 factor: atom '**' atom { atom ** atom1 } | atom { atom } 

完整的实现很无聊,所以我不想赘述。 我邀请您查看我的存储库并使用以下代码


 python3.8 -m story5.driver story5/calc.txt -g story5.calc.CalcParser 

现在,可视化使您可以使用左右箭头键来回移动!


本文和引用代码的许可: CC BY-NC-SA 4.0

Source: https://habr.com/ru/post/zh-CN471988/


All Articles