Skip to content

Commit bd57b14

Browse files
authored
Merge pull request #188 from xp-framework/feature/pfa
Implement partial function application
2 parents f6057be + be50429 commit bd57b14

13 files changed

+407
-44
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"require" : {
99
"xp-framework/core": "^12.0 | ^11.6 | ^10.16",
1010
"xp-framework/reflection": "^3.2 | ^2.15",
11-
"xp-framework/ast": "^11.7",
11+
"xp-framework/ast": "^12.0",
1212
"php" : ">=7.4.0"
1313
},
1414
"require-dev" : {

src/main/php/lang/ast/emit/CallablesAsClosures.class.php

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,35 @@
11
<?php namespace lang\ast\emit;
22

33
use lang\ast\Node;
4-
use lang\ast\nodes\{Expression, InstanceExpression, ScopeExpression, Literal};
4+
use lang\ast\nodes\{Expression, InstanceExpression, ScopeExpression, Literal, Placeholder};
55

66
/**
77
* Rewrites callable expressions to `Callable::fromClosure()`
88
*
99
* @see https://www.php.net/manual/de/closure.fromcallable.php
10+
* @see https://wiki.php.net/rfc/clone_with_v2
1011
* @see https://wiki.php.net/rfc/first_class_callable_syntax
12+
* @see https://wiki.php.net/rfc/partial_function_application_v2
1113
*/
1214
trait CallablesAsClosures {
15+
use RewritePartialFunctionApplications { emitCallable as emitPartial; }
1316

1417
private function emitQuoted($result, $node) {
1518
if ($node instanceof Literal) {
1619

17-
// Rewrite f() => "f"
20+
// Rewrite f(...) => "f"
1821
$result->out->write('"'.trim($node, '"\'').'"');
1922
} else if ($node instanceof InstanceExpression) {
2023

21-
// Rewrite $this->f => [$this, "f"]
24+
// Rewrite $this->f(...) => [$this, "f"]
2225
$result->out->write('[');
2326
$this->emitOne($result, $node->expression);
2427
$result->out->write(',');
2528
$this->emitQuoted($result, $node->member);
2629
$result->out->write(']');
2730
} else if ($node instanceof ScopeExpression) {
2831

29-
// Rewrite T::f => [T::class, "f"]
32+
// Rewrite T::f(...) => [T::class, "f"]
3033
$result->out->write('[');
3134
if ($node->type instanceof Node) {
3235
$this->emitOne($result, $node->type);
@@ -38,7 +41,7 @@ private function emitQuoted($result, $node) {
3841
$result->out->write(']');
3942
} else if ($node instanceof Expression) {
4043

41-
// Rewrite T::{<f>} => [T::class, <f>]
44+
// Rewrite T::{<f>}(...) => [T::class, <f>]
4245
$this->emitOne($result, $node->inline);
4346
} else {
4447

@@ -50,6 +53,8 @@ private function emitQuoted($result, $node) {
5053
protected function emitCallable($result, $callable) {
5154
if ($callable->expression instanceof Literal && 'clone' === $callable->expression->expression) {
5255
$result->out->write('fn($o) => clone $o');
56+
} else if ([Placeholder::$VARIADIC] !== $callable->arguments) {
57+
$this->emitPartial($result, $callable);
5358
} else {
5459
$result->out->write('\Closure::fromCallable(');
5560
$this->emitQuoted($result, $callable->expression);

src/main/php/lang/ast/emit/EmulatePipelines.class.php

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php namespace lang\ast\emit;
22

3-
use lang\ast\nodes\{CallableExpression, CallableNewExpression, Literal, Variable};
3+
use lang\ast\nodes\{CallableExpression, CallableNewExpression, Literal, Placeholder, Variable};
44

55
/**
66
* Emulates pipelines / the pipe operator, including a null-safe version.
@@ -14,9 +14,13 @@
1414
* $in |> 'strlen';
1515
* strlen($in);
1616
*
17-
* // Optimize for first-class callables:
17+
* // Optimize for first-class callables with single placeholder argument:
1818
* $in |> strlen(...);
1919
* strlen($in);
20+
*
21+
* // Optimize for partial functions with single placeholder argument:
22+
* $in |> str_replace('test', 'ok', ?);
23+
* strlen('test', 'ok', $in);
2024
* ```
2125
*
2226
* @see https://wiki.php.net/rfc/pipe-operator-v3
@@ -26,15 +30,29 @@
2630
*/
2731
trait EmulatePipelines {
2832

33+
private function passSingle($arguments, $arg) {
34+
$placeholder= -1;
35+
foreach ($arguments as $n => $argument) {
36+
if ($argument instanceof Placeholder) {
37+
if ($placeholder > -1) return null;
38+
$placeholder= $n;
39+
}
40+
}
41+
42+
$r= $arguments;
43+
$r[$placeholder]= $arg;
44+
return $r;
45+
}
46+
2947
protected function emitPipeTarget($result, $target, $arg) {
30-
if ($target instanceof CallableNewExpression) {
31-
$target->type->arguments= [$arg];
48+
if ($target instanceof CallableNewExpression && ($pass= $this->passSingle($target->arguments, $arg))) {
49+
$target->type->arguments= $pass;
3250
$this->emitOne($result, $target->type);
3351
$target->type->arguments= null;
34-
} else if ($target instanceof CallableExpression) {
52+
} else if ($target instanceof CallableExpression && ($pass= $this->passSingle($target->arguments, $arg))) {
3553
$this->emitOne($result, $target->expression);
3654
$result->out->write('(');
37-
$this->emitOne($result, $arg);
55+
$this->emitArguments($result, $pass);
3856
$result->out->write(')');
3957
} else if ($target instanceof Literal) {
4058
$result->out->write(trim($target->expression, '"\''));

src/main/php/lang/ast/emit/PHP81.class.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class PHP81 extends PHP {
2323
EmulatePipelines,
2424
RemoveVoidCasts,
2525
RewriteBlockLambdaExpressions,
26-
RewriteCallableClone,
26+
RewriteCallables,
2727
RewriteCloneWith,
2828
RewriteDynamicClassConstants,
2929
RewriteStaticVariableInitializations,

src/main/php/lang/ast/emit/PHP82.class.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class PHP82 extends PHP {
2323
EmulatePipelines,
2424
RemoveVoidCasts,
2525
RewriteBlockLambdaExpressions,
26-
RewriteCallableClone,
26+
RewriteCallables,
2727
RewriteCloneWith,
2828
RewriteDynamicClassConstants,
2929
RewriteStaticVariableInitializations,

src/main/php/lang/ast/emit/PHP83.class.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@
1919
* @see https://wiki.php.net/rfc#php_83
2020
*/
2121
class PHP83 extends PHP {
22-
use
22+
use
2323
EmulatePipelines,
2424
RemoveVoidCasts,
25-
RewriteCallableClone,
25+
RewriteCallables,
2626
RewriteCloneWith,
2727
RewriteBlockLambdaExpressions,
2828
RewriteProperties

src/main/php/lang/ast/emit/PHP84.class.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* @see https://wiki.php.net/rfc#php_84
2020
*/
2121
class PHP84 extends PHP {
22-
use EmulatePipelines, RemoveVoidCasts, RewriteCallableClone, RewriteCloneWith, RewriteBlockLambdaExpressions;
22+
use EmulatePipelines, RemoveVoidCasts, RewriteCallables, RewriteCloneWith, RewriteBlockLambdaExpressions;
2323

2424
public $targetVersion= 80400;
2525

src/main/php/lang/ast/emit/PHP85.class.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* @see https://wiki.php.net/rfc#php_85
2020
*/
2121
class PHP85 extends PHP {
22-
use RewriteBlockLambdaExpressions, RewriteCallableClone;
22+
use RewriteBlockLambdaExpressions, RewriteCallables;
2323

2424
public $targetVersion= 80500;
2525

src/main/php/lang/ast/emit/RewriteCallableClone.class.php

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php namespace lang\ast\emit;
2+
3+
use lang\ast\nodes\Literal;
4+
5+
/**
6+
* Rewrites `clone(...)` and partial function applications
7+
*
8+
* @see https://wiki.php.net/rfc/clone_with_v2
9+
* @see https://wiki.php.net/rfc/partial_function_application_v2
10+
*/
11+
trait RewriteCallables {
12+
use RewritePartialFunctionApplications { emitCallable as emitPartial; }
13+
14+
protected function emitCallable($result, $callable) {
15+
if ($callable->expression instanceof Literal && 'clone' === $callable->expression->expression) {
16+
$result->out->write('(fn($o) => clone $o)');
17+
} else {
18+
$this->emitPartial($result, $callable);
19+
}
20+
}
21+
}

0 commit comments

Comments
 (0)