symbolCallInFile as $file => $symbols) { $deps = []; foreach ($symbols as $symbol) { if (isset($this->symbolDeclInFile[$symbol])) { $depFile = $this->symbolDeclInFile[$symbol]; if ($depFile !== $file) { $deps[] = $depFile; } } } $deps = array_unique($deps); $fileDeps[$file] = $deps; $sorter->add($file, $deps); } $sortedFiles = $sorter->sort(); // 添加未参与依赖管理的文件(非 stub 文件且不在已排序列表中) foreach ($list as $file) { if (!$this->isStubFile($file) and !in_array($file, $sortedFiles)) { $sortedFiles[] = $file; } } $list = $sortedFiles; } public function getCppFile(string $file): string { $info = pathinfo($file); return $this->buildDir . '/' . $this->removeCommonPrefix($this->buildDir, $info['dirname'] . '/' . $info['filename'] . '.cc'); } public function getObjectFile(string $cppFile): string { $info = pathinfo($cppFile); return $info['dirname'] . '/' . $info['filename'] . '.o'; } public function hasCppFileCache(string $file): bool { if (!$this->enableCache or $this->climate->arguments->defined('force')) { return false; } $cppFile = $this->getCppFile($file); if (file_exists($cppFile) and filemtime($cppFile) > filemtime($file)) { return true; } return false; } public function prepare(string $file): void { if ($this->hasCppFileCache($file)) { $this->climate->darkGray('skip: ' . $file . ', cache exists'); return; } $phpCode = $this->loadFile($file); $this->symbolCallInFile[$this->file] = []; $this->resetFile(); $this->resetFunction(); $this->resetMethod(); $this->resetClass(); $this->resetNamespace(); $this->climate->info('prepare: ' . $this->getRelativePath($this->file)); try { $ast = $this->parser->parse($phpCode); } catch (\PhpParser\Error $e) { $this->climate->red("Fatal error: {$e->getMessage()} in {$this->file}"); throw new SyntaxError($e->getMessage(), $e->getCode()); } $traverser = new NodeTraverser(); $traverser->addVisitor(new Visitor()); $stmts = $traverser->traverse($ast); foreach ($stmts as $v) { $type = $v->getType(); switch ($type) { case 'Stmt_Namespace': $this->prepareNamespace($v); break; case 'Stmt_Enum': case 'Stmt_Class': case 'Stmt_Trait': $this->prepareClass($v); break; case 'Stmt_Function': $this->prepareFunction($v) . PHP_EOL; break; case 'Stmt_Use': $this->parseUse($v); break; case 'Stmt_Declare': case 'Stmt_Interface': case 'Stmt_Nop': break; case 'Stmt_Const': $this->parseConstDef($v); break; case 'Stmt_Expression': $this->foundStrayCode($v); // no break default: $this->fatalError($v, 'Unsupported statement: ' . $type); } } } protected function findSymbolUsing(NodeAbstract $ast) { $nodeFinder = new NodeFinder(); $functionCalls = $nodeFinder->findInstanceOf($ast, Node\Expr\FuncCall::class); foreach ($functionCalls as $call) { if ($call->name instanceof Node\Name) { // 内置函数不参与依赖管理 $funcName = strtolower($call->name->toString()); if (!$this->isInternalFunction($funcName)) { $this->symbolCallInFile[$this->file][] = $funcName; } } } $depClasses = []; $depClasses = array_merge($depClasses, $nodeFinder->findInstanceOf($ast, Node\Expr\StaticCall::class)); $depClasses = array_merge($depClasses, $nodeFinder->findInstanceOf($ast, Node\Expr\StaticPropertyFetch::class)); $depClasses = array_merge($depClasses, $nodeFinder->findInstanceOf($ast, Node\Expr\ClassConstFetch::class)); $depClasses = array_merge($depClasses, $nodeFinder->findInstanceOf($ast, Node\Expr\New_::class)); foreach ($depClasses as $call) { if ($call->class instanceof Node\Name) { $className = $this->parseIdentifier($call->class); if ($className !== 'self' && $className !== 'static') { $fullClassName = $this->getNamespacedClassName($className); $this->symbolCallInFile[$this->file][] = strtolower($fullClassName); } } } // 依赖去重 $depClasses = array_unique($this->symbolCallInFile[$this->file]); $this->symbolCallInFile[$this->file] = $depClasses; } protected function prepareNamespace(Node\Stmt\Namespace_ $node): void { $this->resetClass(); $this->resetMethod(); $this->resetFunction(); $this->resetNamespace(); $this->namespace = $node->name ? $this->parseIdentifier($node->name) : ''; foreach ($node->stmts as $v2) { $type2 = $v2->getType(); switch ($type2) { case 'Stmt_Class': case 'Stmt_Enum': case 'Stmt_Trait': $this->prepareClass($v2); break; case 'Stmt_Function': $this->prepareFunction($v2) . PHP_EOL; break; case 'Stmt_Use': $this->parseUse($v2); break; case 'Stmt_Const': case 'Stmt_Interface': break; case 'Stmt_Expression': $this->foundStrayCode($v2); // no break default: throw new Unsupported('Unsupported statement: ' . $type2); } } } /** * @param $params array */ protected function parseParams(array $params, FunctionDef $functionDef): void { $list = []; $functionDef->argCountRequired = count($params); $defaultValueCount = 0; $last = array_key_last($params); foreach ($params as $i => $param) { // .stub 存根定义 C++ Native 函数,必须设置函数的参数类型 if ($this->stubFile and !$param->type) { throw new \RuntimeException('No type for ' . $this->parseIdentifier($param->var)); } // 构造方法属性定义语法(Constructor Property Promotion) if ($param->isPromoted()) { if (!$this->classDef or !$this->methodDef or $this->methodDef->name !== '__construct') { $this->fatalError($param, 'Promoted properties are not supported'); } $name = $this->parseIdentifier($param->var); if ($param->type instanceof NullableType) { $type = $param->type->type; $nullable = true; } elseif ($param->type instanceof UnionType) { $type = 'mixed'; $nullable = false; } else { $type = $param->type === null ? '' : $param->type; $nullable = false; } $default = $this->parseParamDefaultValue($param->default); $propertyDef = new PropertyDef($name, $param->flags, $type, $default, $nullable); $this->classDef->properties[$name] = $propertyDef; } if ($param->variadic) { if ($i !== $last) { $this->fatalError($param, 'Variadic parameters must be the last parameter'); } elseif ($param->byRef) { $this->fatalError($param, 'Variadic parameters cannot be passed by reference'); } } $name = $this->parseIdentifier($param->var); if ($this->method and $name == 'this_') { $this->fatalError($param, 'Cannot use `$this` as parameter of class method'); } $argInfo = new ArgInfo(); $type = $this->parseParameterType($param, $argInfo, $name); if ($param->variadic) { $list[] = self::TYPE_ARRAY . ' ' . $name; } else { $list[] = $type . ' ' . $name; } $argInfo->name = $name; $argInfo->type = $type; $argInfo->byRef = $param->byRef; $argInfo->variadic = $param->variadic; $argInfo->property = $param->isPromoted(); if ($param->type and $param->type instanceof NullableType) { $argInfo->nullable = true; } if ($param->default) { if ($param->byRef) { if ($this->isEmptyArray($param->default)) { $argInfo->default = 'php::getEmptyArrayRef()'; $argInfo->defaultValue = null; } elseif ($this->isNull($param->default)) { $argInfo->default = 'nullptr'; $argInfo->defaultValue = null; } else { $argInfo->default = 'php::newReference(' . $this->parseParamDefaultValue($param->default) . ')'; } } else { $argInfo->default = $this->parseParamDefaultValue($param->default); $argInfo->defaultValue = $param->default; } $defaultValueCount++; } elseif ($param->variadic) { // 变长参数可以视为空数组默认值 $defaultValueCount++; $argInfo->default = '{}'; $argInfo->defaultValue = new Node\Expr\Array_(); } $functionDef->argInfoList[] = $argInfo; } $functionDef->params = implode(', ', $list); $functionDef->argCountRequired -= $defaultValueCount; } protected function parseFunctionDecl(Node\Stmt\Function_|Node\Stmt\ClassMethod $v): FunctionDef { // .stub 存根定义 C++ Native 函数,必须设置返回值类型 if ($this->stubFile and !$v->returnType) { // 以下魔术方法都不能声明返回值类型 __construct()/__destruct()/__clone() if (($this->method and !in_array($this->method, ['__construct', '__destruct', '__clone'])) or !$this->method) { $name = $this->class ? $this->class . '::' . $v->name : $v->name; $this->fatalError($v, 'The return type of the function `' . $name . '` must be specified'); } } // 返回值不能是引用类型 if ($v->byRef) { $this->fatalError($v, 'The return type of the function `' . $v->name . '` cannot be a reference type'); } $fnName = $this->parseIdentifier($v->name); $class = ''; $returnType = $this->parseTypeDecl($v->returnType, self::DECL_TYPE_OF_RETURN, $class); $functionDef = new FunctionDef($fnName, $returnType, $this->namespace); $functionDef->returnClass = $class; $functionDef->stub = $this->stubFile; $this->parseParams($v->params, $functionDef); // main 函数,返回值必须为 void 类型,参数必须为空或者 argc, argv 两个参数 if (!$this->class and !$this->namespace and $fnName === 'main') { if (count($v->params) > 0) { if (count($v->params) != 2) { $this->fatalError($v, 'The parameters of the main function must be `(int $argc, array $argv)`.'); } if ($returnType !== self::TYPE_VOID) { $this->fatalError($v, 'main function must return void'); } if (!$this->checkArgType($functionDef->argInfoList[0]->type, self::TYPE_INT)) { $this->fatalError($v, 'The first parameter of the main function must be of type `int`.'); } if (!$this->checkArgType($functionDef->argInfoList[1]->type, self::TYPE_ARRAY)) { $this->fatalError($v, 'The second parameter of the main function must be of type `array`.'); } } } return $functionDef; } protected function prepareFunction(Node\Stmt\ClassMethod|Node\Stmt\Function_ $v): void { $this->resetFunction(); $this->function = $this->parseIdentifier($v->name); $name = $this->getFunctionName($v); if ($this->hasFunction($name)) { $this->fatalError($v, "Duplicate function `{$name}`"); } $functionDef = $this->parseFunctionDecl($v); $this->addFunction($name, $functionDef); if ($this->methodDef) { $functionDef->method = true; $this->methodDef->functionDef = $functionDef; } } protected function getParentClass(NodeAbstract $extends): string { if ($extends instanceof Node\Name\FullyQualified) { return $this->parseIdentifier($extends); } else { return $this->getNamespacedClassName($this->parseIdentifier($extends)); } } protected function prepareClass(Node\Stmt\Class_|Node\Stmt\Trait_|Node\Stmt\Enum_ $class): string { $this->class = $this->parseIdentifier($class->name); $fullClassName = $this->getFullClassName(); $fullClassNameLower = strtolower($fullClassName); if ($class instanceof Node\Stmt\Enum_) { $flags = Modifiers::PUBLIC; } elseif (!$class instanceof Node\Stmt\Trait_) { $flags = $class->flags; } else { $flags = Modifiers::PUBLIC; } $this->classDef = new ClassDef($this->class, $flags, $this->namespace); $this->addClass($fullClassName, $this->classDef); if (!empty($class->extends)) { $this->parentClass = $this->getParentClass($class->extends); $parentClassLower = strtolower($this->parentClass); if ($parentClassLower === $fullClassNameLower) { $this->fatalError($class, "Class {$fullClassName} cannot extend itself"); } $this->classExtends[$fullClassNameLower] = $parentClassLower; if (!$this->isInternalClass($parentClassLower)) { $this->symbolCallInFile[$this->file][] = $parentClassLower; } $this->classDef->extends = $this->parentClass; // 是否继承了内置类 $this->classDef->inheritedFromInternalClass = $this->isInternalClass($parentClassLower); } if ($class instanceof Node\Stmt\Enum_) { $this->classDef->enum = true; } if (!$class instanceof Node\Stmt\Trait_) { $this->classDef->implements = $this->parseImplements($class->implements); } if (isset($this->symbolDeclInFile[$fullClassNameLower])) { $this->fatalError($class, "Duplicate class `{$fullClassName}`"); } $this->symbolDeclInFile[$fullClassNameLower] = $this->file; $code = ''; foreach ($class->stmts as $v) { $type = $v->getType(); switch ($type) { case 'Stmt_ClassConst': $this->parseClassConstDef($v); break; case 'Stmt_Property': $this->parseClassPropertyDef($v); break; case 'Stmt_Nop': case 'Stmt_TraitUse': case 'Stmt_EnumCase': break; case 'Stmt_ClassMethod': $this->prepareClassMethod($v, $class); break; case 'Stmt_Expression': $this->foundStrayCode($v); // no break default: abort($v); } } $this->class = ''; $this->parentClass = ''; return $code; } protected function getMethodName(Node\Stmt\ClassMethod $v): string { return $this->parseIdentifier($v->name); } /** * 检查父类方法是否可以被重写,私有方法不能被重写 */ protected function checkParentMethodCanBeOverridden(Node\Stmt\ClassMethod $v, string $name): void { $classDef = $this->classDef; while (true) { $extends = $classDef->extends; if (!$extends) { break; } // 父类是内置类 if ($classDef->inheritedFromInternalClass) { if (Reflection::getClassMethodModifiers($extends, $name) & \ReflectionMethod::IS_PRIVATE) { goto _error; } break; } $classDef = $this->getClass($extends); if ($classDef->hasMethod($this->method)) { $methodDef = $classDef->getMethod($this->method); if ($methodDef->flags & Modifiers::PRIVATE) { _error: $this->fatalError($v, 'Cannot override private method `' . $classDef->getNamespacedName(false) . '::' . $this->method . '()`'); } } } } protected function parseClassConstDef(Node\Stmt\ClassConst $v): void { $this->resetFunction(); $flags = $this->parseModifiers($v->flags); $class = ''; if ($v->type) { $type = $this->parseTypeDecl($v->type, self::DECL_TYPE_OF_CONST, $class); } else { $type = null; } foreach ($v->consts as $const) { if ($type === null) { $type = match ($const->value->getType()) { 'Expr_Array' => self::TYPE_ARRAY, 'Scalar_String' => self::TYPE_STR, default => self::TYPE_VAR, }; } $constName = $this->parseIdentifier($const->name); $constValue = $this->parseIdentifier($const->value); $constInfo = new ConstantDef($constName, $flags, $type, $constValue); if ($this->context->beforeStmtLines) { $arrayExpr = ''; if ($this->context->localVars) { $arrayExpr .= $this->genScopeVarDecl(); } $arrayExpr .= $this->parseBeforeStmtLines(); $constInfo->arrayExpr = $arrayExpr; } $constInfo->class = $class; $this->classDef->constants[$constInfo->name] = $constInfo; } } protected function parseClassPropertyDef(Node\Stmt\Property $v): void { $oriCtx = $this->context; $this->context = $this->classDef->propertyContext; $flags = $this->parseModifiers($v->flags); $class = ''; $type = $this->parseTypeDecl($v->type, self::DECL_TYPE_OF_PROPERTY, $class); foreach ($v->props as $prop) { $propDef = new PropertyDef($this->parseIdentifier($prop->name), $flags, $type); if ($prop->default) { $propDef->default = $this->parseIdentifier($prop->default); if ($prop->default->getType() == 'Expr_Array') { $propDef->type = self::TYPE_ARRAY; } } $propDef->class = $class; $this->classDef->properties[$propDef->name] = $propDef; } $this->context = $oriCtx; } protected function prepareClassMethod(Node\Stmt\ClassMethod $v, Node\Stmt\Class_|Node\Stmt\Trait_|Node\Stmt\Enum_ $class): void { $name = $this->getMethodName($v); $this->method = $name; $flags = $this->parseModifiers($v->flags); $abstract = $flags & Modifiers::ABSTRACT; if (!$abstract) { $this->methodDef = new MethodDef($flags, $name); if ($this->classDef->hasMethod($name)) { $this->fatalError($v, "Duplicate method `{$this->method}`"); } $this->prepareFunction($v) . PHP_EOL; $this->checkRequiredArgNum($name, $this->methodDef, $v); $this->classDef->addMethod($this->methodDef); } else { if (!($class->flags & Modifiers::ABSTRACT)) { $this->fatalError($v, "Class {$this->class} cannot override non-abstract method {$v->name}"); } if ($this->method === '__construct') { foreach ($v->params as $param) { if ($param->isPromoted()) { $this->fatalError($v, 'Cannot declare promoted property in an abstract constructor'); } } } } $fullClassName = $this->getFullClassName(); $fullMethodName = $fullClassName . '::' . $this->method; $this->classMethodOverride[strtolower($fullMethodName)] = false; // 查找父类是否有同名方法,递归查找 $fullClassNameLower = strtolower($fullClassName); while (isset($this->classExtends[$fullClassNameLower])) { $parentClass = $this->classExtends[$fullClassNameLower]; $parentMethodLower = strtolower($parentClass . '::' . $v->name); // 父类有同名方法,子类覆盖了父类方法,这种情况不能直接使用 C++ 函数,而是使用 ZendVM 动态调用 if (isset($this->classMethodOverride[$parentMethodLower])) { $this->classMethodOverride[$parentMethodLower] = true; } $fullClassNameLower = strtolower($parentClass); } } }