]> granicus.if.org Git - php/commitdiff
Added support for lambda functions and closures
authorDmitry Stogov <dmitry@php.net>
Mon, 14 Jul 2008 09:49:03 +0000 (09:49 +0000)
committerDmitry Stogov <dmitry@php.net>
Mon, 14 Jul 2008 09:49:03 +0000 (09:49 +0000)
35 files changed:
NEWS
Zend/Makefile.am
Zend/Zend.dsp
Zend/ZendTS.dsp
Zend/tests/closure_001.phpt [new file with mode: 0644]
Zend/tests/closure_002.phpt [new file with mode: 0644]
Zend/tests/closure_003.phpt [new file with mode: 0644]
Zend/tests/closure_004.phpt [new file with mode: 0644]
Zend/tests/closure_005.phpt [new file with mode: 0644]
Zend/tests/closure_006.phpt [new file with mode: 0644]
Zend/tests/closure_007.phpt [new file with mode: 0644]
Zend/tests/closure_008.phpt [new file with mode: 0644]
Zend/tests/closure_009.phpt [new file with mode: 0644]
Zend/tests/closure_010.phpt [new file with mode: 0644]
Zend/tests/closure_011.phpt [new file with mode: 0644]
Zend/tests/closure_012.phpt [new file with mode: 0644]
Zend/tests/closure_013.phpt [new file with mode: 0644]
Zend/tests/closure_014.phpt [new file with mode: 0644]
Zend/tests/closure_015.phpt [new file with mode: 0644]
Zend/zend.h
Zend/zend_API.c
Zend/zend_closures.c [new file with mode: 0644]
Zend/zend_closures.h [new file with mode: 0644]
Zend/zend_compile.c
Zend/zend_compile.h
Zend/zend_default_classes.c
Zend/zend_execute.c
Zend/zend_execute_API.c
Zend/zend_language_parser.y
Zend/zend_vm_def.h
Zend/zend_vm_execute.h
Zend/zend_vm_opcodes.h
configure.in
ext/pcre/php_pcre.c
ext/reflection/php_reflection.c

diff --git a/NEWS b/NEWS
index 5e9e24cfde69920dcb168c3cf80a4e1f44539b24..c9586e7d80be3080a950b174b145e51ca2db4913 100644 (file)
--- 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)
index b233b9c8001f65ece6e8c564df8c287151d6a905..def7a91cc3dc4a943610e40dee894fe408401f8f 100644 (file)
@@ -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@
index a92125d89c2f6c5b65515a286559058634f84e93..0c6400fb1de96dd7e34c83fa6ae0cd08d99f83d5 100644 (file)
@@ -123,6 +123,10 @@ SOURCE=.\zend_builtin_functions.c
 # End Source File\r
 # Begin Source File\r
 \r
+SOURCE=.\zend_closures.c\r
+# End Source File\r
+# Begin Source File\r
+\r
 SOURCE=.\zend_compile.c\r
 # End Source File\r
 # Begin Source File\r
index e70f123a03e662f4d7ddd4fc0c48db3bf65d286b..2295b89ebb778a47a1282246f49b911a3cfb0fc4 100644 (file)
@@ -148,6 +148,10 @@ SOURCE=.\zend_builtin_functions.c
 # End Source File\r
 # Begin Source File\r
 \r
+SOURCE=.\zend_closures.c\r
+# End Source File\r
+# Begin Source File\r
+\r
 SOURCE=.\zend_compile.c\r
 # End Source File\r
 # Begin Source File\r
