定制自己的PHP语法-在PHP中实现unless

作者: nick 分类: php 发布时间: 2010-07-24 05:46 ė 6没有评论

首先 ,插一句题外话: 从我发表第一篇PHP执行原理的文章到现在, 已经快2年了, 我很高兴的看到这俩年里PHP中文圈里 ,越来越多的人开始研究PHP源码, 也有越来越多的人开始从事PHP扩展开发.

关于PHP的执行原理, 我想我BLOG的读者都能脱口而出:

1. 词法分析, 去掉注释, 空白, 得到TOKEN
2. 语法分析, 在这个过程中生成Opcode array (op_array)
3. 解释执行, 执行op_array, 一条一条的解释执行Opline(SWITCH, CALL, GOTO)

今天有人问我, 说他看到有PHPer在说unless语句, 我很是纳闷, 后来才知道, 原来是一个国外的PHP大牛自己Hack了PHP的源码, 加入了一个unless语句.

很有意思, 今天, 我也就在这里为大家演示, 如何为我们自己的PHP加入unless语句..

如果你是不了解PHP的执行过程, 请先花点时间看看我之前的文章深入理解PHP原理之Opcodes:

我们的目标, 是要实现如下的语法(以php 5.2.11为基础):

<?php
unless(TRUE) {
//这不会被执行
}
unless(FALSE) {
//这会被执行
}

好得, 看起来unless是if的反义词了.. 那么就好办了, if怎么来, 我们相反的来就可以了..

首先, 词法分析阶段, 我们需要添加unless的TOKEN定义, 编辑Zend/zend_language_scanner.l:

//添加unless的Token定义
<ST_IN_SCRIPTING>"unless" {
    return T_UNLESS;
}

<ST_IN_SCRIPTING>"if" {
    return T_IF;
}

<ST_IN_SCRIPTING>"elseif" {
    return T_ELSEIF;
}

这样, 词法分析器遇到unless的时候, 就会报告发现了一个T_UNLESS Token.

接下来就需要在语法分析阶段, 定义T_UNLESS的语法动作了, 编辑Zend/zend_language_parse.y

unticked_statement:
        '{' inner_statement_list '}'
    |   T_IF '(' expr ')' { zend_do_if_cond(&$3, &$4 TSRMLS_CC); }
		statement { zend_do_if_after_statement(&$4, 1 TSRMLS_CC); }
		elseif_list else_single { zend_do_if_end(TSRMLS_C); }
//添加T_UNLESS的语法动作
	|   T_UNLESS '(' expr ')' { zend_do_unless_cond(&$3, &$4 TSRMLS_CC);}
		statement {zend_do_if_after_statement(&$4, 1 TSRMLS_CC);}
				  {zend_do_if_end(TSRMLS_C)};

对的, 因为if和unless只是在条件的真假上不同, 所以我在if的基础上, 做了这个简单的hack, 接下来, 就应该定义zend_do_unless_cond了, 这个逻辑是用来生成OPCODE的. 还是从zend_do_if_cond为基础来做修改, Zend/zend_compile.c:

void zend_do_if_cond(znode *cond, znode *closing_bracket_token TSRMLS_DC)
{
    int if_cond_op_number = get_next_op_number(CG(active_op_array));
    zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC); 

    opline->opcode = ZEND_JMPZ;  //如果为0则跳转
    opline->op1 = *cond;
    closing_bracket_token->u.opline_num = if_cond_op_number; //跳转地址
    SET_UNUSED(opline->op2);
    INC_BPC(CG(active_op_array));
}

OK, 那么我们的zend_do_unless_cond就可以这样定义:

void zend_do_unless_cond(znode *cond, znode *closing_bracket_token TSRMLS_DC)
{
    int if_cond_op_number = get_next_op_number(CG(active_op_array));
    zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC); 

    opline->opcode = ZEND_JMPNZ;  //如果为不为0则跳转
    opline->op1 = *cond;
    closing_bracket_token->u.opline_num = if_cond_op_number;
    SET_UNUSED(opline->op2);
    INC_BPC(CG(active_op_array));
}

注意上面的OPCODE, 我们把ZEND_JMPZ变成了JMPNZ…

大功告成, 现在需要重新编译PHP了:

cd Zend;
rm zend_language_*.c;
cd phpsrc_dir;
make

编写测试脚本:

<?php
unless(FALSE) {
    echo "Laruence";
}

运行…..

最后, 举的这个例子,相对来说是简单的, 因为有if可以参照, 有兴趣的同学, 可以自己玩玩, 用PHP实现其它语言中的各种语法~~

本文出自 传播、沟通、分享,转载时请注明出处及相应链接。

本文永久链接: http://www.db.run/?p=889

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Ɣ回顶部