Add support for "yield from"

This commit is contained in:
Nikita Popov 2015-04-26 11:46:23 +02:00
parent a6d2cd69f8
commit ab80054e97
9 changed files with 1075 additions and 976 deletions

View File

@ -8,6 +8,7 @@
%left T_LOGICAL_AND
%right T_PRINT
%right T_YIELD
%right T_YIELD_FROM
%left '=' T_PLUS_EQUAL T_MINUS_EQUAL T_MUL_EQUAL T_DIV_EQUAL T_CONCAT_EQUAL T_MOD_EQUAL T_AND_EQUAL T_OR_EQUAL T_XOR_EQUAL T_SL_EQUAL T_SR_EQUAL T_POW_EQUAL
%left '?' ':'
%right T_COALESCE
@ -616,6 +617,7 @@ expr:
| '`' backticks_expr '`' { $$ = Expr\ShellExec[$2]; }
| T_PRINT expr { $$ = Expr\Print_[$2]; }
| T_YIELD { $$ = Expr\Yield_[null, null]; }
| T_YIELD_FROM expr { $$ = Expr\YieldFrom[$2]; }
| T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
'{' inner_statement_list '}'
{ $$ = Expr\Closure[[static: false, byRef: $2, params: $4, uses: $6, returnType: $7, stmts: $9]]; }

View File

@ -17,6 +17,7 @@ class Emulative extends \PhpParser\Lexer
const T_POW_EQUAL = 1003;
const T_COALESCE = 1004;
const T_SPACESHIP = 1005;
const T_YIELD_FROM = 1006;
const PHP_7_0 = '7.0.0dev';
const PHP_5_6 = '5.6.0rc1';
@ -53,6 +54,7 @@ class Emulative extends \PhpParser\Lexer
}
$this->tokenMap[self::T_COALESCE] = Parser::T_COALESCE;
$this->tokenMap[self::T_SPACESHIP] = Parser::T_SPACESHIP;
$this->tokenMap[self::T_YIELD_FROM] = Parser::T_YIELD_FROM;
if (version_compare(PHP_VERSION, self::PHP_5_6, '>=')) {
return;
@ -91,6 +93,10 @@ class Emulative extends \PhpParser\Lexer
$code = str_replace('??', '~__EMU__COALESCE__~', $code);
$code = str_replace('<=>', '~__EMU__SPACESHIP__~', $code);
$code = preg_replace_callback('(yield[ \n\r\t]+from)', function($matches) {
// Encoding $0 in order to preserve exact whitespace
return '~__EMU__YIELDFROM__' . bin2hex($matches[0]) . '__~';
}, $code);
if (version_compare(PHP_VERSION, self::PHP_5_6, '>=')) {
return $code;
@ -127,8 +133,9 @@ class Emulative extends \PhpParser\Lexer
if ('BINARY' === $matches[1]) {
// the binary number can either be an integer or a double, so return a LNUMBER
// or DNUMBER respectively
$isInt = is_int(bindec($matches[2]));
$replace = array(
array(is_int(bindec($matches[2])) ? T_LNUMBER : T_DNUMBER, $matches[2], $this->tokens[$i + 1][2])
array($isInt ? T_LNUMBER : T_DNUMBER, $matches[2], $this->tokens[$i + 1][2])
);
} else if ('ELLIPSIS' === $matches[1]) {
$replace = array(
@ -150,9 +157,13 @@ class Emulative extends \PhpParser\Lexer
$replace = array(
array(self::T_SPACESHIP, '<=>', $this->tokens[$i + 1][2]),
);
} else if ('YIELDFROM' === $matches[1]) {
$content = $this->hex2bin($matches[2]);
$replace = array(
array(self::T_YIELD_FROM, $content, $this->tokens[$i + 1][2] - substr_count($content, "\n"))
);
} else {
// just ignore all other __EMU__ sequences
continue;
throw new \RuntimeException('Invalid __EMU__ sequence');
}
array_splice($this->tokens, $i, 3, $replace);
@ -188,11 +199,18 @@ class Emulative extends \PhpParser\Lexer
return '??';
} else if ('SPACESHIP' === $matches[1]) {
return '<=>';
} else if ('YIELDFROM' === $matches[1]) {
return $this->hex2bin($matches[2]);
} else {
return $matches[0];
}
}
private function hex2bin($str) {
// TODO Drop when removing support for PHP 5.3
return pack('H*', $str);
}
public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) {
$token = parent::getNextToken($value, $startAttributes, $endAttributes);
@ -203,11 +221,9 @@ class Emulative extends \PhpParser\Lexer
if (isset($this->newKeywords[strtolower($value)])) {
return $this->newKeywords[strtolower($value)];
}
// keep track of whether we currently are in an object access (after ->)
} elseif (Parser::T_OBJECT_OPERATOR === $token) {
$this->inObjectAccess = true;
} else {
$this->inObjectAccess = false;
// keep track of whether we currently are in an object access (after ->)
$this->inObjectAccess = Parser::T_OBJECT_OPERATOR === $token;
}
return $token;

