摘要:前言使用和进行语法分析和词法分析,本文以语法定义文件为起点,使用等命令行工具搜索相关源码,以此来展示探索语法分析源码思路语法定义文件在源代码根目录下通过命令查找文件我们找到了文件,里面定义了脚本的语法语法分析树节点类型在查看具体的语法规则
前言
php 使用 lex 和 bison 进行语法分析和词法分析,本文以 bison 语法定义文件为起点,使用 find, grep 等命令行工具搜索相关源码,以此来展示探索 PHP 语法分析源码思路
bison 语法定义文件在 源代码 根目录下通过 find 命令查找 *.y 文件
# find . -name *.y ./sapi/phpdbg/phpdbg_parser.y ./ext/json/json_parser.y ./Zend/zend_ini_parser.y ./Zend/zend_language_parser.y
我们找到了 zend_language_parser.y 文件,里面定义了 PHP 脚本 的语法
语法分析树(AST) AST 节点类型: YYSTYPE在查看具体的语法规则前,我们先看看 PHP 使用什么样的数据结构表示 AST 根节点,使用 grep 命令 搜索 YYSTYPE
# grep -rin --color --include=*.h --include=*.c "#define YYSTYPE" Zend/zeng_language_parser.c:108:#define YYSTYPE zend_parser_stack_elemzend_parser_stack_elem
grep zend_parser_stack_elem 结构体定义
# grep -rin --color --include=*.h --include=*.c "zend_parser_stack_elem" Zend/zend_compile.h:126:typedef union _zend_parser_stack_elem
打开 zend_compile.h 文件
typedef union _zend_parser_stack_elem { zend_ast *ast; zend_string *str; zend_ulong num; } zend_parser_stack_elem;
zend_parser_stack_elem 是一个联合体,一个 AST 节点可能是 num(数值),str(字符串)或者 ast(非终结符)
zend_ast搜索 _zend_ast 结构体定义
# grep -rin --color --include=*.h --include=*.c _zend_ast * Zend/zend_ast.h:153:struct _zend_ast {
打开 zend_ast.h 文件
struct _zend_ast { zend_ast_kind kind; zend_ast_attr attire; uint32_t linen; zend_ast *child[1]; }kind
Type of the node(ZEND_AST_* enum constant)
zend_ast.h 文件头部 enum 枚举类型包含了各个 ZEND_AST_* 定义
enum _zend_ast_kind { /* special nodes */ ZEND_AST_ZVAL = 1 << ZEND_AST_SPECIAL_SHIFT, ZEND_AST_ZNODE, /* declaration nodes */ ZEND_AST_FUNC_DECL, ZEND_AST_CLOSURE, ZEND_AST_METHOD, ZEND_AST_CLASS, ... }attr
Additional attribute,use depending on node type
linenLine number
childArray of children(using struct hack)
zend_ast 其它子类zend_ast.h 文件中还包含其它 和 zend_ast 在结构上类似的结构,类似 OOP 中的 子类
zend_ast_list zend_ast_zval zend_ast_decl 创建 zend_astzend_ast.c 中有一系列的函数用于创建 zend_ast, zend_list
zend_ast_create
zend_ast_create_ex
zend_ast_create_list
zend_ast_createzend_ast_create 函数根据 kind 和一个或多个 child zend_ast 创建一个新的 zend_ast,它在内部调用 zend_ast_create_from_va_list
ZEND_API zend_ast *zend_ast_create(zend_ast_kind kind, ...) { va_list va; zend_ast *ast; va_start(va, kind); ast = zend_ast_create_from_va_list(kind, 0, va); va_end(va); return ast; }zend_ast_create_from_va_list yyparse
bison 语法分析工具一般以 yyparse 函数为入口
# grep --color -rinw --include=*.h --include=*.c yyparse * Zend/zend_language_parser.c:63:#define yyparse zendparse
看来 PHP 给 yyparse 起了个别名,我们再看看代码里面哪些地方引用了 zendparse
# grep --color -rinw --include=*.h --include=*.c zendparse * Zend/zend_language_scanner.c:587: if (!zendparse()) { Zend/zend_language_parser.c:436:int zendparse (void);
我们看到 zend_language_scanner.c 文件中使用了 zenparse()
static zend_op_array *zend_compile(int type) { ... if (!zendparse()) { ... } ... }
PHP 命名规范还是不错的,从 zend_compile 可以推测出这个函数应该是用来编译一段 PHP 代码,返回值 zend_op_array 估计是 PHP 虚拟机字节码数组
# grep --color -rinw --include=*.h --include=*.c zend_compile Zend/zend_language_scanner.c:637: op_array = zend_compile Zend/zend_language_scanner.c:769: op_array = zend_compile
我们在 zend_language_scanner.c 的 637 和 769 行找到了两处对 zend_compile 的引用
623 ZEND_API zend_op_array *compile_file(...) 645 zend_op_array *compile_filename(int type, zval *filename)
顺藤摸瓜,我们接着查找 compile_file,compile_filename
# grep --color -rinw --include=*.h --include=*.c compile_file Zend/zend.c:705 zend_compile_file = compile_file; Zend/zend.c:711 zend_compile_file = compile_file;
zend.c 705 在函数 zend_startup 内
int zend_startup(zend_utility_functions *utility_functions, char **extensions)zend_compile compiler globals(CG)
语法分析和中间代码生成过程中使用了 全局变量 CG 来保存中间结果(AST),搜索 CG 宏定义
/* Compiler */ #ifdef ZTS # define CG(v) ZEND_TSRMG(compiler_globals_id, zend_compiler_globals *, v) #else # define CG(v) (compiler_globals.v) extern ZEND_API struct _zend_compiler_globals compiler_globals; #endif
这里有一个条件编译开关 ZTS,PHP 老鸟因该都知道 "要使用 pthreads 扩展,需要构建 PHP 时启用 ZTS (Zend Thread Safety)",很自然想到:因为 CG 是一个全局变量,所以为了在多线程环境下保证线程安全,需要使用特殊机制(类似 Java 中的 TLS,thread local storage)访问 CG 中的字段
实现有了关于 CG 的铺垫,我们马上来看 zend_compile 函数的实现
// zend_language_scanner.c static zend_op_array *zend_compile(int type) { // 编译生成的字节码数组 zend_op_array *op_array = NULL; zend_bool original_in_compilation = CG(in_compilation); CG(in_compilation) = 1; CG(ast) = NULL; CG(ast_arena) = zend_arena_create(1024 * 32); if (!zendparse()) { ... op_array = emalloc(sizeof(zend_op_array)); init_op_array(op_array, type, INITIAL_OP_ARRAY_SIZE); ... zend_compile_top_stmt(CG(ast)); zend_emit_final_return(type == ZEND_USER_FUNCTION); pass_two(op_array); CG(active_op_array) = original_active_op_array; } ... CG(in_compilation) = original_in_compilation; return op_array; }
这里忽略了一些细节,突出语法分析和字节码生成的流程
调用 zend_parse 进行语法分析,生成的 AST 根节点保存在 GC(ast) 中
为字节码数组分配内存
调用 zend_compile_top_stmt 根据 AST 生成字节码数组保存在 GC(active_op_array) 中
总结文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/30535.html
摘要:前言字节码生成编译的代码主要集中在,文件中包含大量的函数,基本上一个函数对应语法规则文件一个非终结符,函数是所有函数的入口数据结构结构体是字节码抽象结构体并没有像名字那样简单,它包含了大量的字段供虚拟机在运行时使用一如既往的简单,直观,相比 前言 字节码生成(编译)的代码主要集中在 zend_compile.c ,文件中包含大量的 zend_compile_xxx 函数,基本上一个函数...
摘要:前言本文简要介绍虚拟机解释执行字节码的基本逻辑以及相关的数据结构,关于源代码的下载,编译,调试可以参考之前的系列文章我们来看看执行一个简单的脚本的调用栈由于是执行脚本文件,所以调用了函数,最终调用函数和其它语言编写的系统软件类似,函数中 前言 本文简要介绍 zend 虚拟机解释执行字节码的基本逻辑以及相关的数据结构,关于 PHP 源代码的下载,编译,调试可以参考之前的系列文章 exec...
摘要:前言本文通过分析这个语句的编译和执行来窥探解释执行逻辑准备参考之前的系列文章,在环境下下载,编译源代码将代码导入中编辑运行选项,增加运行参数设置断点开始调试是一个测试脚本,放在目录下,中只包含一条简单的赋值语句调用堆栈参考之前的系列文章 前言 本文通过分析 $a=1 这个 PHP 语句的编译和执行来窥探 php-cli 解释执行逻辑 准备 参考之前的系列文章,在 ubuntu 环境下...
摘要:前言语法分析器调用获取词法单元,对于复杂的语言实现一般都会自定义,搜索的宏定义搜索函数的定义打开文件查看函数定义这里出现了两个新的数据类型,,从命名推测是语法分析栈元素语法分析树节点,搜索代码里面哪些地方引用了 前言 yylex bison 语法分析器调用 yylex 获取词法单元,对于复杂的语言实现一般都会自定义 yylex,搜索 yylex 的宏定义 # grep -rin --c...
摘要:前言本文从函数定义的语法规则开始,简要介绍解释器如何编译函数定义函数对应的节点为了看起来清楚一些,我们将语法规则定义与语法动作分开根据语法动作,这条函数定义规则会创建一个类型的结点,我们来看看方法是一个通用的方法,通 前言 本文从函数定义的语法规则开始,简要介绍 PHP 解释器如何 编译 函数定义 函数对应的 AST 节点 为了看起来清楚一些,我们将 语法规则定义 与 语法动作分开: ...
阅读 1811·2019-08-30 13:54
阅读 2726·2019-08-29 17:27
阅读 1110·2019-08-29 17:23
阅读 3352·2019-08-29 15:20
阅读 1228·2019-08-29 11:28
阅读 1568·2019-08-26 10:39
阅读 1317·2019-08-26 10:29
阅读 640·2019-08-26 10:13