TypePHP 编译器
https://swoole.com/aot/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
783 lines
29 KiB
783 lines
29 KiB
<?php
|
|
|
|
namespace TypePhp\Python;
|
|
|
|
use PhpParser\Node;
|
|
use PhpParser\Node\Expr;
|
|
use PhpParser\NodeAbstract;
|
|
use TypePhp\Type;
|
|
|
|
trait PythonModuleTrait
|
|
{
|
|
/** @var array<string, string> TypePHP constructor sugar to the existing phpy facade class. */
|
|
private const PYTHON_CONSTRUCTOR_CLASSES = [
|
|
'list' => 'PyList',
|
|
'dict' => 'PyDict',
|
|
'tuple' => 'PyTuple',
|
|
'set' => 'PySet',
|
|
'str' => 'PyStr',
|
|
'object' => 'PyObject',
|
|
];
|
|
|
|
/** @var array<string, string> Python calls whose precise phpy wrapper is known statically. */
|
|
private const PYTHON_BUILTIN_RETURN_CLASSES = [
|
|
...self::PYTHON_CONSTRUCTOR_CLASSES,
|
|
'int' => 'PyObject',
|
|
'float' => 'PyObject',
|
|
'bytes' => 'PyObject',
|
|
'type' => 'PyType',
|
|
];
|
|
|
|
/** @var array<string, true> Builtins exposed as explicit PyCore methods. */
|
|
private const PYTHON_CORE_FUNCTIONS = [
|
|
'int' => true,
|
|
'float' => true,
|
|
'bytes' => true,
|
|
'scalar' => true,
|
|
];
|
|
|
|
/** @var array<string, string> Builtins with a direct phpy Native ABI constructor. */
|
|
private const PYTHON_NATIVE_CONSTRUCTORS = [
|
|
'list' => 'List',
|
|
'dict' => 'Dict',
|
|
'tuple' => 'Tuple',
|
|
'set' => 'Set',
|
|
'str' => 'Str',
|
|
'object' => 'Object',
|
|
'int' => 'Int',
|
|
'float' => 'Float',
|
|
'bytes' => 'Bytes',
|
|
];
|
|
|
|
/** @var array<string, int> Case-sensitive Python module name to generated slot ID. */
|
|
protected array $pythonModuleMap = [];
|
|
|
|
protected int $pythonModuleIndex = 0;
|
|
|
|
protected bool $pythonRuntimeUsed = false;
|
|
|
|
/** Return true when the expression is statically known to hold a phpy proxy. */
|
|
protected function isPythonObjectExpr(NodeAbstract $expr): bool
|
|
{
|
|
$class = $this->detectClassOfExpr($expr);
|
|
if ($class === '') {
|
|
return false;
|
|
}
|
|
return strcasecmp($class, 'PyObject') === 0
|
|
|| $this->isObjectClassStaticallyAssignableTo($class, 'PyObject');
|
|
}
|
|
|
|
/**
|
|
* Python members are resolved by the Python VM. Only methods explicitly
|
|
* registered on the phpy wrapper may use a cached Zend method pointer;
|
|
* every other name must reach PyObject::__call().
|
|
*/
|
|
protected function isPythonDynamicMethodCall(NodeAbstract $receiver, string $method): bool
|
|
{
|
|
if (!$this->isPythonObjectExpr($receiver)) {
|
|
return false;
|
|
}
|
|
|
|
$class = $this->detectClassOfExpr($receiver);
|
|
return $class === '' || !\TypePhp\Resolver\Reflection::hasMethod($class, $method);
|
|
}
|
|
|
|
protected function parsePythonNativeFacadeMethodCall(Expr\MethodCall $expr, string $receiver): ?string
|
|
{
|
|
if (!$this->isNamedMethod($expr->name) || !$this->isPythonObjectExpr($expr->var)) {
|
|
return null;
|
|
}
|
|
$method = $expr->name->toString();
|
|
$helper = match ($method) {
|
|
'toValue' => 'toValue',
|
|
'toArray' => 'toArray',
|
|
default => null,
|
|
};
|
|
if ($helper === null) {
|
|
return null;
|
|
}
|
|
if ($expr->args !== []) {
|
|
$this->fatalError($expr, "The {$method} method does not accept parameters");
|
|
}
|
|
$this->markPythonRuntimeUsed();
|
|
return 'php::python::' . $helper . '(' . $receiver . ')';
|
|
}
|
|
|
|
protected function parsePythonObjectCall(Expr\FuncCall $expr): ?string
|
|
{
|
|
if (!$expr->name instanceof NodeAbstract || !$this->isPythonObjectExpr($expr->name)) {
|
|
return null;
|
|
}
|
|
if ($expr->isFirstClassCallable()) {
|
|
$this->fatalError($expr, 'Python objects do not support first-class callable syntax yet');
|
|
}
|
|
|
|
$receiver = $this->addTmpVar(Type::VAR);
|
|
$this->context->beforeStmtLines[] = $receiver . ' = ' . $this->parseExpr($expr->name) . ';';
|
|
if ($expr->args === []) {
|
|
return 'php::python::call(' . $receiver . ')';
|
|
}
|
|
return 'php::python::call(' . $receiver . ', ' . $this->parseCallArgs($expr->args) . ')';
|
|
}
|
|
|
|
protected function parsePythonObjectPropertyFetch(Expr\PropertyFetch $expr): ?string
|
|
{
|
|
if ($this->isPropertyFetchUpdate($expr)
|
|
|| !$this->isIdExpr($expr->name)
|
|
|| !$this->isPythonObjectExpr($expr->var)) {
|
|
return null;
|
|
}
|
|
|
|
return 'php::python::getAttr(' . $this->parseIdentifier($expr->var) . ', '
|
|
. $this->propertyNameToStr($expr->name, literal: true) . ')';
|
|
}
|
|
|
|
protected function getPythonBinaryOperator(Expr\BinaryOp $expr): ?string
|
|
{
|
|
return match (true) {
|
|
$expr instanceof Expr\BinaryOp\Plus => 'add',
|
|
$expr instanceof Expr\BinaryOp\Minus => 'sub',
|
|
$expr instanceof Expr\BinaryOp\Mul => 'mul',
|
|
$expr instanceof Expr\BinaryOp\Div => 'truediv',
|
|
$expr instanceof Expr\BinaryOp\Mod => 'mod',
|
|
$expr instanceof Expr\BinaryOp\Pow => 'pow',
|
|
$expr instanceof Expr\BinaryOp\ShiftLeft => 'lshift',
|
|
$expr instanceof Expr\BinaryOp\ShiftRight => 'rshift',
|
|
$expr instanceof Expr\BinaryOp\BitwiseAnd => 'and_',
|
|
$expr instanceof Expr\BinaryOp\BitwiseOr => 'or_',
|
|
$expr instanceof Expr\BinaryOp\BitwiseXor => 'xor',
|
|
$expr instanceof Expr\BinaryOp\Equal => 'eq',
|
|
$expr instanceof Expr\BinaryOp\NotEqual => 'ne',
|
|
$expr instanceof Expr\BinaryOp\Smaller => 'lt',
|
|
$expr instanceof Expr\BinaryOp\SmallerOrEqual => 'le',
|
|
$expr instanceof Expr\BinaryOp\Greater => 'gt',
|
|
$expr instanceof Expr\BinaryOp\GreaterOrEqual => 'ge',
|
|
$expr instanceof Expr\BinaryOp\Identical => 'is_',
|
|
$expr instanceof Expr\BinaryOp\NotIdentical => 'is_not',
|
|
default => null,
|
|
};
|
|
}
|
|
|
|
protected function isPythonBinaryOperatorExpr(NodeAbstract $expr): bool
|
|
{
|
|
return $expr instanceof Expr\BinaryOp
|
|
&& $this->getPythonBinaryOperator($expr) !== null
|
|
&& ($this->isPythonObjectExpr($expr->left) || $this->isPythonObjectExpr($expr->right));
|
|
}
|
|
|
|
protected function pythonOperatorReturnsBool(Expr\BinaryOp $expr): bool
|
|
{
|
|
return $expr instanceof Expr\BinaryOp\Equal
|
|
|| $expr instanceof Expr\BinaryOp\NotEqual
|
|
|| $expr instanceof Expr\BinaryOp\Smaller
|
|
|| $expr instanceof Expr\BinaryOp\SmallerOrEqual
|
|
|| $expr instanceof Expr\BinaryOp\Greater
|
|
|| $expr instanceof Expr\BinaryOp\GreaterOrEqual
|
|
|| $expr instanceof Expr\BinaryOp\Identical
|
|
|| $expr instanceof Expr\BinaryOp\NotIdentical;
|
|
}
|
|
|
|
/**
|
|
* Lower a Python operation through the standard operator module.
|
|
* Braced ArgList elements are sequenced by C++17; call operands that can
|
|
* emit statements are materialized first by parseOrderedOperand().
|
|
*/
|
|
protected function parsePythonBinaryOperator(Expr\BinaryOp $expr): ?string
|
|
{
|
|
if (!$this->isPythonBinaryOperatorExpr($expr)) {
|
|
return null;
|
|
}
|
|
|
|
$left = $this->parseOrderedOperand($expr->left, false);
|
|
$right = $this->parseOrderedOperand($expr->right, false);
|
|
$call = 'php::python::callMember(' . $this->getPythonModuleExpression('operator')
|
|
. ', ' . $this->getLiteralString($this->getPythonBinaryOperator($expr))
|
|
. ', php::ArgList{' . $left . ', ' . $right . '})';
|
|
|
|
return $this->pythonOperatorReturnsBool($expr)
|
|
? $this->convertPythonResultToBool($call)
|
|
: $call;
|
|
}
|
|
|
|
protected function convertPythonResultToBool(string $expression): string
|
|
{
|
|
$this->markPythonRuntimeUsed();
|
|
$scalar = 'php::python::toValue(' . $expression . ')';
|
|
return 'php::toBool(' . $scalar . ')';
|
|
}
|
|
|
|
protected function detectPythonOperatorReturnType(NodeAbstract $expr): ?string
|
|
{
|
|
if ($this->isPythonBinaryOperatorExpr($expr)) {
|
|
return $this->pythonOperatorReturnsBool($expr) ? Type::BOOL : Type::OBJECT;
|
|
}
|
|
if ($this->isPythonUnaryOperatorExpr($expr)) {
|
|
return $expr instanceof Expr\BooleanNot ? Type::BOOL : Type::OBJECT;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
protected function detectPythonOperatorReturnClass(NodeAbstract $expr): ?string
|
|
{
|
|
if ($this->isPythonBinaryOperatorExpr($expr)) {
|
|
return $this->pythonOperatorReturnsBool($expr) ? null : 'PyObject';
|
|
}
|
|
if ($this->isPythonUnaryOperatorExpr($expr)) {
|
|
return $expr instanceof Expr\BooleanNot ? null : 'PyObject';
|
|
}
|
|
return null;
|
|
}
|
|
|
|
protected function getPythonUnaryOperator(NodeAbstract $expr): ?string
|
|
{
|
|
return match (true) {
|
|
$expr instanceof Expr\UnaryMinus => 'neg',
|
|
$expr instanceof Expr\UnaryPlus => 'pos',
|
|
$expr instanceof Expr\BitwiseNot => 'invert',
|
|
$expr instanceof Expr\BooleanNot => 'not_',
|
|
default => null,
|
|
};
|
|
}
|
|
|
|
protected function isPythonUnaryOperatorExpr(NodeAbstract $expr): bool
|
|
{
|
|
return $this->getPythonUnaryOperator($expr) !== null
|
|
&& $this->isPythonObjectExpr($expr->expr);
|
|
}
|
|
|
|
protected function parsePythonUnaryOperator(NodeAbstract $expr): ?string
|
|
{
|
|
if (!$this->isPythonUnaryOperatorExpr($expr)) {
|
|
return null;
|
|
}
|
|
|
|
$operand = $this->parseOrderedOperand($expr->expr, false);
|
|
$call = 'php::python::callMember(' . $this->getPythonModuleExpression('operator')
|
|
. ', ' . $this->getLiteralString($this->getPythonUnaryOperator($expr))
|
|
. ', php::ArgList{' . $operand . '})';
|
|
return $expr instanceof Expr\BooleanNot ? $this->convertPythonResultToBool($call) : $call;
|
|
}
|
|
|
|
protected function convertPythonObjectToBool(NodeAbstract $expr, string $parsed): ?string
|
|
{
|
|
if (!$this->isPythonObjectExpr($expr)) {
|
|
return null;
|
|
}
|
|
$call = 'php::python::callMember(' . $this->getPythonModuleExpression('operator')
|
|
. ', ' . $this->getLiteralString('truth') . ', php::ArgList{' . $parsed . '})';
|
|
return $this->convertPythonResultToBool($call);
|
|
}
|
|
|
|
protected function getPythonAssignOperator(Expr\AssignOp $expr): ?string
|
|
{
|
|
return match (true) {
|
|
$expr instanceof Expr\AssignOp\Plus => 'iadd',
|
|
$expr instanceof Expr\AssignOp\Minus => 'isub',
|
|
$expr instanceof Expr\AssignOp\Mul => 'imul',
|
|
$expr instanceof Expr\AssignOp\Div => 'itruediv',
|
|
$expr instanceof Expr\AssignOp\Mod => 'imod',
|
|
$expr instanceof Expr\AssignOp\Pow => 'ipow',
|
|
$expr instanceof Expr\AssignOp\ShiftLeft => 'ilshift',
|
|
$expr instanceof Expr\AssignOp\ShiftRight => 'irshift',
|
|
$expr instanceof Expr\AssignOp\BitwiseAnd => 'iand',
|
|
$expr instanceof Expr\AssignOp\BitwiseOr => 'ior',
|
|
$expr instanceof Expr\AssignOp\BitwiseXor => 'ixor',
|
|
default => null,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Lower Python compound assignments through operator.i*(). The target
|
|
* receiver and key are materialized before the RHS, then the returned
|
|
* Python object is written back through the original PHP lvalue protocol.
|
|
*/
|
|
protected function parsePythonAssignOperator(Expr\AssignOp $expr): ?string
|
|
{
|
|
$method = $this->getPythonAssignOperator($expr);
|
|
if ($method === null || !$this->isPythonObjectExpr($expr->var)) {
|
|
return null;
|
|
}
|
|
|
|
$writeBack = null;
|
|
if ($this->isVarExpr($expr->var)) {
|
|
$left = $this->parseWritableIdentifier($expr->var);
|
|
$writeBack = static fn(string $value): string => $left . ' = ' . $value;
|
|
} elseif ($expr->var instanceof Expr\ArrayDimFetch) {
|
|
if ($expr->var->dim === null) {
|
|
$this->fatalError($expr->var, 'Cannot use [] for a Python compound assignment');
|
|
}
|
|
$container = $this->parseOrderedOperand($expr->var->var, false);
|
|
$key = $this->parseOrderedOperand($expr->var->dim, false);
|
|
$left = $container . '.item(' . $key . ', false)';
|
|
$writeBack = static fn(string $value): string => $container . '.offsetSet(' . $key . ', ' . $value . ')';
|
|
} elseif ($expr->var instanceof Expr\PropertyFetch && $this->isIdExpr($expr->var->name)) {
|
|
$receiver = $this->parseOrderedOperand($expr->var->var, false);
|
|
$property = $this->propertyNameToStr($expr->var->name, literal: true);
|
|
$left = 'php::python::getAttr(' . $receiver . ', ' . $property . ')';
|
|
$writeBack = static fn(string $value): string => 'typephp_write_property_scoped('
|
|
. $receiver . ', ' . $property . ', ' . $value . ', nullptr)';
|
|
} else {
|
|
return null;
|
|
}
|
|
|
|
$right = $this->parseOrderedOperand($expr->expr, false);
|
|
$call = 'php::python::callMember(' . $this->getPythonModuleExpression('operator')
|
|
. ', ' . $this->getLiteralString($method)
|
|
. ', php::ArgList{' . $left . ', ' . $right . '})';
|
|
if ($this->isVarExpr($expr->var)) {
|
|
return $writeBack($call);
|
|
}
|
|
|
|
$result = $this->addTmpVar(Type::OBJECT);
|
|
return '((' . $result . ' = ' . $call . ', ' . $writeBack($result) . '), ' . $result . ')';
|
|
}
|
|
|
|
/**
|
|
* Resolve a PHP name as a Python module only after normal namespace resolution.
|
|
*/
|
|
protected function resolvePythonModule(NodeAbstract $class): ?string
|
|
{
|
|
if (!$this->isNameExpr($class)) {
|
|
return null;
|
|
}
|
|
|
|
$parts = $this->resolvePythonRootNameParts($class);
|
|
if ($parts === null) {
|
|
return null;
|
|
}
|
|
if (count($parts) < 2 || $parts[1] === '') {
|
|
$this->fatalError($class, 'The special `python` namespace must be followed by a module name');
|
|
}
|
|
|
|
// Preserve the case of every Python symbol after the special root.
|
|
return implode('.', array_slice($parts, 1));
|
|
}
|
|
|
|
/**
|
|
* Resolve a source name according to PHP namespace rules before checking the Python root.
|
|
*
|
|
* @return list<string>|null
|
|
*/
|
|
protected function resolvePythonRootNameParts(NodeAbstract $name): ?array
|
|
{
|
|
if (!$this->isNameExpr($name)) {
|
|
return null;
|
|
}
|
|
|
|
$sourceName = trim($this->parseIdentifier($name), '\\');
|
|
$resolvedName = $name->getAttribute('resolvedName');
|
|
if ($resolvedName instanceof Node\Name) {
|
|
$resolved = $resolvedName->toString();
|
|
} elseif ($name instanceof Node\Name\FullyQualified || $this->namespace === '') {
|
|
$resolved = $sourceName;
|
|
} else {
|
|
$resolved = $this->namespace . '\\' . $sourceName;
|
|
}
|
|
|
|
$parts = explode('\\', trim($resolved, '\\'));
|
|
if (strcasecmp($parts[0] ?? '', 'python') !== 0) {
|
|
return null;
|
|
}
|
|
return $parts;
|
|
}
|
|
|
|
protected function getPythonModuleId(string $module): int
|
|
{
|
|
if (isset($this->pythonModuleMap[$module])) {
|
|
return $this->pythonModuleMap[$module];
|
|
}
|
|
|
|
$id = $this->pythonModuleIndex++;
|
|
$this->pythonModuleMap[$module] = $id;
|
|
|
|
$this->markPythonRuntimeUsed();
|
|
$this->getLiteralString($module);
|
|
|
|
return $id;
|
|
}
|
|
|
|
protected function markPythonRuntimeUsed(): void
|
|
{
|
|
if ($this->pythonRuntimeUsed) {
|
|
return;
|
|
}
|
|
$this->pythonRuntimeUsed = true;
|
|
|
|
}
|
|
|
|
protected function withPythonRuntimeConfigured(string $expression): string
|
|
{
|
|
return '(' . self::PREFIX . 'configure_python_runtime(), ' . $expression . ')';
|
|
}
|
|
|
|
protected function getPythonModuleExpression(string $module): string
|
|
{
|
|
return 'php_get_python_module(' . $this->getPythonModuleId($module) . ', '
|
|
. $this->getLiteralString($module) . ')';
|
|
}
|
|
|
|
protected function resolvePythonBuiltinName(NodeAbstract $name): ?string
|
|
{
|
|
if (!$this->isNameExpr($name) && !$this->isFullNameExpr($name)) {
|
|
return null;
|
|
}
|
|
$parts = $this->resolvePythonRootNameParts($name);
|
|
if ($parts === null) {
|
|
return null;
|
|
}
|
|
if (count($parts) !== 2 || $parts[1] === '') {
|
|
return null;
|
|
}
|
|
return $parts[1];
|
|
}
|
|
|
|
/**
|
|
* Resolve namespace syntax into a Python module and one of its members.
|
|
*
|
|
* @return array{module: string, member: string}|null
|
|
*/
|
|
protected function resolvePythonModuleMember(NodeAbstract $name): ?array
|
|
{
|
|
if (!$this->isNameExpr($name)) {
|
|
return null;
|
|
}
|
|
|
|
$parts = $this->resolvePythonRootNameParts($name);
|
|
if ($parts === null || count($parts) < 3) {
|
|
return null;
|
|
}
|
|
|
|
$member = array_pop($parts);
|
|
return [
|
|
'module' => implode('.', array_slice($parts, 1)),
|
|
'member' => $member,
|
|
];
|
|
}
|
|
|
|
protected function parsePythonFunctionCall(Expr\FuncCall $expr): ?string
|
|
{
|
|
$moduleMember = $this->resolvePythonModuleMember($expr->name);
|
|
if ($moduleMember !== null) {
|
|
if ($expr->isFirstClassCallable()) {
|
|
$this->fatalError($expr, 'Python module callables do not support first-class callable syntax yet');
|
|
}
|
|
|
|
$target = $this->getPythonModuleExpression($moduleMember['module']);
|
|
$member = $this->getLiteralString($moduleMember['member']);
|
|
if ($expr->args === []) {
|
|
return 'php::python::callMember(' . $target . ', ' . $member . ')';
|
|
}
|
|
return 'php::python::callMember(' . $target . ', ' . $member . ', '
|
|
. $this->parseCallArgs($expr->args) . ')';
|
|
}
|
|
|
|
$builtin = $this->resolvePythonBuiltinName($expr->name);
|
|
if ($builtin === null) {
|
|
return null;
|
|
}
|
|
if ($expr->isFirstClassCallable()) {
|
|
$this->fatalError($expr, 'Python builtins do not support first-class callable syntax yet');
|
|
}
|
|
$this->markPythonRuntimeUsed();
|
|
|
|
if ($builtin === 'scalar') {
|
|
if (count($expr->args) !== 1 || $expr->args[0]->name !== null || $expr->args[0]->unpack) {
|
|
$this->fatalError($expr, 'python\\scalar() expects exactly one positional argument');
|
|
}
|
|
return 'php::python::toValue(' . $this->parseCallArgValue($expr->args[0]) . ')';
|
|
}
|
|
|
|
$nativeConstructor = self::PYTHON_NATIVE_CONSTRUCTORS[$builtin] ?? null;
|
|
if ($nativeConstructor !== null && $this->canUsePythonNativeConstructor($expr)) {
|
|
$constructor = 'php::python::Constructor::' . $nativeConstructor;
|
|
$call = $expr->args === []
|
|
? 'php::python::construct(' . $constructor . ')'
|
|
: 'php::python::construct(' . $constructor . ', '
|
|
. $this->parseCallArgValue($expr->args[0]) . ')';
|
|
return $this->withPythonRuntimeConfigured($call);
|
|
}
|
|
|
|
// This is a deliberately closed map. In particular, PyDict's PHP-array
|
|
// constructor is not equivalent to Python's dict(iterable) builtin.
|
|
$constructorClass = self::PYTHON_CONSTRUCTOR_CLASSES[$builtin] ?? null;
|
|
if ($constructorClass !== null) {
|
|
$classEntry = $this->getClassEntryPtr($constructorClass);
|
|
if ($expr->args === []) {
|
|
return $this->withPythonRuntimeConfigured('php::newObject(' . $classEntry . ')');
|
|
}
|
|
return $this->withPythonRuntimeConfigured(
|
|
'php::newObject(' . $classEntry . ', '
|
|
. $this->parseCallArgs($expr->args, '__construct', $constructorClass) . ')'
|
|
);
|
|
}
|
|
|
|
// Explicit PyCore methods either preserve a Python wrapper by design,
|
|
// or (`scalar`) explicitly leave Python's object-preserving rules.
|
|
if (isset(self::PYTHON_CORE_FUNCTIONS[$builtin])) {
|
|
$callable = $this->getClassEntryPtr('PyCore') . ', '
|
|
. $this->getMethodPtr('PyCore', $builtin);
|
|
if ($expr->args === []) {
|
|
return $this->withPythonRuntimeConfigured('php::call(' . $callable . ')');
|
|
}
|
|
return $this->withPythonRuntimeConfigured(
|
|
$this->genRuntimeFunctionCall($callable, $expr->args, $builtin, 'PyCore')
|
|
);
|
|
}
|
|
|
|
$target = $this->getPythonModuleExpression('builtins');
|
|
$name = $this->getLiteralString($builtin);
|
|
if ($expr->args === []) {
|
|
return 'php::python::callMember(' . $target . ', ' . $name . ')';
|
|
}
|
|
return 'php::python::callMember(' . $target . ', ' . $name . ', '
|
|
. $this->parseCallArgs($expr->args) . ')';
|
|
}
|
|
|
|
/**
|
|
* The Native ABI has a deliberately small zero/one positional argument
|
|
* constructor path. Keep named and unpacked calls on Zend so its regular
|
|
* argument validation and unpacking semantics remain unchanged.
|
|
*/
|
|
private function canUsePythonNativeConstructor(Expr\FuncCall $expr): bool
|
|
{
|
|
if (count($expr->args) > 1) {
|
|
return false;
|
|
}
|
|
foreach ($expr->args as $arg) {
|
|
if ($arg->name !== null || $arg->unpack) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
protected function parsePythonModuleAttributeFetch(Expr\ConstFetch $expr, bool $constantExpression): ?string
|
|
{
|
|
$moduleMember = $this->resolvePythonModuleMember($expr->name);
|
|
if ($moduleMember === null) {
|
|
return null;
|
|
}
|
|
if ($constantExpression) {
|
|
$this->fatalError($expr, 'Python module attributes cannot be used in constant expressions');
|
|
}
|
|
|
|
return 'php::python::getAttr(' . $this->getPythonModuleExpression($moduleMember['module'])
|
|
. ', ' . $this->getLiteralString($moduleMember['member']) . ')';
|
|
}
|
|
|
|
protected function detectPythonExpressionReturnType(NodeAbstract $expr): ?string
|
|
{
|
|
if ($expr instanceof Expr\ConstFetch && $this->resolvePythonModuleMember($expr->name) !== null) {
|
|
return Type::OBJECT;
|
|
}
|
|
if ($expr instanceof Expr\MethodCall && $this->isPythonObjectExpr($expr->var)) {
|
|
if (!$this->isIdExpr($expr->name)
|
|
|| $this->isPythonDynamicMethodCall($expr->var, $this->parseIdentifier($expr->name))
|
|
) {
|
|
return Type::OBJECT;
|
|
}
|
|
return null;
|
|
}
|
|
if ($expr instanceof Expr\PropertyFetch && $this->isPythonObjectExpr($expr->var)) {
|
|
return Type::OBJECT;
|
|
}
|
|
if ($expr instanceof Expr\ArrayDimFetch && $this->isPythonObjectExpr($expr->var)) {
|
|
return Type::OBJECT;
|
|
}
|
|
if ($expr instanceof Expr\FuncCall
|
|
&& $expr->name instanceof NodeAbstract
|
|
&& !$this->isNameExpr($expr->name)
|
|
&& $this->isPythonObjectExpr($expr->name)
|
|
) {
|
|
return Type::OBJECT;
|
|
}
|
|
if (!$expr instanceof Expr\FuncCall) {
|
|
return null;
|
|
}
|
|
if ($this->resolvePythonModuleMember($expr->name) !== null) {
|
|
return Type::OBJECT;
|
|
}
|
|
$builtin = $this->resolvePythonBuiltinName($expr->name);
|
|
if ($builtin === null) {
|
|
return null;
|
|
}
|
|
if ($builtin === 'scalar') {
|
|
return Type::VAR;
|
|
}
|
|
return Type::OBJECT;
|
|
}
|
|
|
|
protected function detectPythonExpressionReturnClass(NodeAbstract $expr): ?string
|
|
{
|
|
if ($expr instanceof Expr\ConstFetch && $this->resolvePythonModuleMember($expr->name) !== null) {
|
|
return 'PyObject';
|
|
}
|
|
if ($expr instanceof Expr\MethodCall && $this->isPythonObjectExpr($expr->var)) {
|
|
if (!$this->isIdExpr($expr->name)
|
|
|| $this->isPythonDynamicMethodCall($expr->var, $this->parseIdentifier($expr->name))
|
|
) {
|
|
return 'PyObject';
|
|
}
|
|
return null;
|
|
}
|
|
if ($expr instanceof Expr\PropertyFetch && $this->isPythonObjectExpr($expr->var)) {
|
|
return 'PyObject';
|
|
}
|
|
if ($expr instanceof Expr\ArrayDimFetch && $this->isPythonObjectExpr($expr->var)) {
|
|
return 'PyObject';
|
|
}
|
|
if ($expr instanceof Expr\FuncCall
|
|
&& $expr->name instanceof NodeAbstract
|
|
&& !$this->isNameExpr($expr->name)
|
|
&& $this->isPythonObjectExpr($expr->name)
|
|
) {
|
|
return 'PyObject';
|
|
}
|
|
if (!$expr instanceof Expr\FuncCall) {
|
|
return null;
|
|
}
|
|
if ($this->resolvePythonModuleMember($expr->name) !== null) {
|
|
return 'PyObject';
|
|
}
|
|
$builtin = $this->resolvePythonBuiltinName($expr->name);
|
|
if ($builtin === null) {
|
|
return null;
|
|
}
|
|
if ($builtin === 'scalar') {
|
|
return null;
|
|
}
|
|
return self::PYTHON_BUILTIN_RETURN_CLASSES[$builtin] ?? 'PyObject';
|
|
}
|
|
|
|
protected function parsePythonModuleStaticCall(Expr\StaticCall $expr): ?string
|
|
{
|
|
if (!$this->isIdExpr($expr->name)) {
|
|
return null;
|
|
}
|
|
$module = $this->resolvePythonModule($expr->class);
|
|
if ($module === null) {
|
|
return null;
|
|
}
|
|
|
|
$alias = $this->parseIdentifier($expr->class);
|
|
$method = $this->parseIdentifier($expr->name);
|
|
$this->fatalError(
|
|
$expr,
|
|
"Python module callable `{$alias}::{$method}()` must use `{$alias}\\{$method}()`",
|
|
);
|
|
}
|
|
|
|
protected function parsePythonModuleStaticPropertyFetch(Expr\StaticPropertyFetch $expr): ?string
|
|
{
|
|
if (!$this->isIdExpr($expr->name)) {
|
|
return null;
|
|
}
|
|
$module = $this->resolvePythonModule($expr->class);
|
|
if ($module === null) {
|
|
return null;
|
|
}
|
|
|
|
$alias = $this->parseIdentifier($expr->class);
|
|
$attribute = $this->parseIdentifier($expr->name);
|
|
$this->fatalError(
|
|
$expr,
|
|
"Python module attribute `{$alias}::\${$attribute}` must use `{$alias}\\{$attribute}`",
|
|
);
|
|
}
|
|
|
|
protected function rejectPythonModuleClassConstantFetch(Expr\ClassConstFetch $expr): void
|
|
{
|
|
if (!$this->isIdExpr($expr->name) || $this->resolvePythonModule($expr->class) === null) {
|
|
return;
|
|
}
|
|
|
|
$alias = $this->parseIdentifier($expr->class);
|
|
$member = $this->parseIdentifier($expr->name);
|
|
$this->fatalError(
|
|
$expr,
|
|
"Python module member `{$alias}::{$member}` must use `{$alias}\\{$member}`",
|
|
);
|
|
}
|
|
|
|
protected function genPythonModuleDataDeclarations(): string
|
|
{
|
|
if (!$this->pythonRuntimeUsed) {
|
|
return '';
|
|
}
|
|
|
|
$code = 'extern THREAD_LOCAL bool ' . self::PREFIX . 'python_runtime_configured;' . PHP_EOL
|
|
. 'void ' . self::PREFIX . 'configure_python_runtime();' . PHP_EOL;
|
|
if ($this->pythonModuleMap !== []) {
|
|
$code .= 'extern THREAD_LOCAL zval ' . self::PREFIX . 'python_module_map['
|
|
. count($this->pythonModuleMap) . '];' . PHP_EOL
|
|
. 'php::Object ' . self::PREFIX
|
|
. 'get_python_module(int module_id, const php::Str &module_name);' . PHP_EOL;
|
|
}
|
|
return $code;
|
|
}
|
|
|
|
protected function genPythonModuleStorage(): string
|
|
{
|
|
if (!$this->pythonRuntimeUsed) {
|
|
return '';
|
|
}
|
|
|
|
$code = "// python runtime \n"
|
|
. 'THREAD_LOCAL bool ' . self::PREFIX . 'python_runtime_configured = false;' . PHP_EOL;
|
|
if ($this->pythonModuleMap !== []) {
|
|
$code .= 'THREAD_LOCAL zval ' . self::PREFIX . 'python_module_map['
|
|
. count($this->pythonModuleMap) . ']{};' . PHP_EOL;
|
|
}
|
|
return $code;
|
|
}
|
|
|
|
protected function genPythonModuleGetter(): string
|
|
{
|
|
if (!$this->pythonRuntimeUsed) {
|
|
return '';
|
|
}
|
|
|
|
$code = 'void ' . self::PREFIX . 'configure_python_runtime() {' . PHP_EOL
|
|
. 'if (EXPECTED(' . self::PREFIX . 'python_runtime_configured)) {' . PHP_EOL
|
|
. 'return;' . PHP_EOL
|
|
. '}' . PHP_EOL
|
|
. 'php::python::configureRuntime(true);' . PHP_EOL
|
|
. self::PREFIX . 'python_runtime_configured = true;' . PHP_EOL
|
|
. '}' . PHP_EOL . PHP_EOL;
|
|
|
|
if ($this->pythonModuleMap === []) {
|
|
return $code;
|
|
}
|
|
|
|
return $code
|
|
. 'php::Object ' . self::PREFIX
|
|
. 'get_python_module(int module_id, const php::Str &module_name) {' . PHP_EOL
|
|
. self::PREFIX . 'configure_python_runtime();' . PHP_EOL
|
|
. 'zval *module = &' . self::PREFIX . 'python_module_map[module_id];' . PHP_EOL
|
|
. 'if (UNEXPECTED(Z_ISUNDEF_P(module))) {' . PHP_EOL
|
|
. 'php::Variant imported = php::python::importModule(module_name);' . PHP_EOL
|
|
. '(void) php::Object(imported);' . PHP_EOL
|
|
. 'imported.moveTo(module);' . PHP_EOL
|
|
. '}' . PHP_EOL
|
|
. 'return php::Object(module);' . PHP_EOL
|
|
. '}' . PHP_EOL . PHP_EOL;
|
|
}
|
|
|
|
protected function genPythonModuleCleanup(): string
|
|
{
|
|
if (!$this->pythonRuntimeUsed) {
|
|
return '';
|
|
}
|
|
|
|
$code = '';
|
|
if ($this->pythonModuleMap !== []) {
|
|
$code .= 'for (zval &module : ' . self::PREFIX . 'python_module_map) {' . PHP_EOL
|
|
. 'if (!Z_ISUNDEF(module)) {' . PHP_EOL
|
|
. 'zval_ptr_dtor(&module);' . PHP_EOL
|
|
. 'ZVAL_UNDEF(&module);' . PHP_EOL
|
|
. '}' . PHP_EOL
|
|
. '}' . PHP_EOL;
|
|
}
|
|
return $code . self::PREFIX . 'python_runtime_configured = false;' . PHP_EOL;
|
|
}
|
|
}
|
|
|