暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

【手写数据库内核miniToadb】第7天 完整的SQL解析器框架,分词语法解析一气呵成

开源无限 2024-12-18
72

作者在数据库内核设计从业十余年;设计开发分布式数据内核;编写从零手写数据库教程;现在将过去的数据库内核经验一一分享,有兴趣的朋友加个关注。

一、概述


词法分析器的输出就是token序列,只要词法分析器有输入,就会产生分词的输出, 相应的语法分析器的输入是连续的token序列。

看起来,解析器的内部是词法分析器的输出作为语法分析器的输入的一个模块串联,解析器的输入直接进入词法分析器,而解析器的输出就是语法分析器。

flex与bison程序是可以串联起来使用的,bison的输入默认就是flex的输出,当然也可以指定为标准输入或其它。

让我们将前面已经完成的SQL词法分析与语法分析串联起来。

二、SQL语法解析器


为了使得bison能够识别对应的token类型,需要将flex程序中的每个模式返回类型,此类型就是在bison程序中定义%token
定义的,在bison转换为C语言时,在生成的头文件中会定义为宏。

2.1 token的类型

注意词法分析器中的行首的CREATE
是正则表达式模式,也就是字符串;

而在语法分析器中的规则中也有一个CREATE
字段,此处的并不是字符串,而是一个类型,也就是枚举值,它表示token的类型,对应的值存储在联合体定义的结构中,在语法规则中通过 $n
的形式传递出来。

它在bison程序编译生成的头文件中将列举的token都定义了枚举值。

      enum yytokentype
      {
        CREATE = 258,
        TABLE = 259,
        IDENT = 260
      };

    在flex程序的声明区中包含类型定义的头文件,这样才不会出错编译错误。

      #include "sqlgram.h"

      2.2 token的值

      在词法分析器中,模式的动作需要返回token的类型,同时将标识符的值也要传到bison的内部变量yylval
      当中,在语法解析时根据token类型来匹配对应的语法,当到达叶节点时从此变量当中获取真正的值。

      内部变量的类型是在bison程序中定义的联合体类型,通过头文件来声明。

        CREATE          {
                            return CREATE; 
                        }
        TABLE            {
                            return TABLE;  
                        }
        {identify}      {
                            yylval.sval = strdup(yytext);
                            return IDENT; 
                        }
        {flagself}      {
                            return yytext[0];  
                        }
        {whitespace}    {
                            ; /* ignore */
                        }

        关键字和命令字只需要返回类型,它们的值并不需要,只有标识符,数字,用户定义的数据,运算符号的原本值需要存储起来。

        对于原样返回的符号,这里直接返回该字符,在语法分析时也同样直接匹配。

        空白字符会被词法分析忽略,也不会传到语法分析器中。

        2.3 终节符的值

        对于bison程序,也需要做一处调整,对于标识符的token要带有字符串值,所以将它的类型也声明为字符串,与其它非终节符一样。

          %token <sval> IDENT

          另外为了看到语法解析的效果,在tablename
          attr_name
          attr_type
          处将接收到的符识符打印出来,在其它地方可以任意打印一些内容,只是标识出走过这里就可以。

            tablename:      IDENT
                                {
                                    printf("tablename %s \n", $1);
                                }
                    ;
            attr_name:      IDENT
                                {
                                    printf("attr_name %s \n", $1);
                                }
                    ;
            attr_type:      IDENT
                                {
                                    printf("attr_type %s \n", $1);
                                }
                    ;

            2.5 main函数调整

            因为两个程序要编译为一个可执行程序,flex程序的第三部分代码部分不再需要,只需要保留一处main函数来调用语法解析器的主函数yyparse。

            法解析器会驱动词法解析器从标准输入读取字符串,并进行词法解析,当产生一个token时,会立即传给语法解析器;语法解析器匹配成功后,又会再次调用词法解析器获取token,直到词法解析器为空,或者得到终节符分号为止。

            整个过程嵌套调用,类似于C语言的两层循环调用,flex就是内层循环,每得到一个token就会跳到外层循环,直到内层没有可处理为止,或者外层到了结束符为止。

            2.6 Makefile编译文件

            经过上述调整之后,创建表的解析器就完成了,涉及多个文件的编译,可以使用Makefile工具来帮助执行编译,最终代码参加exam_03。

              sqlparser: sqlscanner.l sqlgram.y
                  bison -d -o sqlgram.c sqlgram.y
                  flex -o sqlscanner.c sqlscanner.l
                  gcc -o $@ sqlscanner.c sqlgram.c  


              clean:
                  rm *.o sqlparser *.c *.h 

              Makefile中包括两部分,执行make
              就会调用flex、bison和gcc编译生成sqlparser可执行文件,而执行make clean
              会清除生成的文件,包括可执行文件。在编译阶段,bison使用-d
              参数来生成头文件,-o
              参数指定生成的C语言文件的名称。

              现在编译就非常简单了,直接执行make就会将flex,bison,gcc三个命令都会执行。

                [senllang@hatch exam_03]$ make
                bison -d -o sqlgram.c sqlgram.y
                flex -o sqlscanner.c sqlscanner.l
                gcc -g -o sqlparser sqlscanner.c sqlgram.c  
                sqlgram.cIn function ‘yyparse’:
                sqlgram.c:1128:16warning: implicit declaration of function ‘yylex’ [-Wimplicit-function-declaration]
                       yychar = yylex ();
                                ^~~~~
                sqlgram.c:1313:7warning: implicit declaration of function ‘yyerror’; did you mean ‘yyerrok’? [-Wimplicit-function-declaration]
                       yyerror (YY_("syntax error"));
                       ^~~~~~~
                       yyerrok
                sqlgram.yAt top level:
                sqlgram.y:60:6warning: conflicting types for ‘yyerror’
                 void yyerror(char *s)
                      ^~~~~~~
                sqlgram.c:1313:7note: previous implicit declaration of ‘yyerror’ was here
                       yyerror (YY_("syntax error"));
                       ^~~~~~~

                现在编译还会有编译告警的产生,这个在后面的章节来解析,现在不会影响效果的展示。

                2.7 结果展示

                我们尝试运行一下,输入已经非常熟悉的创建表的SQL。

                  [senllang@hatch exam_03]$ ./sqlparser 
                  create table student(id int,sname varchar);
                  tablename student 
                  attr_name id 
                  attr_type int 
                  column_def 
                  columndef_list 
                  attr_name sname 
                  attr_type varchar 
                  column_def 
                  columndef_list1 
                  create_stmt 

                  可以看到结果中正确打印了各个token的值,而且从词法分析器将类型与值传递到了语法分析器,并在语法分析器中进行了打印。

                  三、总结


                  语法规则是通过token的类型来进行判断和匹配,这也是解析器性能的关键,数字比较好过字符串比较,如果按将字符串比较,性能会低很多。

                  经过几天的努力,我们终于有了一个SQL解析器的框架,虽然目前只能解析一条创建表的SQL,但是总体形式还在向好的方向发展,继续努力吧!

                  文章转载自开源无限,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                  评论