refactor code

pull/44/head
NathanFreeman 3 weeks ago
parent 0bc91a0d6f
commit 233209a253
  1. 237
      src/Generator/ParamParser.php
  2. 57
      src/Generator/ParamTypes/ParamParser.php
  3. 198
      src/Generator/ParamTypes/ZendParamMacros.php
  4. 140
      src/Generator/ParamTypes/ZendVariables.php
  5. 36
      src/Translator.php

@ -1,237 +0,0 @@
<?php
declare(strict_types=1);
namespace TypePhp\Generator;
use PhpParser\Node\NullableType;
use TypePhp\Entity\ArgInfo;
use TypePhp\Type;
final class ParamParser
{
private const string NONE_PARAMS_TEMPLATE = <<<EOL
zend_execute_data *execute_data = EG(current_execute_data);
ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_QUIET, 0, 0)
ZEND_PARSE_PARAMETERS_END_EX(php::throwErrorIfOccurred());
EOL;
private array $paramNames = ["zend_execute_data *execute_data = EG(current_execute_data);"];
private array $complexTypes = [];
/**
* @param array<ArgInfo> $argInfos
* @return string
*/
public function create(array $argInfos): string
{
if (empty($argInfos)) {
return sprintf("do { %s } while(false);", self::NONE_PARAMS_TEMPLATE).PHP_EOL;
}
[$minNumArgs, $maxNumArgs] = $this->getRequiredAndOptionalParamCounts($argInfos);
$macros[] = $this->genStartParseCode($minNumArgs, $maxNumArgs);
foreach ($argInfos as $index => $argInfo) {
if ($minNumArgs == 0 || $minNumArgs < $maxNumArgs && $index > $minNumArgs - 1) {
$macros[] = $this->genOptionalDirective();
}
$macros[] = $this->genBodyParserCode($argInfo);
}
$macros[] = $this->genEndParseCode();
return sprintf(
"do { %s %s } while(false);",
implode(PHP_EOL, $this->paramNames),
implode(PHP_EOL, $macros));
}
/**
* @return array<ArgInfo>
*/
public function getComplexTypes(): array
{
return $this->complexTypes;
}
/**
* @param array<ArgInfo> $argInfos
* @return array
*/
private function getRequiredAndOptionalParamCounts(array $argInfos): array
{
if (count($argInfos) == 1 && $argInfos[0]->type === Type::REF) {
return [0, -1];
}
$hasRef = false;
foreach ($argInfos as $index => $argInfo) {
if ($argInfo->type === Type::REF) {
$hasRef = true;
break;
}
}
$requiredParamCount = count(array_filter($argInfos, fn($argInfo) => $argInfo->defaultValue === null));
return [$requiredParamCount, $hasRef ? -1 : count($argInfos)];
}
private function genStartParseCode(int $minNumArgs, int $maxNumArgs): string
{
return sprintf("ZEND_PARSE_PARAMETERS_START(%d, %d)", $minNumArgs, $maxNumArgs);
}
private function genOptionalDirective(): string
{
return "Z_PARAM_OPTIONAL";
}
private function genBodyParserCode(ArgInfo $argInfo): string
{
return match ($argInfo->type) {
Type::BOOL => $this->parseBool($argInfo),
Type::INT => $this->parseInt($argInfo),
Type::FLOAT => $this->parseFloat($argInfo),
Type::ARRAY => $this->parseArray($argInfo),
Type::STR => $this->parseString($argInfo),
Type::ARGS => $this->parseArgs($argInfo),
Type::REF => $this->parseReference($argInfo),
Type::RESOURCE, Type::STREAM => $this->parseResource($argInfo),
Type::OBJECT => $this->parseObject($argInfo),
Type::VAR => $this->parseVar($argInfo),
default => ''
};
}
private function genEndParseCode(): string
{
return "ZEND_PARSE_PARAMETERS_END_EX(php::throwErrorIfOccurred());";
}
private function parseBool(ArgInfo $argInfo): string
{
$name = $argInfo->name;
$isNull = $name.'_is_null';
$this->paramNames[] = "zend_bool $name = false; zend_bool $isNull = false;";
return $argInfo->nullable
? "Z_PARAM_BOOL_OR_NULL($name, $isNull)"
: "Z_PARAM_BOOL($name)";
}
private function parseInt(ArgInfo $argInfo): string
{
$name = $argInfo->name;
$isNull = $name.'_is_null';
$this->paramNames[] = "zend_long $name = 0; zend_bool $isNull = false;";
return $argInfo->nullable
? "Z_PARAM_LONG_OR_NULL($name, $isNull)"
: "Z_PARAM_LONG($name)";
}
private function parseFloat(ArgInfo $argInfo): string
{
$name = $argInfo->name;
$isNull = $name.'_is_null';
$this->paramNames[] = "double $name = 0; zend_bool $isNull = false;";
return $argInfo->nullable
? "Z_PARAM_DOUBLE_OR_NULL($name, $isNull)"
: "Z_PARAM_DOUBLE($name)";
}
private function parseArray(ArgInfo $argInfo): string
{
$name = $argInfo->name;
$this->paramNames[] = 'zval *'.$name.' = nullptr;';
return $argInfo->nullable
? "Z_PARAM_ARRAY_OR_NULL($name)"
: "Z_PARAM_ARRAY($name)";
}
private function parseString(ArgInfo $argInfo): string
{
$name = $argInfo->name;
$this->paramNames[] = "zend_string *".$name.' = nullptr;';
return $argInfo->nullable
? "Z_PARAM_STR_OR_NULL($name)"
: "Z_PARAM_STR($name)";
}
private function parseArgs(ArgInfo $argInfo): string
{
$name = $argInfo->name;
$this->paramNames[] = "zval *".$name.'_argv; int '.$name.'_argc = 0;';
return sprintf("Z_PARAM_ARRAY(*, %s, %s)", $name.'_argv', $name.'_argc');
}
private function parseReference(ArgInfo $argInfo): string
{
$name = $argInfo->name;
$this->paramNames[] = "zval *".$name.' = nullptr;';
return "Z_PARAM_ZVAL($name)".PHP_EOL;
}
private function parseResource(ArgInfo $argInfo): string
{
$name = $argInfo->name;
$this->paramNames[] = "zval *".$name.' = nullptr;';
return "Z_PARAM_RESOURCE($name)".PHP_EOL;
}
private function parseObject(ArgInfo $argInfo): string
{
$name = $argInfo->name;
if ($argInfo->class === 'callble') {
$this->paramNames[] = "zend_fcall_info ".$name.'_fci; zend_fcall_info_cache '.$name.'_fcc;';
if ($argInfo->nullable) {
return sprintf("Z_PARAM_FUNC_OR_NULL(%s, %s)", $name.'_fci', $name.'_fcc');
} else {
return sprintf("Z_PARAM_FUNC(%s, %s)", $name.'_fci', $name.'_fcc');
}
}
$this->paramNames[] = "zval *".$name.' = nullptr;';
return $argInfo->nullable
? "Z_PARAM_OBJECT_OR_NULL($argInfo->name)"
: "Z_PARAM_OBJECT($argInfo->name)";
}
private function parseVar(ArgInfo $argInfo): string
{
$name = $argInfo->name;
if ($argInfo->typeNode instanceof NullableType) {
return match ($argInfo->typeNode->type->name) {
"string" => $this->parseString($argInfo),
"array" => $this->parseArgs($argInfo),
"bool" => $this->parseBool($argInfo),
"resource" => $this->parseResource($argInfo),
"float" => $this->parseFloat($argInfo),
default => $this->parseObject($argInfo),
};
} else {
/**
* Union types and intersection types cannot be resolved through macros.
* Although a few macros are provided for certain union types, their coverage is limited.
* Therefore, we need to adopt a specialized approach to handle them. For now,
* we will defer this implementation until after the current PR is merged,
* so as to keep the PR size manageable and avoid making it too difficult to review.
*/
$this->complexTypes[] = $argInfo;
}
$this->paramNames[] = "zval *".$name.' = nullptr;';
return $argInfo->nullable
? "Z_PARAM_ZVAL_OR_NULL($name)"
: "Z_PARAM_ZVAL($name)";
}
}

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace TypePhp\Generator\ParamTypes;
use TypePhp\Entity\ArgInfo;
final class ParamParser
{
/**
* ZEND_PARSE_PARAMETERS_NONE cannot be used here, because it hardcodes a return statement,
* which would result in a type mismatch.
*/
private const string NONE_PARAMS_TEMPLATE = <<<EOL
ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_QUIET, 0, 0)
ZEND_PARSE_PARAMETERS_END_EX(php::throwErrorIfOccurred());
EOL;
/**
* @param array<ArgInfo> $argInfos
* @return string
*/
public function createZendParamTypeCheck(array $argInfos): string
{
// Return the default macro if no parameter information is available.
if (empty($argInfos)) {
return sprintf("do { %s } while(false);", self::NONE_PARAMS_TEMPLATE);
}
[$minNumArgs, $maxNumArgs] = $this->getRequiredAndAllParamCounts($argInfos);
$zendVariables = (new ZendVariables())->createZendVariables($argInfos);
$zendParamMarcos = (new ZendParamMacros())->createZendParamMacros($argInfos, $minNumArgs, $maxNumArgs);
return sprintf(
"do { %s %s } while(false);",
implode(PHP_EOL, $zendVariables),
implode(PHP_EOL, $zendParamMarcos));
}
/**
* Calculate the count of required parameters and total parameters.
* @param array<ArgInfo> $argInfos
* @return array<int>
*/
private function getRequiredAndAllParamCounts(array $argInfos): array
{
$argCount = count($argInfos);
$lastIndex = $argCount - 1;
if ($argCount > 0 && $argInfos[$lastIndex]->variadic) {
return [$lastIndex, -1];
}
$requiredParamCount = count(array_filter($argInfos, fn($argInfo) => $argInfo->defaultValue === null));
return [$requiredParamCount, $argCount];
}
}

