工程中的编译原理 -- Mapfile解析器

前言

Mapfile是MapServer用来描述一个地图的配置文件。它是一个很简单的声明式语言,一个地图(Map)可以有多个层(Layer),每个层可以有很多属性(键值对)。在一个层的定义中,还可以定义若干个类(Class),这个类用以管理不同的样式(Style)。而每个类或者样式都可以由若干个属性(键值对)。

这里有一个实际的例子:

最简单的层的定义

最简单的情形是,我们定义了一个层Layer,但是没有指定任何的属性:

我们期望parser可以输出:

要做到这一步,首先需要定义符号LAYER和END,以及一些对空格,非法字符的处理等:

对于,空格,回车换行等,我们都直接跳过。对应的BNF也非常简单:

为层添加属性

接下来我们来为层添加Name属性,首先还是添加符号NAME和对字符串的定义。这里的字符串被定义为:由双引号括起来的所有内容。

然后我们就可以为BNF添加一个新的节:

在decl中,我们将获得的字符串两头的引号去掉$2.substring。这样decl的值就会是字符串本身,而不是带着双引号的字符串了。修改之后的代码可以解析诸如这样的声明:

并产生这样的输出:

但是如果我们用来解析两个以上的属性:

解析器会报告一个错误:

即,期望一个END符号,但是却看到了一个WORD符号。我们只需要稍事修改,就可以让当前的语法支持多个属性的定义:

先看,pair的定义,它由NAME STRING或者DATA STRING组成,是我们语法中的终结符。再来看pairs的定义:

这个递归的定义可以保证我们可以写一条pair或者多条pairs pair属性定义语句。而对于多条的情况,我们需要将这行属性规约在一起,即当遇到这样的情形时:

我们需要产生这样的输出:{name: “counties”, data: “counties-in-shaanxi-3857”}。但是由于符号是逐个匹配的,我们会得到这样的匹配结果:{name: “counties”}和{data: “counties-in-shaanxi-3857”},因此我们需要编写一个简单的函数来合并这些属性:

按照惯例,这种自定义的函数需要被定义在%{和}%括起来的section中:

现在我们的解析器就可以识别多条属性定义了:

嵌套的结构

现在新的问题又来了,我们的解析器现在可以识别对层的对个属性的解析了,不过由于CLASS并不是由简单的键值对定义的,所以还需要进一步的修改:

类由CLASS关键字和END关键字定义,而类的属性定义和Layer的属性定义并无二致,都可以使用pairs(多条属性)。而classes事实上是pair的另一种形式,就像对属性的定义一样,所以:

这样,解析器就可以识别CLASS子句了。我们注意到,在CLASS中,还可以定义STYLE,因此又需要稍作扩展:

这样,我们的解析器就可以处理样例中的所有语法了:

完整的代码在github上的这个repo中

总结

使用BNF定义一个复杂配置文件的规则,事实上一个比较容易的工作。要手写这样一个解析器需要花费很多的时间,而且当你需要parser多种配置文件时,这将是一个非常无聊且痛苦的事情。学习jison可以帮助你很快的编写出小巧的解析器,在上面的Mapfile的例子中,所有的代码还不到100行。下一次再遇到诸如复杂的文本解析,配置文件读取的时候,先不要忙着编写正则表达式,试试更高效,更轻便的jison吧。

1 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部