Compile-time metaprogramming
thekid opened this issue · 2 comments
Scope of Change
The XP Compiler will be extended to support compile-time metaprogramming
Rationale
Build libraries like xp-forge/partial
into compiler.
Functionality
<<value>>
class Person {
private int $id;
private string $name;
}
The compiler finds an implementation which was previously registered via Transformations::register()
for class nodes. The function gets executed and transforms the above to the following PHP:
class Person implements \lang\Value {
private $id;
private $name;
public function __construct(int $id, string $name) {
$this->id= $id;
$this->name= $name;
}
public function id() { return $this->id; }
public function name() { return $this->name; }
// toString(), hashCode() and compareTo() omitted for brevity
}
Security considerations
Speed impact
Dependencies
Related documents
https://dzone.com/articles/compile-time-metaprogramming
http://groovy-lang.org/metaprogramming.html#_available_ast_transformations
http://groovy-lang.org/metaprogramming.html#developing-ast-xforms
http://notatube.blogspot.de/2010/12/project-lombok-creating-custom.html
xp-framework/compiler#15
https://wiki.php.net/rfc/parser-extension-api
microsoft/TypeScript#5595
E.g. inside the partial library, using a convenience class instead of assembling the code using AST nodes.
Transformations::register('class', function($class) {
if (array_key_exists('value', $class->value->annotations)) {
$class->value->implements[]= '\lang\Value';
foreach ($class->value->body as $type => $node) {
if ('$' !== $type{0}) continue;
$fields[]= $node->value->name;
}
$args= $init= [];
foreach ($fields as $name) {
$class->value->body[$name.'()']= new Code('public function '.$name.'() {
return $this->'.$name.';
}');
$args[]= '$'.$name;
$init[]= '$this->'.$name.'= $'.$name.';';
}
$class->value->body['__construct()']= new Code('public function __construct('.
implode(', ', $args).') { '.implode('', $init).' }'
);
$class->value->body['toString()']= new Code('public function toString() {
return nameof($this)."@".\util\Objects::stringOf(get_object_vars($this));
}');
$class->value->body['hashCode()']= new Code('public function hashCode() {
return nameof($this);
}');
$class->value->body['compareTo()']= new Code('public function compareTo($value) {
return 1;
}');
}
return $class;
});
$ cat Demo.php
<?php
use util\cmd\Console;
<<value>>
class Demo {
private string $id, $name;
public static function main(array<string> $args) {
Console::writeLine(new self(...$args));
}
}
$ xp Demo 1549 Timm
Demo@[
id => "1549"
name => "Timm"
]
API is now far more concise:
use lang\ast\transform\Transformations;
use lang\ast\nodes\Method;
use lang\ast\nodes\Signature;
use lang\ast\Code;
Transformations::register('class', function($class) {
if ($class->value->annotation('getters')) {
foreach ($class->value->properties() as $property) {
$class->value->inject(new Method(
['public'],
$property->name,
new Signature([], $property->type),
[new Code('return $this->'.$property->name.';')]
));
}
}
yield $class;
});