|
|
|
|
@ -1,4 +1,12 @@ |
|
|
|
|
<?php |
|
|
|
|
/** |
|
|
|
|
* This file is part of Swoole-Compiler(AOT). |
|
|
|
|
* |
|
|
|
|
* @link https://www.swoole.com/ |
|
|
|
|
* @contact service@swoole.com |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
declare(strict_types=1); |
|
|
|
|
|
|
|
|
|
namespace PhpAot\Php; |
|
|
|
|
|
|
|
|
|
@ -14,8 +22,11 @@ class Translator extends Preprocessor |
|
|
|
|
use MagicMethodDetector; |
|
|
|
|
|
|
|
|
|
protected string $targetName = 'app'; |
|
|
|
|
|
|
|
|
|
protected bool $verbose = false; |
|
|
|
|
|
|
|
|
|
protected array $phpSrcFiles = []; |
|
|
|
|
|
|
|
|
|
protected array $argInfoHeaderFiles = []; |
|
|
|
|
|
|
|
|
|
protected array $unsupportedFunctions = [ |
|
|
|
|
@ -104,62 +115,246 @@ class Translator extends Preprocessor |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protected function getRegisterClassFunction(string $name): string |
|
|
|
|
public function getRegisterClassFunctionArgs(ClassDef|InterfaceDef $classDef): string |
|
|
|
|
{ |
|
|
|
|
return self::PREFIX.'register_class_'.$name; |
|
|
|
|
return implode(', ', $this->getRegisterClassFunctionCeList($classDef)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protected function getRegisterClassFunctionCeList(ClassDef|InterfaceDef $classDef): array |
|
|
|
|
public function setTargetName(string $name): void |
|
|
|
|
{ |
|
|
|
|
$list = []; |
|
|
|
|
$parentCe = $this->getParentClassCe($classDef); |
|
|
|
|
if ('' !== $parentCe) { |
|
|
|
|
$list = [$parentCe]; |
|
|
|
|
if ($this->climate->arguments->defined('output')) { |
|
|
|
|
$name = $this->climate->arguments->get('output'); |
|
|
|
|
} |
|
|
|
|
// interface 没有 implements |
|
|
|
|
if ($classDef instanceof InterfaceDef) { |
|
|
|
|
$name = str_replace(['-', '*'], '_', $name); |
|
|
|
|
if (!preg_match('/^[a-zA-Z0-9_]+$/', $name)) { |
|
|
|
|
$this->climate->red('The target name must be a valid identifier'); |
|
|
|
|
exit(1); |
|
|
|
|
} |
|
|
|
|
if (in_array($name, Constants::CPP_RESERVED_NAMES)) { |
|
|
|
|
$this->climate->red('The target name must not be a reserved keyword'); |
|
|
|
|
exit(1); |
|
|
|
|
} |
|
|
|
|
$this->targetName = $name; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public function getFiles(string $path): array |
|
|
|
|
{ |
|
|
|
|
$realpath = realpath($path); |
|
|
|
|
if ($realpath === false) { |
|
|
|
|
exit("path not exists: {$path}\n"); |
|
|
|
|
} |
|
|
|
|
$path = $realpath; |
|
|
|
|
|
|
|
|
|
if (is_dir($path)) { |
|
|
|
|
$list = $this->getFilesFromDir($path); |
|
|
|
|
$targetName = basename($path); |
|
|
|
|
$this->setTargetName($targetName); |
|
|
|
|
} else { |
|
|
|
|
$ext = pathinfo($path, PATHINFO_EXTENSION); |
|
|
|
|
if ($ext === 'yml') { |
|
|
|
|
$list = $this->parseProjectYaml($path); |
|
|
|
|
} elseif ($ext === 'php') { |
|
|
|
|
$list = [$path]; |
|
|
|
|
$targetName = FileScanner::getFileName($path); |
|
|
|
|
$this->setTargetName($targetName); |
|
|
|
|
} else { |
|
|
|
|
$this->error('Unsupported file type: ' . $path); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return $list; |
|
|
|
|
} |
|
|
|
|
$implements = $this->getImplementCe($classDef); |
|
|
|
|
|
|
|
|
|
return array_merge($list, $implements); |
|
|
|
|
public function preprocessArgvAdvanced(): void |
|
|
|
|
{ |
|
|
|
|
global $argv; |
|
|
|
|
$processed = [$argv[0]]; |
|
|
|
|
|
|
|
|
|
for ($i = 1; $i < count($argv); $i++) { |
|
|
|
|
$arg = $argv[$i]; |
|
|
|
|
if (preg_match('/^-([a-zA-Z])(.+)$/', $arg, $matches)) { |
|
|
|
|
$option = $matches[1]; |
|
|
|
|
$value = $matches[2]; |
|
|
|
|
$processed[] = "-{$option}"; |
|
|
|
|
$processed[] = $value; |
|
|
|
|
} elseif (preg_match('/^-([a-zA-Z]{2,})$/', $arg, $matches)) { |
|
|
|
|
$options = str_split($matches[1]); |
|
|
|
|
foreach ($options as $opt) { |
|
|
|
|
$processed[] = "-{$opt}"; |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
$processed[] = $arg; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
$argv = $processed; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public function getRegisterClassFunctionArgs(ClassDef|InterfaceDef $classDef): string |
|
|
|
|
public function genExternGlobalVars(string $file): void |
|
|
|
|
{ |
|
|
|
|
return implode(', ', $this->getRegisterClassFunctionCeList($classDef)); |
|
|
|
|
$lines[] = '#include <phpx.h>'; |
|
|
|
|
$lines[] = PHP_EOL; |
|
|
|
|
foreach ($this->globalVars as $name => $type) { |
|
|
|
|
$lines[] = 'extern ' . self::TYPE_VAR . ' ' . $name . ';'; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private function getRegisterClassFunctionArgDef(ClassDef|InterfaceDef $classDef): string |
|
|
|
|
// property offset |
|
|
|
|
foreach ($this->classes as $classDef) { |
|
|
|
|
foreach ($classDef->properties as $propertyDef) { |
|
|
|
|
$lines[] = 'extern uint32_t ' . self::PREFIX . $this->getPropertyOffset($propertyDef->name, $classDef->name, $classDef->namespace) . ';'; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$literalStringsCount = count($this->literalStrings); |
|
|
|
|
$lines[] = 'extern php::Var ' . self::LITERAL_STRINGS . '[' . $literalStringsCount . '];' . PHP_EOL; |
|
|
|
|
|
|
|
|
|
$classEntryCount = count($this->classMap); |
|
|
|
|
$lines[] = 'extern zend_class_entry *' . self::PREFIX . self::CLASS_ENTRY_MAP . '[' . $classEntryCount . '];' . PHP_EOL; |
|
|
|
|
|
|
|
|
|
$code = implode(PHP_EOL, $lines) . PHP_EOL . PHP_EOL; |
|
|
|
|
$this->writeFile($file, $code); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public function genExtension(string $file): void |
|
|
|
|
{ |
|
|
|
|
$depsCeList = $this->getRegisterClassFunctionCeList($classDef); |
|
|
|
|
if (empty($depsCeList)) { |
|
|
|
|
return ''; |
|
|
|
|
if ($this->buildMode == 'bin') { |
|
|
|
|
if (!isset($this->nativeFunctions['main'])) { |
|
|
|
|
$this->climate->red('When the build mode is a binary executable file, the `main()` function must be defined'); |
|
|
|
|
exit(1); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
$this->localHeaders = $this->argInfoHeaderFiles; |
|
|
|
|
$this->genClassCeList(); |
|
|
|
|
$code = $this->render('extension.cc.php'); |
|
|
|
|
$this->writeFile($file, $code); |
|
|
|
|
$this->formatCppCode($file); |
|
|
|
|
$this->localHeaders = []; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return 'zend_class_entry *'.implode(', zend_class_entry *', $depsCeList); |
|
|
|
|
public function hasObjectFileCache(string $cppFile): bool |
|
|
|
|
{ |
|
|
|
|
if (!$this->enableCache or $this->climate->arguments->defined('force')) { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
$objectFile = $this->getObjectFile($cppFile); |
|
|
|
|
if (file_exists($objectFile) and filemtime($objectFile) > filemtime($cppFile)) { |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protected function getClassCe(ClassLikeDef $classDef): string |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public function compileFile(string $cppFile, string $objectFile): void |
|
|
|
|
{ |
|
|
|
|
return self::PREFIX.'class_entry_'.$classDef->getNamespacedName(); |
|
|
|
|
if ($this->hasObjectFileCache($cppFile)) { |
|
|
|
|
$this->climate->darkGray('skip: ' . $cppFile . ', cache exists'); |
|
|
|
|
|
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
$cmd = $this->cppCompiler . ' -c ' . $cppFile . ' -o ' . $objectFile; |
|
|
|
|
$this->addCompilationOption($cmd, false); |
|
|
|
|
$this->climate->comment($cmd); |
|
|
|
|
shell_exec($cmd); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public function setTargetName(string $name): void |
|
|
|
|
public function build(array $objectFiles): void |
|
|
|
|
{ |
|
|
|
|
if ($this->climate->arguments->defined('output')) { |
|
|
|
|
$name = $this->climate->arguments->get('output'); |
|
|
|
|
$objectList = implode(' ', $objectFiles); |
|
|
|
|
$targetFile = $this->targetName; |
|
|
|
|
if ($this->buildMode == 'ext' and !str_ends_with($targetFile, '.so')) { |
|
|
|
|
$targetFile .= '.so'; |
|
|
|
|
} |
|
|
|
|
$name = str_replace(['-', '*'], '_', $name); |
|
|
|
|
if (!preg_match('/^[a-zA-Z0-9_]+$/', $name)) { |
|
|
|
|
$this->climate->red('The target name must be a valid identifier'); |
|
|
|
|
exit(1); |
|
|
|
|
$linkCmd = $this->cppCompiler . ' ' . $objectList . ' -o ' . $targetFile; |
|
|
|
|
$this->addCompilationOption($linkCmd, true); |
|
|
|
|
$this->climate->comment($linkCmd); |
|
|
|
|
shell_exec($linkCmd); |
|
|
|
|
} |
|
|
|
|
if (in_array($name, Constants::CPP_RESERVED_NAMES)) { |
|
|
|
|
$this->climate->red('The target name must not be a reserved keyword'); |
|
|
|
|
exit(1); |
|
|
|
|
|
|
|
|
|
public function genFunctionDeclaration(string $file): void |
|
|
|
|
{ |
|
|
|
|
$code = '#include <phpx.h>' . PHP_EOL; |
|
|
|
|
/** |
|
|
|
|
* @var FunctionDef $func |
|
|
|
|
*/ |
|
|
|
|
foreach ($this->nativeFunctions as $name => $func) { |
|
|
|
|
$code .= 'extern ' . $func->returnType . ' ' . self::PREFIX . $name . '('; |
|
|
|
|
$argInfoList = $func->argInfoList; |
|
|
|
|
if ($argInfoList) { |
|
|
|
|
$list = []; |
|
|
|
|
if ($func->method) { |
|
|
|
|
$list[] = 'php::Object &this_'; |
|
|
|
|
} |
|
|
|
|
$this->targetName = $name; |
|
|
|
|
foreach ($argInfoList as $argInfo) { |
|
|
|
|
$arg = $argInfo->type . ' ' . $argInfo->name; |
|
|
|
|
if ($argInfo->default) { |
|
|
|
|
$arg .= ' = ' . $argInfo->default; |
|
|
|
|
} |
|
|
|
|
$list[] = $arg; |
|
|
|
|
} |
|
|
|
|
$code .= implode(', ', $list); |
|
|
|
|
} |
|
|
|
|
$code .= ');' . PHP_EOL; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$code .= PHP_EOL; |
|
|
|
|
foreach ($this->nativeConstants as $name => $constant) { |
|
|
|
|
$code .= 'extern ' . $constant->type . ' ' . $name . ';' . PHP_EOL; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$code .= 'extern zend_class_entry *php_get_class_entry(int class_id, const char *class_name);' . PHP_EOL; |
|
|
|
|
|
|
|
|
|
$this->writeFile($file, $code); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public function getBuildMode(): string |
|
|
|
|
{ |
|
|
|
|
return $this->buildMode; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public function getArgInfoHeaderFile(string $stubFilenameWithoutExtension, bool $relative = false): string |
|
|
|
|
{ |
|
|
|
|
$basename = self::PREFIX . basename($stubFilenameWithoutExtension); |
|
|
|
|
$absPath = $this->getIncludeDir() . "/{$basename}_arginfo.h"; |
|
|
|
|
if ($relative) { |
|
|
|
|
return ltrim($this->removeCommonPrefix($this->getIncludeDir(), $absPath), '/'); |
|
|
|
|
} |
|
|
|
|
return $absPath; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public function genIncludeHeaderFiles(): string |
|
|
|
|
{ |
|
|
|
|
$headers = array_merge($this->globalHeaders, $this->localHeaders); |
|
|
|
|
$lines = []; |
|
|
|
|
foreach ($headers as $header) { |
|
|
|
|
$lines[] = '#include <' . $header . '>'; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return implode(PHP_EOL, $lines) . PHP_EOL . PHP_EOL; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protected function getRegisterClassFunction(string $name): string |
|
|
|
|
{ |
|
|
|
|
return self::PREFIX . 'register_class_' . $name; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protected function getRegisterClassFunctionCeList(ClassDef|InterfaceDef $classDef): array |
|
|
|
|
{ |
|
|
|
|
$list = []; |
|
|
|
|
$parentCe = $this->getParentClassCe($classDef); |
|
|
|
|
if ($parentCe !== '') { |
|
|
|
|
$list = [$parentCe]; |
|
|
|
|
} |
|
|
|
|
// interface 没有 implements |
|
|
|
|
if ($classDef instanceof InterfaceDef) { |
|
|
|
|
return $list; |
|
|
|
|
} |
|
|
|
|
$implements = $this->getImplementCe($classDef); |
|
|
|
|
|
|
|
|
|
return array_merge($list, $implements); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protected function getClassCe(ClassLikeDef $classDef): string |
|
|
|
|
{ |
|
|
|
|
return self::PREFIX . 'class_entry_' . $classDef->getNamespacedName(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protected function getFilesFromDir(string $path): array |
|
|
|
|
@ -179,7 +374,7 @@ class Translator extends Preprocessor |
|
|
|
|
$list = []; |
|
|
|
|
foreach ($sources as $src) { |
|
|
|
|
$src = trim($src); |
|
|
|
|
if ('/' != $src[0]) { |
|
|
|
|
if ($src[0] != '/') { |
|
|
|
|
$absPath = $projectDir . '/' . $src; |
|
|
|
|
} else { |
|
|
|
|
$absPath = $src; |
|
|
|
|
@ -219,34 +414,6 @@ class Translator extends Preprocessor |
|
|
|
|
return $list; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public function getFiles(string $path): array |
|
|
|
|
{ |
|
|
|
|
$realpath = realpath($path); |
|
|
|
|
if (false === $realpath) { |
|
|
|
|
exit("path not exists: $path\n"); |
|
|
|
|
} |
|
|
|
|
$path = $realpath; |
|
|
|
|
|
|
|
|
|
if (is_dir($path)) { |
|
|
|
|
$list = $this->getFilesFromDir($path); |
|
|
|
|
$targetName = basename($path); |
|
|
|
|
$this->setTargetName($targetName); |
|
|
|
|
} else { |
|
|
|
|
$ext = pathinfo($path, PATHINFO_EXTENSION); |
|
|
|
|
if ('yml' === $ext) { |
|
|
|
|
$list = $this->parseProjectYaml($path); |
|
|
|
|
} elseif ('php' === $ext) { |
|
|
|
|
$list = [$path]; |
|
|
|
|
$targetName = FileScanner::getFileName($path); |
|
|
|
|
$this->setTargetName($targetName); |
|
|
|
|
} else { |
|
|
|
|
$this->error('Unsupported file type: '.$path); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return $list; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protected function getInternalCeInfo(string $ce): array |
|
|
|
|
{ |
|
|
|
|
return [ |
|
|
|
|
@ -264,16 +431,6 @@ class Translator extends Preprocessor |
|
|
|
|
return self::PREFIX . 'class_entry_' . $classDef->extends; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private function getImplementCe(ClassDef $classDef): array |
|
|
|
|
{ |
|
|
|
|
$list = []; |
|
|
|
|
foreach ($classDef->implements as $interface) { |
|
|
|
|
$list[] = self::PREFIX.'class_entry_'.$interface; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return $list; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protected function doConvert(string $phpCode): string |
|
|
|
|
{ |
|
|
|
|
$this->climate->info('convert: ' . $this->file); |
|
|
|
|
@ -337,63 +494,6 @@ class Translator extends Preprocessor |
|
|
|
|
return $this->genIncludeHeaderFiles() . $cppCode; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public function preprocessArgvAdvanced(): void |
|
|
|
|
{ |
|
|
|
|
global $argv; |
|
|
|
|
$processed = [$argv[0]]; |
|
|
|
|
|
|
|
|
|
for ($i = 1; $i < count($argv); ++$i) { |
|
|
|
|
$arg = $argv[$i]; |
|
|
|
|
if (preg_match('/^-([a-zA-Z])(.+)$/', $arg, $matches)) { |
|
|
|
|
$option = $matches[1]; |
|
|
|
|
$value = $matches[2]; |
|
|
|
|
$processed[] = "-{$option}"; |
|
|
|
|
$processed[] = $value; |
|
|
|
|
} elseif (preg_match('/^-([a-zA-Z]{2,})$/', $arg, $matches)) { |
|
|
|
|
$options = str_split($matches[1]); |
|
|
|
|
foreach ($options as $opt) { |
|
|
|
|
$processed[] = "-{$opt}"; |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
$processed[] = $arg; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
$argv = $processed; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public function genExternGlobalVars(string $file): void |
|
|
|
|
{ |
|
|
|
|
$lines[] = '#include <phpx.h>'; |
|
|
|
|
$lines[] = PHP_EOL; |
|
|
|
|
foreach ($this->globalVars as $name => $type) { |
|
|
|
|
$lines[] = 'extern '.self::TYPE_VAR.' '.$name.';'; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// property offset |
|
|
|
|
foreach ($this->classes as $classDef) { |
|
|
|
|
foreach ($classDef->properties as $propertyDef) { |
|
|
|
|
$lines[] = 'extern uint32_t '.self::PREFIX.$this->getPropertyOffset($propertyDef->name, $classDef->name, $classDef->namespace).';'; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$literalStringsCount = count($this->literalStrings); |
|
|
|
|
$lines[] = 'extern php::Var '.self::LITERAL_STRINGS.'['.$literalStringsCount.'];'.PHP_EOL; |
|
|
|
|
|
|
|
|
|
$classEntryCount = count($this->classMap); |
|
|
|
|
$lines[] = 'extern zend_class_entry *'.self::PREFIX.self::CLASS_ENTRY_MAP.'['.$classEntryCount.'];'.PHP_EOL; |
|
|
|
|
|
|
|
|
|
$code = implode(PHP_EOL, $lines).PHP_EOL.PHP_EOL; |
|
|
|
|
$this->writeFile($file, $code); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private function render(string $template): string |
|
|
|
|
{ |
|
|
|
|
ob_start(); |
|
|
|
|
include __DIR__.'/../template/'.$template; |
|
|
|
|
|
|
|
|
|
return ob_get_clean(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protected function genClassCeList(): void |
|
|
|
|
{ |
|
|
|
|
if (empty($this->interfaces) and empty($this->classes)) { |
|
|
|
|
@ -449,107 +549,16 @@ class Translator extends Preprocessor |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$this->classCeInfo[$ce] = [ |
|
|
|
|
'deps' => $deps, |
|
|
|
|
'func' => $this->getRegisterClassFunction($classDef->getNamespacedName()), |
|
|
|
|
'args' => $this->getRegisterClassFunctionArgs($classDef), |
|
|
|
|
'argDef' => $this->getRegisterClassFunctionArgDef($classDef), |
|
|
|
|
]; |
|
|
|
|
$sorter->add($ce, $deps); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$this->classCeList = $sorter->sort(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public function genExtension(string $file): void |
|
|
|
|
{ |
|
|
|
|
if ('bin' == $this->buildMode) { |
|
|
|
|
if (!isset($this->nativeFunctions['main'])) { |
|
|
|
|
$this->climate->red('When the build mode is a binary executable file, the `main()` function must be defined'); |
|
|
|
|
exit(1); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
$this->localHeaders = $this->argInfoHeaderFiles; |
|
|
|
|
$this->genClassCeList(); |
|
|
|
|
$code = $this->render('extension.cc.php'); |
|
|
|
|
$this->writeFile($file, $code); |
|
|
|
|
$this->formatCppCode($file); |
|
|
|
|
$this->localHeaders = []; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public function hasObjectFileCache(string $cppFile): bool |
|
|
|
|
{ |
|
|
|
|
if (!$this->enableCache or $this->climate->arguments->defined('force')) { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
$objectFile = $this->getObjectFile($cppFile); |
|
|
|
|
if (file_exists($objectFile) and filemtime($objectFile) > filemtime($cppFile)) { |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public function compileFile(string $cppFile, string $objectFile): void |
|
|
|
|
{ |
|
|
|
|
if ($this->hasObjectFileCache($cppFile)) { |
|
|
|
|
$this->climate->darkGray('skip: '.$cppFile.', cache exists'); |
|
|
|
|
|
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
$cmd = $this->cppCompiler.' -c '.$cppFile.' -o '.$objectFile; |
|
|
|
|
$this->addCompilationOption($cmd, false); |
|
|
|
|
$this->climate->comment($cmd); |
|
|
|
|
shell_exec($cmd); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public function build(array $objectFiles): void |
|
|
|
|
{ |
|
|
|
|
$objectList = implode(' ', $objectFiles); |
|
|
|
|
$targetFile = $this->targetName; |
|
|
|
|
if ('ext' == $this->buildMode and !str_ends_with($targetFile, '.so')) { |
|
|
|
|
$targetFile .= '.so'; |
|
|
|
|
} |
|
|
|
|
$linkCmd = $this->cppCompiler.' '.$objectList.' -o '.$targetFile; |
|
|
|
|
$this->addCompilationOption($linkCmd, true); |
|
|
|
|
$this->climate->comment($linkCmd); |
|
|
|
|
shell_exec($linkCmd); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public function genFunctionDeclaration(string $file): void |
|
|
|
|
{ |
|
|
|
|
$code = '#include <phpx.h>'.PHP_EOL; |
|
|
|
|
/** |
|
|
|
|
* @var FunctionDef $func |
|
|
|
|
*/ |
|
|
|
|
foreach ($this->nativeFunctions as $name => $func) { |
|
|
|
|
$code .= 'extern '.$func->returnType.' '.self::PREFIX.$name.'('; |
|
|
|
|
$argInfoList = $func->argInfoList; |
|
|
|
|
if ($argInfoList) { |
|
|
|
|
$list = []; |
|
|
|
|
if ($func->method) { |
|
|
|
|
$list[] = 'php::Object &this_'; |
|
|
|
|
} |
|
|
|
|
foreach ($argInfoList as $argInfo) { |
|
|
|
|
$arg = $argInfo->type.' '.$argInfo->name; |
|
|
|
|
if ($argInfo->default) { |
|
|
|
|
$arg .= ' = '.$argInfo->default; |
|
|
|
|
} |
|
|
|
|
$list[] = $arg; |
|
|
|
|
} |
|
|
|
|
$code .= implode(', ', $list); |
|
|
|
|
} |
|
|
|
|
$code .= ');'.PHP_EOL; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$code .= PHP_EOL; |
|
|
|
|
foreach ($this->nativeConstants as $name => $constant) { |
|
|
|
|
$code .= 'extern '.$constant->type.' '.$name.';'.PHP_EOL; |
|
|
|
|
$this->classCeInfo[$ce] = [ |
|
|
|
|
'deps' => $deps, |
|
|
|
|
'func' => $this->getRegisterClassFunction($classDef->getNamespacedName()), |
|
|
|
|
'args' => $this->getRegisterClassFunctionArgs($classDef), |
|
|
|
|
'argDef' => $this->getRegisterClassFunctionArgDef($classDef), |
|
|
|
|
]; |
|
|
|
|
$sorter->add($ce, $deps); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$code .= 'extern zend_class_entry *php_get_class_entry(int class_id, const char *class_name);'.PHP_EOL; |
|
|
|
|
|
|
|
|
|
$this->writeFile($file, $code); |
|
|
|
|
$this->classCeList = $sorter->sort(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protected function getMethodName(Node\Stmt\ClassMethod $v): string |
|
|
|
|
@ -572,7 +581,7 @@ class Translator extends Preprocessor |
|
|
|
|
if ($this->useCppNamespace) { |
|
|
|
|
$ns = explode('\\', $ns); |
|
|
|
|
$ns = array_filter($ns, function ($v) { |
|
|
|
|
return '' !== $v; |
|
|
|
|
return $v !== ''; |
|
|
|
|
}); |
|
|
|
|
foreach ($ns as $name) { |
|
|
|
|
$code .= 'namespace ' . $name . ' {' . PHP_EOL; |
|
|
|
|
@ -618,7 +627,7 @@ class Translator extends Preprocessor |
|
|
|
|
$stubFilenameWithoutExtension = str_replace(['.stub.php', '.php'], '', $file); |
|
|
|
|
$headerFile = $this->getArgInfoHeaderFile($stubFilenameWithoutExtension, true); |
|
|
|
|
if (!str_contains($output, 'Saved')) { |
|
|
|
|
$this->error("failed to generate arginfo header file: `$headerFile`, output: $output"); |
|
|
|
|
$this->error("failed to generate arginfo header file: `{$headerFile}`, output: {$output}"); |
|
|
|
|
} |
|
|
|
|
$this->argInfoHeaderFiles[] = $headerFile; |
|
|
|
|
} |
|
|
|
|
@ -671,22 +680,6 @@ class Translator extends Preprocessor |
|
|
|
|
return $code; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public function getBuildMode(): string |
|
|
|
|
{ |
|
|
|
|
return $this->buildMode; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public function getArgInfoHeaderFile(string $stubFilenameWithoutExtension, bool $relative = false): string |
|
|
|
|
{ |
|
|
|
|
$basename = self::PREFIX.basename($stubFilenameWithoutExtension); |
|
|
|
|
$absPath = $this->getIncludeDir()."/{$basename}_arginfo.h"; |
|
|
|
|
if ($relative) { |
|
|
|
|
return ltrim($this->removeCommonPrefix($this->getIncludeDir(), $absPath), '/'); |
|
|
|
|
} else { |
|
|
|
|
return $absPath; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protected function genNativeMethod($methodCodes): string |
|
|
|
|
{ |
|
|
|
|
$code = ''; |
|
|
|
|
@ -720,7 +713,7 @@ class Translator extends Preprocessor |
|
|
|
|
$callParams = $functionDef->argInfoList ? rtrim($callParams, ',') : ''; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (self::TYPE_VOID !== $functionDef->returnType) { |
|
|
|
|
if ($functionDef->returnType !== self::TYPE_VOID) { |
|
|
|
|
$cppCode .= $this->getIndent() . 'auto retval = ' . $fn . '(' . $callParams . ');' . PHP_EOL; |
|
|
|
|
$cppCode .= $this->getIndent() . 'php::move(retval, return_value);' . PHP_EOL; |
|
|
|
|
} else { |
|
|
|
|
@ -738,7 +731,7 @@ class Translator extends Preprocessor |
|
|
|
|
$cppCode .= $this->getIndent() . self::TYPE_OBJECT . ' this_(&execute_data->This);' . PHP_EOL; |
|
|
|
|
|
|
|
|
|
foreach ($classDef->properties as $property) { |
|
|
|
|
if (self::TYPE_ARRAY === $property->type and $property->default and $property->default !== self::TYPE_ARRAY.'{}') { |
|
|
|
|
if ($property->type === self::TYPE_ARRAY and $property->default and $property->default !== self::TYPE_ARRAY . '{}') { |
|
|
|
|
$propOffset = self::PREFIX . $this->getPropertyOffset($property->name, $classDef->name, $classDef->namespace); |
|
|
|
|
$cppCode .= $this->getIndent() . 'this_.getPropertyIndirect(' . $propOffset . ') = ' . $property->default . ';' . PHP_EOL; |
|
|
|
|
} |
|
|
|
|
@ -750,16 +743,6 @@ class Translator extends Preprocessor |
|
|
|
|
return $cppCode; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private function genFunctionWrapper(FunctionDef $functionDef): string |
|
|
|
|
{ |
|
|
|
|
$name = $functionDef->name; |
|
|
|
|
$cppCode = 'ZEND_FUNCTION('.$name.'){'.PHP_EOL; |
|
|
|
|
$fn = self::PREFIX.$this->getNativeName($functionDef->name); |
|
|
|
|
$cppCode .= $this->genWrapperFunctionArgs($fn, $functionDef); |
|
|
|
|
|
|
|
|
|
return $cppCode; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protected function getClassRegisterCeFunc(ClassDef|InterfaceDef $classDef): void |
|
|
|
|
{ |
|
|
|
|
$cppCode = ''; |
|
|
|
|
@ -786,100 +769,6 @@ class Translator extends Preprocessor |
|
|
|
|
return $cppCode; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private function genClassNative(): string |
|
|
|
|
{ |
|
|
|
|
$code = 'class '.$this->class.' { '; |
|
|
|
|
|
|
|
|
|
$publicMethods = []; |
|
|
|
|
$protectedMethods = []; |
|
|
|
|
$privateMethods = []; |
|
|
|
|
$publicConstants = []; |
|
|
|
|
$protectedConstants = []; |
|
|
|
|
$privateConstants = []; |
|
|
|
|
$publicProperties = []; |
|
|
|
|
$protectedProperties = []; |
|
|
|
|
$privateProperties = []; |
|
|
|
|
|
|
|
|
|
foreach ($this->classDef->constants as $const) { |
|
|
|
|
if ($const->flags & Modifiers::PUBLIC) { |
|
|
|
|
$publicConstants[] = $const; |
|
|
|
|
} |
|
|
|
|
if ($const->flags & Modifiers::PROTECTED) { |
|
|
|
|
$protectedConstants[] = $const; |
|
|
|
|
} |
|
|
|
|
if ($const->flags & Modifiers::PRIVATE) { |
|
|
|
|
$privateConstants[] = $const; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
foreach ($this->classDef->methods as $method) { |
|
|
|
|
if ($method->flags & Modifiers::PUBLIC) { |
|
|
|
|
$publicMethods[] = $method; |
|
|
|
|
} |
|
|
|
|
if ($method->flags & Modifiers::PROTECTED) { |
|
|
|
|
$protectedMethods[] = $method; |
|
|
|
|
} |
|
|
|
|
if ($method->flags & Modifiers::PRIVATE) { |
|
|
|
|
$privateMethods[] = $method; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
foreach ($this->classDef->properties as $property) { |
|
|
|
|
if ($property->flags & Modifiers::PUBLIC) { |
|
|
|
|
$publicProperties[] = $property; |
|
|
|
|
} |
|
|
|
|
if ($property->flags & Modifiers::PROTECTED) { |
|
|
|
|
$protectedProperties[] = $property; |
|
|
|
|
} |
|
|
|
|
if ($property->flags & Modifiers::PRIVATE) { |
|
|
|
|
$privateProperties[] = $property; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if ($privateConstants) { |
|
|
|
|
$code .= 'private:'.PHP_EOL; |
|
|
|
|
$code .= $this->genClassConstantList($privateConstants); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if ($protectedConstants) { |
|
|
|
|
$code .= 'protected:'.PHP_EOL; |
|
|
|
|
$code .= $this->genClassConstantList($protectedConstants); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if ($publicConstants) { |
|
|
|
|
$code .= 'public:'.PHP_EOL; |
|
|
|
|
$code .= $this->genClassConstantList($publicConstants); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if ($privateProperties) { |
|
|
|
|
$code .= 'private:'.PHP_EOL; |
|
|
|
|
$code .= $this->genClassPropertyList($privateProperties); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if ($protectedProperties) { |
|
|
|
|
$code .= 'protected:'.PHP_EOL; |
|
|
|
|
$code .= $this->genClassPropertyList($protectedProperties); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if ($publicProperties) { |
|
|
|
|
$code .= 'public:'.PHP_EOL; |
|
|
|
|
$code .= $this->genClassPropertyList($publicProperties); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$code .= '};'.PHP_EOL.PHP_EOL; |
|
|
|
|
|
|
|
|
|
return $code; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public function genIncludeHeaderFiles(): string |
|
|
|
|
{ |
|
|
|
|
$headers = array_merge($this->globalHeaders, $this->localHeaders); |
|
|
|
|
$lines = []; |
|
|
|
|
foreach ($headers as $header) { |
|
|
|
|
$lines[] = '#include <'.$header.'>'; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return implode(PHP_EOL, $lines).PHP_EOL.PHP_EOL; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* @param array<ConstantDef> $list |
|
|
|
|
*/ |
|
|
|
|
@ -956,7 +845,7 @@ class Translator extends Preprocessor |
|
|
|
|
foreach ($v->props as $prop) { |
|
|
|
|
$propDef = new PropertyDef($this->parseIdentifier($prop->name), $flags, $type); |
|
|
|
|
if ($prop->default) { |
|
|
|
|
if ('Expr_Array' == $prop->default->getType() and count($prop->default->items) > 0) { |
|
|
|
|
if ($prop->default->getType() == 'Expr_Array' and count($prop->default->items) > 0) { |
|
|
|
|
$this->classDef->requireCtor = true; |
|
|
|
|
$propDef->type = self::TYPE_ARRAY; |
|
|
|
|
} |
|
|
|
|
@ -1017,10 +906,10 @@ class Translator extends Preprocessor |
|
|
|
|
|
|
|
|
|
$code .= 'if (' . $tmpVar . ') {' . PHP_EOL; |
|
|
|
|
|
|
|
|
|
++$this->indentLevel; |
|
|
|
|
$this->indentLevel++; |
|
|
|
|
$code .= $this->getIndent() . $tmpVar . '.exec("rewind");' . PHP_EOL; |
|
|
|
|
$code .= $this->getIndent() . 'for (;' . $tmpVar . '.exec("valid"); ' . $tmpVar . '.exec("next")) {' . PHP_EOL; |
|
|
|
|
++$this->indentLevel; |
|
|
|
|
$this->indentLevel++; |
|
|
|
|
|
|
|
|
|
$valueVar = $this->parseIdentifier($node->valueVar); |
|
|
|
|
$this->checkVar($node, $valueVar); |
|
|
|
|
@ -1033,13 +922,134 @@ class Translator extends Preprocessor |
|
|
|
|
} |
|
|
|
|
$code .= $this->parseStmts($node->stmts); |
|
|
|
|
$code .= '}' . PHP_EOL; |
|
|
|
|
--$this->indentLevel; |
|
|
|
|
$this->indentLevel--; |
|
|
|
|
$code .= $this->getIndent() . '} else {' . PHP_EOL; |
|
|
|
|
$code .= $this->getIndent() . $tmpArrayVar . ' = php::call("get_object_vars", {' . $obj . '});' . PHP_EOL; |
|
|
|
|
$code .= $this->parseForeachArray($node, $tmpArrayVar); |
|
|
|
|
--$this->indentLevel; |
|
|
|
|
$this->indentLevel--; |
|
|
|
|
$code .= '}' . PHP_EOL; |
|
|
|
|
|
|
|
|
|
return $code; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private function getRegisterClassFunctionArgDef(ClassDef|InterfaceDef $classDef): string |
|
|
|
|
{ |
|
|
|
|
$depsCeList = $this->getRegisterClassFunctionCeList($classDef); |
|
|
|
|
if (empty($depsCeList)) { |
|
|
|
|
return ''; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return 'zend_class_entry *' . implode(', zend_class_entry *', $depsCeList); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private function getImplementCe(ClassDef $classDef): array |
|
|
|
|
{ |
|
|
|
|
$list = []; |
|
|
|
|
foreach ($classDef->implements as $interface) { |
|
|
|
|
$list[] = self::PREFIX . 'class_entry_' . $interface; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return $list; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private function render(string $template): string |
|
|
|
|
{ |
|
|
|
|
ob_start(); |
|
|
|
|
include __DIR__ . '/../template/' . $template; |
|
|
|
|
|
|
|
|
|
return ob_get_clean(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private function genFunctionWrapper(FunctionDef $functionDef): string |
|
|
|
|
{ |
|
|
|
|
$name = $functionDef->name; |
|
|
|
|
$cppCode = 'ZEND_FUNCTION(' . $name . '){' . PHP_EOL; |
|
|
|
|
$fn = self::PREFIX . $this->getNativeName($functionDef->name); |
|
|
|
|
$cppCode .= $this->genWrapperFunctionArgs($fn, $functionDef); |
|
|
|
|
|
|
|
|
|
return $cppCode; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private function genClassNative(): string |
|
|
|
|
{ |
|
|
|
|
$code = 'class ' . $this->class . ' { '; |
|
|
|
|
|
|
|
|
|
$publicMethods = []; |
|
|
|
|
$protectedMethods = []; |
|
|
|
|
$privateMethods = []; |
|
|
|
|
$publicConstants = []; |
|
|
|
|
$protectedConstants = []; |
|
|
|
|
$privateConstants = []; |
|
|
|
|
$publicProperties = []; |
|
|
|
|
$protectedProperties = []; |
|
|
|
|
$privateProperties = []; |
|
|
|
|
|
|
|
|
|
foreach ($this->classDef->constants as $const) { |
|
|
|
|
if ($const->flags & Modifiers::PUBLIC) { |
|
|
|
|
$publicConstants[] = $const; |
|
|
|
|
} |
|
|
|
|
if ($const->flags & Modifiers::PROTECTED) { |
|
|
|
|
$protectedConstants[] = $const; |
|
|
|
|
} |
|
|
|
|
if ($const->flags & Modifiers::PRIVATE) { |
|
|
|
|
$privateConstants[] = $const; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
foreach ($this->classDef->methods as $method) { |
|
|
|
|
if ($method->flags & Modifiers::PUBLIC) { |
|
|
|
|
$publicMethods[] = $method; |
|
|
|
|
} |
|
|
|
|
if ($method->flags & Modifiers::PROTECTED) { |
|
|
|
|
$protectedMethods[] = $method; |
|
|
|
|
} |
|
|
|
|
if ($method->flags & Modifiers::PRIVATE) { |
|
|
|
|
$privateMethods[] = $method; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
foreach ($this->classDef->properties as $property) { |
|
|
|
|
if ($property->flags & Modifiers::PUBLIC) { |
|
|
|
|
$publicProperties[] = $property; |
|
|
|
|
} |
|
|
|
|
if ($property->flags & Modifiers::PROTECTED) { |
|
|
|
|
$protectedProperties[] = $property; |
|
|
|
|
} |
|
|
|
|
if ($property->flags & Modifiers::PRIVATE) { |
|
|
|
|
$privateProperties[] = $property; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if ($privateConstants) { |
|
|
|
|
$code .= 'private:' . PHP_EOL; |
|
|
|
|
$code .= $this->genClassConstantList($privateConstants); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if ($protectedConstants) { |
|
|
|
|
$code .= 'protected:' . PHP_EOL; |
|
|
|
|
$code .= $this->genClassConstantList($protectedConstants); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if ($publicConstants) { |
|
|
|
|
$code .= 'public:' . PHP_EOL; |
|
|
|
|
$code .= $this->genClassConstantList($publicConstants); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if ($privateProperties) { |
|
|
|
|
$code .= 'private:' . PHP_EOL; |
|
|
|
|
$code .= $this->genClassPropertyList($privateProperties); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if ($protectedProperties) { |
|
|
|
|
$code .= 'protected:' . PHP_EOL; |
|
|
|
|
$code .= $this->genClassPropertyList($protectedProperties); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if ($publicProperties) { |
|
|
|
|
$code .= 'public:' . PHP_EOL; |
|
|
|
|
$code .= $this->genClassPropertyList($publicProperties); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$code .= '};' . PHP_EOL . PHP_EOL; |
|
|
|
|
|
|
|
|
|
return $code; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|