两百行写一个递归下降解析器

来源:互联网 发布:必利劲有依赖性吗 知乎 编辑:程序博客网 时间:2024/04/29 21:32

Peter Cooper最近看到Peter Cooper 用Ruby 实现了一个Recursive Descent Parser。让我惊讶的是,作者仅仅用了255 行Ruby 代 码就实现了。于是我决定分析一下这个神奇的rdparser。

先看看它怎么用吧。

初始化需要定义语法规则:如果左边是终结符,右边对于正则表达式;如果是非终结符,对于非终结符或终结符组成的字符串。并列规则用竖线("|")连接。

 

输出如下:

 

对照下面的图就更明白一些了。

rules

图上有一些位置上的错误,比如brkt-expression 的子结点从左至右的顺序应是(, expression, )。我是用ruby-graphviz 画的,有些问题还没找到解决办法。

rdparser 先把输入规则转换成hash。传给parse_section 分析,得到一个类似s-exp 的数列。最后交给text_syntax_tree 显示出来。

首先分析它是怎么把rules 转变成hash 的。我把代码简化了一下。

 

yield 获得代码块想调用g.×× 但没找到,这时发现了method_missing,把方法名和原本要传递给××方法的所有参数传递给它。

这里还要解释一下

 

经常看到这种用法,这次好好地查了一下。相当于以下代码:

 

这是用来检查是否定义了@h。

接着把起始规则(:main)和待解析的字串(content)传给parse。

 

to_sym 是把字符串变为symbol。Scanner 是作者定义的一个包装类。Scanner.new 其实很简单。

 

其中用到了strscan 库的StringScanner 类。strscan是一个字符串扫描器库。StringScanner 对象是由被扫字符串和"扫描点"构成的. 扫描点是一个索引值,它表示已扫描的位置. 刚开始的时候, 扫描点位于字符串头部, 此时(且只有此时)扫描点将尝试进行匹配. 若匹配成功则推进扫描点.

重点是parse_section。伪代码:

parse_section(rule)

    @depth += 1

    rule 对应的各条并列的语法规则ruleset :

        对ruleset 的各个语法元素r :

            suboutput = []

            如果当前字符串的某个前缀能匹配上r :

                把[{非终结符=>匹配的字串}] 添加进suboutput

        如果全匹配上了 :

            @depth -= 1

            return output

框架如下:

 

sub_rulesets 把那些用竖线(“|”)分隔的规则断开。而sub_rules 把各条规则用空格断开的语法单元全部抽出来。接着一个接一个地分析这些语法元素。主要是根据后缀(s 就相当于正则表达式中的+,? 和正则表达式中的? 作用一模一样)分类讨论。

最后一个关键:match_for_rule,用来根据语法规则解析字串的内容。

 

Peter 还提供了一个显示词法解析树的打印程序:

如果是数组就依次显示各个元素;如果是hash 表,才进入正题,根据depth 缩进显示。

 

抛开注释和调试语句,真正的程序不到161 行。啊,god-like。

顺便说一句,把DEBUG 置为true 就可以看到浩如烟海的调试信息。作者调试的方法很有趣。还记得Ruby 的每条语句都是一个表达式吗,还记得布尔表达式的短路求值吗?

 

如果DEBUG 为false,就不会执行debug_message。

P.S.: 看了代码之后觉得Peter 大哥很牛,去翻了一下他的主页,原来他是Ruby Inside 的写手,写过N多作品。总之,神牛!

原创粉丝点击