openGauss源码解析(88)
openGauss源码解析:SQL引擎源解析(3)
6.2.1 词法分析
openGauss采用flex和bison两个工具来完成词法分析和语法分析的主要工作。对于用户输入的每个SQL语句,它首先交由flex工具进行词法分析。flex工具通过对已经定义好的词法文件进行编译,生成词法分析的代码。
openGauss中的词法文件是scan.l,它根据SQL语言标准对SQL语言中的关键字、标识符、操作符、常量、终结符进行了定义和识别。代码如下:
//定义操作符
op_chars [\~\!\@\#\^\&\|\`\?\+\-\*\/\%\<\>\=]
operator {op_chars}+
//定义数值类型
integer {digit}+
decimal (({digit}*\.{digit}+)|({digit}+\.{digit}*))
decimalfail {digit}+\.\.
real ({integer}|{decimal})[Ee][-+]?{digit}+
realfail1 ({integer}|{decimal})[Ee]
realfail2 ({integer}|{decimal})[Ee][-+]
其中的operator即为操作符的定义,从代码中可以看出,operator是由多个op_chars组成的,而op_chars则是[\~\!\@\#\^\&\|\`\?\+\-\*\/\%\<\>\=]中的任意一个符号。
但这样的定义还不能满足SQL的词法分析的需要,因为并非多个op_chars的组合就能形成一个合法的操作符,因此在scan.l中会对操作符进行更明确的定义(或者说检查)。代码如下:
{operator} {
// “/*”“--”不是操作符,他们起注释的作用
int nchars = yyleng;
char *slashstar = strstr(yytext, "/*");
char *dashdash = strstr(yytext, "--");
if (slashstar && dashdash)
{
// 如果”/*”和”—”同时存在,选择第一个出现的作为注释
if (slashstar > dashdash)
slashstar = dashdash;
}
else if (!slashstar)
slashstar = dashdash;
if (slashstar)
nchars = slashstar - yytext;
// 为了SQL兼容,'+'和'-'不能是多字符操作符的最后一个字符,例如'=-',需要将其作为两个操作符
while (nchars > 1 &&
(yytext[nchars-1] == '+' ||
yytext[nchars-1] == '-'))
{
int ic;
for (ic = nchars-2; ic >= 0; ic--)
{
if (strchr("~!@#^&|`?%", yytext[ic]))
break;
}
if (ic >= 0)
break; // 如果找到匹配的操作符,跳出循环
nchars--; // 否则去掉操作符 '+'和'-',重新检查
}
……
return Op;
}
从operator的定义过程中可以看到其中有一些以yy开头的变量和函数,它是Lex工具的内置变量和函数,如表6-2所示。
表6-2 变量和函数说明
变量或函数名 | 说明 |
yytext | 变量,所匹配的字符串 |
yyleng | 变量,所匹配的字符串的长度 |
yyval | 变量,与标记相对应的值 |
yylex | 函数,调用扫描器,返回标记 |
yyless | 函数,将yytext中前n个以外的字符,重新放回输入流匹配 |
yymore | 函数,将下次分析的结果词汇,接在当前yytext的后面 |
yywrap | 函数,返回1表示扫描完成后结束程序,否则返回0 |
在编译的过程中,scan.l会被编译成scan.cpp文件,从parser目录的Makefile文件中可以看到编译的命令。具体代码如下:
Makefile片段
scan.cpp: scan.l
ifdef FLEX
$(FLEX) $(FLEXFLAGS) -o'$@' $<
# @if [ `wc -l <lex.backup` -eq 1 ]; then rm lex.backup; else echo "Scanner requires backup, see lex.backup."; exit 1; fi
else
@$(missing) flex $< $@
endif
通过对比scan.l和scan.cpp文件可以看出其中的关联关系。代码如下:
scan.l
840 {operator} {
841 ……
851 if (slashstar && dashdash)
scan.cpp
case 59:
YY_RULE_SETUP
#line 840 "scan.l"
{
……
if (slashstar && dashdash)
词法分析将一个SQL划分成多个不同的token,每个token会有自己的词性,在scan.l中定义了如下词性。词性说明请参考表6-3。
表6-3 词法分析词性说明
名称 | 词性 | 说明 |
关键字 | keyword | 如SELECT/FROM/WHERE等,对大小写不敏感 |
标识符 | IDENT | 用户自己定义的名字、常量名、变量名和过程名,若无括号修饰则对大小写不敏感 |
操作符 | operator | 操作符,如果是/*和--会识别为注释 |
常量 | ICONST/FCONST/SCONST /BCONST/XCONST | 包括数值型常量、字符串常量、位串常量等 |
openGauss在kwlist.h中定义了大量的关键字,按照字母的顺序排列,方便在查找关键字时通过二分法进行查找,代码如下:
PG_KEYWORD("abort", ABORT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("absolute", ABSOLUTE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("access", ACCESS, UNRESERVED_KEYWORD)
PG_KEYWORD("account", ACCOUNT, UNRESERVED_KEYWORD)
PG_KEYWORD("action", ACTION, UNRESERVED_KEYWORD)
PG_KEYWORD("add", ADD_P, UNRESERVED_KEYWORD)
PG_KEYWORD("admin", ADMIN, UNRESERVED_KEYWORD)
PG_KEYWORD("after", AFTER, UNRESERVED_KEYWORD)
……
在scan.l中处理“标识符”时,会到关键字列表中进行匹配,如果一个标识符匹配到关键字,则认为是关键字,否则才是标识符,即关键字优先。代码如下:
{identifier} {
……
// 判断是否为关键词
keyword = ScanKeywordLookup(yytext,
yyextra->keywords,
yyextra->num_keywords);
if (keyword != NULL)
{
……
return keyword->value;
}
……
yylval->str = ident;
yyextra->ident_quoted = false;
return IDENT;
}