使用自动化语法分析工具BISON

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) 的函数,这个函数需要用户自己定义,以便能捕获一些用意义的信息,比如:语法错误出现的行号,附近的单词等。

你可能感兴趣的:(sql,实现,语法分析,DBMS,bison)