diff --git a/Zend/tests/closure_001.phpt b/Zend/tests/closure_001.phpt
new file mode 100644 (file)
index 0000000..ebac729
--- /dev/null
@@ -0,0 +1,30 @@
+--TEST--
+Closure 001: Lambda without lexical variables
+--FILE--
+<?php
+
+$lambda1 = function () {
+       echo "Hello World!\n";
+};
+
+$lambda2 = function ($x) {
+       echo "Hello $x!\n";
+};
+
+var_dump(is_callable($lambda1));
+var_dump(is_callable($lambda2));
+$lambda1();
+$lambda2("Universe");
+call_user_func($lambda1);
+call_user_func($lambda2, "Universe");
+
+echo "Done\n";
+?>
+--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 (file)
index 0000000..023d4ec
--- /dev/null
@@ -0,0 +1,29 @@
+--TEST--
+Closure 002: Lambda with lexical variables (global scope)
+--FILE--
+<?php
+
+$x = 4;
+
+$lambda1 = function () use ($x) {
+       echo "$x\n";
+};
+
+$lambda2 = function () use (&$x) {
+       echo "$x\n";
+};
+
+$lambda1();
+$lambda2();
+$x++;
+$lambda1();
+$lambda2();
+
+echo "Done\n";
+?>
+--EXPECT--
+4
+4
+4
+5
+Done
diff --git a/Zend/tests/closure_003.phpt b/Zend/tests/closure_003.phpt
new file mode 100644 (file)
index 0000000..6f5cc70
--- /dev/null
@@ -0,0 +1,33 @@
+--TEST--
+Closure 003: Lambda with lexical variables (local scope)
+--FILE--
+<?php
+
+function run () {
+       $x = 4;
+
+       $lambda1 = function () use ($x) {
+               echo "$x\n";
+       };
+
+       $lambda2 = function () use (&$x) {
+               echo "$x\n";
+       };
+
+       $lambda1();
+       $lambda2();
+       $x++;
+       $lambda1();
+       $lambda2();
+}
+
+run();
+
+echo "Done\n";
+?>
+--EXPECT--
+4
+4
+4
+5
+Done
diff --git a/Zend/tests/closure_004.phpt b/Zend/tests/closure_004.phpt
new file mode 100644 (file)
index 0000000..c1c2efb
--- /dev/null
@@ -0,0 +1,35 @@
+--TEST--
+Closure 004: Lambda with lexical variables (scope lifetime)
+--FILE--
+<?php
+
+function run () {
+       $x = 4;
+
+       $lambda1 = function () use ($x) {
+               echo "$x\n";
+       };
+
+       $lambda2 = function () use (&$x) {
+               echo "$x\n";
+               $x++;
+       };
+
+       return array($lambda1, $lambda2);
+}
+
+list ($lambda1, $lambda2) = run();
+
+$lambda1();
+$lambda2();
+$lambda1();
+$lambda2();
+
+echo "Done\n";
+?>
+--EXPECT--
+4
+4
+4
+5
+Done
diff --git a/Zend/tests/closure_005.phpt b/Zend/tests/closure_005.phpt
new file mode 100644 (file)
index 0000000..4e32faa
--- /dev/null
@@ -0,0 +1,74 @@
+--TEST--
+Closure 005: Lambda inside class, lifetime of $this
+--FILE--
+<?php
+
+class A {
+       private $x;
+
+       function __construct($x) {
+               $this->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 (file)
index 0000000..aa0ec11
--- /dev/null
@@ -0,0 +1,19 @@
+--TEST--
+Closure 006: Nested lambdas
+--FILE--
+<?php
+
+$getClosure = function ($v) {
+       return function () use ($v) {
+               echo "Hello World: $v!\n";
+       };
+};
+
+$closure = $getClosure (2);
+$closure ();
+
+echo "Done\n";
+?>
+--EXPECT--
+Hello World: 2!
+Done
diff --git a/Zend/tests/closure_007.phpt b/Zend/tests/closure_007.phpt
new file mode 100644 (file)
index 0000000..89cd06d
--- /dev/null
@@ -0,0 +1,38 @@
+--TEST--
+Closure 007: Nested lambdas in classes
+--FILE--
+<?php
+
+class A {
+       private $x = 0;
+
+       function getClosureGetter () {
+               return function () {
+                       return function () {
+                               $this->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 (file)
index 0000000..77b50de
--- /dev/null
@@ -0,0 +1,22 @@
+--TEST--
+Closure 008: Use in preg_replace()
+--FILE--
+<?php
+
+function replace_spaces($text) {
+       $lambda = function ($matches) {
+               return str_replace(' ', '&nbsp;', $matches[1]).' ';
+       };
+       return preg_replace_callback('/( +) /', $lambda, $text);
+}
+
+echo replace_spaces("1 2 3\n");
+echo replace_spaces("1  2  3\n");
+echo replace_spaces("1   2   3\n");
+echo "Done\n";
+?>
+--EXPECT--
+1 2 3
+1&nbsp; 2&nbsp; 3
+1&nbsp;&nbsp; 2&nbsp;&nbsp; 3
+Done
diff --git a/Zend/tests/closure_009.phpt b/Zend/tests/closure_009.phpt
new file mode 100644 (file)
index 0000000..10ec32b
--- /dev/null
@@ -0,0 +1,31 @@
+--TEST--
+Closure 009: Use in preg_replace()
+--FILE--
+<?php
+$a = 1;
+$x = function ($x) use ($a) {
+  static $n = 0;
+  $n++;
+  $a = $n.':'.$a;
+  echo $x.':'.$a."\n";
+};
+$y = function ($x) use (&$a) {
+  static $n = 0;
+  $n++;
+  $a = $n.':'.$a;
+  echo $x.':'.$a."\n";
+};
+$x(1);
+$x(2);
+$x(3);
+$y(4);
+$y(5);
+$y(6);
+?>
+--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 (file)
index 0000000..d4787f0
--- /dev/null
@@ -0,0 +1,18 @@
+--TEST--
+Closure 010: Closure calls itself
+--FILE--
+<?php
+$i = 3;
+$lambda = function ($lambda) use (&$i) {
+    if ($i==0) return;
+    echo $i--."\n";
+    $lambda($lambda);
+};
+$lambda($lambda);
+echo "$i\n";
+?>
+--EXPECT--
+3
+2
+1
+0
diff --git a/Zend/tests/closure_011.phpt b/Zend/tests/closure_011.phpt
new file mode 100644 (file)
index 0000000..7071364
--- /dev/null
@@ -0,0 +1,14 @@
+--TEST--
+Closure 011: Lexical copies not static in closure
+--FILE--
+<?php
+$i = 1;
+$lambda = function () use ($i) {
+    return ++$i;
+};
+$lambda();
+echo $lambda()."\n";
+//early prototypes gave 3 here because $i was static in $lambda
+?>
+--EXPECT--
+2
diff --git a/Zend/tests/closure_012.phpt b/Zend/tests/closure_012.phpt
new file mode 100644 (file)
index 0000000..7e1b7a2
--- /dev/null
@@ -0,0 +1,24 @@
+--TEST--
+Closure 012: Undefined lexical variables
+--FILE--
+<?php
+$lambda = function () use ($i) {
+    return ++$i;
+};
+$lambda();
+$lambda();
+var_dump($i);
+$lambda = function () use (&$i) {
+    return ++$i;
+};
+$lambda();
+$lambda();
+var_dump($i);
+?>
+--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 (file)
index 0000000..72d2b3f
--- /dev/null
@@ -0,0 +1,25 @@
+--TEST--
+Closure 013: __invoke() on temporary result
+--FILE--
+<?php
+class Foo {
+       function __invoke() {
+               echo "Hello World!\n";
+       }
+}
+
+function foo() {
+       return function() {
+               echo "Hello World!\n";
+       };
+}
+$test = new Foo;
+$test->__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 (file)
index 0000000..9e4819b
--- /dev/null
@@ -0,0 +1,79 @@
+--TEST--
+Closure 014: return by value/reference
+--FILE--
+<?php
+class C1 {
+       function __invoke() {
+               return 0;
+       }
+}
+class C2 {
+       function &__invoke(&$a) {
+               return $a;
+       }
+}
+class C3 {
+       function __invoke() {
+       }
+}
+
+$x = new C1();
+var_dump($x());
+var_dump($x->__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 (file)
index 0000000..d21aca5
--- /dev/null
@@ -0,0 +1,13 @@
+--TEST--
+Closure 015: converting to string/unicode
+--FILE--
+<?php
+$x = function() { return 1; };
+print (string) $x;
+print "\n";
+print $x;
+print "\n";
+?>
+--EXPECT--
+Closure object
+Closure object
index d5a6974b087c13fda978a681e7bcb6d55be92f0c..0d09f4bc0d237fb51fd2414b6859b8e798505552 100644 (file)
@@ -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)
index d8f627adad7b375001844a6427331ad13e3a00bb..68f0c7880ce1f2f72841067888f22480377ff197 100644 (file)
@@ -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 <stdarg.h>
@@ -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 (file)
index 0000000..5ed979c
--- /dev/null
@@ -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 <chris_se@gmx.net>                         |
+   |          Dmitry Stogov <dmitry@zend.com>                             |
+   +----------------------------------------------------------------------+
+*/
+
+/* $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 (file)
index 0000000..c3a3b87
--- /dev/null
@@ -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 <chris_se@gmx.net>                         |
+   |          Dmitry Stogov <dmitry@zend.com>                             |
+   +----------------------------------------------------------------------+
+*/
+
+/* $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:
+ */
index 723191cf56b87e3b59856d906dd4f78ca5d65d47..5546e2a0da8ec99e26473179bb47c661ea19d8f2 100644 (file)
@@ -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 = &current_op_array->opcodes[current_op_number];
+       current_op->opcode = ZEND_DECLARE_LAMBDA_FUNCTION;
+       zval_dtor(&current_op->op2.u.constant);
+       ZVAL_LONG(&current_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;
index 7198715b6fea87283b91f9b500109e1019280c4b..89ae65108cae990c9559d2625e0b5701fd43ca33 100644 (file)
@@ -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 */
index 1073f1c15b8f57b853904cc1fee018cceb944f6c..d84016a5b0b741580b0eda5f13fff1ccb9b42e6d 100644 (file)
@@ -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);
 }
 
 /*
index e758477ca0107929b3be2c4418e01420545af982..614deb171efba1673ce0544c87f2954905a751d0 100644 (file)
@@ -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 */
index 55d299b415024f2e95ca66336a86e9b6d1ba896a..7fbb65a7df0c2620c88ffc5907ccc91ff9360fc4 100644 (file)
@@ -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 <sys/time.h>
@@ -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)
index 1fc365cfb7cdde94c59a43a5b83d0a15af09cc25..23de160976e44fd0b2fcebaa2a7383b22d2e3b26 100644 (file)
@@ -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); }
index 95893d8f6f69ce095da917086a81c1c3face5ed9..a907f50146bac1c5aabd03bd6af0c864bbe65540 100644 (file)
@@ -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)
index 23e6849f242f55a82ef163c0632ef1db9bb4a23f..3026f4555b5433fc0b2726f460d56d710e005ae9 100644 (file)
@@ -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;
index f3914158f3c2fb5d96ee9a714644330a08708b19..2ab821d7811a6dfd2969ddf00ba16fdc707cc886 100644 (file)
 #define ZEND_HANDLE_EXCEPTION                149
 #define ZEND_USER_OPCODE                     150
 #define ZEND_JMP_SET                         152
+#define ZEND_DECLARE_LAMBDA_FUNCTION         153
index c50f4609d0f4349ae93aaddfa504fe71e051573b..eba216565e2c3ad2c8752c94cc3845d24fbdf01d 100644 (file)
@@ -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 \
index af67b284d10b4b8c0a241855f7df59f2537f49a9..0192ebfafa8f3baf61931e029793e674aaac2792 100644 (file)
@@ -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)) {
index c027dae30842d8157d75c1d7a62e8edd748471ac..f99e009830dde83b462e5670c3c68cb681bd452e 100644 (file)
@@ -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)