Skip to content

Instantly share code, notes, and snippets.

@thekid
Last active September 26, 2015 20:52
Show Gist options
  • Select an option

  • Save thekid/d87c0db1f5902af26c4f to your computer and use it in GitHub Desktop.

Select an option

Save thekid/d87c0db1f5902af26c4f to your computer and use it in GitHub Desktop.
Convert PHP syntax
<?php
use io\collections\FileCollection;
use io\collections\iterate\FilteredIOCollectionIterator;
use io\collections\iterate\ExtensionEqualsFilter;
use util\cmd\Console;
use io\streams\Streams;
class Convert {
const IMPORTS = "<?=imports; ?>\n";
const HASH = '<?=hash;';
private static $DOTTED= [
'newinstance' => true,
'assertInstanceOf' => true
];
private static function rewriteAnnotations($annotation, $version, &$imports, &$replace) {
$tokens= token_get_all('<?php '.$annotation);
$source= '';
$array= [];
for ($i= 1, $s= sizeof($tokens); $i < $s; $i++) {
$token= $tokens[$i];
// Transform arrays
if (T_ARRAY === $token[0] && '(' === $tokens[$i + 1]) {
array_unshift($array, 0);
$token= '[';
} else if ($array && '(' === $token) {
if (0 === $array[0]) {
$token= '';
}
$array[0]++;
} else if ($array && ')' === $token) {
$array[0]--;
if (0 === $array[0]) {
$token= ']';
array_shift($array);
}
}
// expect('a.b.C') => expect(C::class) + use a\b\C;
else if (T_STRING === $token[0] && 'expect' === $token[1] && T_CONSTANT_ENCAPSED_STRING === $tokens[$i + 2][0]) {
$token= $token[1];
$dotted= $tokens[$i + 2][1];
if (!strstr($dotted, '<')) {
$literal= strtr(substr($dotted, 1, -1), '.', '\\');
$p= strrpos($literal, '\\');
if (0 === $p || false === $p) {
$token.= '('.$literal.'::class';
} else {
if (!isset($imports[$literal])) {
$replace[self::IMPORTS].= 'use '.$literal.";\n";
$imports[$literal]= true;
}
$token.= '('.substr($literal, $p + 1).'::class';
}
$i+= 2;
}
}
$source.= is_array($token) ? $token[1] : $token;
}
return strtr($source, [self::HASH => '#']);
}
private static function rewrite($file, $version) {
$tokens= token_get_all(Streams::readAll($file->getInputStream()));
$source= '';
$array= [];
$imports= [];
$replace= [self::IMPORTS => ''];
$type= false;
for ($i= 0, $s= sizeof($tokens); $i < $s; $i++) {
$token= $tokens[$i];
// Record whether inside type body or outside
if (T_INTERFACE === $token[0] || T_CLASS === $token[0] || T_TRAIT === $token[0]) {
$type= true;
}
// Record imports
else if (T_USE === $token[0] && !$type) {
if (empty($imports)) {
$source.= self::IMPORTS;
}
$import= '';
while (++$i < $s && ';' !== $tokens[$i][0]) {
$import.= $tokens[$i][1];
}
$token= 'use'.$import.';';
$imports[trim($import)]= true;
}
// Transform arrays
else if (T_ARRAY === $token[0] && '(' === $tokens[$i + 1]) {
array_unshift($array, 0);
$token= '[';
} else if ($array && '(' === $token) {
if (0 === $array[0]) {
$token= '';
}
$array[0]++;
} else if ($array && ')' === $token) {
$array[0]--;
if (0 === $array[0]) {
$token= ']';
array_shift($array);
}
}
// Rewrite annotations, too.
else if (T_COMMENT === $token[0] && 0 === strncmp($token[1], '#[@', 3)) {
$annotation= '';
do {
$annotation.= strtr($tokens[$i][1], ['#' => self::HASH]);
} while ($i++ < $s && T_COMMENT === $tokens[$i][0] || T_WHITESPACE === $tokens[$i][0]);
$token= self::rewriteAnnotations($annotation, $version, $imports, $replace);
$i-= 1;
}
// __CLASS__ => self::class
else if ('5.5' === $version && T_CLASS_C === $token[0]) {
$token= 'self::class';
}
// newinstance('a.b.C') => newinstance(C::class) + use a\b\C;
else if ('5.5' === $version && T_STRING === $token[0] && isset(self::$DOTTED[$token[1]]) && T_CONSTANT_ENCAPSED_STRING === $tokens[$i + 2][0]) {
$token= $token[1];
$dotted= $tokens[$i + 2][1];
if (!strstr($dotted, '<')) {
$literal= strtr(substr($dotted, 1, -1), '.', '\\');
$p= strrpos($literal, '\\');
if (0 === $p || false === $p) {
$token.= '('.$literal.'::class';
} else {
if (!isset($imports[$literal])) {
$replace[self::IMPORTS].= 'use '.$literal.";\n";
$imports[$literal]= true;
}
$token.= '('.substr($literal, $p + 1).'::class';
}
$i+= 2;
}
}
$source.= is_array($token) ? $token[1] : $token;
}
$source= strtr($source, $replace);
// Console::writeLine($source);
// return;
with ($file->getOutputStream(), function($out) use($source) {
$out->write($source);
});
return true;
}
public static function main($args) {
$sources= new FilteredIOCollectionIterator(
new FileCollection($args[0]),
new ExtensionEqualsFilter(\xp::CLASS_FILE_EXT),
true
);
Console::writeLine($sources);
$version= isset($args[1]) ? $args[1] : '5.5';
Console::write('[');
foreach ($sources as $file) {
Console::write(self::rewrite($file, $version) ? '.' : 'E');
}
Console::write(']');
}
}
@thekid
Copy link
Author

thekid commented Sep 26, 2015

What it does

Converts the following syntactically:

  • Arrays to short syntax
  • @expect('a.b.C') to @expect(C::class) + a "use" statement if necessary
  • assertInstanceOf() and newinstance() to use ::class in PHP 5.5
  • __CLASS__ to self::class in PHP 5.5

Usage:

$ xp Convert xp/rdbms/src/main/php/ 5.4
$ xp Convert xp/rdbms/src/test/php/ 5.5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment