diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 4f35f874..2ec5eaf3 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -27,7 +27,7 @@ use PhpAot\Php\Generator\ClosureGenerator; use PhpAot\Php\Generator\PlaceHolderGenerator; use PhpAot\Php\Generator\PropertyPromotion; use PhpAot\Php\Generator\Utils; -use PhpAot\Php\Parser\StdArrayParser; +use PhpAot\Php\Parser\StdContainerParser; use PhpAot\Php\Platform\Linux; use PhpAot\Php\Platform\Macos; use PhpAot\Php\Platform\PlatformBase; @@ -58,7 +58,7 @@ class CompilerBase extends \PhpAot\Core\Translator use PlaceHolderGenerator; use PropertyPromotion; use MagicMethodDetector; - use StdArrayParser; + use StdContainerParser; use Utils; public const string TYPE_VAR = 'php::Var'; @@ -71,6 +71,9 @@ class CompilerBase extends \PhpAot\Core\Translator public const string TYPE_OBJECT = 'php::Object'; public const string TYPE_ARRAY = 'php::Array'; public const string TYPE_STD_ARRAY = 'php::StdArray'; + public const string TYPE_STD_VECTOR = 'php::StdVector'; + public const string TYPE_STD_MAP = 'php::StdMap'; + public const string TYPE_STD_UNORDERED_MAP = 'php::StdUnorderedMap'; public const string TYPE_ARGS = 'php::Args'; public const string TYPE_STR = 'php::Str'; public const string TYPE_REF = 'php::Ref'; @@ -1529,15 +1532,27 @@ class CompilerBase extends \PhpAot\Core\Translator } elseif ($this->isStaticCall($right) and $this->isNameExpr($right->class) and $this->isIdExpr($right->name)) { $class = $this->parseIdentifier($right->class); if ($class === 'std') { - if ($right->name->toString() === 'array') { + if (in_array($right->name->toString(), ['array', 'vector', 'map', 'unordered_map'], true)) { if ($this->hasVar($var)) { - $this->fatalError($left, "Cannot re-assign `\${$var}` to std::array"); + $this->fatalError($left, "Cannot re-assign `\${$var}` to std::{$right->name->toString()}"); } if ($this->context->scopeLevel > 1) { - $this->fatalError($left, 'Must create std::array in the top-level scope of the function'); + $this->fatalError($left, "Must create std::{$right->name->toString()} in the top-level scope of the function"); } - $this->addLocalVar($var, self::TYPE_STD_ARRAY); - return $this->parseStdArray($var, $right); + if ($right->name->toString() === 'array') { + $this->addLocalVar($var, self::TYPE_STD_ARRAY); + return $this->parseStdArray($var, $right); + } + if ($right->name->toString() === 'vector') { + $this->addLocalVar($var, self::TYPE_STD_VECTOR); + return $this->parseStdVector($var, $right); + } + if ($right->name->toString() === 'map') { + $this->addLocalVar($var, self::TYPE_STD_MAP); + return $this->parseStdMap($var, $right); + } + $this->addLocalVar($var, self::TYPE_STD_UNORDERED_MAP); + return $this->parseStdUnorderedMap($var, $right); } else { $valueExpr = $this->parseStdCall($right); if (!$this->hasVar($var)) { @@ -1548,7 +1563,7 @@ class CompilerBase extends \PhpAot\Core\Translator } } elseif ($this->isVarExpr($right)) { $rightVar = $this->parseIdentifier($right); - $type = $this->getVarType($rightVar); + $type = $this->isStdContainer($rightVar) ? self::TYPE_ARRAY : $this->getVarType($rightVar); if ($this->isTypedObject($rightVar) and $this->isTypedObject($var)) { $leftClass = $this->getObjectType($var); $rightClass = $this->getObjectType($rightVar); @@ -1568,8 +1583,8 @@ class CompilerBase extends \PhpAot\Core\Translator if ($this->getVarType($tmp) === self::TYPE_STR and $left->dim === null) { $this->fatalError($left, 'Cannot use [] for strings'); } - if ($this->isStdArrayExpr($left)) { - return $this->parseStdArrayAssign($left, $right); + if ($this->isStdContainerExpr($left)) { + return $this->parseStdContainerAssign($left, $right); } return $this->parseAssignArrayDim($left, $right); } @@ -2068,7 +2083,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function detectVarType($var): string { $name = $this->parseIdentifier($var); - if ($this->isStdArray($name)) { + if ($this->isStdContainer($name)) { return self::TYPE_ARRAY; } return $this->getVarType($name); @@ -2165,6 +2180,13 @@ class CompilerBase extends \PhpAot\Core\Translator return self::TYPE_ARRAY; } } + if ($this->isStdContainerExpr($expr)) { + if (!$expr->hasAttribute('stdContainerDimFetch')) { + $this->parseStdContainerDimFetch($expr); + } + $attr = $expr->getAttribute('stdContainerDimFetch'); + return $this->context->stdContainers[$attr['var']]['type']; + } break; case 'Expr_New': return self::TYPE_OBJECT; @@ -2617,8 +2639,8 @@ class CompilerBase extends \PhpAot\Core\Translator } if ($this->isArrayDimFetch($node->var)) { - if ($this->isStdArrayExpr($node->var)) { - return $this->parseStdArrayAssignOp($node, $op); + if ($this->isStdContainerExpr($node->var)) { + return $this->parseStdContainerAssignOp($node, $op); } /** * $count[$r] -= 1; @@ -2762,8 +2784,11 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseArrayDimFetch(Expr\ArrayDimFetch $node, bool $write): string { - if ($this->isStdArrayExpr($node)) { - return $this->parseStdArrayDimFetch($node); + if ($this->isStdContainerExpr($node)) { + if ($write && $node->dim === null) { + return $this->parseIdentifier($node->var); + } + return $this->parseStdContainerDimFetch($node); } $var = $this->parseIdentifier($node->var); @@ -3170,17 +3195,23 @@ class CompilerBase extends \PhpAot\Core\Translator protected function parseArg(Node\Arg $arg): string { - if ($this->isArrayDimFetch($arg->value) and $this->isStdArrayExpr($arg->value)) { - $valueExpr = $this->parseStdArrayDimFetch($arg->value); - $attr = $arg->value->getAttribute('stdArrayDimFetch'); - if ($attr['accessLevel'] === $attr['totalLevel']) { - return $this->convertExprFromType($this->context->stdArrays[$attr['var']]['type'], $valueExpr); + if ($this->isArrayDimFetch($arg->value) and $this->isStdContainerExpr($arg->value)) { + if ($this->isStdArrayExpr($arg->value)) { + $valueExpr = $this->parseStdArrayDimFetch($arg->value); + $attr = $arg->value->getAttribute('stdArrayDimFetch'); + if ($attr['accessLevel'] === $attr['totalLevel']) { + return $this->convertExprFromType($this->context->stdArrays[$attr['var']]['type'], $valueExpr); + } else { + return $this->convertArrayExpr($valueExpr); + } } else { - return $this->convertArrayExpr($valueExpr); + $valueExpr = $this->parseStdContainerDimFetch($arg->value); + $attr = $arg->value->getAttribute('stdContainerDimFetch'); + return $this->convertExprFromType($this->context->stdContainers[$attr['var']]['type'], $valueExpr); } } $expr = $this->parseIdentifier($arg->value); - if ($this->isVarExpr($arg->value) and $this->isStdArray($arg->value->name)) { + if ($this->isVarExpr($arg->value) and $this->isStdContainer($arg->value->name)) { return $this->convertArrayExpr($expr); } return $expr; @@ -5375,6 +5406,18 @@ class CompilerBase extends \PhpAot\Core\Translator } else { $code .= $info['decl'] . ' ' . $name . '{};'; } + } elseif ($type === self::TYPE_STD_VECTOR) { + $info = $this->context->stdContainers[$name]; + $code .= $info['decl'] . ' ' . $name; + if ($info['size'] !== null) { + $code .= '(' . $info['size'] . ')'; + } else { + $code .= '{}'; + } + $code .= ';'; + } elseif ($type === self::TYPE_STD_MAP || $type === self::TYPE_STD_UNORDERED_MAP) { + $info = $this->context->stdContainers[$name]; + $code .= $info['decl'] . ' ' . $name . '{};'; } else { $code .= $type . ' ' . $name; if ($type === self::TYPE_INT or $type === self::TYPE_FLOAT or $type === self::TYPE_BOOL) { diff --git a/src/Php/Context/FunctionContext.php b/src/Php/Context/FunctionContext.php index 2e64a0a1..d5554821 100644 --- a/src/Php/Context/FunctionContext.php +++ b/src/Php/Context/FunctionContext.php @@ -19,6 +19,11 @@ class FunctionContext * @var array */ public array $stdArrays = []; + + /** + * @var array + */ + public array $stdContainers = []; public array $localVars = []; public array $staticVars = []; public array $globalVars = []; @@ -51,6 +56,8 @@ class FunctionContext $this->staticVars = []; $this->arguments = []; $this->objects = []; + $this->stdArrays = []; + $this->stdContainers = []; $this->objectProps = []; $this->ceWrappers = []; $this->tmpVarIndex = 0; diff --git a/src/Php/Parser/StdArrayParser.php b/src/Php/Parser/StdArrayParser.php deleted file mode 100644 index 6e532856..00000000 --- a/src/Php/Parser/StdArrayParser.php +++ /dev/null @@ -1,171 +0,0 @@ -hasLocalVar($var) and $this->getVarType($var) === self::TYPE_STD_ARRAY; - } - - protected function isStdArrayExpr(Expr\ArrayDimFetch $expr): bool - { - $info = $this->getStdArrayInfo($expr); - return $info !== null; - } - - protected function fillStdArray(Expr\StaticCall $expr): string - { - if (!$this->isVarExpr($expr->args[0]->value) or !$this->isStdArray($this->parseIdentifier($expr->args[0]->value))) { - $this->fatalError($expr, 'fill() only support std::array'); - } - $array = $this->parseIdentifier($expr->args[0]->value); - $valueExpr = $this->parseExpr($expr->args[1]->value); - $type = $this->context->stdArrays[$array]['type']; - $value = $this->convertExprFromType($type, $valueExpr); - return "{$array}.fill({$value})"; - } - - protected function getStdArrayInfo(Expr\ArrayDimFetch $expr): ?array - { - $tmp = $expr->var; - while (true) { - if ($this->isArrayDimFetch($tmp)) { - $tmp = $tmp->var; - } elseif ($this->isVarExpr($tmp) and $this->isStdArray($this->parseVariable($tmp))) { - return $this->context->stdArrays[$this->parseVariable($tmp)]; - } else { - return null; - } - } - } - - protected function parseStdArrayAssign(NodeAbstract $left, NodeAbstract $right): string - { - $info = $this->getStdArrayInfo($left); - $arrayDimFetch = $this->parseStdArrayDimFetch($left); - return $arrayDimFetch . ' = ' . $this->convertExprFromType($info['type'], $this->parseExpr($right)); - } - - protected function parseStdArrayAssignOp(Expr\AssignOp $expr, string $op): string - { - $binaryOp = $this->removeAssignOp($op); - if ($binaryOp === '.') { - $this->fatalError($expr, 'Cannot concat string to std::array'); - } - - $info = $this->getStdArrayInfo($expr->var); - $arrayDimFetch = $this->parseStdArrayDimFetch($expr->var); - return $arrayDimFetch . ' ' . $binaryOp . '= ' . $this->convertExprFromType($info['type'], $this->parseExpr($expr->expr)); - } - - protected function parseStdArrayDimFetch(Expr\ArrayDimFetch $expr): string - { - $tmp = $expr; - $nesting = []; - $level = 0; - $info = $this->getStdArrayInfo($expr); - - while (true) { - if ($this->isArrayDimFetch($tmp)) { - if ($tmp->dim === null) { - $this->fatalError($tmp, 'std::array expects an index'); - } - $size = $info['sizes'][$level]; - if ($this->isScalarInt($tmp->dim)) { - if ($tmp->dim->value < 0 || $tmp->dim->value >= $size) { - $this->fatalError($tmp, "std::array index out of bounds: index {$tmp->dim->value}, size {$size}"); - } - } - $index = $this->parseExpr($tmp->dim); - $nesting[] = '[' . Symbol::safeIndex($this->convertIntExpr($index), $info['sizes'][$level]) . ']'; - $tmp = $tmp->var; - $level++; - } else { - $nesting[] = $this->parseVariable($tmp); - break; - } - } - - $nesting = array_reverse($nesting); - $expr->setAttribute('stdArrayDimFetch', ['var' => $nesting[0], 'accessLevel' => $level, 'totalLevel' => count($info['sizes'])]); - - return implode('', $nesting); - } - - protected function parseStdArray(string $var, Expr\StaticCall $expr): string - { - $tmp = $expr; - $nesting = []; - $totalBytes = 0; - - while (true) { - if (count($tmp->args) !== 2) { - $this->fatalError($tmp, 'std::array() expects two arguments'); - } - if (!$this->isScalarInt($tmp->args[1]->value)) { - $this->fatalError($tmp, 'std::array() expects second argument to be an integer'); - } - $byte = 0; - $size = $tmp->args[1]->value->value; - $nesting[] = $size; - $typeExpr = $tmp->args[0]->value; - if ($this->isClassConstFetch($typeExpr)) { - if (!$this->isNameExpr($typeExpr->class) || !$this->isIdExpr($typeExpr->name) || $typeExpr->class->toString() !== 'native_types') { - $this->fatalError($tmp, 'An incorrect `std::array` definition'); - } - switch ($typeExpr->name->name) { - case 'type_int': - $type = self::TYPE_INT; - $byte = 8; - break; - case 'type_float': - $type = self::TYPE_FLOAT; - $byte = 8; - break; - case 'type_bool': - $type = self::TYPE_BOOL; - $byte = 1; - break; - default: - $this->fatalError($tmp, 'An incorrect `std::array` definition'); - break; - } - break; - } - $totalBytes += $size * $byte; - if ($this->isStaticCall($typeExpr)) { - $tmp = $typeExpr; - if (!$this->isNameExpr($tmp->class) || !$this->isIdExpr($tmp->name) || $tmp->class->toString() !== 'std' || $tmp->name->toString() !== 'array') { - $this->fatalError($tmp, 'An incorrect `std::array` definition'); - } - } else { - $this->fatalError($tmp, 'std::array() expects first argument to be a class constant'); - } - } - - $decl = str_repeat(self::TYPE_STD_ARRAY . '<', count($nesting)); - $decl .= $type; - for ($i = count($nesting) - 1; $i >= 0; $i--) { - $decl .= ', ' . $nesting[$i] . '>'; - } - $this->context->stdArrays[$var] = [ - 'decl' => $decl, - 'type' => $type, - 'sizes' => array_reverse($nesting), - 'bytes' => $totalBytes, - ]; - return '// ' . $decl; - } -} diff --git a/src/Php/Parser/StdContainerParser.php b/src/Php/Parser/StdContainerParser.php new file mode 100644 index 00000000..98398e54 --- /dev/null +++ b/src/Php/Parser/StdContainerParser.php @@ -0,0 +1,412 @@ +hasLocalVar($var) and in_array($this->getVarType($var), [ + self::TYPE_STD_ARRAY, + self::TYPE_STD_VECTOR, + self::TYPE_STD_MAP, + self::TYPE_STD_UNORDERED_MAP, + ], true); + } + + protected function isStdArray(string $var): bool + { + return $this->hasLocalVar($var) and $this->getVarType($var) === self::TYPE_STD_ARRAY; + } + + protected function isStdVector(string $var): bool + { + return $this->hasLocalVar($var) and $this->getVarType($var) === self::TYPE_STD_VECTOR; + } + + protected function isStdMap(string $var): bool + { + return $this->hasLocalVar($var) and $this->getVarType($var) === self::TYPE_STD_MAP; + } + + protected function isStdUnorderedMap(string $var): bool + { + return $this->hasLocalVar($var) and $this->getVarType($var) === self::TYPE_STD_UNORDERED_MAP; + } + + protected function isStdArrayExpr(Expr\ArrayDimFetch $expr): bool + { + $info = $this->getStdArrayInfo($expr); + return $info !== null; + } + + protected function isStdContainerExpr(Expr\ArrayDimFetch $expr): bool + { + return $this->isStdArrayExpr($expr) || $this->getStdContainerInfo($expr) !== null; + } + + protected function fillStdArray(Expr\StaticCall $expr): string + { + if (!$this->isVarExpr($expr->args[0]->value) or !$this->isStdArray($this->parseIdentifier($expr->args[0]->value))) { + $this->fatalError($expr, 'fill() only support std::array'); + } + $array = $this->parseIdentifier($expr->args[0]->value); + $valueExpr = $this->parseExpr($expr->args[1]->value); + $type = $this->context->stdArrays[$array]['type']; + $value = $this->convertExprFromType($type, $valueExpr); + return "{$array}.fill({$value})"; + } + + protected function getStdArrayInfo(Expr\ArrayDimFetch $expr): ?array + { + $tmp = $expr->var; + while (true) { + if ($this->isArrayDimFetch($tmp)) { + $tmp = $tmp->var; + } elseif ($this->isVarExpr($tmp) and $this->isStdArray($this->parseVariable($tmp))) { + return $this->context->stdArrays[$this->parseVariable($tmp)]; + } else { + return null; + } + } + } + + protected function getStdContainerInfo(Expr\ArrayDimFetch $expr): ?array + { + $tmp = $expr->var; + while (true) { + if ($this->isArrayDimFetch($tmp)) { + $tmp = $tmp->var; + } elseif ($this->isVarExpr($tmp)) { + $var = $this->parseVariable($tmp); + if ($this->isStdVector($var) || $this->isStdMap($var) || $this->isStdUnorderedMap($var)) { + return $this->context->stdContainers[$var]; + } + return null; + } else { + return null; + } + } + } + + protected function parseStdArrayAssign(NodeAbstract $left, NodeAbstract $right): string + { + $info = $this->getStdArrayInfo($left); + $arrayDimFetch = $this->parseStdArrayDimFetch($left); + return $arrayDimFetch . ' = ' . $this->convertExprFromType($info['type'], $this->parseExpr($right)); + } + + protected function parseStdContainerAssign(Expr\ArrayDimFetch $left, NodeAbstract $right): string + { + if ($this->isStdArrayExpr($left)) { + return $this->parseStdArrayAssign($left, $right); + } + + $info = $this->getStdContainerInfo($left); + if ($info['kind'] === 'vector' && $left->dim === null) { + if (!$this->isVarExpr($left->var)) { + $this->fatalError($left, 'std::vector append only supports a vector variable'); + } + $vector = $this->parseVariable($left->var); + return $vector . '.push_back(' . $this->convertExprFromType($info['type'], $this->parseExpr($right)) . ')'; + } + if ($left->dim === null) { + $this->fatalError($left, 'std map expects a key'); + } + + return $this->parseStdContainerOffsetSet($left, $this->convertExprFromType($info['type'], $this->parseExpr($right))); + } + + protected function parseStdArrayAssignOp(Expr\AssignOp $expr, string $op): string + { + $binaryOp = $this->removeAssignOp($op); + if ($binaryOp === '.') { + $this->fatalError($expr, 'Cannot concat string to std::array'); + } + + $info = $this->getStdArrayInfo($expr->var); + $arrayDimFetch = $this->parseStdArrayDimFetch($expr->var); + return $arrayDimFetch . ' ' . $binaryOp . '= ' . $this->convertExprFromType($info['type'], $this->parseExpr($expr->expr)); + } + + protected function parseStdContainerAssignOp(Expr\AssignOp $expr, string $op): string + { + if ($this->isStdArrayExpr($expr->var)) { + return $this->parseStdArrayAssignOp($expr, $op); + } + + $binaryOp = $this->removeAssignOp($op); + if ($binaryOp === '.') { + $this->fatalError($expr, 'Cannot concat string to std container'); + } + + $info = $this->getStdContainerInfo($expr->var); + $containerDimFetch = $this->parseStdContainerDimFetch($expr->var); + return $containerDimFetch . ' ' . $binaryOp . '= ' . $this->convertExprFromType($info['type'], $this->parseExpr($expr->expr)); + } + + protected function parseStdArrayDimFetch(Expr\ArrayDimFetch $expr): string + { + $tmp = $expr; + $nesting = []; + $level = 0; + $info = $this->getStdArrayInfo($expr); + + while (true) { + if ($this->isArrayDimFetch($tmp)) { + if ($tmp->dim === null) { + $this->fatalError($tmp, 'std::array expects an index'); + } + $size = $info['sizes'][$level]; + if ($this->isScalarInt($tmp->dim)) { + if ($tmp->dim->value < 0 || $tmp->dim->value >= $size) { + $this->fatalError($tmp, "std::array index out of bounds: index {$tmp->dim->value}, size {$size}"); + } + } + $index = $this->parseExpr($tmp->dim); + $nesting[] = '[' . Symbol::safeIndex($this->convertIntExpr($index), $info['sizes'][$level]) . ']'; + $tmp = $tmp->var; + $level++; + } else { + $nesting[] = $this->parseVariable($tmp); + break; + } + } + + $nesting = array_reverse($nesting); + $expr->setAttribute('stdArrayDimFetch', ['var' => $nesting[0], 'accessLevel' => $level, 'totalLevel' => count($info['sizes'])]); + + return implode('', $nesting); + } + + protected function parseStdContainerDimFetch(Expr\ArrayDimFetch $expr): string + { + if ($this->isStdArrayExpr($expr)) { + return $this->parseStdArrayDimFetch($expr); + } + + $info = $this->getStdContainerInfo($expr); + $tmp = $expr; + $dims = []; + while (true) { + if ($this->isArrayDimFetch($tmp)) { + $dims[] = $tmp->dim; + $tmp = $tmp->var; + } else { + break; + } + } + if (!$this->isVarExpr($tmp)) { + $this->fatalError($expr, 'std container expects a variable'); + } + if (count($dims) !== 1) { + $this->fatalError($expr, 'Nested std::vector/std::map/std::unordered_map access is not supported'); + } + $dim = $dims[0]; + if ($dim === null) { + $this->fatalError($expr, 'std container expects an index'); + } + + $container = $this->parseVariable($tmp); + $index = $this->parseExpr($dim); + $key = $info['kind'] === 'vector' ? $this->convertIntExpr($index) : $this->convertStdContainerKey($info, $index); + $access = $container . '.offsetGet(' . $key . ')'; + $expr->setAttribute('stdContainerDimFetch', ['var' => $container, 'accessLevel' => 1, 'totalLevel' => 1]); + + return $access; + } + + protected function parseStdContainerOffsetSet(Expr\ArrayDimFetch $expr, string $value): string + { + $info = $this->getStdContainerInfo($expr); + if ($expr->dim === null) { + $this->fatalError($expr, 'std container expects an index'); + } + if (!$this->isVarExpr($expr->var)) { + $this->fatalError($expr, 'std container expects a variable'); + } + $container = $this->parseVariable($expr->var); + $indexExpr = $this->parseExpr($expr->dim); + $index = $info['kind'] === 'vector' ? $this->convertIntExpr($indexExpr) : $this->convertStdContainerKey($info, $indexExpr); + return $container . '.offsetSet(' . $index . ', ' . $value . ')'; + } + + protected function convertStdContainerKey(array $info, string $index): string + { + if ($info['keyType'] === self::TYPE_STR) { + return $this->convertStringExpr($index); + } + return $this->convertIntExpr($index); + } + + protected function parseStdNativeType(NodeAbstract $expr, string $owner): string + { + if (!$this->isClassConstFetch($expr)) { + $this->fatalError($expr, "{$owner} expects a native_types class constant"); + } + if (!$this->isNameExpr($expr->class) || !$this->isIdExpr($expr->name) || $expr->class->toString() !== 'native_types') { + $this->fatalError($expr, "An incorrect `{$owner}` definition"); + } + return match ($expr->name->name) { + 'type_int' => self::TYPE_INT, + 'type_float' => self::TYPE_FLOAT, + 'type_bool' => self::TYPE_BOOL, + default => $this->fatalError($expr, "An incorrect `{$owner}` definition"), + }; + } + + protected function parseStdMapKeyType(NodeAbstract $expr, string $owner): string + { + if (!$this->isClassConstFetch($expr) || !$this->isNameExpr($expr->class) || !$this->isIdExpr($expr->name)) { + $this->fatalError($expr, "{$owner} expects a native_types or complex_types class constant"); + } + if ($expr->class->toString() === 'native_types' && $expr->name->name === 'type_int') { + return self::TYPE_INT; + } + if ($expr->class->toString() === 'complex_types' && in_array($expr->name->name, ['type_string', 'type_str'], true)) { + return self::TYPE_STR; + } + $this->fatalError($expr, "{$owner} key only supports native_types::type_int, complex_types::type_string or complex_types::type_str"); + } + + protected function getStdMapDecl(string $containerType, string $keyType, string $valueType): string + { + $decl = $containerType . '<' . $valueType; + if ($keyType === self::TYPE_STR) { + $decl .= ', ' . self::TYPE_STR; + } + return $decl . '>'; + } + + protected function parseStdArray(string $var, Expr\StaticCall $expr): string + { + $tmp = $expr; + $nesting = []; + $totalBytes = 0; + + while (true) { + if (count($tmp->args) !== 2) { + $this->fatalError($tmp, 'std::array() expects two arguments'); + } + if (!$this->isScalarInt($tmp->args[1]->value)) { + $this->fatalError($tmp, 'std::array() expects second argument to be an integer'); + } + $byte = 0; + $size = $tmp->args[1]->value->value; + $nesting[] = $size; + $typeExpr = $tmp->args[0]->value; + if ($this->isClassConstFetch($typeExpr)) { + if (!$this->isNameExpr($typeExpr->class) || !$this->isIdExpr($typeExpr->name) || $typeExpr->class->toString() !== 'native_types') { + $this->fatalError($tmp, 'An incorrect `std::array` definition'); + } + switch ($typeExpr->name->name) { + case 'type_int': + $type = self::TYPE_INT; + $byte = 8; + break; + case 'type_float': + $type = self::TYPE_FLOAT; + $byte = 8; + break; + case 'type_bool': + $type = self::TYPE_BOOL; + $byte = 1; + break; + default: + $this->fatalError($tmp, 'An incorrect `std::array` definition'); + break; + } + break; + } + $totalBytes += $size * $byte; + if ($this->isStaticCall($typeExpr)) { + $tmp = $typeExpr; + if (!$this->isNameExpr($tmp->class) || !$this->isIdExpr($tmp->name) || $tmp->class->toString() !== 'std' || $tmp->name->toString() !== 'array') { + $this->fatalError($tmp, 'An incorrect `std::array` definition'); + } + } else { + $this->fatalError($tmp, 'std::array() expects first argument to be a class constant'); + } + } + + $decl = str_repeat(self::TYPE_STD_ARRAY . '<', count($nesting)); + $decl .= $type; + for ($i = count($nesting) - 1; $i >= 0; $i--) { + $decl .= ', ' . $nesting[$i] . '>'; + } + $this->context->stdArrays[$var] = [ + 'decl' => $decl, + 'type' => $type, + 'sizes' => array_reverse($nesting), + 'bytes' => $totalBytes, + ]; + return '// ' . $decl; + } + + protected function parseStdVector(string $var, Expr\StaticCall $expr): string + { + if (count($expr->args) < 1 || count($expr->args) > 2) { + $this->fatalError($expr, 'std::vector() expects one or two arguments'); + } + $type = $this->parseStdNativeType($expr->args[0]->value, 'std::vector'); + $size = null; + if (count($expr->args) === 2) { + if (!$this->isScalarInt($expr->args[1]->value)) { + $this->fatalError($expr, 'std::vector() expects second argument to be an integer'); + } + $size = $expr->args[1]->value->value; + } + $decl = self::TYPE_STD_VECTOR . '<' . $type . '>'; + $this->context->stdContainers[$var] = [ + 'kind' => 'vector', + 'decl' => $decl, + 'type' => $type, + 'size' => $size, + ]; + return '// ' . $decl; + } + + protected function parseStdMap(string $var, Expr\StaticCall $expr): string + { + if (count($expr->args) !== 2) { + $this->fatalError($expr, 'std::map() expects two arguments'); + } + $keyType = $this->parseStdMapKeyType($expr->args[0]->value, 'std::map'); + $valueType = $this->parseStdNativeType($expr->args[1]->value, 'std::map'); + $decl = $this->getStdMapDecl(self::TYPE_STD_MAP, $keyType, $valueType); + $this->context->stdContainers[$var] = [ + 'kind' => 'map', + 'decl' => $decl, + 'type' => $valueType, + 'keyType' => $keyType, + ]; + return '// ' . $decl; + } + + protected function parseStdUnorderedMap(string $var, Expr\StaticCall $expr): string + { + if (count($expr->args) !== 2) { + $this->fatalError($expr, 'std::unordered_map() expects two arguments'); + } + $keyType = $this->parseStdMapKeyType($expr->args[0]->value, 'std::unordered_map'); + $valueType = $this->parseStdNativeType($expr->args[1]->value, 'std::unordered_map'); + $decl = $this->getStdMapDecl(self::TYPE_STD_UNORDERED_MAP, $keyType, $valueType); + $this->context->stdContainers[$var] = [ + 'kind' => 'unordered_map', + 'decl' => $decl, + 'type' => $valueType, + 'keyType' => $keyType, + ]; + return '// ' . $decl; + } +} \ No newline at end of file diff --git a/src/polyfills.php b/src/polyfills.php index 67f6cbd1..02bea728 100644 --- a/src/polyfills.php +++ b/src/polyfills.php @@ -13,6 +13,14 @@ class native_types public const type_bool = 'bool'; } +class complex_types { + public const type_any = 'any'; + public const type_str = 'string'; + public const type_string = 'string'; + public const type_array = 'array'; + public const type_object = 'object'; +} + class std { public static function int(mixed $value): int