View File

@ -0,0 +1,26 @@
<?php
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class YieldFrom extends Expr
{
/** @var Expr Expression to yield from */
public $expr;
/**
* Constructs an "yield from" node.
*
* @param Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = array()) {
parent::__construct(null, $attributes);
$this->expr = $expr;
}
public function getSubNodeNames() {
return array('expr');
}
}

File diff suppressed because it is too large Load Diff

View File

@ -312,6 +312,10 @@ class Standard extends PrettyPrinterAbstract
return $this->pPrefixOp('Expr_ErrorSuppress', '@', $node->expr);
}
public function pExpr_YieldFrom(Expr\YieldFrom $node) {
return $this->pPrefixOp('Expr_YieldFrom', 'yield from ', $node->expr);
}
// Casts
public function pExpr_Cast_Int(Cast\Int_ $node) {

View File

@ -66,6 +66,7 @@ abstract class PrettyPrinterAbstract
'Expr_AssignOp_ShiftLeft' => array(160, 1),
'Expr_AssignOp_ShiftRight' => array(160, 1),
'Expr_AssignOp_Pow' => array(160, 1),
'Expr_YieldFrom' => array(165, 1),
'Expr_BinaryOp_LogicalAnd' => array(170, -1),
'Expr_BinaryOp_LogicalXor' => array(180, -1),
'Expr_BinaryOp_LogicalOr' => array(190, -1),

View File

@ -87,6 +87,12 @@ class EmulativeTest extends LexerTest
public function provideTestLexNewFeatures() {
return array(
array('yield from', array(
array(Parser::T_YIELD_FROM, 'yield from'),
)),
array("yield\r\nfrom", array(
array(Parser::T_YIELD_FROM, "yield\r\nfrom"),
)),
array('...', array(
array(Parser::T_ELLIPSIS, '...'),
)),

View File

@ -1,4 +1,4 @@
Generators (yield expression
Generators (yield expression)
-----
<?php
@ -25,6 +25,10 @@ function gen() {
func(yield $foo);
$foo->func(yield $foo);
new Foo(yield $foo);
yield from $foo;
yield from $foo and yield from $bar;
yield from $foo + $bar;
}
-----
array(
@ -226,6 +230,33 @@ array(
)
)
)
15: Expr_YieldFrom(
expr: Expr_Variable(
name: foo
)
)
16: Expr_BinaryOp_LogicalAnd(
left: Expr_YieldFrom(
expr: Expr_Variable(
name: foo
)
)
right: Expr_YieldFrom(
expr: Expr_Variable(
name: bar
)
)
)
17: Expr_YieldFrom(
expr: Expr_BinaryOp_Plus(
left: Expr_Variable(
name: foo
)
right: Expr_Variable(
name: bar
)
)
)
)
)
)

View File

@ -33,6 +33,9 @@ $a ** $b ** $c;
($a ** $b) ** $c;
-1 ** 2;
yield from $a and yield from $b;
yield from ($a and yield from $b);
// The following will currently add unnecessary parentheses, because the pretty printer is not aware that assignment
// and incdec only work on variables.
!$a = $b;
@ -62,6 +65,8 @@ $a + $b++;
$a ** $b ** $c;
($a ** $b) ** $c;
-1 ** 2;
yield from $a and yield from $b;
yield from ($a and yield from $b);
// The following will currently add unnecessary parentheses, because the pretty printer is not aware that assignment
// and incdec only work on variables.
!($a = $b);