@ -0,0 +1,198 @@
<?php
declare(strict_types=1);
namespace TypePhp\Generator\ParamTypes;
use PhpParser\Node\Identifier;
use PhpParser\Node\NullableType;
use TypePhp\Entity\ArgInfo;
use TypePhp\Type;
final class ZendParamMacros
{
/**
* Generate a macro in the extension to validate/check the input parameters.
* @param array<ArgInfo> $argInfos
* @param int $minNumArgs
* @param int $maxNumArgs
* @return array<string>
*/
public function createZendParamMacros(array $argInfos, int $minNumArgs, int $maxNumArgs): array
{
$marcos[] = $this->genStartParseCode($minNumArgs, $maxNumArgs);
foreach ($argInfos as $index => $argInfo) {
if ($maxNumArgs > $minNumArgs && $index == $minNumArgs) {
$marcos[] = $this->genOptionalParseCode();
}
$marcos[] = match ($argInfo->type) {
Type::BOOL => $this->parseBool($argInfo),
Type::INT => $this->parseInt($argInfo),
Type::FLOAT => $this->parseFloat($argInfo),
Type::ARRAY => $this->parseArray($argInfo),
Type::STR => $this->parseString($argInfo),
Type::REF => $this->parseReference($argInfo),
Type::RESOURCE, Type::STREAM => $this->parseResource($argInfo),
Type::OBJECT => $this->parseObject($argInfo, $argInfo->class),
default => $this->parseVar($argInfo)
};
}
$marcos[] = $this->genEndParseCode();
return $marcos;
}
/**
* ZEND_PARAM_PARSE start
* @param int $minNumArgs
* @param int $maxNumArgs
* @return string
*/
private function genStartParseCode(int $minNumArgs, int $maxNumArgs): string
{
return sprintf("ZEND_PARSE_PARAMETERS_START(%d, %d)", $minNumArgs, $maxNumArgs);
}
/**
* ZEND_PARAM_PARSE end
* @return string
*/
private function genEndParseCode(): string
{
return "ZEND_PARSE_PARAMETERS_END_EX(php::throwErrorIfOccurred());";
}
/**
* Z_PARAM_OPTIONAL
* @return string
*/
private function genOptionalParseCode(): string
{
return "Z_PARAM_OPTIONAL";
}
private function parseBool(ArgInfo $argInfo): string
{
$name = $argInfo->name;
return $argInfo->nullable
? sprintf("Z_PARAM_BOOL_OR_NULL(%s, %s_is_null)", $name, $name)
: "Z_PARAM_BOOL($name)";
}
private function parseInt(ArgInfo $argInfo): string
{
$name = $argInfo->name;
return $argInfo->nullable
? sprintf("Z_PARAM_LONG_OR_NULL(%s, %s_is_null)", $name, $name)
: "Z_PARAM_LONG($name)";
}
private function parseFloat(ArgInfo $argInfo): string
{
$name = $argInfo->name;
return $argInfo->nullable
? sprintf("Z_PARAM_DOUBLE_OR_NULL(%s, %s_is_null)", $name, $name)
: "Z_PARAM_DOUBLE($name)";
}
private function parseArray(ArgInfo $argInfo): string
{
$name = $argInfo->name;
return $argInfo->nullable ? "Z_PARAM_ARRAY_OR_NULL($name)" : "Z_PARAM_ARRAY($name)";
}
private function parseString(ArgInfo $argInfo): string
{
$name = $argInfo->name;
return $argInfo->nullable ? "Z_PARAM_STR_OR_NULL($name)" : "Z_PARAM_STR($name)";
}
private function parseArgs(ArgInfo $argInfo): string
{
$name = $argInfo->name;
return sprintf("Z_PARAM_VARIADIC(*, %s_argv, %s_argc)", $name, $name);
}
private function parseReference(ArgInfo $argInfo): string
{
return "Z_PARAM_ZVAL($argInfo->name)";
}
private function parseResource(ArgInfo $argInfo): string
{
return "Z_PARAM_RESOURCE($argInfo->name)";
}
private function parseObject(ArgInfo $argInfo, string $className): string
{
$name = $argInfo->name;
if ($className) {
return $argInfo->nullable
? sprintf("Z_PARAM_OBJECT_OF_CLASS_OR_NULL(%s_obj, %s_ce)", $name, $name)
: sprintf("Z_PARAM_OBJECT_OF_CLASS(%s_obj, %s_ce)", $name, $name);
}
return $argInfo->nullable ? "Z_PARAM_OBJECT_OR_NULL($name)" : "Z_PARAM_OBJECT($name)";
}
/**
* The compiler does not currently support the callable type.
* For now, we'll implement the method first and fill in the callable support later.
* @param \TypePhp\Entity\ArgInfo $argInfo
* @return string
*/
private function parseCallable(ArgInfo $argInfo): string
{
$name = $argInfo->name;
return $argInfo->nullable
? sprintf("Z_PARAM_FUNC_OR_NULL(%s_fci, %s_fcc)", $name, $name)
: sprintf("Z_PARAM_FUNC(%s_fci, %s_fcc)", $name, $name);
}
/**
* For multiple types, we only handle plain types, mixed, and nullable variables.
* The remaining UnionType and IntersectionType are too complex to be processed via macros.
* @param \TypePhp\Entity\ArgInfo $argInfo
* @return string
*/
private function parseVar(ArgInfo $argInfo): string
{
if ($argInfo->variadic) {
return $this->parseArgs($argInfo);
}
if ($argInfo->byRef) {
return $this->parseArgs($argInfo);
}
$name = $argInfo->name;
if (!$argInfo->typeNode instanceof NullableType) {
return $argInfo->nullable ? "Z_PARAM_ZVAL_OR_NULL($name)" : "Z_PARAM_ZVAL($name)";
}
/**
* Nullable only contains Identifier, FullyQualified, and Relative.
* The Relative option is not supported by the compiler.
*/
$type = $argInfo->typeNode->type;
if ($type instanceof Identifier) {
return match ($type->name) {
"int" => $this->parseInt($argInfo),
"string" => $this->parseString($argInfo),
"array" => $this->parseArray($argInfo),
"bool" => $this->parseBool($argInfo),
"float" => $this->parseFloat($argInfo),
"object" => $this->parseObject($argInfo, $argInfo->class),
/**
* A default case is not strictly required here, since the PHP-supported types are already listed above.
* However, it is included anyway to keep the code robust in case new types are introduced in the future.
*/
default => "Z_PARAM_ZVAL_OR_NULL($name)"
};
}
// FullyQualified
return $this->parseObject($argInfo, $type->toString());
}
}

