- URL: https://www.laruence.com/en/2010/07/16/1648.html
- Please include attribution when republishing.
First, a digression: it's been nearly two years since I published my first article on how PHP executes. I'm glad to see that over those two years, more and more people in the Chinese PHP community have started studying the PHP source, and more and more have started doing PHP extension development.
As for how PHP executes, I think the readers of my blog can recite it off the top of their heads:
1. Lexing, strip comments and whitespace, get TOKENs 2. Parsing, which generates the Opcode array (op_array) in the process 3. Interpretation, execute the op_array, interpreting the Oplines one at a time (SWITCH, CALL, GOTO)
Someone asked me today, saying he'd seen a PHPer talking about an unless statement. I was quite puzzled, until I learned that a well-known PHP guru abroad had hacked the PHP source himself and added an unless statement.
Interesting. So today, let me show you how to add an unless statement to your own PHP.
If you don't understand PHP's execution process, please spend a little time on my earlier post Deep dive into PHP internals: Opcodes:
Our goal is to implement the following syntax (based on php 5.2.11):
<?php
unless(TRUE) {
// this will not be executed
}
unless(FALSE) {
// this will be executed
}
Right, so unless looks like the antonym of if. That makes it easy: whatever if does, we just do the opposite.
First, in the lexing stage, we need to add the TOKEN definition for unless. Edit Zend/zend_language_scanner.l:
// add the Token definition for unless
<st_IN_SCRIPTING>"unless" {
return T_UNLESS;
}
<st_IN_SCRIPTING>"if" {
return T_IF;
}
<st_IN_SCRIPTING>"elseif" {
return T_ELSEIF;
}
This way, when the lexer runs into unless, it reports that it found a T_UNLESS Token.
Next we need to define the grammar action for T_UNLESS in the parsing stage. Edit 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); }
// add the grammar action for 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)};
Right, since if and unless differ only in whether the condition is true or false, I did this simple hack on top of if. Next, zend_do_unless_cond should be defined. That logic generates OPCODEs. Again, base the change on zend_do_if_cond, in 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; // jump if it is 0
opline->op1 = *cond;
closing_bracket_token->u.opline_num = if_cond_op_number; // jump address
SET_UNUSED(opline->op2);
INC_BPC(CG(active_op_array));
}
OK, so our zend_do_unless_cond can be defined like this:
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; // jump if it is not 0
opline->op1 = *cond;
closing_bracket_token->u.opline_num = if_cond_op_number;
SET_UNUSED(opline->op2);
INC_BPC(CG(active_op_array));
}
Note the OPCODE above: we changed ZEND_JMPZ to JMPNZ...
Done. Now we need to recompile PHP:
cd Zend; rm zend_language_*.c; cd phpsrc_dir; make
Write a test script:
<?php
unless(FALSE) {
echo "Laruence";
}
Run it.....
Finally, the example given here is relatively simple, since there's an if to model it on. If you're interested, go play around with it and implement all kinds of syntax from other languages in PHP.
Be First to Comment