MySQL 源码|62 - 语法解析(V2):调用词法解析器的逻辑

目录文档:MySQL 源码|源码剖析文档目录

源码位置(版本 = MySQL 8.0.37):sql/sql_class.ccsql/sql_yacc.yy

前置文档:


sql/sql_yacc.yy 中定义了 %define api.prefix {my_sql_parser_},详见 Bison 官方文档 - 3.8 Multiple Parsers in the Same Program,其中描述如下(AI 翻译,如有疑问可查阅原文):

大多数使用 Bison 的程序只解析一种语言,因此只包含一个 Bison 解析器。但是如果同一个程序想要解析多种语言该怎么办呢?这时你需要避免不同语言定义的函数和变量(如 yyparseyylval)之间的名称冲突。为了从同一个编译单元中使用不同的解析器,你还需要避免在生成的头文件中导出的类型和宏(例如 YYSTYPE)产生的冲突。

实现这一点的简便方法是定义 %define 变量 api.prefix。通过使用不同的 api.prefix,可以确保即使一起包含这些头文件也不会产生冲突,并且编译后的对象也可以被链接在一起。指定 %define api.prefix {prefix}(或者通过选项 -Dapi.prefix={prefix} 调用 Bison,参见 Bison 官方文档 - 9 Invoking Bison)会将 Bison 解析器的接口函数和变量重命名为以 prefix 开头而非 yy,并且所有的宏也会以 PREFIX(即 prefix 全部大写)开头而非 YY

重命名的符号包括 yyparse, yylex, yyerror, yynerrs, yylval, yylloc, yycharyydebug。如果你使用了推送解析器,yypush_parse, yypull_parse, yypstate, yypstate_newyypstate_delete 也将被重命名。重命名的宏包括 YYSTYPE, YYLTYPE,以及 YYDEBUG,后者会被特别处理——下面会有更多关于这方面的说明。

因此,MySQL 主解析器的函数 yyparseyylexyyerroryynerrsyylvalyyllocyycharyydebug,以及宏指令名称 YYSTYPEYYLTYPEYYDEBUG 均被重命名,具体如下:

默认名称重命名后的名称
yyparsemy_sql_parser_parse
yylexmy_sql_parser_lex
yyerrormy_sql_parser_error
yynerrsmy_sql_parser_nerrs
yylvalmy_sql_parser_lval
yyllocmy_sql_parser_lloc
yycharmy_sql_parser_char
yydebugmy_sql_parser_debug
YYSTYPEMY_SQL_PARSER_STYPE
YYLTYPEMY_SQL_PARSER_LTYPE
YYDEBUGMY_SQL_PARSER_DEBUG
THD::sql_parser() 函数:调用 yyparse 函数

sql/sql_class.cc 文件中的 THE::sql_parser() 函数调用了由 YACC 生成的 my_sql_parser_parse 函数(即 yyparse 函数)。该函数首先调用 my_sql_parser_parse 函数将 SQL 表达式解析为解析树,然后再调用 lex->make_sql_cmd 函数将解析树解析为 AST 语法树。源码如下:

/**
  Call parser to transform statement into a parse tree.
  Then, transform the parse tree further into an AST, ready for resolving.
*/
bool THD::sql_parser() {
  /*
    SQL parser function generated by YACC from sql_yacc.yy.

    In the case of success returns 0, and THD::is_error() is false.
    Otherwise returns 1, or THD::>is_error() is true.

    The second (output) parameter "root" returns the new parse tree.
    It is undefined (unchanged) on error. If "root" is NULL on success,
    then the parser has already called lex->make_sql_cmd() internally.
  */
  extern int my_sql_parser_parse(class THD * thd,
                                 class Parse_tree_root * *root);

  Parse_tree_root *root = nullptr;
  if (my_sql_parser_parse(this, &root) || is_error()) {
    /*
      Restore the original LEX if it was replaced when parsing
      a stored procedure. We must ensure that a parsing error
      does not leave any side effects in the THD.
    */
    cleanup_after_parse_error();
    return true;
  }
  if (root != nullptr && lex->make_sql_cmd(root)) {
    return true;
  }
  return false;
}

其中 my_sql_parser_parse 函数由 YACC 根据 sql/sql_yacc.yy 中的配置构造,如果解析成功则返回 0 且 THE::is_error() 返回 false,如果解析失败则返回 1 或 THE::is_error() 返回 true。

该函数接受 class THD* 类型的参数 thdclass Parse_tree_root** 类型的参数 root,这两个参数在 sql/sql_yacc.yy 中通过 %parse-param 标记定义,源码如下:

%parse-param { class THD *YYTHD }
%parse-param { class Parse_tree_root **parse_tree }

如果解析成功,第二个参数 root 指向的将成为一个新的解析树;如果解析失败,则不会构造解析树。如果 root 参数为 NULL 且解析成功,则说明解析器已经在内部调用了 lex->make_sql_cmd() 函数。

MY_SQL_PARSER_STYPE:指定返回值类型

在 MySQL 的 SQL 语法解析逻辑中,终结符和语义组需要返回不同的类型。

实现多种返回值的 Bison 语法详见 Bison 官方文档 - 3.4.2 More Than One Value Type,其中描述如下(人工翻译,如有疑问可查阅原文):

在大多数程序中,都可能需要为终结符(kind of tokens)和语义组(grouping)使用不同的数据类型。例如,数值常量可能需要 intlong 类型,而字符串常量则需要 char * 类型,而标识符可能需要指向符号表中元素的指针。

要在单个解析器中使用不止一种语义值的数据类型,Bison 语法有如下两项要求:

(1) 指定所有可能的数据类型的集合,具体方法如下:

(1.1) 让 Bison 从根据分配给 Symbol 的标签计算出联合类型

(1.2) 使用 %union 语法指定类型,详见 Bison 官方文档 - 3.4.4 The Union Declaration

(1.3) 定义 %define variable api.value.typer 为一个联合类型,其成员是类型标签,详见 Bison 官方文档 - 3.4.5 Providing a Structured Semantic Value Type

(1.4) 使用 typedef#define 来定义 YYSTYPE 为一个联合类型,其成员是类型标签

(2) 为使用语义值的每个符号(symbol),包括终结符和非终结符选择其中的一种类型。这可以通过在 %token 语法指定,或使用 %nterm%type 语法指定。

sql/sql_yacc.yy 文件中,没有使用 %union 语法或 %define variable api.value.typer 语法,而是定义了 MY_SQL_PARSER_STYPE 联合体(已配置将 YY 前缀替换为 MY_SQL_PARSER_ 前缀)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值