1
+ <?php namespace lang \ast \emit ;
2
+
3
+ use lang \ast \Node ;
4
+ use lang \ast \nodes \{Literal , Variable };
5
+ use lang \ast \types \{IsUnion , IsFunction , IsArray , IsMap , IsNullable , IsValue , IsLiteral };
6
+
7
+ /**
8
+ * PHP 8.1 syntax
9
+ *
10
+ * @see https://wiki.php.net/rfc#php_81
11
+ */
12
+ class PHP81 extends PHP {
13
+ use RewriteBlockLambdaExpressions;
14
+
15
+ private static $ ENUMS ;
16
+
17
+ static function __static () {
18
+ self ::$ ENUMS = class_exists (\ReflectionEnum::class, false ); // TODO remove once enum PR is merged
19
+ }
20
+
21
+ /** Sets up type => literal mappings */
22
+ public function __construct () {
23
+ $ this ->literals = [
24
+ IsArray::class => function ($ t ) { return 'array ' ; },
25
+ IsMap::class => function ($ t ) { return 'array ' ; },
26
+ IsFunction::class => function ($ t ) { return 'callable ' ; },
27
+ IsValue::class => function ($ t ) { return $ t ->literal (); },
28
+ IsNullable::class => function ($ t ) { $ l = $ this ->literal ($ t ->element ); return null === $ l ? null : '? ' .$ l ; },
29
+ IsUnion::class => function ($ t ) {
30
+ $ u = '' ;
31
+ foreach ($ t ->components as $ component ) {
32
+ if (null === ($ l = $ this ->literal ($ component ))) return null ;
33
+ $ u .= '| ' .$ l ;
34
+ }
35
+ return substr ($ u , 1 );
36
+ },
37
+ IsLiteral::class => function ($ t ) { return $ t ->literal (); }
38
+ ];
39
+ }
40
+
41
+ /**
42
+ * Returns whether a given node is a constant expression:
43
+ *
44
+ * - Any literal
45
+ * - Arrays where all members are literals
46
+ * - Scope expressions with literal members (self::class, T::const)
47
+ * - Binary expression where left- and right hand side are literals
48
+ *
49
+ * @see https://wiki.php.net/rfc/const_scalar_exprs
50
+ * @param lang.ast.Result $result
51
+ * @param lang.ast.Node $node
52
+ * @return bool
53
+ */
54
+ protected function isConstant ($ result , $ node ) {
55
+ if ($ node instanceof Literal) {
56
+ return true ;
57
+ } else if ($ node instanceof ArrayLiteral) {
58
+ foreach ($ node ->values as $ node ) {
59
+ if (!$ this ->isConstant ($ result , $ node )) return false ;
60
+ }
61
+ return true ;
62
+ } else if ($ node instanceof ScopeExpression) {
63
+ return (
64
+ $ node ->member instanceof Literal &&
65
+ is_string ($ node ->type ) &&
66
+ !$ result ->lookup ($ node ->type )->rewriteEnumCase ($ node ->member ->expression , self ::$ ENUMS )
67
+ );
68
+ } else if ($ node instanceof BinaryExpression) {
69
+ return $ this ->isConstant ($ result , $ node ->left ) && $ this ->isConstant ($ result , $ node ->right );
70
+ }
71
+ return false ;
72
+ }
73
+
74
+ protected function emitArguments ($ result , $ arguments ) {
75
+ $ i = 0 ;
76
+ foreach ($ arguments as $ name => $ argument ) {
77
+ if ($ i ++) $ result ->out ->write (', ' );
78
+ if (is_string ($ name )) $ result ->out ->write ($ name .': ' );
79
+ $ this ->emitOne ($ result , $ argument );
80
+ }
81
+ }
82
+
83
+ protected function emitScope ($ result , $ scope ) {
84
+ if ($ scope ->type instanceof Variable) {
85
+ $ this ->emitOne ($ result , $ scope ->type );
86
+ $ result ->out ->write (':: ' );
87
+ $ this ->emitOne ($ result , $ scope ->member );
88
+ } else if ($ scope ->type instanceof Node) {
89
+ $ t = $ result ->temp ();
90
+ $ result ->out ->write ('( ' .$ t .'= ' );
91
+ $ this ->emitOne ($ result , $ scope ->type );
92
+ $ result ->out ->write (')? ' .$ t .':: ' );
93
+ $ this ->emitOne ($ result , $ scope ->member );
94
+ $ result ->out ->write (':null ' );
95
+ } else if ($ scope ->member instanceof Literal && $ result ->lookup ($ scope ->type )->rewriteEnumCase ($ scope ->member ->expression , self ::$ ENUMS )) {
96
+ $ result ->out ->write ($ scope ->type .'::$ ' .$ scope ->member ->expression );
97
+ } else {
98
+ $ result ->out ->write ($ scope ->type .':: ' );
99
+ $ this ->emitOne ($ result , $ scope ->member );
100
+ }
101
+ }
102
+
103
+ protected function emitEnumCase ($ result , $ case ) {
104
+ if (self ::$ ENUMS ) {
105
+ $ result ->out ->write ('case ' .$ case ->name );
106
+ if ($ case ->expression ) {
107
+ $ result ->out ->write ('= ' );
108
+ $ this ->emitOne ($ result , $ case ->expression );
109
+ }
110
+ $ result ->out ->write ('; ' );
111
+ } else {
112
+ parent ::emitEnumCase ($ result , $ case );
113
+ }
114
+ }
115
+
116
+ protected function emitEnum ($ result , $ enum ) {
117
+ if (self ::$ ENUMS ) {
118
+ array_unshift ($ result ->type , $ enum );
119
+ array_unshift ($ result ->meta , []);
120
+ $ result ->locals = [[], []];
121
+
122
+ $ result ->out ->write ('enum ' .$ this ->declaration ($ enum ->name ));
123
+ $ enum ->base && $ result ->out ->write (': ' .$ enum ->base );
124
+ $ enum ->implements && $ result ->out ->write (' implements ' .implode (', ' , $ enum ->implements ));
125
+ $ result ->out ->write ('{ ' );
126
+
127
+ foreach ($ enum ->body as $ member ) {
128
+ $ this ->emitOne ($ result , $ member );
129
+ }
130
+
131
+ // Initializations
132
+ $ result ->out ->write ('static function __init() { ' );
133
+ $ this ->emitInitializations ($ result , $ result ->locals [0 ]);
134
+ $ this ->emitMeta ($ result , $ enum ->name , $ enum ->annotations , $ enum ->comment );
135
+ $ result ->out ->write ('}} ' .$ enum ->name .'::__init(); ' );
136
+ array_shift ($ result ->type );
137
+ } else {
138
+ parent ::emitEnum ($ result , $ enum );
139
+ }
140
+ }
141
+
142
+ protected function emitNew ($ result , $ new ) {
143
+ if ($ new ->type instanceof Node) {
144
+ $ result ->out ->write ('new ( ' );
145
+ $ this ->emitOne ($ result , $ new ->type );
146
+ $ result ->out ->write (')( ' );
147
+ } else {
148
+ $ result ->out ->write ('new ' .$ new ->type .'( ' );
149
+ }
150
+
151
+ $ this ->emitArguments ($ result , $ new ->arguments );
152
+ $ result ->out ->write (') ' );
153
+ }
154
+
155
+ protected function emitThrowExpression ($ result , $ throw ) {
156
+ $ result ->out ->write ('throw ' );
157
+ $ this ->emitOne ($ result , $ throw ->expression );
158
+ }
159
+
160
+ protected function emitCatch ($ result , $ catch ) {
161
+ $ capture = $ catch ->variable ? ' $ ' .$ catch ->variable : '' ;
162
+ if (empty ($ catch ->types )) {
163
+ $ result ->out ->write ('catch( \\Throwable ' .$ capture .') { ' );
164
+ } else {
165
+ $ result ->out ->write ('catch( ' .implode ('| ' , $ catch ->types ).$ capture .') { ' );
166
+ }
167
+ $ this ->emitAll ($ result , $ catch ->body );
168
+ $ result ->out ->write ('} ' );
169
+ }
170
+
171
+ protected function emitNullsafeInstance ($ result , $ instance ) {
172
+ $ this ->emitOne ($ result , $ instance ->expression );
173
+ $ result ->out ->write ('?-> ' );
174
+
175
+ if ('literal ' === $ instance ->member ->kind ) {
176
+ $ result ->out ->write ($ instance ->member ->expression );
177
+ } else {
178
+ $ result ->out ->write ('{ ' );
179
+ $ this ->emitOne ($ result , $ instance ->member );
180
+ $ result ->out ->write ('} ' );
181
+ }
182
+ }
183
+
184
+ protected function emitMatch ($ result , $ match ) {
185
+ if (null === $ match ->expression ) {
186
+ $ result ->out ->write ('match (true) { ' );
187
+ } else {
188
+ $ result ->out ->write ('match ( ' );
189
+ $ this ->emitOne ($ result , $ match ->expression );
190
+ $ result ->out ->write (') { ' );
191
+ }
192
+
193
+ foreach ($ match ->cases as $ case ) {
194
+ $ b = 0 ;
195
+ foreach ($ case ->expressions as $ expression ) {
196
+ $ b && $ result ->out ->write (', ' );
197
+ $ this ->emitOne ($ result , $ expression );
198
+ $ b ++;
199
+ }
200
+ $ result ->out ->write ('=> ' );
201
+ $ this ->emitAsExpression ($ result , $ case ->body );
202
+ $ result ->out ->write (', ' );
203
+ }
204
+
205
+ if ($ match ->default ) {
206
+ $ result ->out ->write ('default=> ' );
207
+ $ this ->emitAsExpression ($ result , $ match ->default );
208
+ }
209
+
210
+ $ result ->out ->write ('} ' );
211
+ }
212
+ }
0 commit comments