TypePHP 编译器 https://swoole.com/aot/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

152 lines
6.6 KiB

<?php
namespace TypePhp\PythonTools;
use RuntimeException;
use Throwable;
use TypePhp\PythonTools\Converter\PythonToTypePhpConverter;
use TypePhp\PythonTools\IdeHelper\HelperRenderer;
use TypePhp\PythonTools\IdeHelper\PhpyModuleScanner;
use TypePhp\PythonTools\IdeHelper\PyObjectHelperRenderer;
final class Command
{
public const GENERATE_HELPER = '--gen-python-helper';
public const CONVERT_SOURCE = '--convert-python-to-php';
/** Return null for a normal compiler invocation, otherwise an exit status. */
public static function execute(array $argv, ?string $workingDirectory = null): ?int
{
$helperIndex = array_search(self::GENERATE_HELPER, $argv, true);
$converterIndex = array_search(self::CONVERT_SOURCE, $argv, true);
if ($helperIndex === false && $converterIndex === false) {
return null;
}
if ($helperIndex !== false && $converterIndex !== false) {
return self::error('Python tool subcommands cannot be combined');
}
try {
if ($helperIndex !== false) {
$root = $workingDirectory ?? getcwd();
if (!is_string($root) || $root === '') {
throw new RuntimeException('Unable to determine the working directory');
}
[$module, $outputDirectory] = self::helperArguments($argv, $helperIndex, $root);
$scanner = new PhpyModuleScanner();
$renderer = new HelperRenderer();
$metadata = $scanner->scan($module);
$relative = str_replace('.', DIRECTORY_SEPARATOR, $module) . '.php';
$file = $outputDirectory . DIRECTORY_SEPARATOR . 'python' . DIRECTORY_SEPARATOR . $relative;
$pyObjectFile = $outputDirectory . DIRECTORY_SEPARATOR . 'PyObject.php';
if (!is_file($pyObjectFile)) {
self::writeFile($pyObjectFile, (new PyObjectHelperRenderer())->render());
fwrite(STDOUT, "Generated PyObject IDE helper: {$pyObjectFile}" . PHP_EOL);
}
$builtins = $module === 'builtins' ? $metadata : $scanner->scan('builtins');
$builtinsFile = $outputDirectory . DIRECTORY_SEPARATOR . 'python.php';
self::writeFile($builtinsFile, $renderer->render($builtins));
fwrite(STDOUT, "Generated Python builtins IDE helper: {$builtinsFile}" . PHP_EOL);
if ($module !== 'builtins') {
self::writeFile($file, $renderer->render($metadata));
fwrite(STDOUT, "Generated Python IDE helper: {$file}" . PHP_EOL);
}
return 0;
}
$file = self::singleArgument($argv, $converterIndex, self::CONVERT_SOURCE, '[your_file.py]');
fwrite(STDOUT, (new PythonToTypePhpConverter())->convertFile($file));
return 0;
} catch (Throwable $exception) {
return self::error($exception->getMessage());
}
}
/** @return array{string, string} */
private static function helperArguments(array $argv, int $optionIndex, string $root): array
{
$module = $argv[$optionIndex + 1] ?? '';
if (!is_string($module) || $module === '' || str_starts_with($module, '-')) {
throw new RuntimeException("Usage: {$argv[0]} " . self::GENERATE_HELPER
. ' [Python Module] [--output-dir <directory>]');
}
$output = null;
for ($index = 1, $count = count($argv); $index < $count; $index++) {
$argument = $argv[$index];
if ($index === $optionIndex || $index === $optionIndex + 1) {
continue;
}
if ($argument === '--output-dir') {
if ($output !== null || !isset($argv[$index + 1]) || $argv[$index + 1] === '') {
throw new RuntimeException('--output-dir requires exactly one directory');
}
$output = $argv[++$index];
continue;
}
if (str_starts_with($argument, '--output-dir=')) {
if ($output !== null) {
throw new RuntimeException('--output-dir may only be specified once');
}
$output = substr($argument, strlen('--output-dir='));
if ($output === '') {
throw new RuntimeException('--output-dir requires exactly one directory');
}
continue;
}
throw new RuntimeException("Unknown argument for " . self::GENERATE_HELPER . ": {$argument}");
}
if ($output === null) {
return [$module, $root . DIRECTORY_SEPARATOR . 'ide-helper'];
}
if (self::isAbsolutePath($output)) {
$absoluteOutput = rtrim($output, '/\\');
return [$module, $absoluteOutput === '' ? DIRECTORY_SEPARATOR : $absoluteOutput];
}
return [$module, $root . DIRECTORY_SEPARATOR . rtrim($output, '/\\')];
}
private static function isAbsolutePath(string $path): bool
{
return str_starts_with($path, '/')
|| str_starts_with($path, '\\')
|| preg_match('/^[A-Za-z]:[\\\\\/]/D', $path) === 1;
}
private static function singleArgument(array $argv, int $optionIndex, string $option, string $placeholder): string
{
$arguments = [];
foreach (array_slice($argv, 1) as $argument) {
if ($argument !== $option) {
$arguments[] = $argument;
}
}
if (count($arguments) !== 1 || $arguments[0] === '' || str_starts_with($arguments[0], '-')) {
throw new RuntimeException("Usage: {$argv[0]} {$option} {$placeholder}");
}
if (!isset($argv[$optionIndex + 1]) || $argv[$optionIndex + 1] !== $arguments[0]) {
throw new RuntimeException("{$option} must be followed by {$placeholder}");
}
return $arguments[0];
}
private static function writeFile(string $file, string $contents): void
{
$directory = dirname($file);
if (!is_dir($directory) && !mkdir($directory, 0777, true) && !is_dir($directory)) {
throw new RuntimeException("Unable to create IDE helper directory: {$directory}");
}
$temporary = $file . '.tmp-' . bin2hex(random_bytes(4));
if (file_put_contents($temporary, $contents) === false || !rename($temporary, $file)) {
@unlink($temporary);
throw new RuntimeException("Unable to write IDE helper: {$file}");
}
}
private static function error(string $message): int
{
fwrite(STDERR, "\033[31mError: {$message}\033[0m" . PHP_EOL);
return 1;
}
}