MySQL 源码|词法解析:状态及状态转移规则(4)
源码位置:(版本 = MySQL 8.0.37)
前置文档:
- MySQL 源码|词法解析的状态存储器(Lex_input_stream)的主要数据成员与函数
- MySQL 源码|词法解析中的 CHARSET_INFO 结构体及衍生函数
- MySQL 源码|词法解析:状态及状态转移规则(1)
- MySQL 源码|词法解析:状态及状态转移规则(2)
- MySQL 源码|词法解析:状态及状态转移规则(3)
MY_LEX_IDENT_SEP
(在 token 和 .
之后)
case MY_LEX_IDENT_SEP: // Found ident and now '.'
yylval->lex_str.str = const_cast<char *>(lip->get_ptr());
yylval->lex_str.length = 1;
c = lip->yyGet(); // should be '.'
if (uchar next_c = lip->yyPeek(); ident_map[next_c]) {
lip->next_state =
MY_LEX_IDENT_START; // Next is an ident (not a keyword)
if (next_c == '$') // We got .$ident
push_deprecated_warn_no_replacement(
lip->m_thd,
"$ as the first character of an unquoted identifier");
} else // Probably ` or "
lip->next_state = MY_LEX_START;
return ((int)c);
令当前 token 为 .
。如果下一个字符是 SQL 字符,则将状态置为 MY_LEX_IDENT_START
(下一个字符是标识符而非关键字),否则下一个字符可能标识符引号或双引号,将状态置为 MY_LEX_START
(初始状态)。
涉及的状态转移规则如下:
当前状态 | 字符类型 | 变更状态 |
---|---|---|
MY_LEX_IDENT_SEP | 下一个字符是 SQL 字符 | 状态 = MY_LEX_IDENT_START ;【返回】46(. 的 ASCII 码) |
其他 | 状态 = MY_LEX_START ;【返回】46(. 的 ASCII 码) |
MY_LEX_NUMBER_IDENT
(在数字字符串中)
Step 1|如果第一个数字是 0
,则判断下一个字符是否为 b
或 x
,处理 0b
和 0x
的问题。如果确认为十六进制数(0x
开头)时,则返回 474(HEX_NUM
);如果确认为二进制数(0b
开头),则返回 292
(BIN_NUM
);如果是 0x
或 0b
开头,但不是十六进制数或二进制数,则将指针向前移动回 0
的位置,并将状态置为 MY_LEX_IDENT_START
(下一个字符是标识符而非关键字),并 break 出 switch 继续处理当前 token。
if (lip->yyGetLast() == '0') {
c = lip->yyGet();
if (c == 'x') {
while (my_isxdigit(cs, (c = lip->yyGet())))
;
if ((lip->yyLength() >= 3) && !ident_map[c]) {
/* skip '0x' */
yylval->lex_str = get_token(lip, 2, lip->yyLength() - 2);
return (HEX_NUM);
}
lip->yyUnget();
state = MY_LEX_IDENT_START;
break;
} else if (c == 'b') {
while ((c = lip->yyGet()) == '0' || c == '1')
;
if ((lip->yyLength() >= 3) && !ident_map[c]) {
/* Skip '0b' */
yylval->lex_str = get_token(lip, 2, lip->yyLength() - 2);
return (BIN_NUM);
}
lip->yyUnget();
state = MY_LEX_IDENT_START;
break;
}
lip->yyUnget();
}
Step 2|遍历所有到所有数字之后
while (my_isdigit(cs, (c = lip->yyGet())))
;
Step 3|如果数字之后不是 SQL 字符,则将状态改为 MY_LEX_INT_OR_REAL
(数字之后,不是标识符),并 break 出 switch 继续处理当前 token。
if (!ident_map[c]) { // Can't be identifier
state = MY_LEX_INT_OR_REAL;
break;
}
Step 4|如果数字之后的字符是 e
或 E
,则考虑是类似 1E+10
这类浮点数的情形。如果 e
或 E
的下一个字符是数字,或 +
、-
符号后跟着数字,则继续解析剩余遍历到所有数字之后,返回 443(FLOAT_NUM
)。如果不是,则将指针向前移动 1 个字符回到数字之后。
if (c == 'e' || c == 'E') {
// The following test is written this way to allow numbers of type 1e1
if (my_isdigit(cs, lip->yyPeek()) || (c = (lip->yyGet())) == '+' ||
c == '-') { // Allow 1E+10
if (my_isdigit(cs,
lip->yyPeek())) // Number must have digit after sign
{
lip->yySkip();
while (my_isdigit(cs, lip->yyGet()))
;
yylval->lex_str = get_token(lip, 0, lip->yyLength());
return (FLOAT_NUM);
}
}
lip->yyUnget();
}
Step 5|继续执行 MY_LEX_IDENT_START
的逻辑,相当于将状态置为 MY_LEX_IDENT_START
,并 break 出 switch 继续处理当前 token。
[[fallthrough]];
涉及的状态转移规则如下:
当前状态 | 字符类型 | 变更状态 |
---|---|---|
MY_LEX_NUMBER_IDENT | 0x 开头的十六进制数 | 【返回】474(HEX_NUM ) |
0b 开头的二进制数 | 【返回】292 (BIN_NUM ) | |
0x 或 0b 开头但不是十六进制数或二进制数 | 状态 = MY_LEX_IDENT_START | |
数字之后不是 SQL 字符 | 状态 = MY_LEX_INT_OR_REAL | |
形如 1e+10 的浮点数 | 【返回】443(FLOAT_NUM ) | |
其他 | 状态 = MY_LEX_IDENT_START |
MY_LEX_IDENT_START
:下一个字符是标识符而非关键字
Step 1|根据多字符字符集或单字符字符集,遍历当前 token 的所有字符。如果是多字节字符集,则将 result_state
置为 IDENT_QUOTED
;如果是单字节字符集,如果最后字符是否为 ASCII 字符为非 ASCII 字符则将 result_state
置为 IDENT_QUOTED
,否则,置为 IDENT
。
switch
逻辑详见:MySQL 源码|词法解析:状态及状态转移规则(3)
result_state = IDENT;
if (use_mb(cs)) {
result_state = IDENT_QUOTED;
while (ident_map[c = lip->yyGet()]) {
switch (my_mbcharlen(cs, c)) {
case 1:
break;
case 0:
if (my_mbmaxlenlen(cs) < 2) break;
[[fallthrough]];
default:
int l;
if ((l = my_ismbchar(cs, lip->get_ptr() - 1,
lip->get_end_of_query())) == 0)
break;
lip->skip_binary(l - 1);
}
}
} else {
for (result_state = 0; ident_map[c = lip->yyGet()]; result_state |= c)
;
/* If there were non-ASCII characters, mark that we must convert */
result_state = result_state & 0x80 ? IDENT_QUOTED : IDENT;
}
Step 2|如果下一个字符是 .
且之后的字符为 SQL 字符,则将状态置为 MY_LEX_IDENT_SEP
。
if (c == '.' && ident_map[lip->yyPeek()])
lip->next_state = MY_LEX_IDENT_SEP; // Next is '.'
Step 3|获取当前 token 并更新 UTF-8 格式数据流,返回 result_state 作为当前状态。
yylval->lex_str = get_token(lip, 0, lip->yyLength());
lip->body_utf8_append(lip->m_cpp_text_start);
lip->body_utf8_append_literal(thd, &yylval->lex_str, cs,
lip->m_cpp_text_end);
return (result_state);
涉及的状态转移规则如下:
当前状态 | 字符类型 | 变更状态 |
---|---|---|
MY_LEX_IDENT_START | 多字节字符的字符串 | 【返回】484(IDENT_QUOTED ) |
单字节字符的字符串 | 【返回】482(IDENT ) | |
当前 token 是多字节字符的字符串,且之后是 “. + SQL 字符” | 状态 = MY_LEX_IDENT_SEP ;【返回】484(IDENT_QUOTED ) | |
当前 token 是单字节字符的字符串,且之后是 “. + SQL 字符” | 状态 = MY_LEX_IDENT_SEP ;【返回】482(IDENT ) |