@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
namespace TypePhp\Generator\ParamTypes;
use PhpParser\Node\Identifier;
use PhpParser\Node\NullableType;
use TypePhp\Entity\ArgInfo;
use TypePhp\Type;
final class ZendVariables
{
/**
* @param array<ArgInfo> $argInfos
* @return array
*/
public function createZendVariables(array $argInfos): array
{
$variables = [];
foreach ($argInfos as $argInfo) {
$variables[] = match ($argInfo->type) {
Type::BOOL => $this->genBoolVariable($argInfo->name),
Type::INT => $this->genIntVariable($argInfo->name),
Type::FLOAT => $this->genFloatVariable($argInfo->name),
Type::ARRAY => $this->genArrayVariable($argInfo->name),
Type::STR => $this->genStringVariable($argInfo->name),
Type::RESOURCE, Type::STREAM => $this->genResourceVariable($argInfo->name),
Type::OBJECT => $this->genObjectVariable($argInfo->name, $argInfo->class),
default => $this->genVarVariable($argInfo),
};
}
return $variables;
}
private function genBoolVariable(string $name): string
{
return sprintf("zend_bool %s = 0; zend_bool %s_is_null = false;", $name, $name);
}
private function genIntVariable(string $name): string
{
return sprintf("zend_long %s = 0; zend_bool %s_is_null = false;", $name, $name);
}
private function genFloatVariable(string $name): string
{
return sprintf("double %s = 0; zend_bool %s_is_null = false;", $name, $name);
}
private function genArrayVariable(string $name): string
{
return sprintf("zval *%s = nullptr;", $name);
}
private function genStringVariable(string $name): string
{
return sprintf("zend_string *%s = nullptr;", $name);
}
private function genArgsVariable(string $name): string
{
return sprintf("zval *%s_argv = nullptr; int %s_argc = 0;", $name, $name);
}
private function genReferenceVariable(string $name): string
{
return sprintf("zval *%s = nullptr;", $name);
}
private function genResourceVariable(string $name): string
{
return sprintf("zval *%s = nullptr;", $name);
}
private function genObjectVariable(string $name, string $className): string
{
if ($className) {
return sprintf(<<<EOL
zend_string *%s_search = zend_string_init("%s", %d, 0);
zend_class_entry *%s_ce = zend_lookup_class(%s_search);
zend_string_release(%s_search);
zval *%s_obj = nullptr;
EOL,
$name, str_replace("\\", "\\\\", $className), strlen($className), $name, $name, $name, $name);
}
return sprintf("zval *%s = nullptr;", $name);
}
/**
* The compiler does not currently support the callable type.
* For now, we'll implement the method first and fill in the callable support later.
* @param string $name
* @return string
*/
private function genCallableVariable(string $name): string
{
return sprintf("zend_fcall_info %s_fci; zend_fcall_info_cache %s_fcc;", $name, $name);
}
private function genVarVariable(ArgInfo $argInfo): string
{
if ($argInfo->variadic) {
return $this->genArgsVariable($argInfo->name);
}
if ($argInfo->byRef) {
return $this->genReferenceVariable($argInfo->name);
}
if (!$argInfo->typeNode instanceof NullableType) {
return sprintf("zval *%s = nullptr;", $argInfo->name);
}
/** @var NullableType $typeNode */
$typeNode = $argInfo->typeNode;
$type = $typeNode->type;
$name = $argInfo->name;
/**
* Nullable only contains Identifier, FullyQualified, and Relative.
* The Relative option is not supported by the compiler.
*/
if ($type instanceof Identifier) {
return match ($type->name) {
'bool' => $this->genBoolVariable($name),
'int' => $this->genIntVariable($name),
'string' => $this->genStringVariable($name),
'float' => $this->genFloatVariable($name),
'array' => $this->genArrayVariable($name),
'object' => $this->genObjectVariable($name, ''),
default => sprintf("zval *%s = nullptr;", $name),
};
}
// FullyQualified
return $this->genObjectVariable($name, $type->toString());
}
}

