Hi Christian, I took a look into your patch and found it too difficult. So I implemented another patch (attached) which is based on your ideas.
>From user's level of view it does exactly the same except for "lexical" variables definition. I don't use any new reserver word because every new reserved word is going to break some user code. I use the special syntax for lambda function definition instead, which looks much clear for me. The following code creates a lambda function with arguments $x, $y and lexical variables $a, $b, $c. $a = function($x, $y | $a, $b $c) {}; The patch shouldn't affect opcode caches and other extensions as it doesn't change any structures. It uses the op_array->static_variables for lexical variables. The patch also fixes several small issues and adds some missing functionality which didn't allow preg_replace_callback() (and may be others) to work with lambda functions. Now the following example works fine. <?php class X { private function foo($x) { echo $x; } function bar($s) { return function ($x | $s) { static $n = 0; $n++; $s = $n.':'.$s; $this->foo($x[0].':'.$s); }; } } $x = new X; $x = $x->bar("bye\n"); $s = 'abc'; preg_replace_callback('/[abc]/', $x, $s); ?> It prints: a:1:bye b:2:1:bye c:3:2:1:bye Of course the patch doesn't break any existent tests. Please review. Thanks. Dmitry. Christian Seiler wrote: > Hi, > > As a followup to the discussion in January, I'd like post a revised > patch to > this list that implements closures and anonymous functions in PHP. > > INTRODUCTION > ------------ > > Closures and lambda functions can make programming much easier in > several ways: > > 1. Lambda functions allow the quick definition of throw-away functions > that are not used elsewhere. Imaging for example a piece of code that > needs to call preg_replace_callback(). Currently, there are three > possibilities to acchieve this: > > a. Define the callback function elsewhere. This distributes code that > belongs together throughout the file and decreases readability. > > b. Define the callback function in-place (but with a name). In that > case > one has to use function_exists() to make sure the function is only > defined once. Example code: > > <?php > function replace_spaces ($text) { > if (!function_exists ('replace_spaces_helper')) { > function replace_spaces_helper ($matches) { > return str_replace ($matches[1], ' ', ' ').' '; > } > } > return preg_replace_callback ('/( +) /', > 'replace_spaces_helper', > $text); > } > ?> > > Here, the additional if() around the function definition makes the > source code difficult to read. > > c. Use the present create_function() in order to create a function at > runtime. This approach has several disadvantages: First of all, > syntax > highlighting does not work because a string is passed to the > function. > It also compiles the function at run time and not at compile > time so > opcode caches can't cache the function. > > 2. Closures provide a very useful tool in order to make lambda > functions even > more useful. Just imagine you want to replace 'hello' through > 'goodbye' in > all elements of an array. PHP provides the array_map() function which > accepts a callback. If you don't wan't to hard-code 'hello' and > 'goodbye' > into your sourcecode, you have only four choices: > > a. Use create_function(). But then you may only pass literal values > (strings, integers, floats) into the function, objects at best as > clones (if var_export() allows for it) and resources not at all. > And > you have to worry about escaping everything correctly. > Especially when > handling user input this can lead to all sorts of security issues. > > b. Write a function that uses global variables. This is ugly, > non-reentrant and bad style. > > c. Create an entire class, instantiate it and pass the member function > as a callback. This is perhaps the cleanest solution for this > problem > with current PHP but just think about it: Creating an entire > class for > this extremely simple purpose and nothing else seems overkill. > > d. Don't use array_map() but simply do it manually (foreach). In this > simple case it may not be that much of an issue (because one simply > wants to iterate over an array) but there are cases where doing > something manually that a function with a callback as parameter > does > for you is quite tedious. > > [Yes, I know that str_replace also accepts arrays as a third > parameter so > this example may be a bit useless. But imagine you want to do a more > complex operation than simple search and replace.] > > PROPOSED PATCH > -------------- > > I now propose a patch that implements compile-time lambda functions and > closures for PHP while keeping the patch as simple as possible. The > patch is > based on a previous patch on mine which was based on ideas discussed here > end of December / start of January. > > Userland perspective > -------------------- > > 1. The patch adds the following syntax as a valid expression: > > function & (parameters) { body } > > (The & is optional and indicates - just as with normal functions - that the > anonymous function returns a reference instead of a value) > > Example usage: > > $lambda = function () { echo "Hello World!\n"; }; > > The variable $lambda then contains a callable resource that may be called > through different means: > > $lambda (); > call_user_func ($lambda); > call_user_func_array ($lambda, array ()); > > This allows for simple lambda functions, for example: > > function replace_spaces ($text) { > $replacement = function ($matches) { > return str_replace ($matches[1], ' ', ' ').' '; > }; > return preg_replace_callback ('/( +) /', $replacement, $text); > } > > 2. The patch implements closures by defining an additional keyword > 'lexical' > that allows an lambda function (and *only* an lambda function) to import > a variable from the "parent scope" to the lambda function scope. Example: > > function replace_in_array ($search, $replacement, $array) { > $map = function ($text) { > lexical $search, $replacement; > if (strpos ($text, $search) > 50) { > return str_replace ($search, $replacement, $text); > } else { > return $text; > } > }; > return array_map ($map, array); > } > > The variables $search and $replacement are variables in the scope of the > function replace_in_array() and the lexical keyword imports these variables > into the scope of the closure. The variables are imported as a reference, > so any change in the closure will result in a change in the variable of the > function itself. > > 3. If a closure is defined inside an object, the closure has full access > to the current object through $this (without the need to use 'lexical' to > import it seperately) and all private and protected methods of that class. > This also applies to nested closures. Essentially, closures inside > methods are > added as public methods to the class that contains the original method. > > 4. Closures may live longer as the methods that declared them. It is > perfectly > possible to have something like this: > > function getAdder($x) { > return function ($y) { > lexical $x; > return $x + $y; > }; > } > > Zend internal perspective > ------------------------- > > The patch basically changes the following in the Zend engine: > > When the compiler reaches a lambda function, it creates a unique name > for that > function ("\0__compiled_lambda_FILENAME_N" where FILENAME is the name of > the > file currently processed and N is a per-file counter). The use of the > filename > in the function name ensures compability with opcode caches. The lambda > function is then immediately added to the function table (either the global > function table or that of the current class if declared inside a class > method). > Instead of a normal ZEND_DECLARE_FUNCTION opcode the new > ZEND_DECLARE_LAMBDA_FUNC is used as an opcode at this point. The op_array > of the new function is initialized with is_lambda = 1 and is_closure = 0. > > When parsing a 'lexical' declaration inside an anonymous function the > parser > saves the name of the variable that is to be imported in an array stored > as a member of the op_array structure (lexical_names). > > The opcode handler for ZEND_DECLARE_LAMBDA_FUNC does the following: > First of > all it creates a new op_array and copies the entire memory structure of the > lambda function into it (the opcodes themselves are not copied since they > are only referenced in the op_array structure). Then it sets is_closure = 1 > on the new op_array, and for each lexical variable name that the compiler > added to the original op_array it creates a reference to that variable from > the current scope into a HashTable member in the new op_array. It also > saves > the current object pointer ($this) as a member of the op_array in order to > allow for the closure to access $this. Finally it registers the new > op_array > as a resource and returns that resource. > > The opcode handler of the 'lexical' construct simply fetches the variable > from that HashTable and imports it into local scope of the inner function > (just like with 'global' only with a different hash table). > > Some hooks were added that allow the 'lambda function' resource to be > called. > Also, there are several checks in place that make sure the lambda function > is not called directly, i.e. if someone explicitely tries to use the > internal > function name instead of using the resource return value of the > declaration. > > The patch > --------- > > The patch is available here: > <http://www.christian-seiler.de/temp/closures-php-5.3-2008-06-16-1.diff> > > Please note that I did NOT include the contents of zend_language_scanner.c > in the patch since that can easily be regenerated and just takes up > enormous > amounts of space. > > The patch itself applies against the 5.3 branch of PHP. > > If I understand the discussion regarding PHP6 on this list correctly, some > people are currently undergoing the task of removing the unicode_semantics > switch and if (UG(unicode)). As soon as this task is finished I will also > provide a patch for CVS HEAD (it doesn't make much sense adopting the patch > now and then having to change it again completely afterwards). > > BC BREAKS > --------- > > * Introduction of a new keyword 'lexical'. Since it is very improbable > that > someone should use it as a function, method, class or property name, I > think this is an acceptable break. > > Other that that, I can find no BC breaks of my patch. > > CAVEATS / POSSIBLE WTFS > ----------------------- > > * On writing $func = function () { }; there is a semicolon necessary. > If left > out it will produce a compile error. Since any attempt to remove that > necessity would unecessarily bloat the grammar, I suggest we simply keep > it the way it is. Also, Lukas Kahwe Smith pointed out that a single > trailing semicolon after a closing brace already exists: do { } while > (); > > * The fact that 'lexical' creates references may cause certain WTFs: > > for ($i = 0; $i < 10; $i++) { > $arr[$i] = function () { lexical $i; return $i; }; > } > > This will not work as expected since $i is a reference and thus all > created closures would reference the same variable. In order to get this > right one has to do: > > for ($i = 0; $i < 10; $i++) { > $loopIndex = $i; > $arr[$i] = function () { lexical $loopIndex; return $loopIndex; }; > unset ($loopIndex); > } > > This can be a WTF for people that don't expect lexical to create an > actual reference, especially since other languages such as JavaScript > don't do it. On the other hand, global and static both DO create > references so that behaviour is consistent with current PHP. > > But complex constructions such as this will probably not be used by > beginners so maintaining a good documentation should solve this. > > * The fact that 'lexical' is needed at all may cause WTFs. Other languages > such as JavaScript implicitely have the entire scope visible to child > functions. But since PHP does the same thing with global variables, I > find a keyword like 'lexical' much more consistent than importing the > entire scope (and always importing the entire scope costs unnecessary > performance). > > FINAL THOUGHTS > -------------- > > My now proposed patch addresses the two main problems of my previous patch: > Support for closures in objects (with access to $this) and opcode > caches. My > patch applies against PHP_5_3 and does not break any tests. It adds a > valuable > new language feature which I'd like to see in PHP. > > Regards, > Christian >
Index: Zend/zend_API.c =================================================================== RCS file: /repository/ZendEngine2/zend_API.c,v retrieving revision 1.296.2.27.2.34.2.38 diff -u -p -d -r1.296.2.27.2.34.2.38 zend_API.c --- Zend/zend_API.c 5 Jun 2008 18:53:06 -0000 1.296.2.27.2.34.2.38 +++ Zend/zend_API.c 19 Jun 2008 10:04:37 -0000 @@ -2615,6 +2615,28 @@ ZEND_API zend_bool zend_is_callable_ex(z } return 0; + case IS_RESOURCE: + { + zend_closure *closure = (zend_closure*) zend_fetch_resource(&callable TSRMLS_CC, -1, NULL, NULL, 1, le_lambda_func); + + if (closure) { + *fptr_ptr = (zend_function*)&closure->op_array; + if (closure->This) { + *zobj_ptr_ptr = &closure->This; + *ce_ptr = Z_OBJCE_P(closure->This); + } else { + *zobj_ptr_ptr = NULL; + *ce_ptr = closure->op_array.scope; + } + if (callable_name) { + *callable_name_len = strlen(closure->op_array.function_name); + *callable_name = estrndup(closure->op_array.function_name, *callable_name_len); + } + return 1; + } + } + /* break missing intentionally */ + default: if (callable_name) { zval expr_copy; Index: Zend/zend_compile.c =================================================================== RCS file: /repository/ZendEngine2/zend_compile.c,v retrieving revision 1.647.2.27.2.41.2.68 diff -u -p -d -r1.647.2.27.2.41.2.68 zend_compile.c --- Zend/zend_compile.c 15 Jun 2008 18:27:37 -0000 1.647.2.27.2.41.2.68 +++ Zend/zend_compile.c 19 Jun 2008 10:04:37 -0000 @@ -1375,6 +1375,29 @@ void zend_do_begin_function_declaration( CG(labels) = NULL; } +void zend_do_begin_lambda_function_declaration(znode *result, znode *function_token, int return_reference TSRMLS_DC) +{ + znode function_name; + zend_op_array *current_op_array = CG(active_op_array); + int current_op_number = get_next_op_number(CG(active_op_array)); + zend_op *current_op; + + function_name.op_type = IS_CONST; + ZVAL_STRINGL(&function_name.u.constant, "lambda", sizeof("lambda")-1, 1); + + zend_do_begin_function_declaration(function_token, &function_name, 0, return_reference, NULL TSRMLS_CC); + + result->op_type = IS_TMP_VAR; + result->u.var = get_temporary_variable(current_op_array);; + + current_op = ¤t_op_array->opcodes[current_op_number]; + current_op->opcode = ZEND_DECLARE_LAMBDA_FUNCTION; + zval_dtor(¤t_op->op2.u.constant); + ZVAL_LONG(¤t_op->op2.u.constant, zend_hash_func(Z_STRVAL(current_op->op1.u.constant), Z_STRLEN(current_op->op1.u.constant))); + current_op->result = *result; +} + + void zend_do_handle_exception(TSRMLS_D) { zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC); @@ -4149,6 +4172,21 @@ void zend_do_fetch_static_variable(znode /* zval_dtor(&varname->u.constant); */ } +void zend_do_fetch_lexical_variable(znode *varname TSRMLS_DC) +{ + znode value; + + /* Empty constant has a special meaning of lexical variable. + * It is substituted by real value in ZEND_DECLARE_LAMBDA_FUNCTION */ + value.op_type = IS_CONST; + ZVAL_EMPTY_STRING(&value.u.constant); + Z_TYPE(value.u.constant) = IS_CONSTANT; + Z_SET_REFCOUNT_P(&value.u.constant, 1); + Z_UNSET_ISREF_P(&value.u.constant); + + zend_do_fetch_static_variable(varname, &value, ZEND_FETCH_STATIC TSRMLS_CC); +} + void zend_do_fetch_global_variable(znode *varname, znode *static_assignment, int fetch_type TSRMLS_DC) { zend_op *opline; Index: Zend/zend_compile.h =================================================================== RCS file: /repository/ZendEngine2/zend_compile.h,v retrieving revision 1.316.2.8.2.12.2.25 diff -u -p -d -r1.316.2.8.2.12.2.25 zend_compile.h --- Zend/zend_compile.h 11 Jun 2008 13:18:39 -0000 1.316.2.8.2.12.2.25 +++ Zend/zend_compile.h 19 Jun 2008 10:04:37 -0000 @@ -429,6 +429,14 @@ void zend_do_end_function_call(znode *fu void zend_do_return(znode *expr, int do_end_vparse TSRMLS_DC); void zend_do_handle_exception(TSRMLS_D); +typedef struct _zend_closure { + zend_op_array op_array; + zval *This; +} zend_closure; + +void zend_do_begin_lambda_function_declaration(znode *result, znode *function_token, int return_reference TSRMLS_DC); +void zend_do_fetch_lexical_variable(znode *varname TSRMLS_DC); + void zend_do_try(znode *try_token TSRMLS_DC); void zend_do_begin_catch(znode *try_token, znode *catch_class, znode *catch_var, znode *first_catch TSRMLS_DC); void zend_do_end_catch(znode *try_token TSRMLS_DC); Index: Zend/zend_execute.c =================================================================== RCS file: /repository/ZendEngine2/zend_execute.c,v retrieving revision 1.716.2.12.2.24.2.30 diff -u -p -d -r1.716.2.12.2.24.2.30 zend_execute.c --- Zend/zend_execute.c 11 Jun 2008 13:18:39 -0000 1.716.2.12.2.24.2.30 +++ Zend/zend_execute.c 19 Jun 2008 10:04:38 -0000 @@ -1259,6 +1259,27 @@ static inline zend_brk_cont_element* zen return jmp_to; } +static int zval_copy_static_var(zval **p, int num_args, va_list args, zend_hash_key *key) +{ + HashTable *target = va_arg(args, HashTable*); + + if (Z_TYPE_PP(p) == IS_CONSTANT && Z_STRLEN_PP(p) == 0) { + TSRMLS_FETCH(); + + if (!EG(active_symbol_table)) { + zend_rebuild_symbol_table(TSRMLS_C); + } + if (zend_hash_quick_find(EG(active_symbol_table), key->arKey, key->nKeyLength, key->h, (void **) &p) == FAILURE) { + return ZEND_HASH_APPLY_KEEP; + } + SEPARATE_ZVAL_TO_MAKE_IS_REF(p); + } + if (zend_hash_quick_add(target, key->arKey, key->nKeyLength, key->h, p, sizeof(zval*), NULL) == SUCCESS) { + Z_ADDREF_PP(p); + } + return ZEND_HASH_APPLY_KEEP; +} + #if ZEND_INTENSIVE_DEBUGGING #define CHECK_SYMBOL_TABLES() \ Index: Zend/zend_execute_API.c =================================================================== RCS file: /repository/ZendEngine2/zend_execute_API.c,v retrieving revision 1.331.2.20.2.24.2.42 diff -u -p -d -r1.331.2.20.2.24.2.42 zend_execute_API.c --- Zend/zend_execute_API.c 5 Jun 2008 18:50:29 -0000 1.331.2.20.2.24.2.42 +++ Zend/zend_execute_API.c 19 Jun 2008 10:04:38 -0000 @@ -739,6 +739,21 @@ int zend_call_function(zend_fcall_info * SEPARATE_ZVAL_IF_NOT_REF(tmp_object_ptr); fci->object_pp = tmp_object_ptr; Z_SET_ISREF_PP(fci->object_pp); + } else if (Z_TYPE_P(fci->function_name) == IS_RESOURCE) { + zend_closure *closure = (zend_closure*) zend_fetch_resource(&fci->function_name TSRMLS_CC, -1, NULL, NULL, 1, le_lambda_func); + + if (!closure) { + return FAILURE; + } + EX(function_state).function = (zend_function*)&closure->op_array; + if (closure->This) { + fci->object_pp = &closure->This; + calling_scope = Z_OBJCE_P(closure->This); + } else { + fci->object_pp = NULL; + calling_scope = closure->op_array.scope; + } + goto init_fci_cache; } if (fci->object_pp && !*fci->object_pp) { @@ -935,6 +950,8 @@ int zend_call_function(zend_fcall_info * return FAILURE; } } + +init_fci_cache: if (fci_cache && (EX(function_state).function->type != ZEND_INTERNAL_FUNCTION || ((zend_internal_function*)EX(function_state).function)->handler != zend_std_call_user_call) Index: Zend/zend_language_parser.y =================================================================== RCS file: /repository/ZendEngine2/zend_language_parser.y,v retrieving revision 1.160.2.4.2.8.2.21 diff -u -p -d -r1.160.2.4.2.8.2.21 zend_language_parser.y --- Zend/zend_language_parser.y 8 Jun 2008 09:38:47 -0000 1.160.2.4.2.8.2.21 +++ Zend/zend_language_parser.y 19 Jun 2008 10:04:38 -0000 @@ -302,7 +302,7 @@ is_reference: unticked_function_declaration_statement: - T_FUNCTION { $1.u.opline_num = CG(zend_lineno); } is_reference T_STRING { zend_do_begin_function_declaration(&$1, &$4, 0, $3.op_type, NULL TSRMLS_CC); } + function is_reference T_STRING { zend_do_begin_function_declaration(&$1, &$3, 0, $2.op_type, NULL TSRMLS_CC); } '(' parameter_list ')' '{' inner_statement_list '}' { zend_do_end_function_declaration(&$1 TSRMLS_CC); } ; @@ -510,8 +510,8 @@ class_statement_list: class_statement: variable_modifiers { CG(access_type) = Z_LVAL($1.u.constant); } class_variable_declaration ';' | class_constant_declaration ';' - | method_modifiers T_FUNCTION { $2.u.opline_num = CG(zend_lineno); } is_reference T_STRING { zend_do_begin_function_declaration(&$2, &$5, 1, $4.op_type, &$1 TSRMLS_CC); } '(' - parameter_list ')' method_body { zend_do_abstract_method(&$5, &$1, &$10 TSRMLS_CC); zend_do_end_function_declaration(&$2 TSRMLS_CC); } + | method_modifiers function is_reference T_STRING { zend_do_begin_function_declaration(&$2, &$4, 1, $3.op_type, &$1 TSRMLS_CC); } '(' + parameter_list ')' method_body { zend_do_abstract_method(&$4, &$1, &$9 TSRMLS_CC); zend_do_end_function_declaration(&$2 TSRMLS_CC); } ; @@ -643,6 +643,22 @@ expr_without_variable: | T_ARRAY '(' array_pair_list ')' { $$ = $3; } | '`' encaps_list '`' { zend_do_shell_exec(&$$, &$2 TSRMLS_CC); } | T_PRINT expr { zend_do_print(&$$, &$2 TSRMLS_CC); } + | function is_reference '(' { zend_do_begin_lambda_function_declaration(&$$, &$1, $2.op_type TSRMLS_CC); } + parameter_list lexical_vars ')' '{' inner_statement_list '}' { zend_do_end_function_declaration(&$1 TSRMLS_CC); $$ = $4; } +; + +function: + T_FUNCTION { $$.u.opline_num = CG(zend_lineno); } +; + +lexical_vars: + /* emptry */ + | '|' lexical_var_list +; + +lexical_var_list: + lexical_var_list ',' T_VARIABLE { zend_do_fetch_lexical_variable(&$3 TSRMLS_CC); } + | T_VARIABLE { zend_do_fetch_lexical_variable(&$1 TSRMLS_CC); } ; function_call: Index: Zend/zend_list.c =================================================================== RCS file: /repository/ZendEngine2/zend_list.c,v retrieving revision 1.66.2.1.2.1.2.1 diff -u -p -d -r1.66.2.1.2.1.2.1 zend_list.c --- Zend/zend_list.c 31 Dec 2007 07:17:04 -0000 1.66.2.1.2.1.2.1 +++ Zend/zend_list.c 19 Jun 2008 10:04:38 -0000 @@ -27,6 +27,7 @@ #include "zend_globals.h" ZEND_API int le_index_ptr; +ZEND_API int le_lambda_func; /* true global */ static HashTable list_destructors; @@ -333,6 +334,18 @@ ZEND_API int zend_fetch_list_dtor_id(cha return 0; } + +static void zend_lambda_func_resource_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) +{ + zend_closure *closure = (zend_closure *)rsrc->ptr; + destroy_zend_function(&closure->op_array TSRMLS_CC); + if (closure->This) { + zval_ptr_dtor(&closure->This); + } + efree(closure); +} + + int zend_init_rsrc_list_dtors(void) { int retval; @@ -340,6 +353,8 @@ int zend_init_rsrc_list_dtors(void) retval = zend_hash_init(&list_destructors, 50, NULL, NULL, 1); list_destructors.nNextFreeElement=1; /* we don't want resource type 0 */ + le_lambda_func = zend_register_list_destructors_ex(zend_lambda_func_resource_dtor, NULL, "lambda function", 0); + return retval; } @@ -364,7 +379,6 @@ char *zend_rsrc_list_get_rsrc_type(int r return NULL; } } - /* * Local variables: * tab-width: 4 Index: Zend/zend_list.h =================================================================== RCS file: /repository/ZendEngine2/zend_list.h,v retrieving revision 1.48.2.1.2.1.2.1 diff -u -p -d -r1.48.2.1.2.1.2.1 zend_list.h --- Zend/zend_list.h 31 Dec 2007 07:17:04 -0000 1.48.2.1.2.1.2.1 +++ Zend/zend_list.h 19 Jun 2008 10:04:38 -0000 @@ -86,6 +86,7 @@ ZEND_API char *zend_rsrc_list_get_rsrc_t ZEND_API int zend_fetch_list_dtor_id(char *type_name); extern ZEND_API int le_index_ptr; /* list entry type for index pointers */ +extern ZEND_API int le_lambda_func; /* list entry type for lambda functions */ #define ZEND_VERIFY_RESOURCE(rsrc) \ if (!rsrc) { \ Index: Zend/zend_vm_def.h =================================================================== RCS file: /repository/ZendEngine2/zend_vm_def.h,v retrieving revision 1.59.2.29.2.48.2.58 diff -u -p -d -r1.59.2.29.2.48.2.58 zend_vm_def.h --- Zend/zend_vm_def.h 11 Jun 2008 13:18:39 -0000 1.59.2.29.2.48.2.58 +++ Zend/zend_vm_def.h 19 Jun 2008 10:04:38 -0000 @@ -2028,6 +2028,20 @@ ZEND_VM_HANDLER(59, ZEND_INIT_FCALL_BY_N } } else { function_name = GET_OP2_ZVAL_PTR(BP_VAR_R); + zend_closure *closure; + + if (Z_TYPE_P(function_name) == IS_RESOURCE && + (closure = (zend_closure*) zend_fetch_resource (&function_name TSRMLS_CC, -1, NULL, NULL, 1, le_lambda_func)) != NULL) { + EX(fbc) = (zend_function*)&closure->op_array; + if ((EX(object) = closure->This) != NULL) { + Z_ADDREF_P(EX(object)); + EX(called_scope) = Z_OBJCE_P(EX(object)); + } else { + EX(called_scope) = closure->op_array.scope; + } + FREE_OP2(); + ZEND_VM_NEXT_OPCODE(); + } if (Z_TYPE_P(function_name) != IS_STRING) { zend_error_noreturn(E_ERROR, "Function name must be a string"); @@ -4327,4 +4341,42 @@ ZEND_VM_HANDLER(143, ZEND_DECLARE_CONST, ZEND_VM_NEXT_OPCODE(); } +ZEND_VM_HANDLER(153, ZEND_DECLARE_LAMBDA_FUNCTION, CONST, CONST) +{ + zend_op *opline = EX(opline); + zend_op_array *op_array; + zend_closure *closure; + + if (zend_hash_quick_find(EG(function_table), Z_STRVAL(opline->op1.u.constant), Z_STRLEN(opline->op1.u.constant), Z_LVAL(opline->op2.u.constant), (void *) &op_array) == FAILURE || + op_array->type != ZEND_USER_FUNCTION) { + zend_error_noreturn(E_ERROR, "Base lambda function for closure not found"); + } + + closure = emalloc(sizeof(zend_closure)); + closure->op_array = *op_array; + + if (closure->op_array.static_variables) { + HashTable *static_variables = closure->op_array.static_variables; + + ALLOC_HASHTABLE(closure->op_array.static_variables); + zend_hash_init(closure->op_array.static_variables, zend_hash_num_elements(static_variables), NULL, ZVAL_PTR_DTOR, 0); + zend_hash_apply_with_arguments(static_variables, (apply_func_args_t)zval_copy_static_var, 1, closure->op_array.static_variables); + } + + if ((closure->op_array.scope = EG(scope)) != NULL) { + closure->op_array.fn_flags |= ZEND_ACC_PUBLIC; + if ((closure->This = EG(This)) != NULL) { + Z_ADDREF_P(closure->This); + } else { + closure->op_array.fn_flags |= ZEND_ACC_STATIC; + } + } else { + closure->This = NULL; + } + (*closure->op_array.refcount)++; + + ZEND_REGISTER_RESOURCE(&EX_T(opline->result.u.var).tmp_var, closure, le_lambda_func); + ZEND_VM_NEXT_OPCODE(); +} + ZEND_VM_EXPORT_HELPER(zend_do_fcall, zend_do_fcall_common_helper) Index: Zend/zend_vm_execute.h =================================================================== RCS file: /repository/ZendEngine2/zend_vm_execute.h,v retrieving revision 1.62.2.30.2.49.2.58 diff -u -p -d -r1.62.2.30.2.49.2.58 zend_vm_execute.h --- Zend/zend_vm_execute.h 11 Jun 2008 13:18:39 -0000 1.62.2.30.2.49.2.58 +++ Zend/zend_vm_execute.h 19 Jun 2008 10:04:40 -0000 @@ -746,6 +746,20 @@ static int ZEND_FASTCALL ZEND_INIT_FCAL } } else { function_name = &opline->op2.u.constant; + zend_closure *closure; + + if (Z_TYPE_P(function_name) == IS_RESOURCE && + (closure = (zend_closure*) zend_fetch_resource (&function_name TSRMLS_CC, -1, NULL, NULL, 1, le_lambda_func)) != NULL) { + EX(fbc) = (zend_function*)&closure->op_array; + if ((EX(object) = closure->This) != NULL) { + Z_ADDREF_P(EX(object)); + EX(called_scope) = Z_OBJCE_P(EX(object)); + } else { + EX(called_scope) = closure->op_array.scope; + } + + ZEND_VM_NEXT_OPCODE(); + } if (Z_TYPE_P(function_name) != IS_STRING) { zend_error_noreturn(E_ERROR, "Function name must be a string"); @@ -934,6 +948,20 @@ static int ZEND_FASTCALL ZEND_INIT_FCAL } } else { function_name = _get_zval_ptr_tmp(&opline->op2, EX(Ts), &free_op2 TSRMLS_CC); + zend_closure *closure; + + if (Z_TYPE_P(function_name) == IS_RESOURCE && + (closure = (zend_closure*) zend_fetch_resource (&function_name TSRMLS_CC, -1, NULL, NULL, 1, le_lambda_func)) != NULL) { + EX(fbc) = (zend_function*)&closure->op_array; + if ((EX(object) = closure->This) != NULL) { + Z_ADDREF_P(EX(object)); + EX(called_scope) = Z_OBJCE_P(EX(object)); + } else { + EX(called_scope) = closure->op_array.scope; + } + zval_dtor(free_op2.var); + ZEND_VM_NEXT_OPCODE(); + } if (Z_TYPE_P(function_name) != IS_STRING) { zend_error_noreturn(E_ERROR, "Function name must be a string"); @@ -1030,6 +1058,20 @@ static int ZEND_FASTCALL ZEND_INIT_FCAL } } else { function_name = _get_zval_ptr_var(&opline->op2, EX(Ts), &free_op2 TSRMLS_CC); + zend_closure *closure; + + if (Z_TYPE_P(function_name) == IS_RESOURCE && + (closure = (zend_closure*) zend_fetch_resource (&function_name TSRMLS_CC, -1, NULL, NULL, 1, le_lambda_func)) != NULL) { + EX(fbc) = (zend_function*)&closure->op_array; + if ((EX(object) = closure->This) != NULL) { + Z_ADDREF_P(EX(object)); + EX(called_scope) = Z_OBJCE_P(EX(object)); + } else { + EX(called_scope) = closure->op_array.scope; + } + if (free_op2.var) {zval_ptr_dtor(&free_op2.var);}; + ZEND_VM_NEXT_OPCODE(); + } if (Z_TYPE_P(function_name) != IS_STRING) { zend_error_noreturn(E_ERROR, "Function name must be a string"); @@ -1154,6 +1196,20 @@ static int ZEND_FASTCALL ZEND_INIT_FCAL } } else { function_name = _get_zval_ptr_cv(&opline->op2, EX(Ts), BP_VAR_R TSRMLS_CC); + zend_closure *closure; + + if (Z_TYPE_P(function_name) == IS_RESOURCE && + (closure = (zend_closure*) zend_fetch_resource (&function_name TSRMLS_CC, -1, NULL, NULL, 1, le_lambda_func)) != NULL) { + EX(fbc) = (zend_function*)&closure->op_array; + if ((EX(object) = closure->This) != NULL) { + Z_ADDREF_P(EX(object)); + EX(called_scope) = Z_OBJCE_P(EX(object)); + } else { + EX(called_scope) = closure->op_array.scope; + } + + ZEND_VM_NEXT_OPCODE(); + } if (Z_TYPE_P(function_name) != IS_STRING) { zend_error_noreturn(E_ERROR, "Function name must be a string"); @@ -2875,6 +2931,44 @@ static int ZEND_FASTCALL ZEND_DECLARE_C ZEND_VM_NEXT_OPCODE(); } +static int ZEND_FASTCALL ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + zend_op *opline = EX(opline); + zend_op_array *op_array; + zend_closure *closure; + + if (zend_hash_quick_find(EG(function_table), Z_STRVAL(opline->op1.u.constant), Z_STRLEN(opline->op1.u.constant), Z_LVAL(opline->op2.u.constant), (void *) &op_array) == FAILURE || + op_array->type != ZEND_USER_FUNCTION) { + zend_error_noreturn(E_ERROR, "Base lambda function for closure not found"); + } + + closure = emalloc(sizeof(zend_closure)); + closure->op_array = *op_array; + + if (closure->op_array.static_variables) { + HashTable *static_variables = closure->op_array.static_variables; + + ALLOC_HASHTABLE(closure->op_array.static_variables); + zend_hash_init(closure->op_array.static_variables, zend_hash_num_elements(static_variables), NULL, ZVAL_PTR_DTOR, 0); + zend_hash_apply_with_arguments(static_variables, (apply_func_args_t)zval_copy_static_var, 1, closure->op_array.static_variables); + } + + if ((closure->op_array.scope = EG(scope)) != NULL) { + closure->op_array.fn_flags |= ZEND_ACC_PUBLIC; + if ((closure->This = EG(This)) != NULL) { + Z_ADDREF_P(closure->This); + } else { + closure->op_array.fn_flags |= ZEND_ACC_STATIC; + } + } else { + closure->This = NULL; + } + (*closure->op_array.refcount)++; + + ZEND_REGISTER_RESOURCE(&EX_T(opline->result.u.var).tmp_var, closure, le_lambda_func); + ZEND_VM_NEXT_OPCODE(); +} + static int ZEND_FASTCALL ZEND_ADD_SPEC_CONST_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { zend_op *opline = EX(opline); @@ -33508,6 +33602,31 @@ void zend_init_opcodes_handlers(void) ZEND_JMP_SET_SPEC_CV_HANDLER, ZEND_JMP_SET_SPEC_CV_HANDLER, ZEND_JMP_SET_SPEC_CV_HANDLER, + ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_CONST_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, ZEND_NULL_HANDLER }; zend_opcode_handlers = (opcode_handler_t*)labels; Index: Zend/zend_vm_opcodes.h =================================================================== RCS file: /repository/ZendEngine2/zend_vm_opcodes.h,v retrieving revision 1.42.2.17.2.1.2.6 diff -u -p -d -r1.42.2.17.2.1.2.6 zend_vm_opcodes.h --- Zend/zend_vm_opcodes.h 28 Mar 2008 14:35:01 -0000 1.42.2.17.2.1.2.6 +++ Zend/zend_vm_opcodes.h 19 Jun 2008 10:04:40 -0000 @@ -152,3 +152,4 @@ #define ZEND_HANDLE_EXCEPTION 149 #define ZEND_USER_OPCODE 150 #define ZEND_JMP_SET 152 +#define ZEND_DECLARE_LAMBDA_FUNCTION 153 Index: ext/pcre/php_pcre.c =================================================================== RCS file: /repository/php-src/ext/pcre/php_pcre.c,v retrieving revision 1.168.2.9.2.21.2.15 diff -u -p -d -r1.168.2.9.2.21.2.15 php_pcre.c --- ext/pcre/php_pcre.c 1 Jun 2008 18:47:20 -0000 1.168.2.9.2.21.2.15 +++ ext/pcre/php_pcre.c 19 Jun 2008 10:04:40 -0000 @@ -1312,7 +1312,7 @@ static void preg_replace_impl(INTERNAL_F } SEPARATE_ZVAL(replace); - if (Z_TYPE_PP(replace) != IS_ARRAY) + if (Z_TYPE_PP(replace) != IS_ARRAY && Z_TYPE_PP(replace) != IS_RESOURCE) convert_to_string_ex(replace); if (is_callable_replace) { if (!zend_is_callable(*replace, 0, &callback_name)) {
-- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: http://www.php.net/unsub.php