php的词法分析 可以理解为 通过一定的规则,把输入的代码 区分出哪些是 是$开头的变量, 哪些是 以两个单引号括起来的字符串,哪些是以两个双引号括起来的字符串 等等, 这些区分出来的东西 称为token ,token 之间的联系 是由语法分析来完成的, 比如 赋值,加减乘除;

 

  语法分析详见这里

  语法分析的驱动程序 yyparse() 调用yylex()这个函数 , 这个函数 可以由flex生成,也可以人为编写,在php中,属于后者;

每次执行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这个结构体中去

   zend_language_parse.y中的token 例如下面的

%token T_INCLUDE      "include (T_INCLUDE)"

  bison官网有一段关于%token的使用的描述:

  yylex函数其实就是zendlex函数

这个yyparse会循环调用lex_scan,lex_scan定义在zend_language_scanning.l中 , 这个函数体大部分是一些正则表达式,以区分出代码中哪些是变量,哪些是字符串,哪些是数组等等

 

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[];
}

此时的yychar接收 = 这个符号的ASICII的值,并放入状态栈,且将空值放入符号栈

  这里面的TOKENS也是一个正则表达式  TOKENS [;:,.\[\]()|^&+-/*=%!~$<>?@],[]括号里的字符只允许出现一次,正好=这个符号在里面,正好匹配

  

  3)接着扫描到', 这个单引号

 <ST_IN_SCRIPTING>b?['] {
register char *s, *t;
char *end;
int bprefix = (yytext[] != '\'') ? : ; while () {
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+, yyleng-bprefix-);
zendlval->value.str.len = yyleng-bprefix-;
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+) != '\n'))) {
CG(zend_lineno)++;
}
s++;
}
*t = ; if (SCNG(output_filter)) {
size_t sz = ;
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;
}

  上面的YYCURSOR会随时变化的,当遇到 第二个单引号时,它会认为扫描过的符号就是字符串了,注意:如果扫描到\'时,会自动跨过,要不然后面的字符会被截掉,最终把字符串放到zendlval中,并返回TOKEN T_CONSTANT_ENCAPSED_STRING

<ST_IN_SCRIPTING>b?['] {
register char *s, *t;
char *end;
int bprefix = (yytext[] != '\'') ? : ; while () {
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+, yyleng-bprefix-);
zendlval->value.str.len = yyleng-bprefix-;
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+) != '\n'))) {
CG(zend_lineno)++;
}
s++;
}
*t = ; if (SCNG(output_filter)) {
size_t sz = ;
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[] != '"') ? : ; while (YYCURSOR < YYLIMIT) {
switch (*YYCURSOR++) {
case '"':
yyleng = YYCURSOR - SCNG(yy_text);
zend_scan_escape_string(zendlval, yytext+bprefix+, yyleng-bprefix-, '"' 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() - ;
SET_DOUBLE_QUOTES_SCANNED_LENGTH(); goto double_quotes_scan_done;
} if (YYCURSOR > YYLIMIT) {
return ;
}
if (yytext[] == '\\' && 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+), (yyleng-));
  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();
return T_CURLY_OPEN;
}

#define IS_LABEL_START(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z') || (c) == '_' || (c) >= 0x7F)

php 词法分析,语法分析的更多相关文章

  1. 从jvm来看,scala中的@究竟是个什么鬼?@模式匹配符号(scala 词法分析 语法分析常用)

    从jvm来看,scala中的@究竟是个什么鬼? 我也是初步尝试来看jvm的类文件,又是初次来分析@,如不对的地方,请各位指正! 先看一下@ 是个什么? object TestScala { def m ...

  2. Python之路3【第一篇】Python基础

    本节内容 Python简介 Python安装 第一个Python程序 编程语言的分类 Python简介 1.Python的由来 python的创始人为吉多·范罗苏姆(Guido van Rossum) ...

  3. SQLite学习笔记(八)&&sqlite实现架构

    该系列的前面一些文章我重点讲了sqlite的核心功能,比如封锁机制,共享缓存,以及事务管理等.但对于sqlite的整体没有作一个全面的介绍,本文将从实现的层面,整体介绍sqlite的框架.各个核心模块 ...

  4. 利用ANTLR4实现一个简单的四则运算计算器

    利用ANTLR4实现一个简单的四则运算计算器 ANTLR4介绍 ANTLR能够自动地帮助你完成词法分析和语法分析的工作, 免去了手写去写词法分析器和语法分析器的麻烦 它是基于LL(k)的, 以递归下降 ...

  5. JS疑难点和GC原理

    js解析与序列化json数据(一)json.stringify()的基本用法: 对象有两个方法:stringify()和parse().在最简单的情况下,这两个方法分别用于把JavaScript对象序 ...

  6. Python基础一. 简介、变量、对象及引用

    一.Python简介 Python是一门计算机编程语言,它是由荷兰人Guido van Rossum在1989年圣诞节期间为了打发无聊的圣诞节而编写的,作为ABC语言的继承 特性: 面向对象.解释型. ...

  7. MySQL prepare 原理

    Prepare的好处  Prepare SQL产生的原因.首先从mysql服务器执行sql的过程开始讲起,SQL执行过程包括以下阶段 词法分析->语法分析->语义分析->执行计划优化 ...

  8. GCC、Makefile编程学习

    相关学习资料 http://gcc.gnu.org/ https://gcc.gnu.org/onlinedocs/ http://zh.wikipedia.org/zh/GCC http://blo ...

  9. Python基础第一篇

    一.第一句python代码 1.python执行过程:1.加载内存-词法分析-语法分析-编译-执行 2.创建hello.py文件,输入内容 #!/usr/bin/env python print &q ...

  10. L1 - 运行机制

    var name = 'kl'; function person(){ alert(name); var name = 'ko'; } person(); 这段代码输出 ‘undefined’,这种现 ...

随机推荐

  1. 【Maven】Nexus配置和使用

    Nexus安装 nexus安装,可以参照:[Maven]Nexus(Maven仓库私服)下载与安装 Nexus简单说明 用途:指定私服的中央地址.将自己的Maven项目指定到私服地址.从私服下载中央库 ...

  2. 2018.10.20 NOIP模拟 蛋糕(线段树+贪心/lis)

    传送门 听说是最长反链衍生出的对偶定理就能秒了. 本蒟蒻直接用线段树模拟维护的. 对于第一维排序. 维护第二维的偏序关系可以借助线段树/树状数组维护逆序对的思想建立权值线段树贪心求解. 代码

  3. Linux上查看造成IO高负载的进程

    方法1:使用iotop工具这是一个python脚本工具,使用方法如:iotop -o方法2:使用工具dmesg使用dmesg之前,需要先开启内核的IO监控:echo 1 >/proc/sys/v ...

  4. mac windows蓝牙问题

    如果是win7.win8或win10三者的64位版本,可以下载驱动解决:http://file2.mydrivers.com/2014/notebook/apple_broadcom_bluetoot ...

  5. 测试-LoadRunner

    1录脚本 设置解析方式,html形式,会精炼成一个函数,此时找有用的url,写出函数:url方式,函数比较多. 参数化 两参数成对时,在脚本处选成对. 加上进程,加上返回值判断. 最后一段接口url, ...

  6. hdu 5037 周期优化

    http://acm.hdu.edu.cn/showproblem.php?pid=5037 有只青蛙踩石子过河,河宽m,有n个石子坐标已知.青蛙每次最多跳L.现在可以在河中再放一些石子,使得青蛙过河 ...

  7. [label][翻译][JavaScript Regular Expression]JavaScript Regular Expressions

    原文:http://www.javascriptkit.com/javatutors/re.shtml 校验用户的输入是每一个软件开发者的必须要做的事情. 正则表达式与模式 如何在JavaScript ...

  8. python的数据存储

    Python存储数据 使用json.dump()和json.load() 不管专注的是什么,程序都把用户提供的信息存储在列表和字典等数据结构中.用户关闭程序时,你几乎总是要保存他们提供的信息:一种简单 ...

  9. Apache Geode with Spark

    在一些特定场景,例如streamingRDD需要和历史数据进行join从而获得一些profile信息,此时形成较小的新数据RDD和很大的历史RDD的join. Spark中直接join实际上效率不高: ...

  10. .net core An assembly specified in the application dependencied mainfest<****.json>was not found解决办法

    最近在开发项目中,遇到了一个问题.在本机开发中部署到本机iis上或者本机控制台都没有问题,运行正常.当发布部署到服务器(windowsServer)中的时候一直运行不起来,用控制台也运行不起来,直接报 ...