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. PythonWEB框架之Flask--3

    13.请求上下文源码分析 第一阶段:将ctx(request,session)放到Local对象上 第二阶段:视图函数导入:request/session request.method -LocalP ...

  2. Devexpress VCL Build v2014 vol 14.2.4 发布

    What's New in 14.2.4 (VCL Product Line)   New Major Features in 14.2 What's New in VCL Products 14.2 ...

  3. 安装指定版本的docker

    安装 Docker 从 2017 年 3 月开始 docker 在原来的基础上分为两个分支版本: Docker CE 和 Docker EE. Docker CE 即社区免费版,Docker EE 即 ...

  4. 判定map中是否存在某元素

    判断某key是否存在可以使用map的count方法来间接判定 count接受一个参数key值,返回map中key值为给定值的元素总数 map<int, string> i_to_s_map ...

  5. 2018.09.14 bzoj2982: combination(Lucas定理)

    传送门 貌似就是lucas的板子题啊. 练一练手感觉挺舒服的^_^ 代码: #include<bits/stdc++.h> #define mod 10007 #define ll lon ...

  6. 2018.08.17 洛谷P3110 [USACO14DEC]驮运(最短路)

    传送门 一道sb最短路,从两个起点和终点跑一边最短路之后直接枚举两人的汇合点求最小值就行了. 代码: #include<bits/stdc++.h> #define N 40005 #de ...

  7. DIV+CSS实战(三)

    一.说明 在上篇博客<DIV+CSS实战(二)>中,实现了头部以及Tab标签卡,下面开始实现内容区域,要实现的效果如下: 二.内容最外层的设计(边框) 给最外层加边框,并且设置高度随着里面 ...

  8. Java(Android)线程池[转]

    介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端执行一个异步任务你还只是如下new T ...

  9. (并查集)A Bug's Life -- POJ -- 2492

    链接: http://poj.org/problem?id=2492 http://acm.hust.edu.cn/vjudge/contest/view.action?cid=82830#probl ...

  10. Billman_ford货币升值——正权回路

    2240和1860那个题目很像啊 都是问货币能不能增多,钻社会制度得空子啊哈哈 唯一不同得是你的起点是任意一个点,这个比较麻烦了,多了一层循环嘞 处理货币名可以用map分配id 然后就是老套的Bill ...