mirror of
https://github.com/php/php-src.git
synced 2025-08-15 21:48:51 +02:00
RFC: Pipe operator (#17118)
Co-authored-by: Gina Peter Banyard <girgias@php.net> Co-authored-by: Arnaud Le Blanc <arnaud.lb@gmail.com> Co-authored-by: Tim Düsterhus <tim@tideways-gmbh.com>
This commit is contained in:
parent
2036c7158d
commit
1c09c0c832
32 changed files with 760 additions and 1 deletions
1
NEWS
1
NEWS
|
@ -54,6 +54,7 @@ PHP NEWS
|
||||||
released on bailout). (DanielEScherzer and ilutov)
|
released on bailout). (DanielEScherzer and ilutov)
|
||||||
. Fixed AST printing for immediately invoked Closure. (Dmitrii Derepko)
|
. Fixed AST printing for immediately invoked Closure. (Dmitrii Derepko)
|
||||||
. Properly handle __debugInfo() returning an array reference. (nielsdos)
|
. Properly handle __debugInfo() returning an array reference. (nielsdos)
|
||||||
|
. Added the pipe (|>) operator. (crell)
|
||||||
|
|
||||||
- Curl:
|
- Curl:
|
||||||
. Added curl_multi_get_handles(). (timwolla)
|
. Added curl_multi_get_handles(). (timwolla)
|
||||||
|
|
|
@ -144,6 +144,8 @@ PHP 8.5 UPGRADE NOTES
|
||||||
RFC: https://wiki.php.net/rfc/attributes-on-constants
|
RFC: https://wiki.php.net/rfc/attributes-on-constants
|
||||||
. The #[\Deprecated] attribute can now be used on constants.
|
. The #[\Deprecated] attribute can now be used on constants.
|
||||||
RFC: https://wiki.php.net/rfc/attributes-on-constants
|
RFC: https://wiki.php.net/rfc/attributes-on-constants
|
||||||
|
. Added the pipe (|>) operator.
|
||||||
|
RFC: https://wiki.php.net/rfc/pipe-operator-v3
|
||||||
|
|
||||||
- Curl:
|
- Curl:
|
||||||
. Added support for share handles that are persisted across multiple PHP
|
. Added support for share handles that are persisted across multiple PHP
|
||||||
|
@ -476,6 +478,7 @@ PHP 8.5 UPGRADE NOTES
|
||||||
|
|
||||||
- Tokenizer:
|
- Tokenizer:
|
||||||
. T_VOID_CAST.
|
. T_VOID_CAST.
|
||||||
|
. T_PIPE.
|
||||||
|
|
||||||
========================================
|
========================================
|
||||||
11. Changes to INI File Handling
|
11. Changes to INI File Handling
|
||||||
|
|
109
Zend/tests/pipe_operator/ast.phpt
Normal file
109
Zend/tests/pipe_operator/ast.phpt
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
--TEST--
|
||||||
|
A pipe operator displays as a pipe operator when outputting syntax, with correct parens.
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
print "Concat, which binds higher\n";
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert(false && foo() . bar() |> baz() . quux());
|
||||||
|
} catch (AssertionError $e) {
|
||||||
|
echo $e->getMessage(), PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert(false && (foo() . bar()) |> baz() . quux());
|
||||||
|
} catch (AssertionError $e) {
|
||||||
|
echo $e->getMessage(), PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert(false && foo() . (bar() |> baz()) . quux());
|
||||||
|
} catch (AssertionError $e) {
|
||||||
|
echo $e->getMessage(), PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert(false && foo() . bar() |> (baz() . quux()));
|
||||||
|
} catch (AssertionError $e) {
|
||||||
|
echo $e->getMessage(), PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert(false && (foo() . bar() |> baz()) . quux());
|
||||||
|
} catch (AssertionError $e) {
|
||||||
|
echo $e->getMessage(), PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert(false && foo() . (bar() |> baz() . quux()));
|
||||||
|
} catch (AssertionError $e) {
|
||||||
|
echo $e->getMessage(), PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
print "<, which binds lower\n";
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert(false && foo() < bar() |> baz());
|
||||||
|
} catch (AssertionError $e) {
|
||||||
|
echo $e->getMessage(), PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert(false && (foo() < bar()) |> baz());
|
||||||
|
} catch (AssertionError $e) {
|
||||||
|
echo $e->getMessage(), PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert(false && foo() < (bar() |> baz()));
|
||||||
|
} catch (AssertionError $e) {
|
||||||
|
echo $e->getMessage(), PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert(false && foo() |> bar() < baz());
|
||||||
|
} catch (AssertionError $e) {
|
||||||
|
echo $e->getMessage(), PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert(false && (foo() |> bar()) < baz());
|
||||||
|
} catch (AssertionError $e) {
|
||||||
|
echo $e->getMessage(), PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert(false && foo() |> (bar() < baz()));
|
||||||
|
} catch (AssertionError $e) {
|
||||||
|
echo $e->getMessage(), PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print "misc examples\n";
|
||||||
|
|
||||||
|
try {
|
||||||
|
assert(false && foo() |> (bar() |> baz(...)));
|
||||||
|
} catch (AssertionError $e) {
|
||||||
|
echo $e->getMessage(), PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
Concat, which binds higher
|
||||||
|
assert(false && foo() . bar() |> baz() . quux())
|
||||||
|
assert(false && foo() . bar() |> baz() . quux())
|
||||||
|
assert(false && foo() . (bar() |> baz()) . quux())
|
||||||
|
assert(false && foo() . bar() |> baz() . quux())
|
||||||
|
assert(false && (foo() . bar() |> baz()) . quux())
|
||||||
|
assert(false && foo() . (bar() |> baz() . quux()))
|
||||||
|
<, which binds lower
|
||||||
|
assert(false && foo() < bar() |> baz())
|
||||||
|
assert(false && (foo() < bar()) |> baz())
|
||||||
|
assert(false && foo() < bar() |> baz())
|
||||||
|
assert(false && foo() |> bar() < baz())
|
||||||
|
assert(false && foo() |> bar() < baz())
|
||||||
|
assert(false && foo() |> (bar() < baz()))
|
||||||
|
misc examples
|
||||||
|
assert(false && foo() |> (bar() |> baz(...)))
|
37
Zend/tests/pipe_operator/call_by_ref.phpt
Normal file
37
Zend/tests/pipe_operator/call_by_ref.phpt
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
--TEST--
|
||||||
|
Pipe operator rejects by-reference functions.
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function _modify(int &$a): string {
|
||||||
|
$a += 1;
|
||||||
|
return "foo";
|
||||||
|
}
|
||||||
|
|
||||||
|
function _append(array &$a): string {
|
||||||
|
$a['bar'] = 'beep';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple variables
|
||||||
|
try {
|
||||||
|
$a = 5;
|
||||||
|
$res1 = $a |> _modify(...);
|
||||||
|
var_dump($res1);
|
||||||
|
} catch (\Error $e) {
|
||||||
|
echo $e->getMessage(), PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complex variables.
|
||||||
|
try {
|
||||||
|
$a = ['foo' => 'beep'];
|
||||||
|
$res2 = $a |> _append(...);
|
||||||
|
var_dump($res2);
|
||||||
|
} catch (\Error $e) {
|
||||||
|
echo $e->getMessage(), PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECTF--
|
||||||
|
_modify(): Argument #1 ($a) could not be passed by reference
|
||||||
|
_append(): Argument #1 ($a) could not be passed by reference
|
17
Zend/tests/pipe_operator/call_prefer_by_ref.phpt
Normal file
17
Zend/tests/pipe_operator/call_prefer_by_ref.phpt
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
--TEST--
|
||||||
|
Pipe operator accepts prefer-by-reference functions.
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$a = ['hello', 'world'];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$r = $a |> array_multisort(...);
|
||||||
|
var_dump($r);
|
||||||
|
} catch (\Error $e) {
|
||||||
|
echo $e->getMessage(), PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
bool(true)
|
20
Zend/tests/pipe_operator/complex_ordering.phpt
Normal file
20
Zend/tests/pipe_operator/complex_ordering.phpt
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
--TEST--
|
||||||
|
Functions are executed in the expected order
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function foo() { echo __FUNCTION__, PHP_EOL; return 1; }
|
||||||
|
function bar() { echo __FUNCTION__, PHP_EOL; return false; }
|
||||||
|
function baz($in) { echo __FUNCTION__, PHP_EOL; return $in; }
|
||||||
|
function quux($in) { echo __FUNCTION__, PHP_EOL; return $in; }
|
||||||
|
|
||||||
|
$result = foo()
|
||||||
|
|> (bar() ? baz(...) : quux(...))
|
||||||
|
|> var_dump(...);
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
quux
|
||||||
|
int(1)
|
19
Zend/tests/pipe_operator/compound_userland_calls.phpt
Normal file
19
Zend/tests/pipe_operator/compound_userland_calls.phpt
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
--TEST--
|
||||||
|
Pipe operator chains
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function _test1(int $a): int {
|
||||||
|
return $a + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _test2(int $a): int {
|
||||||
|
return $a * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
$res1 = 5 |> '_test1' |> '_test2';
|
||||||
|
|
||||||
|
var_dump($res1);
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
int(12)
|
37
Zend/tests/pipe_operator/exception_interruption.phpt
Normal file
37
Zend/tests/pipe_operator/exception_interruption.phpt
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
--TEST--
|
||||||
|
A pipe interrupted by an exception, to demonstrate correct order of execution.
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function foo() { echo __FUNCTION__, PHP_EOL; return 1; }
|
||||||
|
function bar() { echo __FUNCTION__, PHP_EOL; return false; }
|
||||||
|
function baz($in) { echo __FUNCTION__, PHP_EOL; return $in; }
|
||||||
|
function quux($in) { echo __FUNCTION__, PHP_EOL; throw new \Exception('Oops'); }
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = foo()
|
||||||
|
|> (bar() ? baz(...) : quux(...))
|
||||||
|
|> var_dump(...);
|
||||||
|
}
|
||||||
|
catch (Throwable $e) {
|
||||||
|
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = foo()
|
||||||
|
|> (throw new Exception('Break'))
|
||||||
|
|> (bar() ? baz(...) : quux(...))
|
||||||
|
|> var_dump(...);
|
||||||
|
}
|
||||||
|
catch (Throwable $e) {
|
||||||
|
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECTF--
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
quux
|
||||||
|
Exception: Oops
|
||||||
|
foo
|
||||||
|
Exception: Break
|
15
Zend/tests/pipe_operator/function_not_found.phpt
Normal file
15
Zend/tests/pipe_operator/function_not_found.phpt
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
--TEST--
|
||||||
|
Pipe operator throws normally on missing function
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
try {
|
||||||
|
$res1 = 5 |> '_test';
|
||||||
|
}
|
||||||
|
catch (Throwable $e) {
|
||||||
|
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
Error: Call to undefined function _test()
|
30
Zend/tests/pipe_operator/generators.phpt
Normal file
30
Zend/tests/pipe_operator/generators.phpt
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
--TEST--
|
||||||
|
Generators
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function producer(): \Generator {
|
||||||
|
yield 1;
|
||||||
|
yield 2;
|
||||||
|
yield 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
function map_incr(iterable $it): \Generator {
|
||||||
|
foreach ($it as $val) {
|
||||||
|
yield $val +1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = producer() |> map_incr(...) |> iterator_to_array(...);
|
||||||
|
|
||||||
|
var_dump($result);
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
array(3) {
|
||||||
|
[0]=>
|
||||||
|
int(2)
|
||||||
|
[1]=>
|
||||||
|
int(3)
|
||||||
|
[2]=>
|
||||||
|
int(4)
|
||||||
|
}
|
80
Zend/tests/pipe_operator/mixed_callable_call.phpt
Normal file
80
Zend/tests/pipe_operator/mixed_callable_call.phpt
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
--TEST--
|
||||||
|
Pipe operator handles all callable styles
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function times3(int $x): int
|
||||||
|
{
|
||||||
|
return $x * 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
function times5(int $x): int
|
||||||
|
{
|
||||||
|
return $x * 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Test
|
||||||
|
{
|
||||||
|
public function times7(int $x): int
|
||||||
|
{
|
||||||
|
return $x * 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function times11(int $x): int
|
||||||
|
{
|
||||||
|
return $x * 11;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StaticTest
|
||||||
|
{
|
||||||
|
public static function times13(int $x): int
|
||||||
|
{
|
||||||
|
return $x * 13;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function times17(int $x): int
|
||||||
|
{
|
||||||
|
return $x * 17;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function times19(int $x): int
|
||||||
|
{
|
||||||
|
return $x * 19;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Times23
|
||||||
|
{
|
||||||
|
function __invoke(int $x): int
|
||||||
|
{
|
||||||
|
return $x * 23;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$times29 = function(int $x): int {
|
||||||
|
return $x * 29;
|
||||||
|
};
|
||||||
|
|
||||||
|
function times2(int $x): int {
|
||||||
|
return $x * 2;
|
||||||
|
};
|
||||||
|
|
||||||
|
$test = new Test();
|
||||||
|
|
||||||
|
$res1 = 1
|
||||||
|
|> times3(...)
|
||||||
|
|> 'times5'
|
||||||
|
|> $test->times7(...)
|
||||||
|
|> [$test, 'times11']
|
||||||
|
|> StaticTest::times13(...)
|
||||||
|
|> [StaticTest::class, 'times17']
|
||||||
|
|> new Times23()
|
||||||
|
|> $times29
|
||||||
|
|> fn($x) => times2($x)
|
||||||
|
;
|
||||||
|
|
||||||
|
var_dump($res1);
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
int(340510170)
|
22
Zend/tests/pipe_operator/namespaced_functions.phpt
Normal file
22
Zend/tests/pipe_operator/namespaced_functions.phpt
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
--TEST--
|
||||||
|
Pipe operator handles namespaces
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Beep {
|
||||||
|
function test(int $x) {
|
||||||
|
echo $x, PHP_EOL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Bar {
|
||||||
|
use function \Beep\test;
|
||||||
|
|
||||||
|
5 |> test(...);
|
||||||
|
|
||||||
|
5 |> \Beep\test(...);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
5
|
||||||
|
5
|
89
Zend/tests/pipe_operator/optimizations.phpt
Normal file
89
Zend/tests/pipe_operator/optimizations.phpt
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
--TEST--
|
||||||
|
Pipe operator optimizes away most callables
|
||||||
|
--INI--
|
||||||
|
opcache.enable=1
|
||||||
|
opcache.enable_cli=1
|
||||||
|
opcache.opt_debug_level=0x20000
|
||||||
|
--EXTENSIONS--
|
||||||
|
opcache
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function _test1(int $a): int {
|
||||||
|
return $a + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Other {
|
||||||
|
public function foo(int $a): int {
|
||||||
|
return $a * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function bar(int $a): int {
|
||||||
|
return $a -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$o = new Other();
|
||||||
|
|
||||||
|
$res1 = 5
|
||||||
|
|> _test1(...)
|
||||||
|
|> $o->foo(...)
|
||||||
|
|> Other::bar(...)
|
||||||
|
;
|
||||||
|
|
||||||
|
var_dump($res1);
|
||||||
|
?>
|
||||||
|
--EXPECTF--
|
||||||
|
$_main:
|
||||||
|
; (lines=18, args=0, vars=2, tmps=2)
|
||||||
|
; (after optimizer)
|
||||||
|
; %s:1-27
|
||||||
|
0000 V2 = NEW 0 string("Other")
|
||||||
|
0001 DO_FCALL
|
||||||
|
0002 ASSIGN CV0($o) V2
|
||||||
|
0003 INIT_FCALL 1 %d string("_test1")
|
||||||
|
0004 SEND_VAL int(5) 1
|
||||||
|
0005 T2 = DO_UCALL
|
||||||
|
0006 INIT_METHOD_CALL 1 CV0($o) string("foo")
|
||||||
|
0007 SEND_VAL_EX T2 1
|
||||||
|
0008 V3 = DO_FCALL
|
||||||
|
0009 T2 = QM_ASSIGN V3
|
||||||
|
0010 INIT_STATIC_METHOD_CALL 1 string("Other") string("bar")
|
||||||
|
0011 SEND_VAL T2 1
|
||||||
|
0012 V2 = DO_UCALL
|
||||||
|
0013 ASSIGN CV1($res1) V2
|
||||||
|
0014 INIT_FCALL 1 %d string("var_dump")
|
||||||
|
0015 SEND_VAR CV1($res1) 1
|
||||||
|
0016 DO_ICALL
|
||||||
|
0017 RETURN int(1)
|
||||||
|
LIVE RANGES:
|
||||||
|
2: 0001 - 0002 (new)
|
||||||
|
2: 0010 - 0011 (tmp/var)
|
||||||
|
|
||||||
|
_test1:
|
||||||
|
; (lines=4, args=1, vars=1, tmps=1)
|
||||||
|
; (after optimizer)
|
||||||
|
; %s:3-5
|
||||||
|
0000 CV0($a) = RECV 1
|
||||||
|
0001 T1 = ADD CV0($a) int(1)
|
||||||
|
0002 VERIFY_RETURN_TYPE T1
|
||||||
|
0003 RETURN T1
|
||||||
|
|
||||||
|
Other::foo:
|
||||||
|
; (lines=4, args=1, vars=1, tmps=1)
|
||||||
|
; (after optimizer)
|
||||||
|
; %s:8-10
|
||||||
|
0000 CV0($a) = RECV 1
|
||||||
|
0001 T1 = ADD CV0($a) CV0($a)
|
||||||
|
0002 VERIFY_RETURN_TYPE T1
|
||||||
|
0003 RETURN T1
|
||||||
|
|
||||||
|
Other::bar:
|
||||||
|
; (lines=4, args=1, vars=1, tmps=1)
|
||||||
|
; (after optimizer)
|
||||||
|
; %s:12-14
|
||||||
|
0000 CV0($a) = RECV 1
|
||||||
|
0001 T1 = SUB CV0($a) int(1)
|
||||||
|
0002 VERIFY_RETURN_TYPE T1
|
||||||
|
0003 RETURN T1
|
||||||
|
int(11)
|
15
Zend/tests/pipe_operator/optional_parameters.phpt
Normal file
15
Zend/tests/pipe_operator/optional_parameters.phpt
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
--TEST--
|
||||||
|
Pipe operator accepts optional-parameter functions
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function _test(int $a, int $b = 3) {
|
||||||
|
return $a + $b;
|
||||||
|
}
|
||||||
|
|
||||||
|
$res1 = 5 |> '_test';
|
||||||
|
|
||||||
|
var_dump($res1);
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
int(8)
|
16
Zend/tests/pipe_operator/precedence_addition.phpt
Normal file
16
Zend/tests/pipe_operator/precedence_addition.phpt
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
--TEST--
|
||||||
|
Pipe binds lower than addition
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function _test1(int $a): int {
|
||||||
|
return $a * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should add 5+2 first, then pipe that to _test1.
|
||||||
|
$res1 = 5 + 2 |> '_test1';
|
||||||
|
|
||||||
|
var_dump($res1);
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
int(14)
|
17
Zend/tests/pipe_operator/precedence_coalesce.phpt
Normal file
17
Zend/tests/pipe_operator/precedence_coalesce.phpt
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
--TEST--
|
||||||
|
Pipe binds higher than coalesce
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function get_username(int $a): string {
|
||||||
|
return (string)$a;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = 5
|
||||||
|
|> get_username(...)
|
||||||
|
?? 'default';
|
||||||
|
|
||||||
|
var_dump($user);
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
string(1) "5"
|
16
Zend/tests/pipe_operator/precedence_comparison.phpt
Normal file
16
Zend/tests/pipe_operator/precedence_comparison.phpt
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
--TEST--
|
||||||
|
Pipe binds higher than comparison
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function _test1(int $a): int {
|
||||||
|
return $a * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The pipe should run before the comparison.
|
||||||
|
$res1 = 5 |> _test1(...) == 10 ;
|
||||||
|
var_dump($res1);
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECTF--
|
||||||
|
bool(true)
|
36
Zend/tests/pipe_operator/precedence_ternary.phpt
Normal file
36
Zend/tests/pipe_operator/precedence_ternary.phpt
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
--TEST--
|
||||||
|
Pipe binds higher than ternary
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function _test1(int $a): int {
|
||||||
|
return $a + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _test2(int $a): int {
|
||||||
|
return $a * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _test3(int $a): int {
|
||||||
|
return $a * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
function is_odd(int $a): bool {
|
||||||
|
return (bool)($a % 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
$res1 = 5 |> is_odd(...) ? 'odd' : 'even';
|
||||||
|
var_dump($res1);
|
||||||
|
|
||||||
|
// The pipe binds first, resulting in bool ? int : string, which is well-understood.
|
||||||
|
$x = true;
|
||||||
|
$y = 'beep';
|
||||||
|
$z = 'default';
|
||||||
|
$ret3 = $x ? $y |> strlen(...) : $z;
|
||||||
|
var_dump($ret3);
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
string(3) "odd"
|
||||||
|
int(4)
|
11
Zend/tests/pipe_operator/simple_builtin_call.phpt
Normal file
11
Zend/tests/pipe_operator/simple_builtin_call.phpt
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
--TEST--
|
||||||
|
Pipe operator supports built-in functions
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$res1 = "Hello" |> 'strlen';
|
||||||
|
|
||||||
|
var_dump($res1);
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
int(5)
|
15
Zend/tests/pipe_operator/simple_userland_call.phpt
Normal file
15
Zend/tests/pipe_operator/simple_userland_call.phpt
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
--TEST--
|
||||||
|
Pipe operator supports user-defined functions
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function _test(int $a): int {
|
||||||
|
return $a + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$res1 = 5 |> '_test';
|
||||||
|
|
||||||
|
var_dump($res1);
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
int(6)
|
21
Zend/tests/pipe_operator/too_many_parameters.phpt
Normal file
21
Zend/tests/pipe_operator/too_many_parameters.phpt
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
--TEST--
|
||||||
|
Pipe operator fails on multi-parameter functions
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function _test(int $a, int $b) {
|
||||||
|
return $a + $b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
$res1 = 5 |> '_test';
|
||||||
|
}
|
||||||
|
catch (Throwable $e) {
|
||||||
|
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECTF--
|
||||||
|
ArgumentCountError: Too few arguments to function %s, 1 passed in %s on line %s and exactly 2 expected
|
20
Zend/tests/pipe_operator/type_mismatch.phpt
Normal file
20
Zend/tests/pipe_operator/type_mismatch.phpt
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
--TEST--
|
||||||
|
Pipe operator respects types
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function _test(int $a, int $b) {
|
||||||
|
return $a + $b;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$res1 = "Hello" |> '_test';
|
||||||
|
var_dump($res1);
|
||||||
|
}
|
||||||
|
catch (Throwable $e) {
|
||||||
|
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECTF--
|
||||||
|
TypeError: _test(): Argument #1 ($a) must be of type int, string given, called in %s on line %d
|
21
Zend/tests/pipe_operator/void_return.phpt
Normal file
21
Zend/tests/pipe_operator/void_return.phpt
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
--TEST--
|
||||||
|
Pipe operator fails void return chaining in strict mode
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
function nonReturnFunction($bar): void {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = "Hello World"
|
||||||
|
|> 'nonReturnFunction'
|
||||||
|
|> 'strlen';
|
||||||
|
var_dump($result);
|
||||||
|
}
|
||||||
|
catch (Throwable $e) {
|
||||||
|
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
TypeError: strlen(): Argument #1 ($string) must be of type string, null given
|
21
Zend/tests/pipe_operator/wrapped_chains.phpt
Normal file
21
Zend/tests/pipe_operator/wrapped_chains.phpt
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
--TEST--
|
||||||
|
Pipe operator chains saved as a closure
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
function _test1(int $a): int {
|
||||||
|
return $a + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _test2(int $a): int {
|
||||||
|
return $a * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
$func = fn($x) => $x |> '_test1' |> '_test2';
|
||||||
|
|
||||||
|
$res1 = $func(5);
|
||||||
|
|
||||||
|
var_dump($res1);
|
||||||
|
?>
|
||||||
|
--EXPECT--
|
||||||
|
int(12)
|
|
@ -2518,6 +2518,7 @@ simple_list:
|
||||||
case ZEND_AST_GREATER_EQUAL: BINARY_OP(" >= ", 180, 181, 181);
|
case ZEND_AST_GREATER_EQUAL: BINARY_OP(" >= ", 180, 181, 181);
|
||||||
case ZEND_AST_AND: BINARY_OP(" && ", 130, 130, 131);
|
case ZEND_AST_AND: BINARY_OP(" && ", 130, 130, 131);
|
||||||
case ZEND_AST_OR: BINARY_OP(" || ", 120, 120, 121);
|
case ZEND_AST_OR: BINARY_OP(" || ", 120, 120, 121);
|
||||||
|
case ZEND_AST_PIPE: BINARY_OP(" |> ", 183, 183, 184);
|
||||||
case ZEND_AST_ARRAY_ELEM:
|
case ZEND_AST_ARRAY_ELEM:
|
||||||
if (ast->child[1]) {
|
if (ast->child[1]) {
|
||||||
zend_ast_export_ex(str, ast->child[1], 80, indent);
|
zend_ast_export_ex(str, ast->child[1], 80, indent);
|
||||||
|
|
|
@ -154,6 +154,7 @@ enum _zend_ast_kind {
|
||||||
ZEND_AST_MATCH_ARM,
|
ZEND_AST_MATCH_ARM,
|
||||||
ZEND_AST_NAMED_ARG,
|
ZEND_AST_NAMED_ARG,
|
||||||
ZEND_AST_PARENT_PROPERTY_HOOK_CALL,
|
ZEND_AST_PARENT_PROPERTY_HOOK_CALL,
|
||||||
|
ZEND_AST_PIPE,
|
||||||
|
|
||||||
/* 3 child nodes */
|
/* 3 child nodes */
|
||||||
ZEND_AST_METHOD_CALL = 3 << ZEND_AST_NUM_CHILDREN_SHIFT,
|
ZEND_AST_METHOD_CALL = 3 << ZEND_AST_NUM_CHILDREN_SHIFT,
|
||||||
|
|
|
@ -6427,6 +6427,57 @@ static bool can_match_use_jumptable(zend_ast_list *arms) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void zend_compile_pipe(znode *result, zend_ast *ast)
|
||||||
|
{
|
||||||
|
zend_ast *operand_ast = ast->child[0];
|
||||||
|
zend_ast *callable_ast = ast->child[1];
|
||||||
|
|
||||||
|
/* Compile the left hand side down to a value first. */
|
||||||
|
znode operand_result;
|
||||||
|
zend_compile_expr(&operand_result, operand_ast);
|
||||||
|
|
||||||
|
/* Wrap simple values in a ZEND_QM_ASSIGN opcode to ensure references
|
||||||
|
* always fail. They will already fail in complex cases like arrays,
|
||||||
|
* so those don't need a wrapper. */
|
||||||
|
znode wrapped_operand_result;
|
||||||
|
if (operand_result.op_type & (IS_CV|IS_VAR)) {
|
||||||
|
zend_emit_op_tmp(&wrapped_operand_result, ZEND_QM_ASSIGN, &operand_result, NULL);
|
||||||
|
} else {
|
||||||
|
wrapped_operand_result = operand_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Turn the operand into a function parameter list. */
|
||||||
|
zend_ast *arg_list_ast = zend_ast_create_list(1, ZEND_AST_ARG_LIST, zend_ast_create_znode(&wrapped_operand_result));
|
||||||
|
|
||||||
|
zend_ast *fcall_ast;
|
||||||
|
znode callable_result;
|
||||||
|
|
||||||
|
/* Turn $foo |> bar(...) into bar($foo). */
|
||||||
|
if (callable_ast->kind == ZEND_AST_CALL
|
||||||
|
&& callable_ast->child[1]->kind == ZEND_AST_CALLABLE_CONVERT) {
|
||||||
|
fcall_ast = zend_ast_create(ZEND_AST_CALL,
|
||||||
|
callable_ast->child[0], arg_list_ast);
|
||||||
|
/* Turn $foo |> bar::baz(...) into bar::baz($foo). */
|
||||||
|
} else if (callable_ast->kind == ZEND_AST_STATIC_CALL
|
||||||
|
&& callable_ast->child[2]->kind == ZEND_AST_CALLABLE_CONVERT) {
|
||||||
|
fcall_ast = zend_ast_create(ZEND_AST_STATIC_CALL,
|
||||||
|
callable_ast->child[0], callable_ast->child[1], arg_list_ast);
|
||||||
|
/* Turn $foo |> $bar->baz(...) into $bar->baz($foo). */
|
||||||
|
} else if (callable_ast->kind == ZEND_AST_METHOD_CALL
|
||||||
|
&& callable_ast->child[2]->kind == ZEND_AST_CALLABLE_CONVERT) {
|
||||||
|
fcall_ast = zend_ast_create(ZEND_AST_METHOD_CALL,
|
||||||
|
callable_ast->child[0], callable_ast->child[1], arg_list_ast);
|
||||||
|
/* Turn $foo |> $expr into ($expr)($foo) */
|
||||||
|
} else {
|
||||||
|
zend_compile_expr(&callable_result, callable_ast);
|
||||||
|
callable_ast = zend_ast_create_znode(&callable_result);
|
||||||
|
fcall_ast = zend_ast_create(ZEND_AST_CALL,
|
||||||
|
callable_ast, arg_list_ast);
|
||||||
|
}
|
||||||
|
|
||||||
|
zend_compile_expr(result, fcall_ast);
|
||||||
|
}
|
||||||
|
|
||||||
static void zend_compile_match(znode *result, zend_ast *ast)
|
static void zend_compile_match(znode *result, zend_ast *ast)
|
||||||
{
|
{
|
||||||
zend_ast *expr_ast = ast->child[0];
|
zend_ast *expr_ast = ast->child[0];
|
||||||
|
@ -11769,6 +11820,9 @@ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */
|
||||||
case ZEND_AST_MATCH:
|
case ZEND_AST_MATCH:
|
||||||
zend_compile_match(result, ast);
|
zend_compile_match(result, ast);
|
||||||
return;
|
return;
|
||||||
|
case ZEND_AST_PIPE:
|
||||||
|
zend_compile_pipe(result, ast);
|
||||||
|
return;
|
||||||
default:
|
default:
|
||||||
ZEND_ASSERT(0 /* not supported */);
|
ZEND_ASSERT(0 /* not supported */);
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
|
||||||
%left T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
|
%left T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
|
||||||
%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP
|
%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP
|
||||||
%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL
|
%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL
|
||||||
|
%left T_PIPE
|
||||||
%left '.'
|
%left '.'
|
||||||
%left T_SL T_SR
|
%left T_SL T_SR
|
||||||
%left '+' '-'
|
%left '+' '-'
|
||||||
|
@ -237,6 +238,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
|
||||||
%token T_COALESCE "'??'"
|
%token T_COALESCE "'??'"
|
||||||
%token T_POW "'**'"
|
%token T_POW "'**'"
|
||||||
%token T_POW_EQUAL "'**='"
|
%token T_POW_EQUAL "'**='"
|
||||||
|
%token T_PIPE "'|>'"
|
||||||
/* We need to split the & token in two to avoid a shift/reduce conflict. For T1&$v and T1&T2,
|
/* We need to split the & token in two to avoid a shift/reduce conflict. For T1&$v and T1&T2,
|
||||||
* with only one token lookahead, bison does not know whether to reduce T1 as a complete type,
|
* with only one token lookahead, bison does not know whether to reduce T1 as a complete type,
|
||||||
* or shift to continue parsing an intersection type. */
|
* or shift to continue parsing an intersection type. */
|
||||||
|
@ -1292,6 +1294,8 @@ expr:
|
||||||
{ $$ = zend_ast_create_binary_op(ZEND_IS_EQUAL, $1, $3); }
|
{ $$ = zend_ast_create_binary_op(ZEND_IS_EQUAL, $1, $3); }
|
||||||
| expr T_IS_NOT_EQUAL expr
|
| expr T_IS_NOT_EQUAL expr
|
||||||
{ $$ = zend_ast_create_binary_op(ZEND_IS_NOT_EQUAL, $1, $3); }
|
{ $$ = zend_ast_create_binary_op(ZEND_IS_NOT_EQUAL, $1, $3); }
|
||||||
|
| expr T_PIPE expr
|
||||||
|
{ $$ = zend_ast_create(ZEND_AST_PIPE, $1, $3); }
|
||||||
| expr '<' expr
|
| expr '<' expr
|
||||||
{ $$ = zend_ast_create_binary_op(ZEND_IS_SMALLER, $1, $3); }
|
{ $$ = zend_ast_create_binary_op(ZEND_IS_SMALLER, $1, $3); }
|
||||||
| expr T_IS_SMALLER_OR_EQUAL expr
|
| expr T_IS_SMALLER_OR_EQUAL expr
|
||||||
|
|
|
@ -1861,6 +1861,10 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_
|
||||||
RETURN_TOKEN(T_COALESCE_EQUAL);
|
RETURN_TOKEN(T_COALESCE_EQUAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<ST_IN_SCRIPTING>"|>" {
|
||||||
|
RETURN_TOKEN(T_PIPE);
|
||||||
|
}
|
||||||
|
|
||||||
<ST_IN_SCRIPTING>"||" {
|
<ST_IN_SCRIPTING>"||" {
|
||||||
RETURN_TOKEN(T_BOOLEAN_OR);
|
RETURN_TOKEN(T_BOOLEAN_OR);
|
||||||
}
|
}
|
||||||
|
|
1
ext/tokenizer/tokenizer_data.c
generated
1
ext/tokenizer/tokenizer_data.c
generated
|
@ -173,6 +173,7 @@ char *get_token_type_name(int token_type)
|
||||||
case T_COALESCE: return "T_COALESCE";
|
case T_COALESCE: return "T_COALESCE";
|
||||||
case T_POW: return "T_POW";
|
case T_POW: return "T_POW";
|
||||||
case T_POW_EQUAL: return "T_POW_EQUAL";
|
case T_POW_EQUAL: return "T_POW_EQUAL";
|
||||||
|
case T_PIPE: return "T_PIPE";
|
||||||
case T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG";
|
case T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG";
|
||||||
case T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG";
|
case T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG";
|
||||||
case T_BAD_CHARACTER: return "T_BAD_CHARACTER";
|
case T_BAD_CHARACTER: return "T_BAD_CHARACTER";
|
||||||
|
|
|
@ -742,6 +742,11 @@ const T_POW = UNKNOWN;
|
||||||
* @cvalue T_POW_EQUAL
|
* @cvalue T_POW_EQUAL
|
||||||
*/
|
*/
|
||||||
const T_POW_EQUAL = UNKNOWN;
|
const T_POW_EQUAL = UNKNOWN;
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
* @cvalue T_PIPE
|
||||||
|
*/
|
||||||
|
const T_PIPE = UNKNOWN;
|
||||||
/**
|
/**
|
||||||
* @var int
|
* @var int
|
||||||
* @cvalue T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
|
* @cvalue T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
|
||||||
|
|
3
ext/tokenizer/tokenizer_data_arginfo.h
generated
3
ext/tokenizer/tokenizer_data_arginfo.h
generated
|
@ -1,5 +1,5 @@
|
||||||
/* This is a generated file, edit the .stub.php file instead.
|
/* This is a generated file, edit the .stub.php file instead.
|
||||||
* Stub hash: 19d25d22098f46283b517352cbb302db962b50fd */
|
* Stub hash: c5235344b7c651d27c2c33c90696a418a9c96837 */
|
||||||
|
|
||||||
static void register_tokenizer_data_symbols(int module_number)
|
static void register_tokenizer_data_symbols(int module_number)
|
||||||
{
|
{
|
||||||
|
@ -151,6 +151,7 @@ static void register_tokenizer_data_symbols(int module_number)
|
||||||
REGISTER_LONG_CONSTANT("T_COALESCE", T_COALESCE, CONST_PERSISTENT);
|
REGISTER_LONG_CONSTANT("T_COALESCE", T_COALESCE, CONST_PERSISTENT);
|
||||||
REGISTER_LONG_CONSTANT("T_POW", T_POW, CONST_PERSISTENT);
|
REGISTER_LONG_CONSTANT("T_POW", T_POW, CONST_PERSISTENT);
|
||||||
REGISTER_LONG_CONSTANT("T_POW_EQUAL", T_POW_EQUAL, CONST_PERSISTENT);
|
REGISTER_LONG_CONSTANT("T_POW_EQUAL", T_POW_EQUAL, CONST_PERSISTENT);
|
||||||
|
REGISTER_LONG_CONSTANT("T_PIPE", T_PIPE, CONST_PERSISTENT);
|
||||||
REGISTER_LONG_CONSTANT("T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG", T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, CONST_PERSISTENT);
|
REGISTER_LONG_CONSTANT("T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG", T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, CONST_PERSISTENT);
|
||||||
REGISTER_LONG_CONSTANT("T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG", T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG, CONST_PERSISTENT);
|
REGISTER_LONG_CONSTANT("T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG", T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG, CONST_PERSISTENT);
|
||||||
REGISTER_LONG_CONSTANT("T_BAD_CHARACTER", T_BAD_CHARACTER, CONST_PERSISTENT);
|
REGISTER_LONG_CONSTANT("T_BAD_CHARACTER", T_BAD_CHARACTER, CONST_PERSISTENT);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue