源码链接
概述
接上前几篇博客,我已经解析了 scan.l 和 gram.y ,分别通过 Flex 和 Bison 生成词法分析器 core_yylex() 和语法分析器 base_yyparse() ,其实就是两个函数通过不断调用其它的函数进而行使了分析器的功能。而现在该如何让这两个分析器配合起来使用呢?答案就在 parse.cpp 中。该文件中的 raw_parse() 会调用 base_yyparse() ,而在前几篇博客中我也讲过 base_yyparse() 又会调用 core_yylex() ,这样就形成了一个解析语句并生成原始的语法解析树的流程。而在 parse.cpp 中,除 raw_parse() 外,还有其它重要的函数,我一逐个来介绍一下。
函数
raw_parser()
//代码清单1
//src/common/backend/parser/parser.cpp
List* raw_parser(const char* str, List** query_string_locationlist)
{
core_yyscan_t yyscanner;
base_yy_extra_type yyextra;
int yyresult;
resetOperatorPlusFlag();
resetIsTimeCapsuleFlag();
resetCreateFuncFlag();
yyscanner = scanner_init(str, &yyextra.core_yy_extra, ScanKeywords, NumScanKeywords);
yyextra.lookahead_num = 0;
parser_init(&yyextra);
yyresult = base_yyparse(yyscanner);
scanner_finish(yyscanner);
if (yyresult) { /* error */
return NIL;
}
if (query_string_locationlist != NULL) {
*query_string_locationlist = yyextra.core_yy_extra.query_string_locationlist;
if (PointerIsValid(*query_string_locationlist) &&
(size_t)lfirst_int(list_tail(*query_string_locationlist)) < (strlen(str) - 1)) {
*query_string_locationlist = lappend_int(*query_string_locationlist, strlen(str));
}
}
return yyextra.parsetree;
}
这个函数是词法分析和语法分析的入口,它的作用是处理字符串形式的查询语句。第一个参数便是以字符串形式存在的查询语句,第二个参数是一个 List** 类型的指针变量,用于存储 List* 类型的存储了各个查询语句结束地址的链表的首地址。而返回值是存储于 List 结构的原始的语法解析树。
主要执行流程如下:
- 声明变量 yyscanner 和 yyextra
- 调用 scanner_init() 函数初始化 yyscanner 和 yyextra
- 设置 yyextra.lookahead_num = 0
- 调用 parser_init() 设置 yyextra.parsetree = NIL
- 调用 base_yyparse() 进行语法分析,同时间接进行了词法分析
- 调用 scanner_finish() 释放内存
- 返回语法树
在代码清单1第5、6行的 core_yyscan_t 和 base_yy_extra_type 类型:
//代码清单2
//src/include/parser/scanner.h
typedef void* core_yyscan_t;
//代码清单3
//src/include/parser/gramparse.h
typedef struct base_yy_extra_type {
/*
* Fields used by the core scanner.
*/
core_yy_extra_type core_yy_extra;
/*
* State variables for base_yylex().
*/
int lookahead_num; /* lookahead num. Currently can be:0,1,2 */
int lookahead_token[MAX_LOOKAHEAD_NUM]; /* token lookahead type */
core_YYSTYPE lookahead_yylval[MAX_LOOKAHEAD_NUM]; /* yylval for lookahead token */
YYLTYPE lookahead_yylloc[MAX_LOOKAHEAD_NUM]; /* yylloc for lookahead token */
/*
* State variables that belong to the grammar.
*/
List* parsetree; /* final parse result is delivered here */
} base_yy_extra_type;
可以看到,core_yyscan_t是空指针类型,base_yy_extra_type是一个结构体,主要保存用于词法语法分析过程中的的私有状态变量。
base_yylex()
//代码清单4
//src/common/backend/parser/parser.cpp
int base_yylex(YYSTYPE* lvalp, YYLTYPE* llocp, core_yyscan_t yyscanner)
这个函数在词法分析器和语法分析器中起着中间过滤器的作用,通俗地说,它可以使得连续几个关键字的令牌( token )可以简化成一个,因为有些关键字本来就是在一起行使特定的功能的,比如 NOT 和 NULL 组合成 NOT NULL,这样的话可以减轻语法分析器的负担,使得整个的分析更加顺畅。
is_empty_query()
//代码清单5
//src/common/backend/parser/parser.cpp
static bool is_empty_query(char* query_string)
这个函数用来检测查询语句是不是只有注释和分号的空语句,注意注释只能用 "/*" 和 "*/" 括起来,参数 query_string 即指向用来存储单条查询语句的字符串的指针。然而这个函数的作用很有限,它会先略过语句开头的空格和注释,然后通过判断剩余的字符是不是只有一个 ';' 来判断这个是不是空语句,然而这样做的坏处是不能将类似下面这样的语句归为空语句:
; /*该函数不能将这种一个分号后连接注释和空格的语句或者只有注释连分号都没有的语句判定为空语句*/
get_next_snippet()
//代码清单6
//src/common/backend/parser/parser.cpp
char** get_next_snippet(char** query_string_single, const char* query_string, List* query_string_locationlist, int* stmt_num)
这个函数用于从按序输入的查询语句中从前往后找到一条非空的语句,然后返回。需要注意的是,这个函数的第一个参数用来指定储存单条分割后非空语句的区域,第二个参数是指向存储着为分割的原始的存储着所有查询语句的字符串的指针,第三个参数是一条链表的头,顺着头我们可以得到各条语句的结束位置,第四个参数是整型指针以便调用函数能够利用它指向的整型值。我们利用第四个参数指定的整数在第三个参数指定节点的数据,它标示了第二个参数指定的字符串中特定位置的单条非空语句,这个函数最终将这条非空语句保存到第一个参数指定的内存区域。
总结
该文件在整个对语句的解析中起着至关重要的作用,一个最大的亮点无疑是 raw_parser() 函数,它是词法分析器和语法分析器的上层调用者,起着很重要的解析作用,最终返回一个原始的还有待进一步处理的语法解析树。另外,base_yylex() 和 is_empty_query() 都在一定程度上减轻了分析器的负担,get_next_snippet() 也有着过滤的作用,能将空语句过滤。