Implement preg_replace_callback_array function

This commit is contained in:
Wei Dai 2015-03-13 11:52:36 +08:00
parent 61406a527f
commit 25566c67fe
7 changed files with 335 additions and 77 deletions

View File

@ -519,67 +519,56 @@ function gen_code($f, $spec, $kind, $export, $code, $op1, $op2, $name) {
// Updating code according to selected threading model
switch($kind) {
case ZEND_VM_KIND_CALL:
$code = preg_replace_callback(
$code = preg_replace_callback_array(
array(
"/EXECUTE_DATA/m",
"/ZEND_VM_DISPATCH_TO_HANDLER\(\s*([A-Z_]*)\s*\)/m",
"/ZEND_VM_DISPATCH_TO_HELPER\(\s*([A-Za-z_]*)\s*\)/m",
"/ZEND_VM_DISPATCH_TO_HELPER_EX\(\s*([A-Za-z_]*)\s*,\s*[A-Za-z_]*\s*,\s*(.*)\s*\);/m",
),
function($matches) use ($spec, $prefix, $op1, $op2) {
if (strncasecmp($matches[0], "EXECUTE_DATA", strlen("EXECUTE_DATA")) == 0) {
return "execute_data";
} else if (strncasecmp($matches[0], "ZEND_VM_DISPATCH_TO_HANDLER", strlen("ZEND_VM_DISPATCH_TO_HANDLER")) == 0) {
"/EXECUTE_DATA/m" => function($matches) use ($spec, $prefix, $op1, $op2) {
return "execute_data";
},
"/ZEND_VM_DISPATCH_TO_HANDLER\(\s*([A-Z_]*)\s*\)/m" => function($matches) use ($spec, $prefix, $op1, $op2) {
return "return " . $matches[1] . ($spec?"_SPEC":"") . $prefix[$op1] . $prefix[$op2] . "_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)";
} else if (strncasecmp($matches[0], "ZEND_VM_DISPATCH_TO_HELPER_EX", strlen("ZEND_VM_DISPATCH_TO_HELPER_EX")) == 0) {
return "return " . helper_name($matches[1], $spec, $op1, $op2) . "(" . $matches[2]. ", ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);";
} else {
},
"/ZEND_VM_DISPATCH_TO_HELPER\(\s*([A-Za-z_]*)\s*\)/m" => function($matches) use ($spec, $prefix, $op1, $op2) {
return "return " . helper_name($matches[1], $spec, $op1, $op2) . "(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)";
}
},
$code);
},
"/ZEND_VM_DISPATCH_TO_HELPER_EX\(\s*([A-Za-z_]*)\s*,\s*[A-Za-z_]*\s*,\s*(.*)\s*\);/m" => function($matches) use ($spec, $prefix, $op1, $op2) {
return "return " . helper_name($matches[1], $spec, $op1, $op2) . "(" . $matches[2]. ", ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);";
},
), $code);
break;
case ZEND_VM_KIND_SWITCH:
$code = preg_replace_callback(
$code = preg_replace_callback_array(
array(
"/EXECUTE_DATA/m",
"/ZEND_VM_DISPATCH_TO_HANDLER\(\s*([A-Z_]*)\s*\)/m",
"/ZEND_VM_DISPATCH_TO_HELPER\(\s*([A-Za-z_]*)\s*\)/m",
"/ZEND_VM_DISPATCH_TO_HELPER_EX\(\s*([A-Za-z_]*)\s*,\s*([A-Za-z_]*)\s*,\s*(.*)\s*\);/m",
),
function($matches) use ($spec, $prefix, $op1, $op2) {
if (strncasecmp($matches[0], "EXECUTE_DATA", strlen("EXECUTE_DATA")) == 0) {
"/EXECUTE_DATA/m" => function($matches) use ($spec, $prefix, $op1, $op2) {
return "execute_data";
} else if (strncasecmp($matches[0], "ZEND_VM_DISPATCH_TO_HANDLER", strlen("ZEND_VM_DISPATCH_TO_HANDLER")) == 0) {
},
"/ZEND_VM_DISPATCH_TO_HANDLER\(\s*([A-Z_]*)\s*\)/m" => function($matches) use ($spec, $prefix, $op1, $op2) {
return "goto " . $matches[1] . ($spec?"_SPEC":"") . $prefix[$op1] . $prefix[$op2] . "_LABEL";
} else if (strncasecmp($matches[0], "ZEND_VM_DISPATCH_TO_HELPER_EX", strlen("ZEND_VM_DISPATCH_TO_HELPER_EX")) == 0) {
return $matches[2] . " = " . $matches[3] . "; goto " . helper_name($matches[1], $spec, $op1, $op2) . ";";
} else {
},
"/ZEND_VM_DISPATCH_TO_HELPER\(\s*([A-Za-z_]*)\s*\)/m" => function($matches) use ($spec, $prefix, $op1, $op2) {
return "goto " . helper_name($matches[1], $spec, $op1, $op2);
}
},
},
"/ZEND_VM_DISPATCH_TO_HELPER_EX\(\s*([A-Za-z_]*)\s*,\s*([A-Za-z_]*)\s*,\s*(.*)\s*\);/m" => function($matches) use ($spec, $prefix, $op1, $op2) {
return $matches[2] . " = " . $matches[3] . "; goto " . helper_name($matches[1], $spec, $op1, $op2) . ";";
},
),
$code);
break;
case ZEND_VM_KIND_GOTO:
$code = preg_replace_callback(
$code = preg_replace_callback_array(
array(
"/EXECUTE_DATA/m",
"/ZEND_VM_DISPATCH_TO_HANDLER\(\s*([A-Z_]*)\s*\)/m",
"/ZEND_VM_DISPATCH_TO_HELPER\(\s*([A-Za-z_]*)\s*\)/m",
"/ZEND_VM_DISPATCH_TO_HELPER_EX\(\s*([A-Za-z_]*)\s*,\s*([A-Za-z_]*)\s*,\s*(.*)\s*\);/m",
),
function($matches) use ($spec, $prefix, $op1, $op2) {
if (strncasecmp($matches[0], "EXECUTE_DATA", strlen("EXECUTE_DATA")) == 0) {
return "execute_data";
} else if (strncasecmp($matches[0], "ZEND_VM_DISPATCH_TO_HANDLER", strlen("ZEND_VM_DISPATCH_TO_HANDLER")) == 0) {
"/EXECUTE_DATA/m" => function($matches) use ($spec, $prefix, $op1, $op2) {
return "execute_data";
},
"/ZEND_VM_DISPATCH_TO_HANDLER\(\s*([A-Z_]*)\s*\)/m" => function($matches) use ($spec, $prefix, $op1, $op2) {
return "goto " . $matches[1] . ($spec?"_SPEC":"") . $prefix[$op1] . $prefix[$op2] . "_HANDLER";
} else if (strncasecmp($matches[0], "ZEND_VM_DISPATCH_TO_HELPER_EX", strlen("ZEND_VM_DISPATCH_TO_HELPER_EX")) == 0) {
return $matches[2] . " = " . $matches[3] . "; goto " . helper_name($matches[1], $spec, $op1, $op2) . ";";
} else {
},
"/ZEND_VM_DISPATCH_TO_HELPER\(\s*([A-Za-z_]*)\s*\)/m" => function($matches) use ($spec, $prefix, $op1, $op2) {
return "goto " . helper_name($matches[1], $spec, $op1, $op2);
}
},
$code);
},
"/ZEND_VM_DISPATCH_TO_HELPER_EX\(\s*([A-Za-z_]*)\s*,\s*([A-Za-z_]*)\s*,\s*(.*)\s*\);/m" => function($matches) use ($spec, $prefix, $op1, $op2) {
return $matches[2] . " = " . $matches[3] . "; goto " . helper_name($matches[1], $spec, $op1, $op2) . ";";
},
), $code);
break;
}

