From d5ef2f466cb112fd977a71419fa4b67d0aa0a2ac Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Mon, 14 Jul 2008 09:49:03 +0000 Subject: [PATCH] Added support for lambda functions and closures --- NEWS | 1 + Zend/Makefile.am | 2 +- Zend/Zend.dsp | 4 + Zend/ZendTS.dsp | 4 + Zend/tests/closure_001.phpt | 30 +++ Zend/tests/closure_002.phpt | 29 +++ Zend/tests/closure_003.phpt | 33 ++++ Zend/tests/closure_004.phpt | 35 ++++ Zend/tests/closure_005.phpt | 74 +++++++ Zend/tests/closure_006.phpt | 19 ++ Zend/tests/closure_007.phpt | 38 ++++ Zend/tests/closure_008.phpt | 22 +++ Zend/tests/closure_009.phpt | 31 +++ Zend/tests/closure_010.phpt | 18 ++ Zend/tests/closure_011.phpt | 14 ++ Zend/tests/closure_012.phpt | 24 +++ Zend/tests/closure_013.phpt | 25 +++ Zend/tests/closure_014.phpt | 79 ++++++++ Zend/tests/closure_015.phpt | 13 ++ Zend/zend.h | 2 + Zend/zend_API.c | 11 ++ Zend/zend_closures.c | 329 ++++++++++++++++++++++++++++++++ Zend/zend_closures.h | 42 ++++ Zend/zend_compile.c | 60 +++++- Zend/zend_compile.h | 8 +- Zend/zend_default_classes.c | 2 + Zend/zend_execute.c | 1 + Zend/zend_execute_API.c | 9 +- Zend/zend_language_parser.y | 28 ++- Zend/zend_vm_def.h | 24 +++ Zend/zend_vm_execute.h | 76 ++++++++ Zend/zend_vm_opcodes.h | 1 + configure.in | 3 +- ext/pcre/php_pcre.c | 2 +- ext/reflection/php_reflection.c | 45 +++++ 35 files changed, 1126 insertions(+), 12 deletions(-) create mode 100644 Zend/tests/closure_001.phpt create mode 100644 Zend/tests/closure_002.phpt create mode 100644 Zend/tests/closure_003.phpt create mode 100644 Zend/tests/closure_004.phpt create mode 100644 Zend/tests/closure_005.phpt create mode 100644 Zend/tests/closure_006.phpt create mode 100644 Zend/tests/closure_007.phpt create mode 100644 Zend/tests/closure_008.phpt create mode 100644 Zend/tests/closure_009.phpt create mode 100644 Zend/tests/closure_010.phpt create mode 100644 Zend/tests/closure_011.phpt create mode 100644 Zend/tests/closure_012.phpt create mode 100644 Zend/tests/closure_013.phpt create mode 100644 Zend/tests/closure_014.phpt create mode 100644 Zend/tests/closure_015.phpt create mode 100644 Zend/zend_closures.c create mode 100644 Zend/zend_closures.h diff --git a/NEWS b/NEWS index 5e9e24cfde..c9586e7d80 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,7 @@ PHP NEWS - Changed mhash to be a wrapper layer around the hash extension. (Scott) - Improved PHP syntax and semantics: + . Added lambda functions and closures (Christian Seiler, Dmitry) . Added "jump label" operator (limited "goto"). (Dmitry, Sara) . Added NOWDOC syntax. (Gwynne Raskind, Stas, Dmitry) . Added HEREDOC syntax with double quotes. (Lars Strojny, Felipe) diff --git a/Zend/Makefile.am b/Zend/Makefile.am index b233b9c800..def7a91cc3 100644 --- a/Zend/Makefile.am +++ b/Zend/Makefile.am @@ -17,7 +17,7 @@ libZend_la_SOURCES=\ zend_objects_API.c zend_ts_hash.c zend_stream.c \ zend_default_classes.c \ zend_iterators.c zend_interfaces.c zend_exceptions.c \ - zend_strtod.c + zend_strtod.c zend_closures.c libZend_la_LDFLAGS = libZend_la_LIBADD = @ZEND_EXTRA_LIBS@ diff --git a/Zend/Zend.dsp b/Zend/Zend.dsp index a92125d89c..0c6400fb1d 100644 --- a/Zend/Zend.dsp +++ b/Zend/Zend.dsp @@ -123,6 +123,10 @@ SOURCE=.\zend_builtin_functions.c # End Source File # Begin Source File +SOURCE=.\zend_closures.c +# End Source File +# Begin Source File + SOURCE=.\zend_compile.c # End Source File # Begin Source File diff --git a/Zend/ZendTS.dsp b/Zend/ZendTS.dsp index e70f123a03..2295b89ebb 100644 --- a/Zend/ZendTS.dsp +++ b/Zend/ZendTS.dsp @@ -148,6 +148,10 @@ SOURCE=.\zend_builtin_functions.c # End Source File # Begin Source File +SOURCE=.\zend_closures.c +# End Source File +# Begin Source File + SOURCE=.\zend_compile.c # End Source File # Begin Source File diff --git a/Zend/tests/closure_001.phpt b/Zend/tests/closure_001.phpt new file mode 100644 index 0000000000..ebac729b16 --- /dev/null +++ b/Zend/tests/closure_001.phpt @@ -0,0 +1,30 @@ +--TEST-- +Closure 001: Lambda without lexical variables +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +Hello World! +Hello Universe! +Hello World! +Hello Universe! +Done diff --git a/Zend/tests/closure_002.phpt b/Zend/tests/closure_002.phpt new file mode 100644 index 0000000000..023d4ecff1 --- /dev/null +++ b/Zend/tests/closure_002.phpt @@ -0,0 +1,29 @@ +--TEST-- +Closure 002: Lambda with lexical variables (global scope) +--FILE-- + +--EXPECT-- +4 +4 +4 +5 +Done diff --git a/Zend/tests/closure_003.phpt b/Zend/tests/closure_003.phpt new file mode 100644 index 0000000000..6f5cc70bf1 --- /dev/null +++ b/Zend/tests/closure_003.phpt @@ -0,0 +1,33 @@ +--TEST-- +Closure 003: Lambda with lexical variables (local scope) +--FILE-- + +--EXPECT-- +4 +4 +4 +5 +Done diff --git a/Zend/tests/closure_004.phpt b/Zend/tests/closure_004.phpt new file mode 100644 index 0000000000..c1c2efb25c --- /dev/null +++ b/Zend/tests/closure_004.phpt @@ -0,0 +1,35 @@ +--TEST-- +Closure 004: Lambda with lexical variables (scope lifetime) +--FILE-- + +--EXPECT-- +4 +4 +4 +5 +Done diff --git a/Zend/tests/closure_005.phpt b/Zend/tests/closure_005.phpt new file mode 100644 index 0000000000..4e32faa017 --- /dev/null +++ b/Zend/tests/closure_005.phpt @@ -0,0 +1,74 @@ +--TEST-- +Closure 005: Lambda inside class, lifetime of $this +--FILE-- +x = $x; + } + + function __destruct() { + echo "Destroyed\n"; + } + + function getIncer($val) { + return function() use ($val) { + $this->x += $val; + }; + } + + function getPrinter() { + return function() { + echo $this->x."\n"; + }; + } + + function getError() { + return static function() { + echo $this->x."\n"; + }; + } + + function printX() { + echo $this->x."\n"; + } +} + +$a = new A(3); +$incer = $a->getIncer(2); +$printer = $a->getPrinter(); +$error = $a->getError(); + +$a->printX(); +$printer(); +$incer(); +$a->printX(); +$printer(); + +unset($a); + +$incer(); +$printer(); + +unset($incer); +$printer(); + +unset($printer); + +$error(); + +echo "Done\n"; +?> +--EXPECTF-- +3 +3 +5 +5 +7 +7 +Destroyed + +Fatal error: Using $this when not in object context in %sclosure_005.php on line 28 diff --git a/Zend/tests/closure_006.phpt b/Zend/tests/closure_006.phpt new file mode 100644 index 0000000000..aa0ec11995 --- /dev/null +++ b/Zend/tests/closure_006.phpt @@ -0,0 +1,19 @@ +--TEST-- +Closure 006: Nested lambdas +--FILE-- + +--EXPECT-- +Hello World: 2! +Done diff --git a/Zend/tests/closure_007.phpt b/Zend/tests/closure_007.phpt new file mode 100644 index 0000000000..89cd06d8a1 --- /dev/null +++ b/Zend/tests/closure_007.phpt @@ -0,0 +1,38 @@ +--TEST-- +Closure 007: Nested lambdas in classes +--FILE-- +x++; + }; + }; + } + + function printX () { + echo $this->x."\n"; + } +} + +$a = new A; +$a->printX(); +$getClosure = $a->getClosureGetter(); +$a->printX(); +$closure = $getClosure(); +$a->printX(); +$closure(); +$a->printX(); + +echo "Done\n"; +?> +--EXPECT-- +0 +0 +0 +1 +Done diff --git a/Zend/tests/closure_008.phpt b/Zend/tests/closure_008.phpt new file mode 100644 index 0000000000..77b50de8ab --- /dev/null +++ b/Zend/tests/closure_008.phpt @@ -0,0 +1,22 @@ +--TEST-- +Closure 008: Use in preg_replace() +--FILE-- + +--EXPECT-- +1 2 3 +1  2  3 +1   2   3 +Done diff --git a/Zend/tests/closure_009.phpt b/Zend/tests/closure_009.phpt new file mode 100644 index 0000000000..10ec32b80d --- /dev/null +++ b/Zend/tests/closure_009.phpt @@ -0,0 +1,31 @@ +--TEST-- +Closure 009: Use in preg_replace() +--FILE-- + +--EXPECT-- +1:1:1 +2:2:1 +3:3:1 +4:1:1 +5:2:1:1 +6:3:2:1:1 diff --git a/Zend/tests/closure_010.phpt b/Zend/tests/closure_010.phpt new file mode 100644 index 0000000000..d4787f0a2b --- /dev/null +++ b/Zend/tests/closure_010.phpt @@ -0,0 +1,18 @@ +--TEST-- +Closure 010: Closure calls itself +--FILE-- + +--EXPECT-- +3 +2 +1 +0 diff --git a/Zend/tests/closure_011.phpt b/Zend/tests/closure_011.phpt new file mode 100644 index 0000000000..707136463d --- /dev/null +++ b/Zend/tests/closure_011.phpt @@ -0,0 +1,14 @@ +--TEST-- +Closure 011: Lexical copies not static in closure +--FILE-- + +--EXPECT-- +2 diff --git a/Zend/tests/closure_012.phpt b/Zend/tests/closure_012.phpt new file mode 100644 index 0000000000..7e1b7a2793 --- /dev/null +++ b/Zend/tests/closure_012.phpt @@ -0,0 +1,24 @@ +--TEST-- +Closure 012: Undefined lexical variables +--FILE-- + +--EXPECTF-- +Notice: Undefined variable: i in %sclosure_012.php on line 2 + +Notice: Undefined variable: i in %sclosure_012.php on line 7 +NULL +int(2) + diff --git a/Zend/tests/closure_013.phpt b/Zend/tests/closure_013.phpt new file mode 100644 index 0000000000..72d2b3f963 --- /dev/null +++ b/Zend/tests/closure_013.phpt @@ -0,0 +1,25 @@ +--TEST-- +Closure 013: __invoke() on temporary result +--FILE-- +__invoke(); +$test = foo(); +$test->__invoke(); +$test = foo()->__invoke(); +?> +--EXPECT-- +Hello World! +Hello World! +Hello World! diff --git a/Zend/tests/closure_014.phpt b/Zend/tests/closure_014.phpt new file mode 100644 index 0000000000..9e4819b9dd --- /dev/null +++ b/Zend/tests/closure_014.phpt @@ -0,0 +1,79 @@ +--TEST-- +Closure 014: return by value/reference +--FILE-- +__invoke()); +$x(); +$x->__invoke(); +$x = function() { + return 0; +}; +var_dump($x()); +var_dump($x->__invoke()); +$x(); +$x->__invoke(); + +$x = new C2(); +$a = $b = $c = $d = 1; +$e =& $x($a); +$e = 2; +var_dump($a); +$e =& $x->__invoke($b); +$e = 3; +var_dump($b); +$x($b); +$x->__invoke($b); +$x = function & (&$a) { + return $a; +}; +$e =& $x($c); +$e = 4; +var_dump($c); +$e =& $x->__invoke($d); +$e = 5; +var_dump($d); +$x($d); +$x->__invoke($d); + +$x = new C3(); +var_dump($x()); +var_dump($x->__invoke()); +$x(); +$x->__invoke(); +$x = function() { +}; +var_dump($x()); +var_dump($x->__invoke()); +$x(); +$x->__invoke(); +?> +--EXPECT-- +int(0) +int(0) +int(0) +int(0) +int(2) +int(3) +int(4) +int(5) +NULL +NULL +NULL +NULL diff --git a/Zend/tests/closure_015.phpt b/Zend/tests/closure_015.phpt new file mode 100644 index 0000000000..d21aca5721 --- /dev/null +++ b/Zend/tests/closure_015.phpt @@ -0,0 +1,13 @@ +--TEST-- +Closure 015: converting to string/unicode +--FILE-- + +--EXPECT-- +Closure object +Closure object diff --git a/Zend/zend.h b/Zend/zend.h index d5a6974b08..0d09f4bc0d 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -518,6 +518,8 @@ typedef int (*zend_write_func_t)(const char *str, uint str_length); #define IS_CONSTANT_TYPE_MASK 0x0f #define IS_CONSTANT_RT_NS_CHECK 0x10 #define IS_CONSTANT_INDEX 0x80 +#define IS_LEXICAL_VAR 0x20 +#define IS_LEXICAL_REF 0x40 /* overloaded elements data types */ #define OE_IS_ARRAY (1<<0) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index d8f627adad..68f0c7880c 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -26,6 +26,7 @@ #include "zend_modules.h" #include "zend_constants.h" #include "zend_exceptions.h" +#include "zend_closures.h" #ifdef HAVE_STDARG_H #include @@ -2615,6 +2616,16 @@ ZEND_API zend_bool zend_is_callable_ex(zval *callable, uint check_flags, char ** } return 0; + case IS_OBJECT: + if (zend_get_closure(callable, ce_ptr, fptr_ptr, NULL, zobj_ptr_ptr TSRMLS_CC) == SUCCESS) { + if (callable_name) { + *callable_name_len = strlen((*fptr_ptr)->common.function_name); + *callable_name = estrndup((*fptr_ptr)->common.function_name, *callable_name_len); + } + return 1; + } + /* break missing intentionally */ + default: if (callable_name) { zval expr_copy; diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c new file mode 100644 index 0000000000..5ed979cc62 --- /dev/null +++ b/Zend/zend_closures.c @@ -0,0 +1,329 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2008 Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Christian Seiler | + | Dmitry Stogov | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "zend.h" +#include "zend_API.h" +#include "zend_closures.h" +#include "zend_objects.h" +#include "zend_objects_API.h" +#include "zend_globals.h" + +#define ZEND_INVOKE_FUNC_NAME "__invoke" +#define ZEND_CLOSURE_PRINT_NAME "Closure object" + +typedef struct _zend_closure { + zend_object std; + zend_function func; + zval *this_ptr; + zend_function *invoke; +} zend_closure; + +static zend_class_entry *zend_ce_closure; +static zend_object_handlers closure_handlers; + +ZEND_METHOD(Closure, __invoke) /* {{{ */ +{ + zval ***arguments; + zval *closure_result_ptr = NULL; + + arguments = emalloc(sizeof(zval**) * ZEND_NUM_ARGS()); + if (zend_get_parameters_array_ex(ZEND_NUM_ARGS(), arguments) == FAILURE) { + efree(arguments); + zend_error(E_ERROR, "Cannot get arguments for calling closure"); + RETURN_FALSE; + } + + if (call_user_function_ex(CG(function_table), NULL, this_ptr, &closure_result_ptr, ZEND_NUM_ARGS(), arguments, 1, NULL TSRMLS_CC) == FAILURE) { + efree(arguments); + RETURN_FALSE; + } + + efree(arguments); + if (closure_result_ptr) { + if (Z_ISREF_P(closure_result_ptr) && return_value_ptr) { + if (return_value) { + zval_ptr_dtor(&return_value); + } + *return_value_ptr = closure_result_ptr; + } else { + RETVAL_ZVAL(closure_result_ptr, 1, 1); + } + } +} +/* }}} */ + +static zend_function *zend_closure_get_constructor(zval *object TSRMLS_DC) /* {{{ */ +{ + zend_error(E_ERROR, "Instantiation of 'Closure' is not allowed"); + return NULL; +} +/* }}} */ + +static int zend_closure_compare_objects(zval *o1, zval *o2 TSRMLS_DC) /* {{{ */ +{ + return (Z_OBJ_HANDLE_P(o1) != Z_OBJ_HANDLE_P(o2)); +} +/* }}} */ + +static int zend_closure_cast_object_tostring(zval *readobj, zval *writeobj, int type TSRMLS_DC) /* {{{ */ +{ + zend_class_entry *ce; + + switch (type) { + case IS_STRING: + INIT_PZVAL(writeobj); + ZVAL_STRINGL(writeobj, ZEND_CLOSURE_PRINT_NAME, sizeof(ZEND_CLOSURE_PRINT_NAME)-1, 1); + return SUCCESS; + default: + ce = Z_OBJCE_P(readobj); + zend_error(E_NOTICE, "Object of class %s could not be converted to %s", ce->name, zend_get_type_by_const(type)); + INIT_PZVAL(writeobj); + Z_TYPE_P(writeobj) = IS_NULL; + break; + } + return FAILURE; +} +/* }}} */ + +static zend_function *zend_closure_get_method(zval **object_ptr, char *method_name, int method_len TSRMLS_DC) /* {{{ */ +{ + char *lc_name; + ALLOCA_FLAG(use_heap) + + lc_name = do_alloca(method_len + 1, use_heap); + zend_str_tolower_copy(lc_name, method_name, method_len); + if ((method_len == sizeof(ZEND_INVOKE_FUNC_NAME)-1) && + memcmp(lc_name, ZEND_INVOKE_FUNC_NAME, sizeof(ZEND_INVOKE_FUNC_NAME)-1) == 0) { + zend_closure *closure = (zend_closure *)zend_object_store_get_object(*object_ptr TSRMLS_CC); + + if (!closure->invoke) { + closure->invoke = (zend_function*)emalloc(sizeof(zend_function)); + closure->invoke->common = closure->func.common; + closure->invoke->type = ZEND_INTERNAL_FUNCTION; + closure->invoke->internal_function.handler = ZEND_MN(Closure___invoke); + closure->invoke->internal_function.module = 0; + closure->invoke->internal_function.scope = zend_ce_closure; + closure->invoke->internal_function.function_name = ZEND_INVOKE_FUNC_NAME; + } + free_alloca(lc_name, use_heap); + return (zend_function *)closure->invoke; + } + free_alloca(lc_name, use_heap); + return NULL; +} +/* }}} */ + +static void zend_closure_free_storage(void *object TSRMLS_DC) /* {{{ */ +{ + zend_closure *closure = (zend_closure *)object; + + zend_object_std_dtor(&closure->std TSRMLS_CC); + + if (closure->func.type == ZEND_USER_FUNCTION) { + zend_execute_data *ex = EG(current_execute_data); + while (ex) { + if (ex->op_array == &closure->func.op_array) { + zend_error(E_ERROR, "Cannot destroy active lambda function"); + } + ex = ex->prev_execute_data; + } + destroy_op_array(&closure->func.op_array TSRMLS_CC); + } + + if (closure->this_ptr) { + zval_ptr_dtor(&closure->this_ptr); + } + + if (closure->invoke) { + efree(closure->invoke); + } + + efree(closure); +} +/* }}} */ + +static zend_object_value zend_closure_new(zend_class_entry *class_type TSRMLS_DC) /* {{{ */ +{ + zend_closure *closure; + zend_object_value object; + + closure = emalloc(sizeof(zend_closure)); + memset(closure, 0, sizeof(zend_closure)); + + zend_object_std_init(&closure->std, class_type TSRMLS_CC); + + object.handle = zend_objects_store_put(closure, (zend_objects_store_dtor_t)zend_objects_destroy_object, (zend_objects_free_object_storage_t) zend_closure_free_storage, NULL TSRMLS_CC); + object.handlers = &closure_handlers; + + return object; +} +/* }}} */ + +void zend_register_closure_ce(TSRMLS_D) /* {{{ */ +{ + zend_class_entry ce; + + INIT_CLASS_ENTRY(ce, "Closure", NULL); + zend_ce_closure = zend_register_internal_class(&ce TSRMLS_CC); + zend_ce_closure->ce_flags |= ZEND_ACC_FINAL_CLASS; + zend_ce_closure->create_object = zend_closure_new; + + memcpy(&closure_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + closure_handlers.get_constructor = zend_closure_get_constructor; + closure_handlers.get_method = zend_closure_get_method; + closure_handlers.compare_objects = zend_closure_compare_objects; + closure_handlers.cast_object = zend_closure_cast_object_tostring; + closure_handlers.clone_obj = NULL; +} +/* }}} */ + +static int zval_copy_static_var(zval **p, int num_args, va_list args, zend_hash_key *key) /* {{{ */ +{ + HashTable *target = va_arg(args, HashTable*); + zend_bool is_ref; + + if (Z_TYPE_PP(p) & (IS_LEXICAL_VAR|IS_LEXICAL_REF)) { + TSRMLS_FETCH(); + is_ref = Z_TYPE_PP(p) & IS_LEXICAL_REF; + + 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) { + if (is_ref) { + zval *tmp; + + ALLOC_INIT_ZVAL(tmp); + Z_SET_ISREF_P(tmp); + zend_hash_quick_add(EG(active_symbol_table), key->arKey, key->nKeyLength, key->h, &tmp, sizeof(zval*), (void**)&p); + } else { + p = &EG(uninitialized_zval_ptr); + zend_error(E_NOTICE,"Undefined variable: %s", key->arKey); + } + } else { + if (is_ref) { + SEPARATE_ZVAL_TO_MAKE_IS_REF(p); + } else if (Z_ISREF_PP(p)) { + SEPARATE_ZVAL(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; +} +/* }}} */ + +ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_entry *scope, zval *this_ptr TSRMLS_DC) /* {{{ */ +{ + zend_closure *closure; + + object_init_ex(res, zend_ce_closure); + + closure = (zend_closure *)zend_object_store_get_object(res TSRMLS_CC); + + closure->func = *func; + + if (closure->func.type == ZEND_USER_FUNCTION) { + if (closure->func.op_array.static_variables) { + HashTable *static_variables = closure->func.op_array.static_variables; + + ALLOC_HASHTABLE(closure->func.op_array.static_variables); + zend_hash_init(closure->func.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->func.op_array.static_variables); + } + (*closure->func.op_array.refcount)++; + } + + closure->func.common.scope = scope; + if (scope) { + closure->func.common.fn_flags |= ZEND_ACC_PUBLIC; + if (this_ptr && (closure->func.common.fn_flags & ZEND_ACC_STATIC) == 0) { + closure->this_ptr = this_ptr; + Z_ADDREF_P(this_ptr); + } else { + closure->this_ptr = NULL; + } + } else { + closure->this_ptr = NULL; + } +} +/* }}} */ + +ZEND_API int zend_get_closure(zval *obj, zend_class_entry **ce_ptr, zend_function **fptr_ptr, zval **zobj_ptr, zval ***zobj_ptr_ptr TSRMLS_DC) /* {{{ */ +{ + if (Z_TYPE_P(obj) == IS_OBJECT) { + zend_class_entry *ce = Z_OBJCE_P(obj); + + if (ce == zend_ce_closure) { + zend_closure *closure = (zend_closure *)zend_object_store_get_object(obj TSRMLS_CC); + + *fptr_ptr = &closure->func; + if (closure->this_ptr) { + if (zobj_ptr) { + *zobj_ptr = closure->this_ptr; + } + if (zobj_ptr_ptr) { + *zobj_ptr_ptr = &closure->this_ptr; + } + *ce_ptr = Z_OBJCE_P(closure->this_ptr); + } else { + if (zobj_ptr) { + *zobj_ptr = NULL; + } + if (zobj_ptr_ptr) { + *zobj_ptr_ptr = NULL; + } + *ce_ptr = closure->func.common.scope; + } + return SUCCESS; + } else if (zend_hash_find(&ce->function_table, ZEND_INVOKE_FUNC_NAME, sizeof(ZEND_INVOKE_FUNC_NAME), (void**)fptr_ptr) == SUCCESS) { + *ce_ptr = ce; + if ((*fptr_ptr)->common.fn_flags & ZEND_ACC_STATIC) { + if (zobj_ptr) { + *zobj_ptr = NULL; + } + if (zobj_ptr_ptr) { + *zobj_ptr_ptr = NULL; + } + } else { + if (zobj_ptr) { + *zobj_ptr = obj; + } + if (zobj_ptr_ptr) { + *zobj_ptr_ptr = NULL; + } + } + return SUCCESS; + } + } + return FAILURE; +} +/* }}} */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/Zend/zend_closures.h b/Zend/zend_closures.h new file mode 100644 index 0000000000..c3a3b87095 --- /dev/null +++ b/Zend/zend_closures.h @@ -0,0 +1,42 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2008 Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Christian Seiler | + | Dmitry Stogov | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef ZEND_CLOSURES_H +#define ZEND_CLOSURES_H + +BEGIN_EXTERN_C() + +void zend_register_closure_ce(TSRMLS_D); + +ZEND_API void zend_create_closure(zval *res, zend_function *op_array, zend_class_entry *scope, zval *this_ptr TSRMLS_DC); +ZEND_API int zend_get_closure(zval *obj, zend_class_entry **ce_ptr, zend_function **fptr_ptr, zval **zobj_ptr, zval ***zobj_ptr_ptr TSRMLS_DC); + +END_EXTERN_C() + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + */ diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 723191cf56..5546e2a0da 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1401,6 +1401,33 @@ void zend_do_begin_function_declaration(znode *function_token, znode *function_n CG(labels) = NULL; } +void zend_do_begin_lambda_function_declaration(znode *result, znode *function_token, int return_reference, int is_static 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; + if (is_static) { + CG(active_op_array)->fn_flags |= ZEND_ACC_STATIC; + } + CG(active_op_array)->fn_flags |= ZEND_ACC_CLOSURE; +} + + void zend_do_handle_exception(TSRMLS_D) { zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC); @@ -4155,13 +4182,13 @@ void zend_do_fetch_static_variable(znode *varname, znode *static_assignment, int } opline = get_next_op(CG(active_op_array) TSRMLS_CC); - opline->opcode = ZEND_FETCH_W; /* the default mode must be Write, since fetch_simple_variable() is used to define function arguments */ + opline->opcode = (fetch_type == ZEND_FETCH_LEXICAL) ? ZEND_FETCH_R : ZEND_FETCH_W; /* the default mode must be Write, since fetch_simple_variable() is used to define function arguments */ opline->result.op_type = IS_VAR; opline->result.u.EA.type = 0; opline->result.u.var = get_temporary_variable(CG(active_op_array)); opline->op1 = *varname; SET_UNUSED(opline->op2); - opline->op2.u.EA.type = fetch_type; + opline->op2.u.EA.type = ZEND_FETCH_STATIC; result = opline->result; if (varname->op_type == IS_CONST) { @@ -4169,12 +4196,39 @@ void zend_do_fetch_static_variable(znode *varname, znode *static_assignment, int } fetch_simple_variable(&lval, varname, 0 TSRMLS_CC); /* Relies on the fact that the default fetch is BP_VAR_W */ - zend_do_assign_ref(NULL, &lval, &result TSRMLS_CC); + if (fetch_type == ZEND_FETCH_LEXICAL) { + znode dummy; + + zend_do_begin_variable_parse(TSRMLS_C); + zend_do_assign(&dummy, &lval, &result TSRMLS_CC); + zend_do_free(&dummy TSRMLS_CC); + } else { + zend_do_assign_ref(NULL, &lval, &result TSRMLS_CC); + } CG(active_op_array)->opcodes[CG(active_op_array)->last-1].result.u.EA.type |= EXT_TYPE_UNUSED; /* zval_dtor(&varname->u.constant); */ } +void zend_do_fetch_lexical_variable(znode *varname, zend_bool is_ref TSRMLS_DC) +{ + znode value; + + if (Z_STRLEN(varname->u.constant) == sizeof("this") - 1 && + memcmp(Z_STRVAL(varname->u.constant), "this", sizeof("this") - 1) == 0) { + zend_error(E_COMPILE_ERROR, "Cannot use $this as lexical variable"); + return; + } + + value.op_type = IS_CONST; + ZVAL_NULL(&value.u.constant); + Z_TYPE(value.u.constant) |= is_ref ? IS_LEXICAL_REF : IS_LEXICAL_VAR; + Z_SET_REFCOUNT_P(&value.u.constant, 1); + Z_UNSET_ISREF_P(&value.u.constant); + + zend_do_fetch_static_variable(varname, &value, is_ref ? ZEND_FETCH_STATIC : ZEND_FETCH_LEXICAL TSRMLS_CC); +} + void zend_do_fetch_global_variable(znode *varname, znode *static_assignment, int fetch_type TSRMLS_DC) { zend_op *opline; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 7198715b6f..89ae65108c 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -151,6 +151,8 @@ typedef struct _zend_try_catch_element { /* class implement interface(s) flag */ #define ZEND_ACC_IMPLEMENT_INTERFACES 0x80000 +#define ZEND_ACC_CLOSURE 0x100000 + char *zend_visibility_string(zend_uint fn_flags); @@ -429,6 +431,9 @@ void zend_do_end_function_call(znode *function_name, znode *result, znode *argum void zend_do_return(znode *expr, int do_end_vparse TSRMLS_DC); void zend_do_handle_exception(TSRMLS_D); +void zend_do_begin_lambda_function_declaration(znode *result, znode *function_token, int return_reference, int is_static TSRMLS_DC); +void zend_do_fetch_lexical_variable(znode *varname, zend_bool is_ref 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); @@ -614,7 +619,8 @@ int zendlex(znode *zendlval TSRMLS_DC); #define ZEND_FETCH_LOCAL 1 #define ZEND_FETCH_STATIC 2 #define ZEND_FETCH_STATIC_MEMBER 3 -#define ZEND_FETCH_GLOBAL_LOCK 4 +#define ZEND_FETCH_GLOBAL_LOCK 4 +#define ZEND_FETCH_LEXICAL 5 /* class fetches */ diff --git a/Zend/zend_default_classes.c b/Zend/zend_default_classes.c index 1073f1c15b..d84016a5b0 100644 --- a/Zend/zend_default_classes.c +++ b/Zend/zend_default_classes.c @@ -24,6 +24,7 @@ #include "zend_builtin_functions.h" #include "zend_interfaces.h" #include "zend_exceptions.h" +#include "zend_closures.h" ZEND_API void zend_register_default_classes(TSRMLS_D) @@ -31,6 +32,7 @@ ZEND_API void zend_register_default_classes(TSRMLS_D) zend_register_interfaces(TSRMLS_C); zend_register_default_exception(TSRMLS_C); zend_register_iterator_wrapper(TSRMLS_C); + zend_register_closure_ce(TSRMLS_C); } /* diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index e758477ca0..614deb171e 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -34,6 +34,7 @@ #include "zend_ini.h" #include "zend_exceptions.h" #include "zend_interfaces.h" +#include "zend_closures.h" #include "zend_vm.h" /* Virtual current working directory support */ diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 55d299b415..7fbb65a7df 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -30,6 +30,7 @@ #include "zend_constants.h" #include "zend_extensions.h" #include "zend_exceptions.h" +#include "zend_closures.h" #include "zend_vm.h" #ifdef HAVE_SYS_TIME_H #include @@ -826,7 +827,11 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache TS } } - if (Z_TYPE_P(fci->function_name) != IS_STRING) { + if (Z_TYPE_P(fci->function_name) == IS_OBJECT) { + if (zend_get_closure(fci->function_name, &calling_scope, &EX(function_state).function, NULL, &fci->object_pp TSRMLS_CC) == SUCCESS) { + goto init_fci_cache; + } + } else if (Z_TYPE_P(fci->function_name) != IS_STRING) { return FAILURE; } @@ -935,6 +940,8 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache TS 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) diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 1fc365cfb7..23de160976 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -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,10 +643,30 @@ 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, 0 TSRMLS_CC); } + parameter_list ')' lexical_vars '{' inner_statement_list '}' { zend_do_end_function_declaration(&$1 TSRMLS_CC); $$ = $4; } + | T_STATIC function is_reference '(' { zend_do_begin_lambda_function_declaration(&$$, &$2, $3.op_type, 1 TSRMLS_CC); } + parameter_list ')' lexical_vars '{' inner_statement_list '}' { zend_do_end_function_declaration(&$2 TSRMLS_CC); $$ = $5; } +; + +function: + T_FUNCTION { $$.u.opline_num = CG(zend_lineno); } +; + +lexical_vars: + /* emptry */ + | T_USE '(' lexical_var_list ')' +; + +lexical_var_list: + lexical_var_list ',' T_VARIABLE { zend_do_fetch_lexical_variable(&$3, 0 TSRMLS_CC); } + | lexical_var_list ',' '&' T_VARIABLE { zend_do_fetch_lexical_variable(&$4, 1 TSRMLS_CC); } + | T_VARIABLE { zend_do_fetch_lexical_variable(&$1, 0 TSRMLS_CC); } + | '&' T_VARIABLE { zend_do_fetch_lexical_variable(&$2, 1 TSRMLS_CC); } ; function_call: - T_STRING '(' { $2.u.opline_num = zend_do_begin_function_call(&$1, 1 TSRMLS_CC); } + T_STRING '(' { $2.u.opline_num = zend_do_begin_function_call(&$1, 1 TSRMLS_CC); } function_call_parameter_list ')' { zend_do_end_function_call(&$1, &$$, &$4, 0, $2.u.opline_num TSRMLS_CC); zend_do_extended_fcall_end(TSRMLS_C); } | T_PAAMAYIM_NEKUDOTAYIM T_STRING '(' { $3.u.opline_num = zend_do_begin_function_call(&$2, 0 TSRMLS_CC); } diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 95893d8f6f..a907f50146 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -2029,6 +2029,15 @@ ZEND_VM_HANDLER(59, ZEND_INIT_FCALL_BY_NAME, ANY, CONST|TMP|VAR|CV) } else { function_name = GET_OP2_ZVAL_PTR(BP_VAR_R); + if (Z_TYPE_P(function_name) == IS_OBJECT && + zend_get_closure(function_name, &EX(called_scope), &EX(fbc), &EX(object), NULL TSRMLS_CC) == SUCCESS) { + if (EX(object)) { + Z_ADDREF_P(EX(object)); + } + 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 +4336,19 @@ ZEND_VM_HANDLER(143, ZEND_DECLARE_CONST, CONST, 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; + + 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"); + } + + zend_create_closure(&EX_T(opline->result.u.var).tmp_var, op_array, EG(scope), EG(This) TSRMLS_CC); + + ZEND_VM_NEXT_OPCODE(); +} + ZEND_VM_EXPORT_HELPER(zend_do_fcall, zend_do_fcall_common_helper) diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 23e6849f24..3026f4555b 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -747,6 +747,15 @@ static int ZEND_FASTCALL ZEND_INIT_FCALL_BY_NAME_SPEC_CONST_HANDLER(ZEND_OPCODE } else { function_name = &opline->op2.u.constant; + if (Z_TYPE_P(function_name) == IS_OBJECT && + zend_get_closure(function_name, &EX(called_scope), &EX(fbc), &EX(object), NULL TSRMLS_CC) == SUCCESS) { + if (EX(object)) { + Z_ADDREF_P(EX(object)); + } + + ZEND_VM_NEXT_OPCODE(); + } + if (Z_TYPE_P(function_name) != IS_STRING) { zend_error_noreturn(E_ERROR, "Function name must be a string"); } @@ -935,6 +944,15 @@ static int ZEND_FASTCALL ZEND_INIT_FCALL_BY_NAME_SPEC_TMP_HANDLER(ZEND_OPCODE_H } else { function_name = _get_zval_ptr_tmp(&opline->op2, EX(Ts), &free_op2 TSRMLS_CC); + if (Z_TYPE_P(function_name) == IS_OBJECT && + zend_get_closure(function_name, &EX(called_scope), &EX(fbc), &EX(object), NULL TSRMLS_CC) == SUCCESS) { + if (EX(object)) { + Z_ADDREF_P(EX(object)); + } + 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"); } @@ -1031,6 +1049,15 @@ static int ZEND_FASTCALL ZEND_INIT_FCALL_BY_NAME_SPEC_VAR_HANDLER(ZEND_OPCODE_H } else { function_name = _get_zval_ptr_var(&opline->op2, EX(Ts), &free_op2 TSRMLS_CC); + if (Z_TYPE_P(function_name) == IS_OBJECT && + zend_get_closure(function_name, &EX(called_scope), &EX(fbc), &EX(object), NULL TSRMLS_CC) == SUCCESS) { + if (EX(object)) { + Z_ADDREF_P(EX(object)); + } + 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"); } @@ -1155,6 +1182,15 @@ static int ZEND_FASTCALL ZEND_INIT_FCALL_BY_NAME_SPEC_CV_HANDLER(ZEND_OPCODE_HA } else { function_name = _get_zval_ptr_cv(&opline->op2, EX(Ts), BP_VAR_R TSRMLS_CC); + if (Z_TYPE_P(function_name) == IS_OBJECT && + zend_get_closure(function_name, &EX(called_scope), &EX(fbc), &EX(object), NULL TSRMLS_CC) == SUCCESS) { + if (EX(object)) { + Z_ADDREF_P(EX(object)); + } + + 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 +2911,21 @@ static int ZEND_FASTCALL ZEND_DECLARE_CONST_SPEC_CONST_CONST_HANDLER(ZEND_OPCOD 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; + + 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"); + } + + zend_create_closure(&EX_T(opline->result.u.var).tmp_var, op_array, EG(scope), EG(This) TSRMLS_CC); + + 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 +33559,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; diff --git a/Zend/zend_vm_opcodes.h b/Zend/zend_vm_opcodes.h index f3914158f3..2ab821d781 100644 --- a/Zend/zend_vm_opcodes.h +++ b/Zend/zend_vm_opcodes.h @@ -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 diff --git a/configure.in b/configure.in index c50f4609d0..eba216565e 100644 --- a/configure.in +++ b/configure.in @@ -1403,7 +1403,8 @@ PHP_ADD_SOURCES(Zend, \ zend_variables.c zend.c zend_API.c zend_extensions.c zend_hash.c \ zend_list.c zend_indent.c zend_builtin_functions.c zend_sprintf.c \ zend_ini.c zend_qsort.c zend_multibyte.c zend_ts_hash.c zend_stream.c \ - zend_iterators.c zend_interfaces.c zend_exceptions.c zend_strtod.c zend_gc.c) + zend_iterators.c zend_interfaces.c zend_exceptions.c zend_strtod.c zend_gc.c \ + zend_closures.c) if test -r "$abs_srcdir/Zend/zend_objects.c"; then PHP_ADD_SOURCES(Zend, zend_objects.c zend_object_handlers.c zend_objects_API.c \ diff --git a/ext/pcre/php_pcre.c b/ext/pcre/php_pcre.c index af67b284d1..0192ebfafa 100644 --- a/ext/pcre/php_pcre.c +++ b/ext/pcre/php_pcre.c @@ -1312,7 +1312,7 @@ static void preg_replace_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool is_callabl } SEPARATE_ZVAL(replace); - if (Z_TYPE_PP(replace) != IS_ARRAY) + if (Z_TYPE_PP(replace) != IS_ARRAY && (Z_TYPE_PP(replace) != IS_OBJECT || !is_callable_replace)) convert_to_string_ex(replace); if (is_callable_replace) { if (!zend_is_callable(*replace, 0, &callback_name)) { diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index c027dae308..f99e009830 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -38,6 +38,7 @@ #include "zend_constants.h" #include "zend_ini.h" #include "zend_interfaces.h" +#include "zend_closures.h" /* Undefine "getParameters" macro defined in "main/php3_compat.h" */ #ifdef getParameters @@ -1562,6 +1563,20 @@ ZEND_METHOD(reflection_function, getStaticVariables) } /* }}} */ +/* {{{ proto public mixed ReflectionFunction::getClosure() + Invokes the function */ +ZEND_METHOD(reflection_function, getClosure) +{ + reflection_object *intern; + zend_function *fptr; + + METHOD_NOTSTATIC_NUMPARAMS(reflection_function_ptr, 0); + GET_REFLECTION_OBJECT_PTR(fptr); + + zend_create_closure(return_value, fptr, NULL, NULL TSRMLS_CC); +} +/* }}} */ + /* {{{ proto public mixed ReflectionFunction::invoke(mixed* args) Invokes the function */ ZEND_METHOD(reflection_function, invoke) @@ -2290,6 +2305,34 @@ ZEND_METHOD(reflection_method, __toString) } /* }}} */ +/* {{{ proto public mixed ReflectionMethod::getClosure([mixed object]) + Invokes the function */ +ZEND_METHOD(reflection_method, getClosure) +{ + reflection_object *intern; + zval *obj; + zend_function *mptr; + + METHOD_NOTSTATIC(reflection_method_ptr); + GET_REFLECTION_OBJECT_PTR(mptr); + + if (mptr->common.fn_flags & ZEND_ACC_STATIC) { + zend_create_closure(return_value, mptr, mptr->common.scope, NULL TSRMLS_CC); + } else { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &obj) == FAILURE) { + return; + } + + if (!instanceof_function(Z_OBJCE_P(obj), mptr->common.scope TSRMLS_CC)) { + _DO_THROW("Given object is not an instance of the class this method was declared in"); + /* Returns from this function */ + } + + zend_create_closure(return_value, mptr, mptr->common.scope, obj TSRMLS_CC); + } +} +/* }}} */ + /* {{{ proto public mixed ReflectionMethod::invoke(mixed object, mixed* args) Invokes the method. */ ZEND_METHOD(reflection_method, invoke) @@ -4641,6 +4684,7 @@ static const zend_function_entry reflection_function_functions[] = { ZEND_ME(reflection_function, __toString, NULL, 0) ZEND_ME(reflection_function, export, arginfo_reflection_function_export, ZEND_ACC_STATIC|ZEND_ACC_PUBLIC) ZEND_ME(reflection_function, isDisabled, NULL, 0) + ZEND_ME(reflection_function, getClosure, NULL, 0) ZEND_ME(reflection_function, invoke, arginfo_reflection_function_invoke, 0) ZEND_ME(reflection_function, invokeArgs, arginfo_reflection_function_invokeArgs, 0) {NULL, NULL, NULL} @@ -4684,6 +4728,7 @@ static const zend_function_entry reflection_method_functions[] = { ZEND_ME(reflection_method, isConstructor, NULL, 0) ZEND_ME(reflection_method, isDestructor, NULL, 0) ZEND_ME(reflection_method, getModifiers, NULL, 0) + ZEND_ME(reflection_method, getClosure, NULL, 0) ZEND_ME(reflection_method, invoke, arginfo_reflection_method_invoke, 0) ZEND_ME(reflection_method, invokeArgs, arginfo_reflection_method_invokeArgs, 0) ZEND_ME(reflection_method, getDeclaringClass, NULL, 0) -- 2.40.0