引言
在上一篇博客中我对flex&bison工具的工作流程和文件结构做了简单总结,在本篇中我将尝试对scan.l文件中的内容做简要的解析。
文件路径
/src/common/backend/parser/scan.l
scan.l
关于flex源文件的文件结构在上一篇博客中已经介绍,此处不再赘述。我们直接来看scan.l的源代码部分。
定义部分(第Ⅰ段)
代码部分
在 %{ … %} 所包含的代码块中,内容都是C语言代码,flex会将代码照搬到生成的C文件中。scan.l代码部分进行了对头文件的声明并定义了一些函数声明和宏,并且重定义了一些flex的宏,从而改变程序的规则。
下为scan.l头部的代码部分(省略了注释、一些函数声明和宏定义),可以看到其中进行了对头文件的声明、静态函数和外部函数的声明、定义新的宏以及对已有的宏的重定义工作。
%{
#include "postgres.h"
#include "knl/knl_variable.h"
#include <ctype.h>
#include <unistd.h>
#include "parser/parser.h"
……
#include "utils/plpgsql.h"
#undef fprintf
#define fprintf(file, fmt, msg) ereport(ERROR, (errmsg_internal("%s", msg)))
#define YYSTYPE core_YYSTYPE
#if FLEX_MAJOR_VERSION >= 2 && FLEX_MINOR_VERSION >= 6
#define YY_DECL int core_yylex \
(YYSTYPE * yylval_param, YYLTYPE * yylloc_param , yyscan_t yyscanner)
#endif
……
#define COMMENT_NOT_IGNORED() (yyextra->is_hint_str || yyextra->include_ora_comment)
……
static void addlit(char *ytext, int yleng, core_yyscan_t yyscanner);
static void addlitchar(unsigned char ychar, core_yyscan_t yyscanner);
……
extern int core_yyget_column(yyscan_t yyscanner);
extern void core_yyset_column(int column_no, yyscan_t yyscanner);
%}
选项 %option部分
flex 提供了一个机制使得我们可以在扫描器(即此scan.l文件)的说明中设定一些flex工具的可选项,而不需使用flex命令来进行设置。如下为scan.l中的 %option内容(截取部分代码):
%option reentrant
%option bison-bridge
%option bison-locations
……
%option noyywrap
%option noyyalloc
%option noyyrealloc
%option noyyfree
%option warn
%option prefix="core_yy"
例如选项 %option reentrant,其使 flex能够生成一个可重入的扫描器。所生成的扫描器在一个或多个控制线程中不仅可移植,而且安全性好。可重入扫描器通常应用于多线程应用程序。任何一个线程都可以在不考虑与其他线程同步的情况下创建并执行一个可重入的flex扫描器。默认情况下,flex生成一个不可重入的扫描器。
状态定义部分
openGauss词法分析采用有限状态自动机(FA)的分词方法(另外的分词方法有直接扫描法,正则表达式法等),关于有限状态自动机的工作原理可以参考这篇文章linux–Flex and Bison。
下为scan.l中定义的状态机的各个状态以及各个状态对应的扫描到的符号类型(也即openGauss可以识别的所有词法类型)。
%x xb // 位字符串文字
%x xc // 扩展的C样式注释
%x xd // 分割标识符(双引号标识符)
%x xh // 十六进制数字字符串
%x xe // 扩展的带引号的字符串
%x xq // 标准带引号的字符串
%x xdolq // 用$ $指定的字符串
%x xui // 带Unicode转义的带引号标识符
%x xus // 带Unicode转义的带引号字符串
%x xeu // 扩展引号字符串中的Unicode代理对
模式定义部分
模式定义的形式为<模式名> [正则表达式],此处指定了输入的符号匹配到不同的正则式时对应的处理模式(在规则定义中,由模式/行为代码块定义对不同模式执行的C语言代码)。
下为scan.l中的模式定义部分(截取部分代码)。例如,space模式可以由制表符、回车符、换行符和换页符匹配到;non_newline定义为除换行符和回车符的所有token对应的模式,意为此处没有换行操作,相反的,如果识别到的token为换行符或回车符,则匹配到newline模式;而comment则定义为”--”{non_newline}*,意为由”--”开头且其后若不为 ’\r’ 或 ‘\n’ ,那么将其识别为一句注释(comment)。
space [ \t\n\r\f]
horiz_space [ \t\f]
newline [\n\r]
non_newline [^\n\r]
comment ("--"{non_newline}*)
规则部分(第Ⅱ段)
规则部分分为模式/行为代码部分和C语言代码部分。
模式/行为代码
上述正则表达式匹配到相应模式后,则会执行模式/行为代码中的C语言代码块。另外行为代码块中的代码可以为空。部分模式/行为代码定义如下:
// 整型数字
{integer} {
SET_YYLLOC();
yyextra->is_hint_str = false;
return process_integer_literal(yytext, yylval);
}
// 十进制数字
{decimal} {
SET_YYLLOC();
yylval->str = pstrdup(yytext);
yyextra->is_hint_str = false;
return FCONST;
}
C语言代码部分
这一部分定义一些在规则段中使用的工具函数。在scan.l中未发现这一部分。
附加的C语言部分(第Ⅲ段)
这部分是C语言代码,它们会被复制到生成文件的最末,通常用于一些未定义接口的定义以及用户自定义函数、静态函数等。scan.l中此处包含若干个工具函数,例如:
void addErrorList(const char* message, int lines)
{
PLpgSQL_error* erritem;
MemoryContext oldcxt;
oldcxt = MemoryContextSwitchTo(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_OPTIMIZER));
erritem = (PLpgSQL_error*)palloc(sizeof(PLpgSQL_error));
erritem->errmsg = pstrdup(message);
erritem->line = lines;
u_sess->plsql_cxt.errorList = lappend(u_sess->plsql_cxt.errorList, erritem);
MemoryContextSwitchTo(oldcxt);
}
这是一个辅助函数,它的作用是辅助更新错误信息以及定位错误的发生处。
总结
在本篇博客中我对词法解析相关的flex源文件scan.l的文件内容进行了学习和简单解析,在接下来的博客中我会展开对词法解析相关cpp源代码的学习和解读工作。