BISON
用于语法分析器的自动生成,这个工具可以在网上下载获得。
化点时间学习这个工具的用法,并用于
SQL
语言的分析,可以让我们把精力专注在语法规则上,而不是具体的分析函数编写上。对整个
DBMS
来说,使用自动化工具进行语言处理程序的自动生成,使得语言分析模块成为最可靠最方便维护的模块之一。
BISON
源文件的结构
我们需要按照
BISON
的要求,书写
BISON
的源程序(
gramma.y
),然后由
BISON
把它翻译为
C
文件。因此,
BISON
是编译程序的翻译器。
BISON
的源文件通常由八个部分组成:
一.
自由定义部分
:
%{
/*
任何合法的
C
语句
*/
%}
这部分被
BISON
原封不动地复制到输出的
.C
文件中。
二.语法栈的联合
(UNION)
结构
语法分析程序使用一个堆栈来存放规约到的各个语法成分,堆栈用一个数组表示,这个数组的每个元素需要能够描述每一个语法成分,所以采用一个
UNION
:
%union
{
/*
产生式
对应的语法成分
*/
}
Union
中的每一个项,都是一个语法规则的每一个非终结符;以整数四则表达式为例:
exp : exp ‘+’ exp
| exp ‘-‘ exp
| exp ‘*’ exp
| exp ‘/’ exp
| ‘(‘ exp ‘)’
| lt_integer
;
lt_integer: LT_INTEGER;
有两个语法规则,对应了两个非终结符号
: exp
是表达式,
lt_integer
表示整数常量(
LT_INTEGER
表示词法分析程序返回的一个确认为整数的单词)。
对应的,这个
union
可以书写为:
%{
par_exp_t* exp;
int lt_integer;
};
其中
par_exp_t
用来描述被识别出的
exp
的信息,
int
存放被识别出的整数的值。
上面的例子很简单,所以
union
只有两个字段;在
DM
的语法分析程序中,这个
UNION
大约有
490
个字段,也就是,大概有
490
个语法规则产生式。
三.非终结符的类型声明
上面定义了分析栈的
UNION
类型
,
还需要把字段名与语法非终结符号对应起来:
%type <
字段名
>
非终结符号
如上例,这部分应该写为:
%type <exp> exp
%type <lt_integer> lt_integer
看上去似乎有点多余,每一行都是一个简单的重复。但前面一个表示的是
UNION
中对应的字段名,后一个是语法符号;如果我们把
UNION
改为
:
%{
par_exp_t* eeee;
int iiii;
};
那么对应的类型声明需要改为:
%type <eeee> exp
%type <iiii> lt_integer;
这种不一致的写法,事实上会造成混乱,所以在
DM
系统中,采用上面一致的写法。
四:单词声明
语法分析的输入是连续的有确定意义的单词。下面需要声明分析程序支持的单词:
%token LT_INTEGER
对于
SQL
语法,关键字如:
SELECT, FROM, WHERE
等,都可以定义为单词
:
%token KW_SELECT, KW_FROM
%token KW_WHERE
五
.
确定运算符的优先级
%left ‘-‘ ‘+’
%left ‘*’ ‘/’
%left ‘(‘ ‘)’
%left
表示是左结合的,表示先规约左边的产生式,反应到表达式计算中:
1 + 2 + 3
别识别为:((
1 + 2
)
+ 3),
而不是
(1 + (2 + 3))
优先级低的符号列在前面,高有限级的符号列在后面
;
同一行的表示优先级相同。所以上面的书写方式, 符合“先乘除,后加减,括号最优先”的原则。
除了
%left
以后,还有
%right, %nonassoc
等用来只是右结合,或者不结合等说明符号,可查看
bison
的详细说明。
六
.
声明语法的开始符号
%start exp
这是告知
bison,
这是语法最终需要规约的非终结符号。
七
.
语法规则定义
这是语法分析程序的核心定义部分,用
%%
开始
,
前面已经列出了关于表达式的语法规则:
%%
exp : exp ‘+’ exp
| exp ‘-‘ exp
| exp ‘*’ exp
| exp ‘/’ exp
| ‘(‘ exp ‘)’
| lt_integer
;
lt_integer: LT_INTEGER;
八.自由添加的
C
源代码
在语法规则定义部分的后面,可以用
%%
开始,定义
C
的辅助代码。这部分代码将被原封不动地复制到输出的
.C
文件中。
给语法规则配上规约动作
规约动作是一段
C
代码,它的作用是每当分析器识别出一个语法符号时,调用该代码,完成一定的动作。通常,我们使用这段代码,来建立当前语法节点与子节点勾连动作。规约动作应该紧接在语法规则的后面。
如上例
:
exp : exp ‘+’ exp
{ $$ = new_node(PAR_EXP, 1); /* A */
$$->tag = 1; /* B */
$$->exp1 = $1; /* C */
$$->exp2 = $3; /* D */
g_root = $$; /* E */
}
| ‘(‘ exp ‘)’
{
$$ = $2; /* F */
}
;
这里仅列出了其中的两个子规则
,
其中
A, B, C, D
四个语句构成了第一个子规则的语句块
:
A;
为识别出的
exp
生成一个结构
,
用
$$
指向它。
$$
是一个
bison
定义的特殊标记,其意义是当前语法栈的规约元素。如果没有规约动作代码,缺省情况下赋予
$$
为
NULL
。
new_node
是一个需要自己编写的函数
,
用于生成各个子节点,
PAR_EXP
是一个事先定义的常量。显然,对于不同的规则,需要定义不同的常量类型。象
new_node
这样的函数,一般放在
.y
文件的最后一个部分。
B:
用来区分是哪个子规则规约的,这里用
tag = 1
来表示两个子表达式
‘+’
运算
C.
保留第一个子表达式;
$1
表示这个产生式的第
1
个语法成分所在的语法栈中对应的值
D.
保留第二个子表达式;
$3
表示这个产生式的第
3
个语法成分所在的语法栈中对应的值;注意这里的
’+’
也占一个位置,
用
$2
,这里因为有
tag=1
,已经把相应的信息保存到
$$
中,所以不需要管它。
E:
这是一个比较特别的语句,
它把
$$
赋给了一个全局量。因为
exp
是个开始符号,当分析结束时,这个
g_root
就是语法树的根。
F:
因为
加了括号的表达式与原表达式等价,所以直接把
$2
赋给
$$
就可以了,不需要再生成
par_exp
节点。
最终的函数yyparse()
yyarse
()是
bison
生成的分析器的主函数。
调用
yyarse()
,如果一切顺利,那么上例中的
g_root
将指向一个完成的语法树。
出错处理
如果输入的字符串有语法错误,则分析器将停止分析,在退出
yyparse()
函数前,会调用一个
yyerror(char*s)
的函数,这个函数需要用户自己定义,以便能捕获一些用意义的信息,比如:语法错误出现的行号,附近的单词等。