php的词法分析 能够理解为 经过必定的规则,把输入的代码 区分出哪些是 是$开头的变量, 哪些是 以两个单引号括起来的字符串,哪些是以两个双引号括起来的字符串 等等, 这些区分出来的东西 称为token ,token 之间的联系 是由语法分析来完成的, 好比 赋值,加减乘除;php
语法分析详见这里html
语法分析的驱动程序 yyparse() 调用yylex()这个函数 , 这个函数 能够由flex生成,也能够人为编写,在php中,属于后者;node
每次执行yylex()函数,会返回一个token, 每一个token都会有类型和相应的值 , 类型通常在zend_language_parse.y中的%token表示,这些类型实际上是数值,存放于zend_language_parse.h正则表达式
token的值经过yylval供bison使用,在php中实际上是zendlval,zendlval的类型是一个zval的结构体数组
#define YYSTYPE zval YYSTYPE yylval; #define yylval zendlval
把分析好的yytext , 经过zend_copy_value将其拷贝到zval这个结构体中去less
zend_language_parse.y中的token 例以下面的ide
%token T_INCLUDE "include (T_INCLUDE)"
bison官网有一段关于%token的使用的描述:函数
yylex函数其实就是zendlex函数fetch
这个yyparse会循环调用lex_scan,lex_scan定义在zend_language_scanning.l中 , 这个函数体大部分是一些正则表达式,以区分出代码中哪些是变量,哪些是字符串,哪些是数组等等flex
int lex_scan(zval *zendlval TSRMLS_DC)
{
....
}
下面举例说明:
<?php
$name='taek';
?>
下面的关于上面 $name='taek'; 的巴斯科范式
start:
top_statement_list { zend_do_end_compilation(TSRMLS_C); }
;
top_statement_list:
top_statement_list { zend_do_extended_info(TSRMLS_C); } top_statement { HANDLE_INTERACTIVE(); }
| /* empty */
;
top_statement:
statement
;
statement:
unticked_statement { DO_TICKS(); }
;
unticked_statement:
| expr ';' { zend_do_free(&$1 TSRMLS_CC); }
;
expr:
r_variable { $$ = $1; }
| expr_without_variable { $$ = $1; }
;
expr_without_variable:
| variable '=' expr { zend_check_writable_variable(&$1); zend_do_assign(&$$, &$1, &$3 TSRMLS_CC); }
| scalar { $$ = $1; }
| expr '.' expr { zend_do_binary_op(ZEND_CONCAT, &$$, &$1, &$3 TSRMLS_CC); }
;
scalar:
| common_scalar { $$ = $1; }
;
common_scalar:
T_LNUMBER { $$ = $1; }
| T_DNUMBER { $$ = $1; }
| T_CONSTANT_ENCAPSED_STRING { $$ = $1; }
;
variable:
| base_variable_with_function_calls { $$ = $1; }
;
base_variable_with_function_calls:
base_variable { $$ = $1; }
;
base_variable:
reference_variable { $$ = $1; $$.EA = ZEND_PARSED_VARIABLE; }
;
reference_variable:
| compound_variable { zend_do_begin_variable_parse(TSRMLS_C); fetch_simple_variable(&$$, &$1, 1 TSRMLS_CC); }
;
compound_variable:
T_VARIABLE { $$ = $1; }
| '$' '{' expr '}' { $$ = $3; }
;
1)发现第一个字符(这里先不考虑空白)为$开头,第二个字符为n ,符合 LABEL这个正则,接着继续扫描,直到= ,使用下面的规则 将变量名存储到zendlval中
<ST_IN_SCRIPTING,ST_DOUBLE_QUOTES,ST_HEREDOC,ST_BACKQUOTE,ST_VAR_OFFSET>"$"{LABEL} {
zend_copy_value(zendlval, (yytext+1), (yyleng-1));
zendlval->type = IS_STRING;
return T_VARIABLE;
}
返回token的类型是 T_VARIABLE , 值为$name放入zendlval中,yylex()返回的yychar就是这个T_VARIABLE,通过处理,分析器通过yytable的返回,这是一个移进操做,便将T_VARIABLE 放入状态栈,$name放入符号栈 , yyparse()继续执行yylex()函数,由于这里返回的是T_VARIABLE,通过yytable查找,发现是归约操做,即把$name替换为
注意:LABEL 是一个正则表达式 LABEL [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]* 其中 \x7f-\xff 这个是识别utf8下面汉字的16进制,也就 是说,php的变量名称里面能够有汉字
<?php
$个人名字叫='taek';
echo $个人名字叫;
上面的代码是能够正常运行的,估计是易语言出现后,才增长的功能吧
2)扫描到=这个符号
<ST_IN_SCRIPTING>{TOKENS} { return yytext[0]; }
此时的yychar接收 = 这个符号的ASICII的值,并放入状态栈,且将空值放入符号栈
这里面的TOKENS也是一个正则表达式 TOKENS [;:,.\[\]()|^&+-/*=%!~$<>?@],[]括号里的字符只容许出现一次,正好=这个符号在里面,正好匹配
3)接着扫描到', 这个单引号
1 <ST_IN_SCRIPTING>b?['] {
2 register char *s, *t; 3 char *end; 4 int bprefix = (yytext[0] != '\'') ? 1 : 0; 5
6 while (1) { 7 if (YYCURSOR < YYLIMIT) { 8 if (*YYCURSOR == '\'') { 9 YYCURSOR++; 10 yyleng = YYCURSOR - SCNG(yy_text); 11
12 break; 13 } else if (*YYCURSOR++ == '\\' && YYCURSOR < YYLIMIT) { 14 YYCURSOR++; 15 } 16 } else { 17 yyleng = YYLIMIT - SCNG(yy_text); 18
19 /* Unclosed single quotes; treat similar to double quotes, but without a separate token 20 * for ' (unrecognized by parser), instead of old flex fallback to "Unexpected character..." 21 * rule, which continued in ST_IN_SCRIPTING state after the quote */
22 return T_ENCAPSED_AND_WHITESPACE; 23 } 24 } 25
26 zendlval->value.str.val = estrndup(yytext+bprefix+1, yyleng-bprefix-2); 27 zendlval->value.str.len = yyleng-bprefix-2; 28 zendlval->type = IS_STRING; 29
30 /* convert escape sequences */
31 s = t = zendlval->value.str.val; 32 end = s+zendlval->value.str.len; 33 while (s<end) { 34 if (*s=='\\') { 35 s++; 36
37 switch(*s) { 38 case '\\': 39 case '\'': 40 *t++ = *s; 41 zendlval->value.str.len--; 42 break; 43 default: 44 *t++ = '\\'; 45 *t++ = *s; 46 break; 47 } 48 } else { 49 *t++ = *s; 50 } 51
52 if (*s == '\n' || (*s == '\r' && (*(s+1) != '\n'))) { 53 CG(zend_lineno)++; 54 } 55 s++; 56 } 57 *t = 0; 58
59 if (SCNG(output_filter)) { 60 size_t sz = 0; 61 s = zendlval->value.str.val; 62 SCNG(output_filter)((unsigned char **)&(zendlval->value.str.val), &sz, (unsigned char *)s, (size_t)zendlval->value.str.len TSRMLS_CC); 63 zendlval->value.str.len = sz; 64 efree(s); 65 } 66 return T_CONSTANT_ENCAPSED_STRING; 67 }
上面的YYCURSOR会随时变化的,当遇到 第二个单引号时,它会认为扫描过的符号就是字符串了,注意:若是扫描到\'时,会自动跨过,要否则后面的字符会被截掉,最终把字符串放到zendlval中,并返回TOKEN T_CONSTANT_ENCAPSED_STRING
<ST_IN_SCRIPTING>b?['] {
register char *s, *t; char *end; int bprefix = (yytext[0] != '\'') ? 1 : 0; while (1) { if (YYCURSOR < YYLIMIT) { if (*YYCURSOR == '\'') { YYCURSOR++; yyleng = YYCURSOR - SCNG(yy_text); break; } else if (*YYCURSOR++ == '\\' && YYCURSOR < YYLIMIT) { YYCURSOR++; } } else { yyleng = YYLIMIT - SCNG(yy_text); /* Unclosed single quotes; treat similar to double quotes, but without a separate token * for ' (unrecognized by parser), instead of old flex fallback to "Unexpected character..." * rule, which continued in ST_IN_SCRIPTING state after the quote */
return T_ENCAPSED_AND_WHITESPACE; } } zendlval->value.str.val = estrndup(yytext+bprefix+1, yyleng-bprefix-2); zendlval->value.str.len = yyleng-bprefix-2; zendlval->type = IS_STRING; /* convert escape sequences */ s = t = zendlval->value.str.val; end = s+zendlval->value.str.len; while (s<end) { if (*s=='\\') { s++; switch(*s) { case '\\': case '\'': *t++ = *s; zendlval->value.str.len--; break; default: *t++ = '\\'; *t++ = *s; break; } } else { *t++ = *s; } if (*s == '\n' || (*s == '\r' && (*(s+1) != '\n'))) { CG(zend_lineno)++; } s++; } *t = 0; if (SCNG(output_filter)) { size_t sz = 0; s = zendlval->value.str.val; SCNG(output_filter)((unsigned char **)&(zendlval->value.str.val), &sz, (unsigned char *)s, (size_t)zendlval->value.str.len TSRMLS_CC); zendlval->value.str.len = sz; efree(s); } return T_CONSTANT_ENCAPSED_STRING; }
<ST_IN_SCRIPTING>b?['] { 中的b ,全称为binary ,便可定义 二进制的字符串,见下图
我在php 5.4.12 版本中 ,是能够运行的,不像上面黑框中所说的effect on of PHP 6.0.0, 但不知道什么状况下会定义一个二进制的字符串?
if (*YYCURSOR == '\'') {
YYCURSOR++;
yyleng = YYCURSOR - SCNG(yy_text);
break;
}
这段代码的意思是 若是 碰上第二个单引号,那么跳过,否则这就意味着结束了
1)yylex扫描 $name后,yychar 值为 T_VARIABLE 这个token , 通过yytable运算,执行移进操做,将获得的状态值放入状态栈,同时将$name放入符号栈
而后根据 产生式
compound_variable:
| T_VARIABLE { $$ = $1; }
| '$' '{' expr '}' { $$ = $3; }
;
进行归约,先把状态栈pop出一个元素,也就是T_VARIABLE,接着符号栈pop出一个元素,也就是$name, 最后yygogo这个数组计算出新的状态,这个状态就是上面产生式的编号,将此编号放入着状态栈,$name再入符号栈
接着通过下列几个产生式的归约
reference_variable:
| compound_variable { zend_do_begin_variable_parse(TSRMLS_C); fetch_simple_variable(&$$, &$1, 1 TSRMLS_CC); }
;
base_variable:
reference_variable { $$ = $1; $$.EA = ZEND_PARSED_VARIABLE; }
;
base_variable_with_function_calls:
base_variable { $$ = $1; }
;
variable:
| base_variable_with_function_calls { $$ = $1; }
;
最终将variable这个产生式的编号入状态栈
4)yylex扫描'taek'后,yychar 的值为T_CONSTANT_ENCAPSED_STRING 这个token,经yytable数组计算出的值,执行移进操做,此值入状态栈 , 将'taek' 放入 符号栈 ,程序又回到yybackup:执行这里的代码
yyn = yypact[yystate];
if (yyn == YYPACT_NINF)
goto yydefault;
yydefault:
yyn = yydefact[yystate];
if (yyn == 0)
goto yyerrlab;
goto yyreduce;
若是yyn不为0,则由产生式
common_scalar:
T_LNUMBER { $$ = $1; }
| T_DNUMBER { $$ = $1; }
| T_CONSTANT_ENCAPSED_STRING { $$ = $1; }
;
进行归约,将状态栈,符号栈pop出若干元素,再将该产生式编号 放入状态栈,'taek'经处理(可能)后,再放入符合栈,接着再次进行归约,经过下面几个产生式
expr:
r_variable { $$ = $1; }
| expr_without_variable { $$ = $1; }
;
expr_without_variable:
| variable '=' expr { zend_check_writable_variable(&$1); zend_do_assign(&$$, &$1, &$3 TSRMLS_CC); }
| scalar { $$ = $1; }
| expr '.' expr { zend_do_binary_op(ZEND_CONCAT, &$$, &$1, &$3 TSRMLS_CC); }
;
scalar:
| common_scalar { $$ = $1; }
;
进行归约,最终将expr这个并产生式的编号入状态栈, 至此'taek'归约成功
5)yylex扫描 . 这个链接符,并将其ASICII返回给zendparse中的yychar, 执行移进操做, 将ASICII值放入状态栈中,符号栈放一个空元素
6) yylex接着扫描 'world' ,流程跟4同样,符号栈中的'world',在状态栈 对应的是expr(数字而已)
7)再次归约,也就是说expr . expr 可由产生式
expr_without_variable:
| variable '=' expr { zend_check_writable_variable(&$1); zend_do_assign(&$$, &$1, &$3 TSRMLS_CC); }
| scalar { $$ = $1; }
| expr '.' expr { zend_do_binary_op(ZEND_CONCAT, &$$, &$1, &$3 TSRMLS_CC); }
;
进行归约,状态栈,符号栈 pop出三个元素, expr_without_variable 这个产生式
8) variable = expr ,由产生式
expr_without_variable:
| variable '=' expr { zend_check_writable_variable(&$1); zend_do_assign(&$$, &$1, &$3 TSRMLS_CC); }
| scalar { $$ = $1; }
| expr '.' expr { zend_do_binary_op(ZEND_CONCAT, &$$, &$1, &$3 TSRMLS_CC); }
;
进行归约,将状态栈,符号栈pop出若干元素, 再将该产生式编号入状态栈,同时将yylval入符号栈,
8) yylex扫描到;这个符号,将其ASICII值传给yychar, 入状态栈,符号栈入一个空元素
9)expr ; 由产生式
unticked_statement:
| expr ';' { zend_do_free(&$1 TSRMLS_CC); }
;
进行归约,状态栈,符合栈pop出若干个元素, 再将该产生式编号 放入状态栈,同时将
8)
common_scalar
4) 接着是分号的asicII进入状态栈,空值进入符号栈, 此时进行归约操做,并产生opcode代码,
5) 结束
双引号:
<ST_IN_SCRIPTING>b?["] { int bprefix = (yytext[0] != '"') ? 1 : 0; while (YYCURSOR < YYLIMIT) { switch (*YYCURSOR++) { case '"': yyleng = YYCURSOR - SCNG(yy_text); zend_scan_escape_string(zendlval, yytext+bprefix+1, yyleng-bprefix-2, '"' TSRMLS_CC); return T_CONSTANT_ENCAPSED_STRING; case '$': if (IS_LABEL_START(*YYCURSOR) || *YYCURSOR == '{') { break; } continue; case '{': if (*YYCURSOR == '$') { break; } continue; case '\\': if (YYCURSOR < YYLIMIT) { YYCURSOR++; } /* fall through */ default: continue; } YYCURSOR--; break; } /* Remember how much was scanned to save rescanning */ SET_DOUBLE_QUOTES_SCANNED_LENGTH(YYCURSOR - SCNG(yy_text) - yyleng); YYCURSOR = SCNG(yy_text) + yyleng; BEGIN(ST_DOUBLE_QUOTES); return '"'; }
对于双引号括起来的字符串,会对里面的字符进行判断
1)发现 $符号,记录已扫描过字符的位置,而后设置新条件:BEGIN(ST_DOUBLE_QUOTES);能够理解为在ST_DOUBLE_QUOTES的规则中寻找一个恰当的规则 ,接着 继续 执行lex_scan,最终找到的是
<ST_DOUBLE_QUOTES>{ANY_CHAR} { if (GET_DOUBLE_QUOTES_SCANNED_LENGTH()) { YYCURSOR += GET_DOUBLE_QUOTES_SCANNED_LENGTH() - 1; SET_DOUBLE_QUOTES_SCANNED_LENGTH(0); goto double_quotes_scan_done; } if (YYCURSOR > YYLIMIT) { return 0; } if (yytext[0] == '\\' && YYCURSOR < YYLIMIT) { YYCURSOR++; } while (YYCURSOR < YYLIMIT) { switch (*YYCURSOR++) { case '"': break; case '$': if (IS_LABEL_START(*YYCURSOR) || *YYCURSOR == '{') { break; } continue; case '{': if (*YYCURSOR == '$') { break; } continue; case '\\': if (YYCURSOR < YYLIMIT) { YYCURSOR++; } /* fall through */
default: continue; } YYCURSOR--; break; } double_quotes_scan_done: yyleng = YYCURSOR - SCNG(yy_text); zend_scan_escape_string(zendlval, yytext, yyleng, '"' TSRMLS_CC); return T_ENCAPSED_AND_WHITESPACE; }
最终返回T_ENCAPSED_AND_WHITESPACE 标签,并将 字符串放入zendval中
2)再次循环执行lex_scan , 由于全局变量已保存了前面扫描过的字符了,因此从那以后进行扫描 ,
若是 发现 $, 若是$后面的字符在 a-z , A-Z , _ , \x7f-\xff 的范围内,或者$后面是一个左括号 { , 那么说明$后面的将是一个变量,跳转到ST_DOUBLE_QUOTES,进入
<ST_IN_SCRIPTING,ST_DOUBLE_QUOTES,ST_HEREDOC,ST_BACKQUOTE,ST_VAR_OFFSET>"$"{LABEL} { zend_copy_value(zendlval, (yytext+1), (yyleng-1)); zendlval->type = IS_STRING; return T_VARIABLE; }
详见这个例子
<?php $ABC='ABC'; $name="taek$ABC#"; echo $name;
结果是 taekABC#
PHP记法分析把 $ABC看成一个变量,而没有把$ABC#看成变量,由于ABC#不符合 LABEL [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]* 这个规则
2)发现{,若是紧接其后是一个$,则
<ST_DOUBLE_QUOTES,ST_BACKQUOTE,ST_HEREDOC>"{$" { zendlval->value.lval = (long) '{'; yy_push_state(ST_IN_SCRIPTING TSRMLS_CC); yyless(1); return T_CURLY_OPEN; }
#define IS_LABEL_START(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z') || (c) == '_' || (c) >= 0x7F)