Skip to content

Commit d6318ad

Browse files
authored
Merge pull request #114 from xp-framework/feature/callable_syntax
Implement first-class callable syntax
2 parents ad91d48 + cf6f56a commit d6318ad

File tree

11 files changed

+273
-8
lines changed

11 files changed

+273
-8
lines changed

ChangeLog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ XP Compiler ChangeLog
33

44
## ?.?.? / ????-??-??
55

6+
* Merged PR #114: Implements first-class callable syntax: `strlen(...)`
7+
now returns a closure which if invoked with a string argument, returns
8+
its length. Includes support for static and instance methods as well as
9+
indirect references like `$closure(...)` and `self::{$expression}(...)`,
10+
see https://wiki.php.net/rfc/first_class_callable_syntax
11+
612
## 6.6.0 / 2021-07-10
713

814
* Emit null-coalesce operator as `$a ?? $a= expression` instead of as

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"keywords": ["module", "xp"],
88
"require" : {
99
"xp-framework/core": "^10.0 | ^9.0 | ^8.0 | ^7.0",
10-
"xp-framework/ast": "^7.3",
10+
"xp-framework/ast": "^7.4",
1111
"php" : ">=7.0.0"
1212
},
1313
"require-dev" : {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php namespace lang\ast\emit;
2+
3+
use lang\ast\Node;
4+
use lang\ast\nodes\{InstanceExpression, ScopeExpression, Literal};
5+
6+
/**
7+
* Rewrites callable expressions to `Callable::fromClosure()`
8+
*
9+
* @see https://wiki.php.net/rfc/first_class_callable_syntax
10+
*/
11+
trait CallablesAsClosures {
12+
13+
protected function emitCallable($result, $callable) {
14+
$result->out->write('\Closure::fromCallable(');
15+
if ($callable->expression instanceof Literal) {
16+
17+
// Rewrite f() => "f"
18+
$result->out->write('"'.trim($callable->expression->expression, '"\'').'"');
19+
} else if ($callable->expression instanceof InstanceExpression) {
20+
21+
// Rewrite $this->f => [$this, "f"]
22+
$result->out->write('[');
23+
$this->emitOne($result, $callable->expression->expression);
24+
if ($callable->expression->member instanceof Literal) {
25+
$result->out->write(',"'.trim($callable->expression->member, '"\'').'"');
26+
} else {
27+
$result->out->write(',');
28+
$this->emitOne($result, $callable->expression->member);
29+
}
30+
$result->out->write(']');
31+
} else if ($callable->expression instanceof ScopeExpression) {
32+
33+
// Rewrite self::f => ["self", "f"]
34+
$result->out->write('[');
35+
if ($callable->expression->type instanceof Node) {
36+
$this->emitOne($result, $callable->expression->type);
37+
} else {
38+
$result->out->write('"'.$callable->expression->type.'"');
39+
}
40+
if ($callable->expression->member instanceof Literal) {
41+
$result->out->write(',"'.trim($callable->expression->member, '"\'').'"');
42+
} else {
43+
$result->out->write(',');
44+
$this->emitOne($result, $callable->expression->member);
45+
}
46+
$result->out->write(']');
47+
} else {
48+
49+
// Emit other expressions as-is
50+
$this->emitOne($result, $callable->expression);
51+
}
52+
$result->out->write(')');
53+
}
54+
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -527,10 +527,9 @@ protected function emitTrait($result, $trait) {
527527
$this->emitOne($result, $member);
528528
$result->out->write("\n");
529529
}
530+
$result->out->write('}');
530531

531-
$result->out->write('static function __init() {');
532532
$this->emitMeta($result, $trait->name, $trait->annotations, $trait->comment);
533-
$result->out->write('}} '.$trait->name.'::__init();');
534533
}
535534

536535
protected function emitUse($result, $use) {
@@ -946,6 +945,11 @@ protected function emitNewClass($result, $new) {
946945
array_shift($result->type);
947946
}
948947

948+
protected function emitCallable($result, $callable) {
949+
$this->emitOne($result, $callable->expression);
950+
$result->out->write('(...)');
951+
}
952+
949953
protected function emitInvoke($result, $invoke) {
950954
$this->emitOne($result, $invoke->expression);
951955
$result->out->write('(');

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

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

3+
use lang\ast\Node;
4+
use lang\ast\nodes\{InstanceExpression, ScopeExpression, Literal};
35
use lang\ast\types\{IsUnion, IsFunction, IsArray, IsMap, IsNullable, IsValue, IsLiteral};
46

57
/**
@@ -26,4 +28,51 @@ public function __construct() {
2628
},
2729
];
2830
}
31+
32+
protected function emitCallable($result, $callable) {
33+
$t= $result->temp();
34+
$result->out->write('(is_callable('.$t.'=');
35+
if ($callable->expression instanceof Literal) {
36+
37+
// Rewrite f() => "f"
38+
$result->out->write('"'.trim($callable->expression->expression, '"\'').'"');
39+
} else if ($callable->expression instanceof InstanceExpression) {
40+
41+
// Rewrite $this->f => [$this, "f"]
42+
$result->out->write('[');
43+
$this->emitOne($result, $callable->expression->expression);
44+
if ($callable->expression->member instanceof Literal) {
45+
$result->out->write(',"'.trim($callable->expression->member, '"\'').'"');
46+
} else {
47+
$result->out->write(',');
48+
$this->emitOne($result, $callable->expression->member);
49+
}
50+
$result->out->write(']');
51+
} else if ($callable->expression instanceof ScopeExpression) {
52+
53+
// Rewrite self::f => [self::class, "f"]
54+
$result->out->write('[');
55+
if ($callable->expression->type instanceof Node) {
56+
$this->emitOne($result, $callable->expression->type);
57+
} else {
58+
$result->out->write($callable->expression->type.'::class');
59+
}
60+
if ($callable->expression->member instanceof Literal) {
61+
$result->out->write(',"'.trim($callable->expression->member, '"\'').'"');
62+
} else {
63+
$result->out->write(',');
64+
$this->emitOne($result, $callable->expression->member);
65+
}
66+
$result->out->write(']');
67+
} else {
68+
69+
// Emit other expressions as-is
70+
$this->emitOne($result, $callable->expression);
71+
}
72+
73+
// Emit equivalent of Closure::fromCallable() which doesn't exist until PHP 7.1
74+
$a= $result->temp();
75+
$result->out->write(')?function(...'.$a.') use('.$t.') { return '.$t.'(...'.$a.'); }:');
76+
$result->out->write('(function() { throw new \Error("Given argument is not callable"); })())');
77+
}
2978
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* @see https://wiki.php.net/rfc#php_71
99
*/
1010
class PHP71 extends PHP {
11-
use OmitPropertyTypes;
11+
use OmitPropertyTypes, CallablesAsClosures;
1212
use RewriteNullCoalesceAssignment, RewriteLambdaExpressions, RewriteClassOnObjects, RewriteExplicitOctals, RewriteEnums;
1313

1414
/** Sets up type => literal mappings */

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* @see https://wiki.php.net/rfc#php_72
99
*/
1010
class PHP72 extends PHP {
11-
use OmitPropertyTypes;
11+
use OmitPropertyTypes, CallablesAsClosures;
1212
use RewriteNullCoalesceAssignment, RewriteLambdaExpressions, RewriteClassOnObjects, RewriteExplicitOctals, RewriteEnums;
1313

1414
/** Sets up type => literal mappings */

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* @see https://wiki.php.net/rfc#php_74
99
*/
1010
class PHP74 extends PHP {
11-
use RewriteBlockLambdaExpressions, RewriteClassOnObjects, RewriteExplicitOctals, RewriteEnums;
11+
use RewriteBlockLambdaExpressions, RewriteClassOnObjects, RewriteExplicitOctals, RewriteEnums, CallablesAsClosures;
1212

1313
/** Sets up type => literal mappings */
1414
public function __construct() {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* @see https://wiki.php.net/rfc#php_80
1010
*/
1111
class PHP80 extends PHP {
12-
use RewriteBlockLambdaExpressions, RewriteExplicitOctals, RewriteEnums;
12+
use RewriteBlockLambdaExpressions, RewriteExplicitOctals, RewriteEnums, CallablesAsClosures;
1313

1414
/** Sets up type => literal mappings */
1515
public function __construct() {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* @see https://wiki.php.net/rfc#php_81
1010
*/
1111
class PHP81 extends PHP {
12-
use RewriteBlockLambdaExpressions;
12+
use RewriteBlockLambdaExpressions, CallablesAsClosures;
1313

1414
/** Sets up type => literal mappings */
1515
public function __construct() {

0 commit comments

Comments
 (0)