Skip to content

Commit bbf3c94

Browse files
authored
Merge pull request #56 from xp-framework/feature/clone-with
Implement "clone with"
2 parents aa29d43 + ddc6edd commit bbf3c94

File tree

3 files changed

+78
-41
lines changed

3 files changed

+78
-41
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php namespace lang\ast\nodes;
2+
3+
use lang\ast\Node;
4+
5+
class CloneExpression extends Node {
6+
public $kind= 'clone';
7+
public $arguments;
8+
9+
public function __construct($arguments, $line= -1) {
10+
$this->arguments= $arguments;
11+
$this->line= $line;
12+
}
13+
14+
/** @return iterable */
15+
public function children() {
16+
foreach ($this->arguments as &$expr) {
17+
yield &$expr;
18+
}
19+
}
20+
}

src/main/php/lang/ast/syntax/PHP.class.php

Lines changed: 47 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
CastExpression,
1414
CatchStatement,
1515
ClassDeclaration,
16+
CloneExpression,
1617
ClosureExpression,
1718
Comment,
1819
Constant,
@@ -153,18 +154,8 @@ public function __construct() {
153154
if ('(' === $parse->token->value) {
154155
$parse->expecting('(', 'invoke expression');
155156

156-
// Resolve ambiguity by looking ahead: `func(...)` which is a first-class
157-
// callable reference vs. `func(...$it)` - a call with an unpacked argument
158-
if ('...' === $parse->token->value) {
159-
$dots= $parse->token;
160-
$parse->forward();
161-
if (')' === $parse->token->value) {
162-
$parse->forward();
163-
return new CallableExpression(new ScopeExpression($scope, $expr, $token->line), $left->line);
164-
}
165-
166-
array_unshift($parse->queue, $parse->token);
167-
$parse->token= $dots;
157+
if ($this->callable($parse)) {
158+
return new CallableExpression(new ScopeExpression($scope, $expr, $token->line), $token->line);
168159
}
169160

170161
$arguments= $this->arguments($parse);
@@ -189,16 +180,8 @@ public function __construct() {
189180

190181
// Resolve ambiguity by looking ahead: `func(...)` which is a first-class
191182
// callable reference vs. `func(...$it)` - a call with an unpacked argument
192-
if ('...' === $parse->token->value) {
193-
$dots= $parse->token;
194-
$parse->forward();
195-
if (')' === $parse->token->value) {
196-
$parse->forward();
197-
return new CallableExpression($left, $left->line);
198-
}
199-
200-
array_unshift($parse->queue, $parse->token);
201-
$parse->token= $dots;
183+
if ($this->callable($parse)) {
184+
return new CallableExpression($left, $token->line);
202185
}
203186

204187
$arguments= $this->arguments($parse);
@@ -233,7 +216,6 @@ public function __construct() {
233216
$this->prefix('-', 90);
234217
$this->prefix('++', 90);
235218
$this->prefix('--', 90);
236-
$this->prefix('clone', 90);
237219

238220
$this->assignment('=');
239221
$this->assignment('&=');
@@ -294,6 +276,24 @@ public function __construct() {
294276
}
295277
});
296278

279+
$this->prefix('clone', 90, function($parse, $token) {
280+
281+
// clone $x vs. clone($x) or clone($x, ["id" => 6100])
282+
if ('(' === $parse->token->value) {
283+
$parse->forward();
284+
if ($this->callable($parse)) {
285+
return new CallableExpression(new Literal('clone', $token->line), $token->line);
286+
}
287+
288+
$arguments= $this->arguments($parse);
289+
$parse->expecting(')', 'clone arguments');
290+
} else {
291+
$arguments= [$this->expression($parse, 90)];
292+
}
293+
294+
return new CloneExpression($arguments, $token->line);
295+
});
296+
297297
$this->prefix('{', 0, function($parse, $token) {
298298
$statements= $this->statements($parse);
299299
$parse->expecting('}', 'block');
@@ -339,25 +339,16 @@ public function __construct() {
339339

340340
// Resolve ambiguity by looking ahead: `new T(...)` which is a first-class
341341
// callable reference vs. `new T(...$it)` - a call with an unpacked argument
342-
if ('...' === $parse->token->value) {
343-
$dots= $parse->token;
344-
$parse->forward();
345-
if (')' === $parse->token->value) {
346-
$parse->forward();
347-
348-
if (null === $type) {
349-
$class= $this->class($parse, null);
350-
$class->annotations= $annotations;
351-
$new= new NewClassExpression($class, null, $token->line);
352-
} else {
353-
$new= new NewExpression($type, null, $token->line);
354-
}
355-
356-
return new CallableNewExpression($new, $token->line);
342+
if ($this->callable($parse)) {
343+
if (null === $type) {
344+
$class= $this->class($parse, null);
345+
$class->annotations= $annotations;
346+
$new= new NewClassExpression($class, null, $token->line);
347+
} else {
348+
$new= new NewExpression($type, null, $token->line);
357349
}
358350

359-
array_unshift($parse->queue, $parse->token);
360-
$parse->token= $dots;
351+
return new CallableNewExpression($new, $token->line);
361352
}
362353

363354
$arguments= $this->arguments($parse);
@@ -1725,6 +1716,22 @@ public function class($parse, $name, $comment= null, $modifiers= []) {
17251716
return $decl;
17261717
}
17271718

1719+
public function callable($parse) {
1720+
if ('...' === $parse->token->value) {
1721+
$dots= $parse->token;
1722+
$parse->forward();
1723+
if (')' === $parse->token->value) {
1724+
$parse->forward();
1725+
return true;
1726+
}
1727+
1728+
// Not first-class callable syntax but unpack
1729+
array_unshift($parse->queue, $parse->token);
1730+
$parse->token= $dots;
1731+
}
1732+
return false;
1733+
}
1734+
17281735
public function arguments($parse) {
17291736
$arguments= [];
17301737
while (')' !== $parse->token->value) {

src/test/php/lang/ast/unittest/parse/OperatorTest.class.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
BinaryExpression,
77
Braced,
88
ClassDeclaration,
9+
CloneExpression,
910
InstanceExpression,
1011
InstanceOfExpression,
1112
InvokeExpression,
@@ -115,11 +116,20 @@ public function append_array() {
115116
#[Test]
116117
public function clone_expression() {
117118
$this->assertParsed(
118-
[new UnaryExpression('prefix', new Variable('a', self::LINE), 'clone', self::LINE)],
119+
[new CloneExpression([new Variable('a', self::LINE)], self::LINE)],
119120
'clone $a;'
120121
);
121122
}
122123

124+
#[Test]
125+
public function clone_with() {
126+
$with= [[new Literal('"id"', self::LINE), new Literal('6100', self::LINE)]];
127+
$this->assertParsed(
128+
[new CloneExpression([new Variable('a', self::LINE), new ArrayLiteral($with, self::LINE)], self::LINE)],
129+
'clone($a, ["id" => 6100]);'
130+
);
131+
}
132+
123133
#[Test]
124134
public function error_suppression() {
125135
$this->assertParsed(

0 commit comments

Comments
 (0)