|
|
|
|
@ -11,10 +11,17 @@ namespace PhpAot\Php; |
|
|
|
|
use MJS\TopSort\CircularDependencyException; |
|
|
|
|
use MJS\TopSort\ElementNotFoundException; |
|
|
|
|
use MJS\TopSort\Implementations\StringSort; |
|
|
|
|
use PhpAot\Php\Entity\ClassDef; |
|
|
|
|
use PhpAot\Php\Entity\ConstantDef; |
|
|
|
|
use PhpAot\Php\Entity\FunctionDef; |
|
|
|
|
use PhpAot\Php\Entity\MethodDef; |
|
|
|
|
use PhpAot\Php\Entity\PropertyDef; |
|
|
|
|
use PhpAot\Php\Exception\SyntaxError; |
|
|
|
|
use PhpAot\Php\Exception\Unsupported; |
|
|
|
|
use PhpParser\Modifiers; |
|
|
|
|
use PhpParser\Node; |
|
|
|
|
use PhpParser\Node\NullableType; |
|
|
|
|
use PhpParser\Node\UnionType; |
|
|
|
|
use PhpParser\NodeAbstract; |
|
|
|
|
use PhpParser\NodeFinder; |
|
|
|
|
use PhpParser\NodeTraverser; |
|
|
|
|
@ -161,6 +168,7 @@ class Preprocessor extends CompilerBase |
|
|
|
|
$this->symbolCallInFile[$this->file] = []; |
|
|
|
|
$this->resetFile(); |
|
|
|
|
$this->resetFunction(); |
|
|
|
|
$this->resetMethod(); |
|
|
|
|
$this->resetClass(); |
|
|
|
|
$this->resetNamespace(); |
|
|
|
|
|
|
|
|
|
@ -208,6 +216,10 @@ class Preprocessor extends CompilerBase |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protected function findSymbolUsing(NodeAbstract $ast) |
|
|
|
|
{ |
|
|
|
|
$nodeFinder = new NodeFinder(); |
|
|
|
|
$functionCalls = $nodeFinder->findInstanceOf($ast, Node\Expr\FuncCall::class); |
|
|
|
|
|
|
|
|
|
@ -222,8 +234,8 @@ class Preprocessor extends CompilerBase |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$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\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) { |
|
|
|
|
@ -242,13 +254,18 @@ class Preprocessor extends CompilerBase |
|
|
|
|
|
|
|
|
|
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': |
|
|
|
|
@ -269,17 +286,153 @@ class Preprocessor extends CompilerBase |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* @param $params array<Node\Param> |
|
|
|
|
*/ |
|
|
|
|
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 { |
|
|
|
|
$this->fatalError($param, 'Only null and empty array can be used as default value for reference parameter'); |
|
|
|
|
} |
|
|
|
|
} 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->stubFile) { |
|
|
|
|
$this->addFunction($name, $this->parseFunctionDecl($v)); |
|
|
|
|
} else { |
|
|
|
|
$functionNameLower = strtolower($name); |
|
|
|
|
if (isset($this->symbolDeclInFile[$functionNameLower])) { |
|
|
|
|
$this->fatalError($v, "Duplicate function `{$functionNameLower}`"); |
|
|
|
|
} |
|
|
|
|
$this->symbolDeclInFile[$functionNameLower] = $this->file; |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -298,6 +451,17 @@ class Preprocessor extends CompilerBase |
|
|
|
|
$fullClassName = $this->getFullClassName(); |
|
|
|
|
$fullClassNameLower = strtolower($fullClassName); |
|
|
|
|
|
|
|
|
|
if ($class instanceof Node\Stmt\Enum_) { |
|
|
|
|
$flags = Modifiers::PUBLIC; |
|
|
|
|
} else if (!$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); |
|
|
|
|
@ -308,8 +472,17 @@ class Preprocessor extends CompilerBase |
|
|
|
|
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}`"); |
|
|
|
|
} |
|
|
|
|
@ -321,13 +494,17 @@ class Preprocessor extends CompilerBase |
|
|
|
|
$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->prepareMethod($v, $class); |
|
|
|
|
$this->prepareClassMethod($v, $class); |
|
|
|
|
break; |
|
|
|
|
case 'Stmt_Expression': |
|
|
|
|
$this->foundStrayCode($v); |
|
|
|
|
@ -342,12 +519,118 @@ class Preprocessor extends CompilerBase |
|
|
|
|
return $code; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protected function prepareMethod(Node\Stmt\ClassMethod $v, Node\Stmt\Class_|Node\Stmt\Trait_|Node\Stmt\Enum_ $class): void |
|
|
|
|
protected function getMethodName(Node\Stmt\ClassMethod $v): string |
|
|
|
|
{ |
|
|
|
|
$this->method = $v->name; |
|
|
|
|
$abstract = $v->flags & Modifiers::ABSTRACT; |
|
|
|
|
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}"); |
|
|
|
|
|