View File

@ -1353,37 +1353,16 @@ static zend_string *php_replace_in_subject(zval *regex, zval *replace, zval *sub
/* {{{ preg_replace_impl
*/
static void preg_replace_impl(INTERNAL_FUNCTION_PARAMETERS, int is_callable_replace, int is_filter)
static void preg_replace_impl(zval *return_value, int argc, zval *regex, zval *replace, zval *subject, zend_long limit, zval *zcount, int is_callable_replace, int is_filter)
{
zval *regex,
*replace,
*subject,
*subject_entry,
*zcount = NULL;
zval *subject_entry;
int limit_val = -1;
zend_long limit = -1;
zend_string *result;
zend_string *string_key;
zend_ulong num_key;
zend_string *callback_name;
int replace_count=0, old_replace_count;
#ifndef FAST_ZPP
/* Get function parameters and do error-checking. */
if (zend_parse_parameters(ZEND_NUM_ARGS(), "zzz|lz/", &regex, &replace, &subject, &limit, &zcount) == FAILURE) {
return;
}
#else
ZEND_PARSE_PARAMETERS_START(3, 5)
Z_PARAM_ZVAL(regex)
Z_PARAM_ZVAL(replace)
Z_PARAM_ZVAL(subject)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(limit)
Z_PARAM_ZVAL_EX(zcount, 0, 1)
ZEND_PARSE_PARAMETERS_END();
#endif
if (!is_callable_replace && Z_TYPE_P(replace) == IS_ARRAY && Z_TYPE_P(regex) != IS_ARRAY) {
php_error_docref(NULL, E_WARNING, "Parameter mismatch, pattern is a string while replacement is an array");
RETURN_FALSE;
@ -1403,7 +1382,7 @@ static void preg_replace_impl(INTERNAL_FUNCTION_PARAMETERS, int is_callable_repl
zend_string_release(callback_name);
}
if (ZEND_NUM_ARGS() > 3) {
if (argc > 3) {
limit_val = (int)limit;
}
@ -1433,7 +1412,8 @@ static void preg_replace_impl(INTERNAL_FUNCTION_PARAMETERS, int is_callable_repl
}
}
} ZEND_HASH_FOREACH_END();
} else { /* if subject is not an array */
} else {
/* if subject is not an array */
old_replace_count = replace_count;
if ((result = php_replace_in_subject(regex, replace, subject, limit_val, is_callable_replace, &replace_count)) != NULL) {
if (!is_filter || replace_count > old_replace_count) {
@ -1443,11 +1423,10 @@ static void preg_replace_impl(INTERNAL_FUNCTION_PARAMETERS, int is_callable_repl
}
}
}
if (ZEND_NUM_ARGS() > 4) {
if (argc > 4) {
zval_dtor(zcount);
ZVAL_LONG(zcount, replace_count);
}
}
/* }}} */
@ -1455,7 +1434,26 @@ static void preg_replace_impl(INTERNAL_FUNCTION_PARAMETERS, int is_callable_repl
Perform Perl-style regular expression replacement. */
static PHP_FUNCTION(preg_replace)
{
preg_replace_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, 0);
zval *regex, *replace, *subject, *zcount = NULL;
zend_long limit = -1;
#ifndef FAST_ZPP
/* Get function parameters and do error-checking. */
if (zend_parse_parameters(ZEND_NUM_ARGS(), "zzz|lz/", &regex, &replace, &subject, &limit, &zcount) == FAILURE) {
return;
}
#else
ZEND_PARSE_PARAMETERS_START(3, 5)
Z_PARAM_ZVAL(regex)
Z_PARAM_ZVAL(replace)
Z_PARAM_ZVAL(subject)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(limit)
Z_PARAM_ZVAL_EX(zcount, 0, 1)
ZEND_PARSE_PARAMETERS_END();
#endif
preg_replace_impl(return_value, ZEND_NUM_ARGS(), regex, replace, subject, limit, zcount, 0, 0);
}
/* }}} */
@ -1463,7 +1461,84 @@ static PHP_FUNCTION(preg_replace)
Perform Perl-style regular expression replacement using replacement callback. */
static PHP_FUNCTION(preg_replace_callback)
{
preg_replace_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1, 0);
zval *regex, *replace, *subject, *zcount = NULL;
zend_long limit = -1;
#ifndef FAST_ZPP
/* Get function parameters and do error-checking. */
if (zend_parse_parameters(ZEND_NUM_ARGS(), "zzz|lz/", &regex, &replace, &subject, &limit, &zcount) == FAILURE) {
return;
}
#else
ZEND_PARSE_PARAMETERS_START(3, 5)
Z_PARAM_ZVAL(regex)
Z_PARAM_ZVAL(replace)
Z_PARAM_ZVAL(subject)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(limit)
Z_PARAM_ZVAL_EX(zcount, 0, 1)
ZEND_PARSE_PARAMETERS_END();
#endif
preg_replace_impl(return_value, ZEND_NUM_ARGS(), regex, replace, subject, limit, zcount, 1, 0);
}
/* }}} */
/* {{{ proto mixed preg_replace_callback_array(array pattern, mixed subject [, int limit [, int &count]])
Perform Perl-style regular expression replacement using replacement callback. */
static PHP_FUNCTION(preg_replace_callback_array)
{
zval regex, tmp_ret, *replace, *subject, *pattern, *zcount = NULL;
zend_long limit = -1;
zend_ulong num_idx, count = 0;
zend_string *str_idx;
int argc;
#ifndef FAST_ZPP
/* Get function parameters and do error-checking. */
if (zend_parse_parameters(ZEND_NUM_ARGS(), "az|lz/", &pattern, &subject, &limit, &zcount) == FAILURE) {
return;
}
#else
ZEND_PARSE_PARAMETERS_START(2, 4)
Z_PARAM_ARRAY(pattern)
Z_PARAM_ZVAL(subject)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(limit)
Z_PARAM_ZVAL_EX(zcount, 0, 1)
ZEND_PARSE_PARAMETERS_END();
#endif
argc = ZEND_NUM_ARGS();
ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(pattern), num_idx, str_idx, replace) {
if (str_idx) {
ZVAL_STR_COPY(&regex, str_idx);
} else {
php_error_docref(NULL, E_WARNING, "Delimiter must not be alphanumeric or backslash");
zval_dtor(return_value);
ZVAL_COPY(return_value, subject);
break;
}
if (Z_ISNULL_P(return_value)) {
preg_replace_impl(&tmp_ret, argc + 1, &regex, replace, subject, limit, zcount, 1, 0);
} else {
preg_replace_impl(&tmp_ret, argc + 1, &regex, replace, return_value, limit, zcount, 1, 0);
zval_ptr_dtor(return_value);
}
if (zcount && Z_TYPE_P(zcount) == IS_LONG) {
count += Z_LVAL_P(zcount);
}
ZVAL_COPY_VALUE(return_value, &tmp_ret);
zval_ptr_dtor(&regex);
} ZEND_HASH_FOREACH_END();
if (zcount && Z_TYPE_P(zcount) == IS_LONG) {
zval_dtor(zcount);
ZVAL_LONG(zcount, count);
}
}
/* }}} */
@ -1471,7 +1546,26 @@ static PHP_FUNCTION(preg_replace_callback)
Perform Perl-style regular expression replacement and only return matches. */
static PHP_FUNCTION(preg_filter)
{
preg_replace_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, 1);
zval *regex, *replace, *subject, *zcount = NULL;
zend_long limit = -1;
#ifndef FAST_ZPP
/* Get function parameters and do error-checking. */
if (zend_parse_parameters(ZEND_NUM_ARGS(), "zzz|lz/", &regex, &replace, &subject, &limit, &zcount) == FAILURE) {
return;
}
#else
ZEND_PARSE_PARAMETERS_START(3, 5)
Z_PARAM_ZVAL(regex)
Z_PARAM_ZVAL(replace)
Z_PARAM_ZVAL(subject)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(limit)
Z_PARAM_ZVAL_EX(zcount, 0, 1)
ZEND_PARSE_PARAMETERS_END();
#endif
preg_replace_impl(return_value, ZEND_NUM_ARGS(), regex, replace, subject, limit, zcount, 0, 1);
}
/* }}} */
@ -1780,7 +1874,7 @@ static PHP_FUNCTION(preg_quote)
/* Reallocate string and return it */
out_str = zend_string_realloc(out_str, q - out_str->val, 0);
RETURN_STR(out_str);
RETURN_NEW_STR(out_str);
}
/* }}} */
@ -1953,6 +2047,13 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_preg_replace_callback, 0, 0, 3)
ZEND_ARG_INFO(1, count)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_preg_replace_callback_array, 0, 0, 2)
ZEND_ARG_INFO(0, pattern)
ZEND_ARG_INFO(0, subject)
ZEND_ARG_INFO(0, limit)
ZEND_ARG_INFO(1, count)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_preg_split, 0, 0, 2)
ZEND_ARG_INFO(0, pattern)
ZEND_ARG_INFO(0, subject)
@ -1980,6 +2081,7 @@ static const zend_function_entry pcre_functions[] = {
PHP_FE(preg_match_all, arginfo_preg_match_all)
PHP_FE(preg_replace, arginfo_preg_replace)
PHP_FE(preg_replace_callback, arginfo_preg_replace_callback)
PHP_FE(preg_replace_callback_array, arginfo_preg_replace_callback_array)
PHP_FE(preg_filter, arginfo_preg_replace)
PHP_FE(preg_split, arginfo_preg_split)
PHP_FE(preg_quote, arginfo_preg_quote)

View File

@ -0,0 +1,39 @@
--TEST--
preg_replace_callback_array()
--FILE--
<?php
function f() {
throw new Exception();
}
try {
var_dump(preg_replace_callback_array(array('/\w/' => 'f'), 'z'));
} catch(Exception $e) {}
function g($x) {
return "'$x[0]'";
}
var_dump(preg_replace_callback_array(array('@\b\w{1,2}\b@' => 'g'), array('a b3 bcd', 'v' => 'aksfjk', 12 => 'aa bb')));
var_dump(preg_replace_callback_array(array('~\A.~' => 'g'), array(array('xyz'))));
var_dump(preg_replace_callback_array(array('~\A.~' => create_function('$m', 'return strtolower($m[0]);')), 'ABC'));
?>
--EXPECTF--
array(3) {
[0]=>
string(12) "'a' 'b3' bcd"
["v"]=>
string(6) "aksfjk"
[12]=>
string(9) "'aa' 'bb'"
}
Notice: Array to string conversion in %spreg_replace_callback_array.php on line %d
array(1) {
[0]=>
string(7) "'A'rray"
}
string(3) "aBC"

View File

@ -0,0 +1,40 @@
--TEST--
preg_replace_callback_array() 2
--FILE--
<?php
var_dump(preg_replace_callback_array());
var_dump(preg_replace_callback_array(1));
var_dump(preg_replace_callback_array(1,2));
var_dump(preg_replace_callback_array(1,2,3));
$a = 5;
var_dump(preg_replace_callback_array(1,2,3,$a));
$a = "";
var_dump(preg_replace_callback_array(array("" => ""),"","",$a));
$a = array();
var_dump(preg_replace_callback($a,$a,$a,$a));
echo "Done\n";
?>
--EXPECTF--
Warning: preg_replace_callback_array() expects at least 2 parameters, 0 given in %s on line %d
NULL
Warning: preg_replace_callback_array() expects at least 2 parameters, 1 given in %s on line %d
NULL
Warning: preg_replace_callback_array() expects parameter 1 to be array, integer given in %s on line %d
NULL
Warning: preg_replace_callback_array() expects parameter 1 to be array, integer given in %s on line %d
NULL
Warning: preg_replace_callback_array() expects parameter 1 to be array, integer given in %s on line %d
NULL
Warning: preg_replace_callback_array() expects parameter 3 to be integer, string given in %s on line %d
NULL
Warning: preg_replace_callback() expects parameter 4 to be integer, array given in %s on line %d
NULL
Done

View File

@ -0,0 +1,39 @@
--TEST--
preg_replace_callback_array() 3
--FILE--
<?php
$code = "test-EXECUTE_DATA-xcvxcv-ZEND_VM_DISPATCH_TO_HELPER";
$code = preg_replace_callback_array(
array(
"/EXECUTE_DATA/m" => function($matches) { return "execute_data";},
"/ZEND_VM_DISPATCH_TO_HANDLER/m" => function($matches) { return "handler"; },
"/ZEND_VM_DISPATCH_TO_HELPER/m" => function($matches) { return "helper"; },
"/ZEND_VM_DISPATCH_TO_HELPER_EX/m" => function($matches) { return "helper_ex"; },
),
$code);
var_dump($code);
$code = array("test-EXECUTE_DATA-ZEND_VM_DISPATCH_TO_HELPER_EX-test",
"test-sdf-xcvxcv-ZEND_VM_DISPATCH_TO_HELPER_EX-test-EXECUTE_DATA-test");
$code = preg_replace_callback_array(
array(
"/EXECUTE_DATA/m" => function($matches) { return "execute_data";},
"/ZEND_VM_DISPATCH_TO_HANDLER/m" => function($matches) { return "handler"; },
"/ZEND_VM_DISPATCH_TO_HELPER/m" => function($matches) { return "helper"; },
),
$code, -1, $count);
var_dump($code, $count);
?>
--EXPECTF--
string(31) "test-execute_data-xcvxcv-helper"
array(2) {
[0]=>
string(32) "test-execute_data-helper_EX-test"
[1]=>
string(48) "test-sdf-xcvxcv-helper_EX-test-execute_data-test"
}
int(4)

View File

@ -0,0 +1,40 @@
--TEST--
preg_replace_callback_array() 4
--FILE--
<?php
$code = "test-EXECUTE_DATA-xcvxcv-ZEND_VM_DISPATCH_TO_HELPER";
$code = preg_replace_callback_array(
array(
"/EXECUTE_DATA/m" => function($matches) { return "execute_data";},
"/ZEND_VM_DISPATCH_TO_HANDLER/m" => function($matches) { return "handler"; },
"/ZEND_VM_DISPATCH_TO_HELPER/m" => function($matches) { return "helper"; },
"/ZEND_VM_DISPATCH_TO_HELPER_EX/m" => function($matches) { return "helper_ex"; },
),
$code);
var_dump($code);
$code = array("test-EXECUTE_DATA-ZEND_VM_DISPATCH_TO_HELPER_EX-test",
"test-sdf-xcvxcv-ZEND_VM_DISPATCH_TO_HELPER_EX-test-EXECUTE_DATA-test");
$code = preg_replace_callback_array(
array(
"/ZEND_VM_DISPATCH_TO_HANDLER/m" => function($matches) { return "handler"; },
23234 => function($matches) { return "execute_data";},
"/ZEND_VM_DISPATCH_TO_HELPER/m" => function($matches) { return "helper"; },
),
$code, -1, $count);
var_dump($code, $count);
?>
--EXPECTF--
string(31) "test-execute_data-xcvxcv-helper"
Warning: preg_replace_callback_array(): Delimiter must not be alphanumeric or backslash in %s on line %d
array(2) {
[0]=>
string(52) "test-EXECUTE_DATA-ZEND_VM_DISPATCH_TO_HELPER_EX-test"
[1]=>
string(68) "test-sdf-xcvxcv-ZEND_VM_DISPATCH_TO_HELPER_EX-test-EXECUTE_DATA-test"
}
int(0)

View File

@ -97,6 +97,15 @@ string(%d) "Extension [ <persistent> extension #%d pcre version <no_version> ] {
Parameter #4 [ <optional> &$count ]
}
}
Function [ <internal:pcre> preg_replace_callback_array ] {
- Parameters [4] {
Parameter #0 [ <required> $pattern ]
Parameter #1 [ <required> $subject ]
Parameter #2 [ <optional> $limit ]
Parameter #3 [ <optional> &$count ]
}
}
Function [ <internal:pcre> function preg_filter ] {
- Parameters [5] {