Skip to content

Commit 0aab0ab

Browse files
committed
Improve function, closure, lambda and method node layouts
1 parent 10c4ef6 commit 0aab0ab

File tree

7 files changed

+199
-131
lines changed

7 files changed

+199
-131
lines changed

ChangeLog.md

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

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

6+
* Improved function, closure, lambda and method node layouts - @thekid
67
* Fixed closures not being able to use by reference - @thekid
78
* Implemented parameter annotations via `$param: inject` - @thekid
89

src/main/php/lang/ast/Emitter.class.php

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ protected function catches($catch) {
121121
}
122122

123123
protected function param($param) {
124+
if (empty($param)) {
125+
throw new \Exception("Parameter borked: ".\util\Objects::stringOf($param));
126+
}
127+
124128
$param[2] && $this->out->write($this->paramType($param[2]).' ');
125129
if ($param[3]) {
126130
$this->out->write('... $'.$param[0]);
@@ -226,54 +230,57 @@ protected function emitArray($node) {
226230
$this->out->write(']');
227231
}
228232

233+
// [$name, $signature, $statements]
229234
protected function emitFunction($node) {
230235
$this->out->write('function '.$node->value[0].'(');
231-
$this->params($node->value[2]);
236+
$this->params($node->value[1][0]);
232237
$this->out->write(')');
233-
if ($t= $this->returnType($node->value[4])) {
238+
if ($t= $this->returnType($node->value[1][1])) {
234239
$this->out->write(':'.$t);
235240
}
236241
$this->out->write('{');
237-
$this->emit($node->value[3]);
242+
$this->emit($node->value[2]);
238243
$this->out->write('}');
239244
}
240245

246+
// [$signature, $use, $statements]
241247
protected function emitClosure($node) {
242248
$this->out->write('function(');
243-
$this->params($node->value[2]);
249+
$this->params($node->value[0][0]);
244250
$this->out->write(')');
245-
if ($t= $this->returnType($node->value[4])) {
251+
if ($t= $this->returnType($node->value[0][1])) {
246252
$this->out->write(':'.$t);
247253
}
248-
if (isset($node->value[5])) {
249-
$this->out->write(' use('.implode(',', $node->value[5]).') ');
254+
if (isset($node->value[1])) {
255+
$this->out->write(' use('.implode(',', $node->value[1]).') ');
250256
}
251257
$this->out->write('{');
252-
$this->emit($node->value[3]);
258+
$this->emit($node->value[2]);
253259
$this->out->write('}');
254260
}
255261

262+
// [$signature, $expression]
256263
protected function emitLambda($node) {
257264
$this->out->write('function(');
258-
$this->params($node->value[2]);
265+
$this->params($node->value[0][0]);
259266
$this->out->write(')');
260-
if ($t= $this->returnType($node->value[4])) {
267+
if ($t= $this->returnType($node->value[0][1])) {
261268
$this->out->write(':'.$t);
262269
}
263270

264271
$capture= [];
265-
foreach ($this->search($node->value[3], 'variable') as $var) {
272+
foreach ($this->search($node->value[1], 'variable') as $var) {
266273
$capture[$var->value]= true;
267274
}
268275
unset($capture['this']);
269-
foreach ($node->value[2] as $param) {
276+
foreach ($node->value[0][0] as $param) {
270277
unset($capture[$param[0]]);
271278
}
272279
$capture && $this->out->write(' use($'.implode(', $', array_keys($capture)).')');
273280

274-
$this->out->write('{');
275-
$this->emit($node->value[3]);
276-
$this->out->write('}');
281+
$this->out->write('{ return ');
282+
$this->emit($node->value[1]);
283+
$this->out->write('; }');
277284
}
278285

279286
protected function emitClass($node) {
@@ -352,32 +359,33 @@ protected function emitProperty($node) {
352359
$this->out->write(';');
353360
}
354361

362+
// [$name, $modifiers, $signature, $annotations, $statements]
355363
protected function emitMethod($node) {
356364
$this->meta[0][self::METHOD][$node->value[0]]= [
357-
DETAIL_RETURNS => $this->name($node->value[4]) ?: 'var',
358-
DETAIL_ANNOTATIONS => isset($node->value[6]['member']) ? $node->value[6]['member'] : [],
359-
DETAIL_TARGET_ANNO => isset($node->value[6]['param']) ? $node->value[6]['param'] : [],
365+
DETAIL_RETURNS => $this->name($node->value[2][1]) ?: 'var',
366+
DETAIL_ANNOTATIONS => isset($node->value[3]['member']) ? $node->value[3]['member'] : [],
367+
DETAIL_TARGET_ANNO => isset($node->value[3]['param']) ? $node->value[3]['param'] : [],
360368
];
361369

362370
$declare= $promote= $params= '';
363-
foreach ($node->value[2] as $param) {
371+
foreach ($node->value[2][0] as $param) {
364372
if (isset($param[4])) {
365373
$declare= $param[4].' $'.$param[0].';';
366374
$promote.= '$this->'.$param[0].'= $'.$param[0].';';
367375
}
368376
}
369377
$this->out->write($declare);
370378
$this->out->write(implode(' ', $node->value[1]).' function '.$node->value[0].'(');
371-
$this->params($node->value[2]);
379+
$this->params($node->value[2][0]);
372380
$this->out->write(')');
373-
if ($t= $this->returnType($node->value[4])) {
381+
if ($t= $this->returnType($node->value[2][1])) {
374382
$this->out->write(':'.$t);
375383
}
376-
if (null === $node->value[3]) {
384+
if (null === $node->value[4]) {
377385
$this->out->write(';');
378386
} else {
379387
$this->out->write(' {'.$promote);
380-
$this->emit($node->value[3]);
388+
$this->emit($node->value[4]);
381389
$this->out->write('}');
382390
}
383391
}

src/main/php/lang/ast/Parse.class.php

Lines changed: 86 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -168,17 +168,19 @@ private function setup() {
168168

169169
// ($a) ==> $a + 1 vs. ($a ?? $b)->invoke();
170170
if (':' === $this->token->value || '==>' === $this->token->value) {
171-
$this->token= $this->advance();
172-
$node= $this->func(null, []);
173-
$this->queue= [$this->token];
174-
$this->token= new Node($this->symbol(';'));
175171
$node->arity= 'lambda';
172+
173+
$this->token= $this->advance();
174+
$signature= $this->signature();
175+
$this->token= $this->advance();
176+
$node->value= [$signature, $this->expression(0)];
176177
} else {
178+
$node->arity= 'braced';
179+
177180
$this->token= $this->advance();
178181
$this->token= $this->expect('(');
179182
$node->value= $this->expression(0);
180183
$this->token= $this->expect(')');
181-
$node->arity= 'braced';
182184
}
183185
return $node;
184186
});
@@ -267,15 +269,56 @@ private function setup() {
267269
// Closure `$a= function() { ... };` vs. declaration `function a() { ... }`;
268270
// the latter explicitely becomes a statement by pushing a semicolon.
269271
if ('(' === $this->token->symbol->id) {
270-
$node= $this->func(null, []);
271272
$node->arity= 'closure';
273+
$signature= $this->signature();
274+
275+
if ('use' === $this->token->value) {
276+
$this->token= $this->advance();
277+
$this->token= $this->advance();
278+
$use= [];
279+
while (')' !== $this->token->symbol->id) {
280+
if ('&' === $this->token->value) {
281+
$this->token= $this->advance();
282+
$use[]= '&$'.$this->token->value;
283+
} else {
284+
$use[]= '$'.$this->token->value;
285+
}
286+
$this->token= $this->advance();
287+
if (')' === $this->token->symbol->id) break;
288+
$this->token= $this->expect(',');
289+
}
290+
$this->token= $this->expect(')');
291+
} else {
292+
$use= null;
293+
}
294+
295+
$this->token= $this->expect('{');
296+
$statements= $this->statements();
297+
$this->token= $this->expect('}');
298+
299+
$node->value= [$signature, $use, $statements];
272300
} else {
301+
$node->arity= 'function';
273302
$name= $this->token->value;
274303
$this->token= $this->advance();
275-
$node= $this->func($name, []);
276-
$node->arity= 'function';
304+
$signature= $this->signature();
305+
306+
if ('==>' === $this->token->value) { // Compact syntax, terminated with ';'
307+
$n= new Node($this->token->symbol);
308+
$this->token= $this->advance();
309+
$n->value= $this->expression(0);
310+
$n->arity= 'return';
311+
$statements= [$n];
312+
$this->token= $this->expect(';');
313+
} else { // Regular function
314+
$this->token= $this->expect('{');
315+
$statements= $this->statements();
316+
$this->token= $this->expect('}');
317+
}
318+
277319
$this->queue= [$this->token];
278320
$this->token= new Node($this->symbol(';'));
321+
$node->value= [$name, $signature, $statements];
279322
}
280323

281324
return $node;
@@ -710,64 +753,20 @@ private function parameters() {
710753
return $parameters;
711754
}
712755

713-
private function func($name, $modifiers) {
714-
$node= new Node($this->token->symbol);
715-
716-
$this->scope= new Scope($this->scope); {
717-
$this->token= $this->expect('(');
718-
$parameters= $this->parameters();
719-
$this->token= $this->expect(')');
756+
private function signature() {
757+
$this->token= $this->expect('(');
758+
$parameters= $this->parameters();
759+
$this->token= $this->expect(')');
720760

721-
if (':' === $this->token->value) {
722-
$this->token= $this->advance();
723-
$return= $this->scope->resolve($this->token->value);
724-
$this->token= $this->advance();
725-
} else {
726-
$return= null;
727-
}
728-
729-
if ('use' === $this->token->value) {
730-
$this->token= $this->advance();
731-
$this->token= $this->advance();
732-
$use= [];
733-
while (')' !== $this->token->symbol->id) {
734-
if ('&' === $this->token->value) {
735-
$this->token= $this->advance();
736-
$use[]= '&$'.$this->token->value;
737-
} else {
738-
$use[]= '$'.$this->token->value;
739-
}
740-
$this->token= $this->advance();
741-
if (')' === $this->token->symbol->id) break;
742-
$this->token= $this->expect(',');
743-
}
744-
$this->token= $this->expect(')');
745-
} else {
746-
$use= null;
747-
}
748-
749-
if ('{' === $this->token->value) {
750-
$this->token= $this->advance();
751-
$statements= $this->statements();
752-
$this->token= $this->expect('}');
753-
} else if (';' === $this->token->value) {
754-
$statements= null;
755-
$this->token= $this->expect(';');
756-
} else if ('==>' === $this->token->value) {
757-
$n= new Node($this->token->symbol);
758-
$this->token= $this->advance();
759-
$n->value= $this->expression(0);
760-
$n->arity= 'return';
761-
$statements= [$n];
762-
$this->token= $this->expect(';');
763-
} else {
764-
$this->token= $this->expect('{ or ==>');
765-
}
761+
if (':' === $this->token->value) {
762+
$this->token= $this->advance();
763+
$return= $this->scope->resolve($this->token->value);
764+
$this->token= $this->advance();
765+
} else {
766+
$return= null;
766767
}
767768

768-
$this->scope= $this->scope->parent;
769-
$node->value= [$name, $modifiers, $parameters, $statements, $return, $use];
770-
return $node;
769+
return [$parameters, $return];
771770
}
772771

773772
private function type($name, $modifiers= []) {
@@ -845,12 +844,33 @@ private function body() {
845844
$this->token= $this->advance();
846845
$this->token= $this->expect(';');
847846
} else if ('function' === $this->token->symbol->id) {
847+
$member= new Node($this->token->symbol);
848+
$member->arity= 'method';
849+
848850
$this->token= $this->advance();
849851
$name= $this->token->value;
850852
$this->token= $this->advance();
851-
$member= $this->func($name, $modifiers);
852-
$member->arity= 'method';
853-
$member->value[]= $annotations;
853+
$signature= $this->signature();
854+
855+
if ('{' === $this->token->value) { // Regular body
856+
$this->token= $this->advance();
857+
$statements= $this->statements();
858+
$this->token= $this->expect('}');
859+
} else if (';' === $this->token->value) { // Abstract or interface method
860+
$statements= null;
861+
$this->token= $this->expect(';');
862+
} else if ('==>' === $this->token->value) { // Compact syntax, terminated with ';'
863+
$n= new Node($this->token->symbol);
864+
$this->token= $this->advance();
865+
$n->value= $this->expression(0);
866+
$n->arity= 'return';
867+
$statements= [$n];
868+
$this->token= $this->expect(';');
869+
} else {
870+
$this->token= $this->expect('{, ; or ==>');
871+
}
872+
873+
$member->value= [$name, $modifiers, $signature, $annotations, $statements];
854874
$body[]= $member;
855875
$modifiers= [];
856876
$annotations= null;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php namespace lang\ast\unittest\parse;
2+
3+
class CompactFunctionsTest extends ParseTest {
4+
5+
#[@test]
6+
public function function_returning_null() {
7+
$this->assertNodes(
8+
[['function' => ['a', [[], null], [['==>' => ['null' => 'null']]]]]],
9+
$this->parse('function a() ==> null;')
10+
);
11+
}
12+
13+
#[@test]
14+
public function short_method() {
15+
$block= [['==>' => ['true' => 'true']]];
16+
$this->assertNodes(
17+
[['class' => ['\\A', [], null, [], [
18+
['function' => ['a', ['public'], [[], null], null, $block]]
19+
], []]]],
20+
$this->parse('class A { public function a() ==> true; }')
21+
);
22+
}
23+
}

0 commit comments

Comments
 (0)