Vczh Library++语法分析器开发指南
前言
在日常的开发工作中我们总是时不时需要写一些语法分析器。语法分析器不一定指的是一门语言的编译器前端,也有可能仅仅是一个自己设计格式的配置文件的读写程序,或者是一门用来简化我们开发的DSL(领域专用语言)。我们可以选择使用XML,不过因为XML的噪音实在是太多,所以自己写语法分析器在有些情况下是必要的,特别是那种经常需要修改的文件,使用XML有时候会增加我们的负担,除非我们专门为此开发一个编辑器程序。
这篇文章将紧密结合一个带函数的四则运算计算器的例子(DocumentationSamplesExpressionCalculatorExpressionCalculator.sln)来说明如何使用Vczh Library++提供的工具来大幅度简化我们的语法分析器的开发,并最终给出一个可以编译的例子。虽然这个例子实在是老掉牙了,不过开发一个四则运算计算器可以覆盖大部分开发语法分析的过程中会遇到的问题,所以也不失为一个好的例子。
这个例子可以在Vczh Library++的代码里面找到。
制定语法
我们需要对带函数的四则运算计算器下一个定义,这样我们才可以有目的地完成这个任务。我们对四则运算式子是很熟悉的,一个四则运算式子包含加减乘除、括号和数字。我们还可以支持负号:-a,其实是(0-a)的简写形式。那么什么是支持函数呢?这里我们只考虑单参数函数的情况,譬如说三角函数和对数指数等等。譬如说下面的式子就是满足定义的带函数的四则运算式子:
sin(1+2) + cos(3*-4)
Vczh Library++使用语法的角度来对待一个字符串,因此我们可以把上面的定义转换成语法。一个语法用来表示字符串的一个子集。我们可以通过语法来表达什么样的字符串是满足规定的,什么样的字符串是不满足规定的。不过一个具有现实意义的语法总是会有一些局限性的,譬如说你很难用上下文无关的文法来表达一个字符串:a…ab…bc…c,其中三种字母的数量都相等。幸好在绝大多数情况下我们都不需要去面对这些高难度的问题,因此可以用一些简单的规则来处理:
RULE = EXPRESSION
RULE是这个规则的名字,而EXPRESSION是这个规则的定义。语法可以由一条规则组成,也可以由很多条规则组成。当所有的规则都列出来之后,那么每一个规则的名字都是一个字符串的集合。大部分情况下你需要指定一个“总入口”来代表整个语法。
举个例子,假设我们判断一个字符串是不是无符号整数。一个无符号整数只能由数字字符组成。于是我们可以先用一条规则来代表“数字字符”。这里我们可以使用“|”来代表“或”,那么下面的规则就表示DIGIT是’0’或’1’或…或’9’:
DIGIT = ‘0’ | ‘1’ | ‘2’ | ‘3’ | ‘4’ | ‘5’ | ‘6’ | ‘7’ | ‘8’ | ‘9’
那么,无符号整数就是“很多数字字符”:
INTEGER = DIGIT | INTEGER DIGIT
无符号整数INTEGER要么是一个数字字符,要么就是一个合法的无符号整数后面再加上一个数字字符。无符号整数加上一个数字字符仍然是一个无符号整数。
现在可以来检验一下。譬如说“1”是一个无符号整数,那么从INTEGER开始,分析“1”所走的路径就是
INTEGER
= DIGIT (INTEGER = DIGIT)
= ‘1’ (DIGIT = ‘1’)
字符串“123”显然也应该是一个无符号整数。“123”是一些数字字符组成的,因此走的路径跟单个字符稍微有些不同。这里将会交替使用INTEGER的两条路径来模拟循环:
INTEGER
= INTEGER DIGIT (INTEGER = INTEGER DIGIT)
= INTEGER DIGIT DIGIT (INTEGER = INTEGER DIGIT)
= DIGIT DIGIT DIGIT (INTEGER = DIGIT)
= ‘1’ DIGIT DIGIT (DIGIT = ‘1’)
= ‘1’ ‘2’ DIGIT (DIGIT = ‘2’)
= ‘1’ ‘2’ ‘3’ (DIGIT = ‘3’)
在使用INTEGER分析“123”的时候,我们可以交替使用INTEGER = DIGIT和INTEGER = INTEGER DIGIT这两条规则来将一个INTEGER替换成恰好三个DIGIT,然后再将DIGIT替换成’1’、’2’和’3’三个字符,从而确信“123”满足INTEGER的定义,因此“123”是一个无符号整数。
替换的过程并不是唯一的,我们完全可以使用另一种顺序来将INTEGER替换成“123”:
INTEGER
= INTEGER DIGIT (INTEGER = INTEGER DIGIT)
= INTEGER ‘3’ (DIGIT = ‘3’)
= INTEGER DIGIT ‘3’ (INTEGER = INTEGER DIGIT)
= INTEGER ‘2’ ‘3’ (DIGIT = ‘2’)
= DIGIT ‘2’ ‘3’ (INTEGER = DIGIT)
= ‘1’ ‘2’ ‘3’ (DIGIT = ‘1’)
这正是语法的一个特点:替换顺序与结果无关。
现在我们将这个例子再深入一点,如何用语法规则来描述一个逗号分隔的无符号整数列表呢?逗号分隔的无符号整数列表可以是一个整数“123”,也可以使多个整数“1,23,456”。这也是重复的一种,只是跟INTEGER的那种重复有所区别——多了一个逗号。根据上面的描述可以知道,逗号分隔的无符号整数列表有两种情况,第一种是单独的一个整数,第二种是一个已经完成的列表后面跟着一个逗号和一个整数。那么事情就变得简单了。假设我们使用LIST来代表这个列表,那么根据上面的描述我们可以用类似的技巧来描述它:
LIST = INTEGER | LIST ‘,’ INTEGER
用LIST来分析一个数字列表的过程与用INTEGER分析一个无符号整数是相似的。因为篇幅问题,这里只展示使用LIST处理“1,23,456”的其中一种方法:
LIST
= LIST ‘,’ INTEGER (LIST = LIST ‘,’ INTEGER)
= LIST ‘,’ INTEGER ‘,’ INTEGER (LIST = LIST ‘,’ INTEGER)
= INTEGER ‘,’ INTEGER ‘,’ INTEGER (LIST = INTEGER)
= DIGIT ‘,’ INTEGER ‘,’ INTEGER (INTEGER = DIGIT)
= ‘1’ ‘,’ INTEGER
补充:软件开发 , C语言 ,