parent
e345b0d82a
commit
0bc91a0d6f
2 changed files with 274 additions and 0 deletions
@ -0,0 +1,237 @@ |
||||
<?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)"; |
||||
} |
||||
} |
||||
Loading…
Reference in new issue