@ -11,16 +11,21 @@ namespace TypePhp;
use Ajaxray\AnsiKit\AnsiTerminal;
use Ajaxray\AnsiKit\Components\Progressbar;
use MJS\TopSort\Implementations\StringSort;
use PhpParser\Modifiers;
use PhpParser\Node;
use PhpParser\NodeAbstract;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor\NameResolver;
use TypePhp\Analysis\SsaBuilder;
use TypePhp\Backend\CompilerFactory;
use TypePhp\Build\FileScanner;
use TypePhp\Build\NativeCommandOptionsTrait;
use TypePhp\Build\NativeBuilder;
use TypePhp\Build\NativeCommandOptionsTrait;
use TypePhp\Build\PrecompiledHeaderManager;
use TypePhp\Build\ResourceCompilationTrait;
use TypePhp\Build\SourcePipelineTrait;
use TypePhp\Config\ProjectYamlLoader;
use TypePhp\Diagnostics\CompileTimeAttributeDiagnostic;
use TypePhp\Build\ResourceCompilationTrait;
use TypePhp\Entity\ArgInfo;
use TypePhp\Entity\ClassDef;
use TypePhp\Entity\ClassLikeDef;
@ -34,22 +39,17 @@ use TypePhp\Exception\Skip;
use TypePhp\Exception\SyntaxError;
use TypePhp\Generator\DefaultArgumentGenerator;
use TypePhp\Generator\LibraryImportStubGenerator;
use TypePhp\Generator\ParamParser;
use TypePhp\Generator\ParamTypes\ParamParser;
use TypePhp\Generator\Symbol;
use TypePhp\Metadata\Constants;
use TypePhp\Platform\PlatformFactory;
use TypePhp\Platform\Windows;
use TypePhp\Resolver\Reflection;
use TypePhp\Resolver\ClassConstantValueTrait;
use TypePhp\Transform\Visitor;
use TypePhp\Transform\ConstructorLowering;
use TypePhp\Resolver\Reflection;
use TypePhp\Transform\ConstantExpressionValidationVisitor;
use TypePhp\Transform\ConstructorLowering;
use TypePhp\Transform\RuntimeAttributeFactoryLowering;
use PhpParser\Modifiers;
use PhpParser\Node;
use PhpParser\NodeAbstract;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor\NameResolver;
use TypePhp\Transform\Visitor;
class Translator extends Preprocessor
{
@ -3174,9 +3174,6 @@ CODE;
{
$cppCode = '';
$callParams = '';
if ($functionDef->argCountRequired > 0) {
$cppCode .= $this->genWrapperRequiredArgCountCheck($functionDef, $displayName);
}
/**
* 若当前文件声明了 declare(strict_types=1);,则需为当前函数标记 ZEND_ACC_STRICT_TYPES 标志。
@ -3314,6 +3311,10 @@ CODE;
{
$name = $classDef->getNamespacedName();
$cppCode = 'ZEND_METHOD(' . $name . ', ' . $methodDef->name . '){' . PHP_EOL;
$paramParser = new ParamParser();
$cppCode .= $paramParser->createZendParamTypeCheck($methodDef->functionDef->argInfoList);
$cppCode .= $this->getIndent() . Type::OBJECT . ' this_(&execute_data->This);' . PHP_EOL;
$fn = self::PREFIX . $this->getNativeMethodName($classDef, $methodDef);
$implicitMethodArgs = [];
@ -3541,9 +3542,6 @@ CODE;
$code .= $this->genScopeVarDecl();
$code .= "\n";
$paramParser = new ParamParser();
$code .= $paramParser->create($this->functionDef->argInfoList);
// Runtime union/nullable parameter type checks
foreach ($this->functionDef->argInfoList as $i => $argInfo) {
if (!empty($argInfo->typeCheck)) {
@ -4800,6 +4798,10 @@ CODE;
{
$name = $this->escapeZendFnName($functionDef->getNamespacedName());
$cppCode = 'ZEND_FUNCTION(' . $name . '){' . PHP_EOL;
$paramParser = new ParamParser();
$cppCode .= $paramParser->createZendParamTypeCheck($functionDef->argInfoList);
$fn = self::PREFIX . $this->getNativeName($functionDef->name, $functionDef->namespace);
$cppCode .= $this->genWrapperFunctionArgs($fn, $functionDef, $functionDef->getNamespacedName());

Loading…
Cancel
Save