Skip to content

Commit 42c8060

Browse files
committed
Add emitter for PHP enums
See xp-framework/ast#23
1 parent 6ed6c1c commit 42c8060

File tree

3 files changed

+108
-10
lines changed

3 files changed

+108
-10
lines changed

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.0",
10+
"xp-framework/ast": "dev-feature/php-enums as 7.1.0",
1111
"php" : ">=7.0.0"
1212
},
1313
"require-dev" : {

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

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,26 @@ protected function declaration($name) {
4040
* - Binary expression where left- and right hand side are literals
4141
*
4242
* @see https://wiki.php.net/rfc/const_scalar_exprs
43+
* @param lang.ast.Result $result
4344
* @param lang.ast.Node $node
4445
* @return bool
4546
*/
46-
protected function isConstant($node) {
47+
protected function isConstant($result, $node) {
4748
if ($node instanceof Literal) {
4849
return true;
4950
} else if ($node instanceof ArrayLiteral) {
5051
foreach ($node->values as $node) {
51-
if (!$this->isConstant($node)) return false;
52+
if (!$this->isConstant($result, $node)) return false;
5253
}
5354
return true;
5455
} else if ($node instanceof ScopeExpression) {
55-
return $node->member instanceof Literal;
56+
return (
57+
$node->member instanceof Literal &&
58+
is_string($node->type) &&
59+
'enum' !== $result->lookup($node->type)->kind()
60+
);
5661
} else if ($node instanceof BinaryExpression) {
57-
return $this->isConstant($node->left) && $this->isConstant($node->right);
62+
return $this->isConstant($result, $node->left) && $this->isConstant($result, $node->right);
5863
}
5964
return false;
6065
}
@@ -188,7 +193,7 @@ protected function emitStatic($result, $static) {
188193
foreach ($static->initializations as $variable => $initial) {
189194
$result->out->write('static $'.$variable);
190195
if ($initial) {
191-
if ($this->isConstant($initial)) {
196+
if ($this->isConstant($result, $initial)) {
192197
$result->out->write('=');
193198
$this->emitOne($result, $initial);
194199
} else {
@@ -284,7 +289,7 @@ protected function emitParameter($result, $parameter) {
284289
$result->out->write(($parameter->reference ? '&' : '').'$'.$parameter->name);
285290
}
286291
if ($parameter->default) {
287-
if ($this->isConstant($parameter->default)) {
292+
if ($this->isConstant($result, $parameter->default)) {
288293
$result->out->write('=');
289294
$this->emitOne($result, $parameter->default);
290295
} else {
@@ -349,6 +354,46 @@ protected function emitLambda($result, $lambda) {
349354
$this->emitOne($result, $lambda->body);
350355
}
351356

357+
protected function emitEnumCase($result, $case) {
358+
$result->out->write('public static $'.$case->name.';');
359+
}
360+
361+
protected function emitEnum($result, $enum) {
362+
array_unshift($result->type, $enum);
363+
array_unshift($result->meta, []);
364+
$result->locals= [[], []];
365+
366+
$result->out->write('final class '.$this->declaration($enum->name).' implements \UnitEnum');
367+
$enum->implements && $result->out->write(', '.implode(', ', $enum->implements));
368+
$result->out->write('{');
369+
370+
$cases= [];
371+
foreach ($enum->body as $member) {
372+
if ($member->is('enumcase')) $cases[]= $member;
373+
$this->emitOne($result, $member);
374+
}
375+
376+
// Name and constructor
377+
$result->out->write('public $name; private function __construct($name) { $this->name= $name; }');
378+
379+
// Enum cases
380+
$result->out->write('public static function cases() { return [');
381+
foreach ($cases as $case) {
382+
$result->out->write('self::$'.$case->name.', ');
383+
}
384+
$result->out->write(']; }');
385+
386+
// Initializations
387+
$result->out->write('static function __init() {');
388+
foreach ($cases as $case) {
389+
$result->out->write('self::$'.$case->name.'= new self("'.$case->name.'");');
390+
}
391+
$this->emitInitializations($result, $result->locals[0]);
392+
$this->emitMeta($result, $enum->name, $enum->annotations, $enum->comment);
393+
$result->out->write('}} '.$enum->name.'::__init();');
394+
array_shift($result->type);
395+
}
396+
352397
protected function emitClass($result, $class) {
353398
array_unshift($result->type, $class);
354399
array_unshift($result->meta, []);
@@ -518,7 +563,7 @@ protected function emitProperty($result, $property) {
518563

519564
$result->out->write(implode(' ', $property->modifiers).' '.$this->propertyType($property->type).' $'.$property->name);
520565
if (isset($property->expression)) {
521-
if ($this->isConstant($property->expression)) {
566+
if ($this->isConstant($result, $property->expression)) {
522567
$result->out->write('=');
523568
$this->emitOne($result, $property->expression);
524569
} else if (in_array('static', $property->modifiers)) {
@@ -571,7 +616,7 @@ protected function emitMethod($result, $method) {
571616
];
572617
}
573618

574-
if (isset($param->default) && !$this->isConstant($param->default)) {
619+
if (isset($param->default) && !$this->isConstant($result, $param->default)) {
575620
$meta[DETAIL_TARGET_ANNO][$param->name]['default']= [$param->default];
576621
}
577622
}
@@ -911,7 +956,7 @@ protected function emitScope($result, $scope) {
911956
$result->out->write(')?'.$t.'::');
912957
$this->emitOne($result, $scope->member);
913958
$result->out->write(':null');
914-
} else if ($scope->member instanceof Literal && '$enum' === $result->lookup($scope->type)->kind()) {
959+
} else if ($scope->member instanceof Literal && 'enum' === $result->lookup($scope->type)->kind()) {
915960
$result->out->write($scope->type.'::$'.$scope->member->expression);
916961
} else {
917962
$result->out->write($scope->type.'::');
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php namespace lang\ast\unittest\emit;
2+
3+
use unittest\actions\VerifyThat;
4+
use unittest\{Assert, Action, Test};
5+
6+
#[Action(eval: 'new VerifyThat(fn() => function_exists("enum_exists"))')]
7+
class EnumTest extends EmittingTest {
8+
9+
#[Test]
10+
public function name_property() {
11+
$t= $this->type('enum <T> {
12+
case Hearts;
13+
case Diamonds;
14+
case Clubs;
15+
case Spades;
16+
17+
public static function run() {
18+
return self::Hearts->name;
19+
}
20+
}');
21+
22+
Assert::equals('Hearts', $t->getMethod('run')->invoke(null));
23+
}
24+
25+
#[Test]
26+
public function cases_method() {
27+
$t= $this->type('enum <T> {
28+
case Hearts;
29+
case Diamonds;
30+
case Clubs;
31+
case Spades;
32+
}');
33+
34+
Assert::equals(
35+
['Hearts', 'Diamonds', 'Clubs', 'Spades'],
36+
array_map(function($suit) { return $suit->name; }, $t->getMethod('cases')->invoke(null))
37+
);
38+
}
39+
40+
#[Test]
41+
public function used_as_parameter_default() {
42+
$t= $this->type('enum <T> {
43+
case ASC;
44+
case DESC;
45+
46+
public static function run($order= self::ASC) {
47+
return $order->name;
48+
}
49+
}');
50+
51+
Assert::equals('ASC', $t->getMethod('run')->invoke(null));
52+
}
53+
}

0 commit comments

Comments
 (0)