Mysql源码学习――词法分析MYSQLlex(一)

2014-11-24 10:35:15 · 作者: · 浏览: 0

词法分析MYSQLlex

客户端向服务器发送过来SQL语句后,服务器首先要进行词法分析,而后进行语法分析,语义分析,构造执行树,生成执行计划。词法分析是第一阶段,虽然在理解Mysql实现上意义不是很大,但作为基础还是学习下比较好。

词法分析即将输入的语句进行分词(token),解析出每个token的意义。分词的本质便是正则表达式的匹配过程,比较流行的分词工具应该是lex,通过简单的规则制定,来实现分词。Lex一般和yacc结合使用。关于lex和yacc的基础知识请参考Yacc 与Lex 快速入门- IBM。如果想深入学习的话,可以看下《LEX与YACC》。

然而Mysql并没有使用lex来实现词法分析,但是语法分析却用了yacc,而yacc需要词法分析函数yylex,故在sql_yacc.cc文件最前面我们可以看到如下的宏定义:

/* Substitute the variable and function names. */

#define yyparse MYSQLparse

#define yylex MYSQLlex

  这里的MYSQLlex也就是本文的重点,即MYSQL自己的词法分析程序。源码版本5.1.48。源码太长,贴不上来,算啦..在sql_lex.cc里面。

  我们第一次进入词法分析,state默认值为MY_LEX_START,就是开始状态了,其实state的宏的意义可以从名称上猜个差不多,再比如MY_LEX_IDEN便是标识符。对START状态的处理伪代码如下:

case MY_LEX_START:

{

Skip空格

获取第一个有效字符c

state = state_map[c];

Break;

}

  我困惑了,这尼玛肿么出来个state_map?找到了在函数开始出有个赋值的地方:

uchar *state_map= cs->state_map;

  cs?!不会是反恐精英吧!!快速监视下cs为my_charset_latin1,哥了然了,原来cs是latin字符集,character set的缩写吧。那么为神马state_map可以直接决定状态?找到其赋值的地方,在init_state_maps函数中,代码如下所示:

/* Fill state_map with states to get a faster parser */

for (i=0; i < 256 ; i++)

{

if (my_isalpha(cs,i))

state_map[i]=(uchar) MY_LEX_IDENT;

else if (my_isdigit(cs,i))

state_map[i]=(uchar) MY_LEX_NUMBER_IDENT;

#if defined(USE_MB) && defined(USE_MB_IDENT)

else if (my_mbcharlen(cs, i)>1)

state_map[i]=(uchar) MY_LEX_IDENT;

#endif

else if (my_isspace(cs,i))

state_map[i]=(uchar) MY_LEX_SKIP;

else

state_map[i]=(uchar) MY_LEX_CHAR;

}

state_map[(uchar)'_']=state_map[(uchar)'$']=(uchar) MY_LEX_IDENT;

state_map[(uchar)'\'']=(uchar) MY_LEX_STRING;

state_map[(uchar)'.']=(uchar) MY_LEX_REAL_OR_POINT;

state_map[(uchar)'>']=state_map[(uchar)'=']=state_map[(uchar)'!']= (uchar) MY_LEX_CMP_OP;

state_map[(uchar)'<']= (uchar) MY_LEX_LONG_CMP_OP;

state_map[(uchar)'&']=state_map[(uchar)'|']=(uchar) MY_LEX_BOOL;

state_map[(uchar)'#']=(uchar) MY_LEX_COMMENT;

state_map[(uchar)';']=(uchar) MY_LEX_SEMICOLON;

state_map[(uchar)':']=(uchar) MY_LEX_SET_VAR;

state_map[0]=(uchar) MY_LEX_EOL;

state_map[(uchar)'\\']= (uchar) MY_LEX_ESCAPE;

state_map[(uchar)'/']= (uchar) MY_LEX_LONG_COMMENT;

state_map[(uchar)'*']= (uchar) MY_LEX_END_LONG_COMMENT;

state_map[(uchar)'@']= (uchar) MY_LEX_USER_END;

state_map[(uchar) '`']= (uchar) MY_LEX_USER_VARIABLE_DELIMITER;

state_map[(uchar)'"']= (uchar) MY_LEX_STRING_OR_DELIMITER;

  先来看这个for循环,256应该是256个字符了,每个字符的处理应该如下规则:如果是字母,则state = MY_LEX_IDENT;如果是数字,则state = MY_LEX_NUMBER_IDENT,如果是空格,则state = MY_LEX_SKIP,剩下的全为MY_LEX_CHAR。 

for循环之后,又对一些特殊字符进行了处理,由于我们的语句“select @@version_comment limit 1”中有个特殊字符@,这个字符的state进行了特殊处理,为MY_LEX_USER_END。

对于my_isalpha等这几个函数是如何进行判断一个字符属于什么范畴的呢?跟进去看下,发现是宏定义:

#define my_isalpha(s, c) (((s)->ctype+1)[(uchar) (c)] & (_MY_U | _MY_L))

Wtf,肿么又来了个ctype,c作为ctype的下标,_MY_U | _MY_L如下所示,

#define _MY_U 01 /* Upper case */

#define _MY_L 02 /* Lower case */

  ctype里面到底存放了什么?在ctype-latin1.c源文件里面,我们找到了my_charset_latin1字符集的初始值:

CHARSET_INFO my_charset_latin1=

{

8,0,0, /* number */

MY_CS_COMPILED | MY_CS_PRIMARY, /* state */

"latin1", /* cs name */

"latin1_swedish_ci", /* n