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;

}

posted @ 2024-04-30 10:10  openGauss-bot  阅读(6)  评论(0编辑  收藏  举报