/** * Processes the symbol variable that will be used to return * the result of the symbol call. If a temporal variable is used * as returned value only the body is freed between calls * * @param CompilationContext $compilationContext */ public function processExpectedComplexLiteralReturn(CompilationContext $compilationContext) { $expr = $this->_expression; $expression = $expr->getExpression(); /** * Create temporary variable if needed */ $mustInit = false; $isExpecting = $expr->isExpectingReturn(); if ($isExpecting) { $symbolVariable = $expr->getExpectingVariable(); if (is_object($symbolVariable)) { $readDetector = new ReadDetector($expression); if ($readDetector->detect($symbolVariable->getName(), $expression)) { $symbolVariable = $compilationContext->symbolTable->getTempComplexLiteralVariableForWrite('variable', $compilationContext, $expression); } else { $mustInit = true; } } else { $symbolVariable = $compilationContext->symbolTable->getTempComplexLiteralVariableForWrite('variable', $compilationContext, $expression); } } $this->_mustInit = $mustInit; $this->_symbolVariable = $symbolVariable; $this->_isExpecting = $isExpecting; }
/** * Compiles a method call * * @param Expression $expr * @param CompilationContext $compilationContext */ public function compile(Expression $expr, CompilationContext $compilationContext) { $expression = $expr->getExpression(); $exprVariable = new Expression($expression['variable']); $exprVariable->setReadOnly(true); $exprCompiledVariable = $exprVariable->compile($compilationContext); $builtInType = false; switch ($exprCompiledVariable->getType()) { case 'variable': $variableVariable = $compilationContext->symbolTable->getVariableForRead($exprCompiledVariable->getCode(), $compilationContext, $expression); switch ($variableVariable->getType()) { case 'variable': $caller = $variableVariable; break; default: /* Check if there is a built-in type optimizer available */ $builtInTypeClass = 'Zephir\\Types\\' . ucfirst($variableVariable->getType()) . 'Type'; if (class_exists($builtInTypeClass)) { /** * @var $builtInType \Zephir\Types\AbstractType */ $builtInType = new $builtInTypeClass(); $caller = $exprCompiledVariable; } else { throw new CompilerException("Methods cannot be called on variable type: " . $variableVariable->getType(), $expression); } } break; default: /* Check if there is a built-in type optimizer available */ $builtInTypeClass = 'Zephir\\Types\\' . ucfirst($exprCompiledVariable->getType()) . 'Type'; if (class_exists($builtInTypeClass)) { $builtInType = new $builtInTypeClass(); $caller = $exprCompiledVariable; } else { throw new CompilerException("Cannot use expression: " . $exprCompiledVariable->getType() . " as method caller", $expression['variable']); } } $codePrinter = $compilationContext->codePrinter; $type = $expression['call-type']; /** * In normal method calls and dynamic string method calls we just use the name given by the user */ if ($type == self::CALL_NORMAL || $type == self::CALL_DYNAMIC_STRING) { $methodName = strtolower($expression['name']); } else { $variableMethod = $compilationContext->symbolTable->getVariableForRead($expression['name'], $compilationContext, $expression); if (is_object($builtInType)) { throw new CompilerException("Dynamic method invocation for type: " . $variableMethod->getType() . " is not supported", $expression); } if ($variableMethod->isNotVariableAndString()) { throw new CompilerException("Cannot use variable type: " . $variableMethod->getType() . " as dynamic method name", $expression); } } $symbolVariable = null; /** * Create temporary variable if needed */ $mustInit = false; $isExpecting = $expr->isExpectingReturn(); if ($isExpecting) { $symbolVariable = $expr->getExpectingVariable(); if (is_object($symbolVariable)) { $readDetector = new ReadDetector($expression); if ($caller == $symbolVariable || $readDetector->detect($symbolVariable->getName(), $expression)) { $symbolVariable = $compilationContext->symbolTable->getTempVariableForObserveOrNullify('variable', $compilationContext, $expression); } else { $mustInit = true; } } else { $symbolVariable = $compilationContext->symbolTable->getTempVariableForObserveOrNullify('variable', $compilationContext, $expression); } } /** * Method calls only return zvals so we need to validate the target variable is also a zval */ if (!$builtInType) { if ($isExpecting) { if (!$symbolVariable->isVariable()) { throw new CompilerException("Returned values by functions can only be assigned to variant variables", $expression); } /** * At this point, we don't know the exact dynamic type returned by the method call */ $symbolVariable->setDynamicTypes('undefined'); } } else { return $builtInType->invokeMethod($methodName, $caller, $compilationContext, $this, $expression); } $check = true; if (isset($expression['check'])) { $check = $expression['check']; } /** * Try to check if the method exist in the callee, only when method call is self::CALL_NORMAL */ if ($type == self::CALL_NORMAL) { if ($variableVariable->getRealName() == 'this') { $classDefinition = $compilationContext->classDefinition; if (!$classDefinition->hasMethod($methodName)) { if ($check) { $found = false; $interfaces = $classDefinition->isAbstract() ? $classDefinition->getImplementedInterfaces() : null; if (is_array($interfaces)) { $compiler = $compilationContext->compiler; foreach ($interfaces as $interface) { $classInterfaceDefinition = $compiler->getClassDefinition($interface); if ($classInterfaceDefinition->hasMethod($methodName)) { $found = true; $classMethod = $classInterfaceDefinition->getMethod($methodName); break; } } } if (!$found) { $possibleMethod = $classDefinition->getPossibleMethodName($expression['name']); if ($possibleMethod && $expression['name'] != $possibleMethod) { throw new CompilerException("Class '" . $classDefinition->getCompleteName() . "' does not implement method: '" . $expression['name'] . "'. Did you mean '" . $possibleMethod . "'?", $expression); } else { throw new CompilerException("Class '" . $classDefinition->getCompleteName() . "' does not implement method: '" . $expression['name'] . "'", $expression); } } } } else { if ($check) { $classMethod = $classDefinition->getMethod($methodName); } } if ($check) { /** * Private methods must be called in their declaration scope */ if ($classMethod->isPrivate()) { if ($classMethod->getClassDefinition() != $classDefinition) { throw new CompilerException("Cannot call private method '" . $expression['name'] . "' out of its scope", $expression); } } /** * Try to produce an exception if method is called with a wrong number of parameters */ if (isset($expression['parameters'])) { $callNumberParameters = count($expression['parameters']); } else { $callNumberParameters = 0; } $expectedNumberParameters = $classMethod->getNumberOfRequiredParameters(); if (!$expectedNumberParameters && $callNumberParameters > 0) { $numberParameters = $classMethod->getNumberOfParameters(); if ($callNumberParameters > $numberParameters) { throw new CompilerException("Method '" . $classDefinition->getCompleteName() . "::" . $expression['name'] . "' called with a wrong number of parameters, the method has: " . $expectedNumberParameters . ", passed: " . $callNumberParameters, $expression); } } if ($callNumberParameters < $expectedNumberParameters) { throw new CompilerException("Method '" . $classDefinition->getCompleteName() . "::" . $expression['name'] . "' called with a wrong number of parameters, the method has: " . $expectedNumberParameters . ", passed: " . $callNumberParameters, $expression); } $method = $classMethod; } } else { /** * Variables whose dynamic type is 'object' can be used * to determine method existence in compile time */ if ($check && $variableVariable->hasAnyDynamicType('object')) { $classTypes = $variableVariable->getClassTypes(); if (count($classTypes)) { $numberImplemented = 0; $compiler = $compilationContext->compiler; foreach ($classTypes as $classType) { if ($compiler->isClass($classType) || $compiler->isInterface($classType) || $compiler->isBundledClass($classType) || $compiler->isBundledInterface($classType)) { if ($compiler->isClass($classType) || $compiler->isInterface($classType)) { $classDefinition = $compiler->getClassDefinition($classType); } else { $classDefinition = $compiler->getInternalClassDefinition($classType); } if (!$classDefinition) { throw new CompilerException("Cannot locate class definition for class " . $classType, $expression); } if (!$classDefinition->hasMethod($methodName)) { if (!$classDefinition->isInterface()) { if (count($classTypes) == 1) { throw new CompilerException("Class '" . $classType . "' does not implement method: '" . $expression['name'] . "'", $expression); } } continue; } $method = $classDefinition->getMethod($methodName); /** * Private methods must be called in their declaration scope */ if ($method->isPrivate()) { if ($method->getClassDefinition() != $classDefinition) { throw new CompilerException("Cannot call private method '" . $expression['name'] . "' out of its scope", $expression); } } /** * Check visibility for protected methods */ if ($method->isProtected() && $method->getClassDefinition() != $classDefinition && $method->getClassDefinition() != $classDefinition->getExtendsClass()) { throw new CompilerException("Cannot call protected method '" . $expression['name'] . "' out of its scope", $expression); } /** * Try to produce an exception if a method is called with a wrong number of parameters * We only check extension parameters if methods are extension methods * Internal methods may have invalid Reflection information */ if ($method instanceof ClassMethod && !$method->isBundled()) { if (isset($expression['parameters'])) { $callNumberParameters = count($expression['parameters']); } else { $callNumberParameters = 0; } $classMethod = $classDefinition->getMethod($methodName); $expectedNumberParameters = $classMethod->getNumberOfRequiredParameters(); if (!$expectedNumberParameters && $callNumberParameters > 0) { $numberParameters = $classMethod->getNumberOfParameters(); if ($callNumberParameters > $numberParameters) { $className = $classDefinition->getCompleteName(); throw new CompilerException("Method '" . $className . "::" . $expression['name'] . "' called with a wrong number of parameters, the method has: " . $expectedNumberParameters . ", passed: " . $callNumberParameters, $expression); } } if ($callNumberParameters < $expectedNumberParameters) { throw new CompilerException("Method '" . $classDefinition->getCompleteName() . "::" . $expression['name'] . "' called with a wrong number of parameters, the method has: " . $expectedNumberParameters . ", passed: " . $callNumberParameters, $expression); } } /** * The method is checked in the first class that implements the method * We could probably have collisions here */ $numberImplemented++; break; } else { $numberImplemented++; $compilationContext->logger->warning("Class \"" . $classType . "\" does not exist at compile time", "nonexistent-class", $expression); } } if ($numberImplemented == 0) { if (!$classDefinition->isInterface()) { if (count($classTypes) > 1) { throw new CompilerException("None of classes: '" . join(' or ', $classTypes) . "' implement method: '" . $expression['name'] . "'", $expression); } else { throw new CompilerException("Class '" . $classTypes[0] . "' does not implement method: '" . $expression['name'] . "'", $expression); } } else { // @TODO, raise an exception here? } } } } } } if (isset($method)) { $this->_reflection = $method; } /** * Transfer the return type-hint to the returned variable */ if ($isExpecting) { if (isset($method)) { if ($method instanceof ClassMethod) { if ($method->isVoid()) { throw new CompilerException("Method '" . $classDefinition->getCompleteName() . "::" . $expression['name'] . "' is marked as 'void' and it does not return anything", $expression); } $returnClassTypes = $method->getReturnClassTypes(); if ($returnClassTypes !== null) { $symbolVariable->setDynamicTypes('object'); foreach ($returnClassTypes as &$returnClassType) { $returnClassType = $compilationContext->getFullName($returnClassType); } $symbolVariable->setClassTypes($returnClassTypes); } $returnTypes = $method->getReturnTypes(); if ($returnTypes !== null) { foreach ($returnTypes as $dataType => $returnType) { $symbolVariable->setDynamicTypes($dataType); } } } } } /** * Some parameters in internal methods receive parameters as references */ if (isset($expression['parameters'])) { $references = array(); if ($type == self::CALL_NORMAL || $type == self::CALL_DYNAMIC_STRING) { if (isset($method)) { if ($method instanceof \ReflectionMethod) { $position = 0; foreach ($method->getParameters() as $parameter) { if ($parameter->isPassedByReference()) { $references[$position] = true; } $position++; } } } } } /** * Include fcall header */ $compilationContext->headersManager->add('kernel/fcall'); /** * Call methods must grown the stack */ $compilationContext->symbolTable->mustGrownStack(true); /** * Mark references */ if (isset($expression['parameters'])) { $params = $this->getResolvedParams($expression['parameters'], $compilationContext, $expression, isset($method) ? $method : null); if (count($references)) { foreach ($params as $position => $param) { if (isset($references[$position])) { $compilationContext->codePrinter->output('Z_SET_ISREF_P(' . $param . ');'); } } } // We check here if a correct parameter type is passed to the called method if ($type == self::CALL_NORMAL) { if (isset($method) && $method instanceof ClassMethod && isset($expression['parameters'])) { $resolvedTypes = $this->getResolvedTypes(); $resolvedDynamicTypes = $this->getResolvedDynamicTypes(); //$typeInference = $method->getStaticTypeInferencePass(); foreach ($method->getParameters() as $n => $parameter) { if (isset($parameter['data-type'])) { if (!isset($resolvedTypes[$n])) { continue; } /** * If the passed parameter is different to the expected type we show a warning */ if ($resolvedTypes[$n] != $parameter['data-type']) { switch ($resolvedTypes[$n]) { case 'bool': case 'boolean': switch ($parameter['data-type']) { /* compatible types */ case 'bool': case 'boolean': case 'variable': break; default: $compilationContext->logger->warning("Passing possible incorrect type for parameter: " . $classDefinition->getCompleteName() . '::' . $method->getName() . '(' . $parameter['name'] . '), passing: ' . $resolvedDynamicTypes[$n] . ', ' . "expecting: " . $parameter['data-type'], "possible-wrong-parameter", $expression); break; } break; case 'array': switch ($parameter['data-type']) { /* compatible types */ case 'array': case 'variable': break; case 'callable': /** * Array can be a callable type, example: [$this, "method"] * * @todo we need to check this array if can... */ break; default: $compilationContext->logger->warning("Passing possible incorrect type for parameter: " . $classDefinition->getCompleteName() . '::' . $method->getName() . '(' . $parameter['name'] . '), passing: ' . $resolvedDynamicTypes[$n] . ', ' . "expecting: " . $parameter['data-type'], "possible-wrong-parameter", $expression); break; } break; case 'callable': switch ($parameter['data-type']) { /* compatible types */ case 'callable': case 'variable': break; default: $compilationContext->logger->warning("Passing possible incorrect type for parameter: " . $classDefinition->getCompleteName() . '::' . $method->getName() . '(' . $parameter['name'] . '), passing: ' . $resolvedDynamicTypes[$n] . ', ' . "expecting: " . $parameter['data-type'], "possible-wrong-parameter", $expression); break; } break; case 'string': switch ($parameter['data-type']) { /* compatible types */ case 'string': case 'variable': break; default: $compilationContext->logger->warning("Passing possible incorrect type for parameter: " . $classDefinition->getCompleteName() . '::' . $method->getName() . '(' . $parameter['name'] . '), passing: ' . $resolvedDynamicTypes[$n] . ', ' . "expecting: " . $parameter['data-type'], "possible-wrong-parameter", $expression); break; } break; /** * Passing polymorphic variables to static typed parameters * could lead to potential unexpected type coercions */ /** * Passing polymorphic variables to static typed parameters * could lead to potential unexpected type coercions */ case 'variable': if ($resolvedDynamicTypes[$n] != $parameter['data-type']) { if ($resolvedDynamicTypes[$n] == 'undefined') { $compilationContext->logger->warning("Passing possible incorrect type to parameter: " . $classDefinition->getCompleteName() . '::' . $parameter[$n]['name'] . ', passing: ' . $resolvedDynamicTypes[$n] . ', ' . "expecting: " . $parameter[$n]['data-type'], "possible-wrong-parameter-undefined", $expression); } //echo '1: ', $resolvedTypes[$n], ' ', $resolvedDynamicTypes[$n], ' ', $parameter[0]['data-type'], ' ', PHP_EOL; } break; } } } } } } } else { $params = array(); } // Add the last call status to the current symbol table $this->addCallStatusFlag($compilationContext); // Initialize non-temporary variables if ($mustInit) { $symbolVariable->setMustInitNull(true); $symbolVariable->trackVariant($compilationContext); } // Generate the code according to the call type if ($type == self::CALL_NORMAL || $type == self::CALL_DYNAMIC_STRING) { $realMethod = $this->getRealCalledMethod($compilationContext, $variableVariable, $methodName); $isInternal = false; if (is_object($realMethod[1])) { $isInternal = $realMethod[1]->isInternal(); if ($isInternal && $realMethod[0] > 1) { throw new CompilerException("Cannot resolve method: '" . $expression['name'] . "' in polymorphic variable", $expression); } } if (!$isInternal) { // Check if the method call can have an inline cache $methodCache = $compilationContext->cacheManager->getMethodCache(); $cachePointer = $methodCache->get($compilationContext, $methodName, $variableVariable); if (!count($params)) { if ($isExpecting) { if ($symbolVariable->getName() == 'return_value') { $codePrinter->output('ZEPHIR_RETURN_CALL_METHOD(' . $variableVariable->getName() . ', "' . $methodName . '", ' . $cachePointer . ');'); } else { $codePrinter->output('ZEPHIR_CALL_METHOD(&' . $symbolVariable->getName() . ', ' . $variableVariable->getName() . ', "' . $methodName . '", ' . $cachePointer . ');'); } } else { $codePrinter->output('ZEPHIR_CALL_METHOD(NULL, ' . $variableVariable->getName() . ', "' . $methodName . '", ' . $cachePointer . ');'); } } else { if ($isExpecting) { if ($symbolVariable->getName() == 'return_value') { $codePrinter->output('ZEPHIR_RETURN_CALL_METHOD(' . $variableVariable->getName() . ', "' . $methodName . '", ' . $cachePointer . ', ' . join(', ', $params) . ');'); } else { $codePrinter->output('ZEPHIR_CALL_METHOD(&' . $symbolVariable->getName() . ', ' . $variableVariable->getName() . ', "' . $methodName . '", ' . $cachePointer . ', ' . join(', ', $params) . ');'); } } else { $codePrinter->output('ZEPHIR_CALL_METHOD(NULL, ' . $variableVariable->getName() . ', "' . $methodName . '", ' . $cachePointer . ', ' . join(', ', $params) . ');'); } } } else { if (!count($params)) { if ($isExpecting) { if ($symbolVariable->getName() == 'return_value') { $codePrinter->output('ZEPHIR_RETURN_CALL_INTERNAL_METHOD_P0(' . $variableVariable->getName() . ', ' . $method->getInternalName() . ');'); } else { $codePrinter->output('ZEPHIR_CALL_INTERNAL_METHOD_P0(&' . $symbolVariable->getName() . ', ' . $variableVariable->getName() . ', ' . $method->getInternalName() . ');'); } } else { $codePrinter->output('ZEPHIR_CALL_INTERNAL_METHOD_NORETURN_P0(' . $variableVariable->getName() . ', ' . $method->getInternalName() . ');'); } } else { if ($isExpecting) { if ($symbolVariable->getName() == 'return_value') { $codePrinter->output('ZEPHIR_RETURN_CALL_INTERNAL_METHOD_P' . count($params) . '(' . $variableVariable->getName() . ', ' . $method->getInternalName() . ', ' . join(', ', $params) . ');'); } else { $codePrinter->output('ZEPHIR_CALL_INTERNAL_METHOD_P' . count($params) . '(&' . $symbolVariable->getName() . ', ' . $variableVariable->getName() . ', ' . $method->getInternalName() . ', ' . join(', ', $params) . ');'); } } else { $codePrinter->output('ZEPHIR_CALL_INTERNAL_METHOD_NORETURN_P' . count($params) . '(' . $variableVariable->getName() . ', ' . $method->getInternalName() . ', ' . join(', ', $params) . ');'); } } } } else { if ($type == self::CALL_DYNAMIC) { switch ($variableMethod->getType()) { case 'string': case 'variable': break; default: throw new Exception('Cannot use variable type: ' . $variableMethod->getType() . ' as method caller'); } $cachePointer = 'NULL, 0'; if (!count($params)) { if ($isExpecting) { if ($symbolVariable->getName() == 'return_value') { $codePrinter->output('ZEPHIR_RETURN_CALL_METHOD_ZVAL(' . $variableVariable->getName() . ', ' . $variableMethod->getName() . ', ' . $cachePointer . ');'); } else { $codePrinter->output('ZEPHIR_CALL_METHOD_ZVAL(&' . $symbolVariable->getName() . ', ' . $variableVariable->getName() . ', ' . $variableMethod->getName() . ', ' . $cachePointer . ');'); } } else { $codePrinter->output('ZEPHIR_CALL_METHOD_ZVAL(NULL, ' . $variableVariable->getName() . ', ' . $variableMethod->getName() . ', ' . $cachePointer . ');'); } } else { if ($isExpecting) { if ($symbolVariable->getName() == 'return_value') { $codePrinter->output('ZEPHIR_RETURN_CALL_METHOD_ZVAL(' . $variableVariable->getName() . ', ' . $variableMethod->getName() . ', ' . $cachePointer . ', ' . join(', ', $params) . ');'); } else { $codePrinter->output('ZEPHIR_CALL_METHOD_ZVAL(&' . $symbolVariable->getName() . ', ' . $variableVariable->getName() . ', ' . $variableMethod->getName() . ', ' . $cachePointer . ', ' . join(', ', $params) . ');'); } } else { $codePrinter->output('ZEPHIR_CALL_METHOD_ZVAL(NULL, ' . $variableVariable->getName() . ', ' . $variableMethod->getName() . ', ' . $cachePointer . ', ' . join(', ', $params) . ');'); } } } } // Temporary variables must be copied if they have more than one reference foreach ($this->getMustCheckForCopyVariables() as $checkVariable) { $codePrinter->output('zephir_check_temp_parameter(' . $checkVariable . ');'); } // We can mark temporary variables generated as idle foreach ($this->getTemporalVariables() as $tempVariable) { $tempVariable->setIdle(true); } // Release parameters marked as references if (isset($expression['parameters'])) { if (count($references)) { foreach ($params as $position => $param) { if (isset($references[$position])) { $compilationContext->codePrinter->output('Z_UNSET_ISREF_P(' . $param . ');'); } } } } $this->addCallStatusOrJump($compilationContext); if ($isExpecting) { return new CompiledExpression('variable', $symbolVariable->getRealName(), $expression); } return new CompiledExpression('null', null, $expression); }
/** * Compiles a static method call * * @param array $expr * @param CompilationContext $compilationContext */ public function compile(Expression $expr, CompilationContext $compilationContext) { $expression = $expr->getExpression(); $methodName = strtolower($expression['name']); if (isset($expression['dynamic'])) { $dynamicMethod = $expression['dynamic']; } else { $dynamicMethod = false; } $symbolVariable = null; /** * Create temporary variable if needed */ $mustInit = false; $isExpecting = $expr->isExpectingReturn(); if ($isExpecting) { $symbolVariable = $expr->getExpectingVariable(); if (is_object($symbolVariable)) { $readDetector = new ReadDetector($expression); if ($readDetector->detect($symbolVariable->getName(), $expression)) { $symbolVariable = $compilationContext->symbolTable->getTempVariableForObserveOrNullify('variable', $compilationContext, $expression); } else { $mustInit = true; } } else { $symbolVariable = $compilationContext->symbolTable->getTempVariableForObserveOrNullify('variable', $compilationContext, $expression); } } /** * Method calls only return zvals so we need to validate the target variable is also a zval */ if ($isExpecting) { /** * At this point, we don't know the exact dynamic type returned by the static method call */ $symbolVariable->setDynamicTypes('undefined'); if ($symbolVariable->getType() != 'variable') { throw new CompilerException("Returned values by functions can only be assigned to variant variables", $expression); } } /** * Include fcall header */ $compilationContext->headersManager->add('kernel/fcall'); $compiler = $compilationContext->compiler; $dynamicClass = $expression['dynamic-class']; if (!$dynamicClass) { $className = $expression['class']; $classDefinition = false; if ($className != 'self' && $className != 'parent') { if (is_string($className)) { $className = $compilationContext->getFullName($className); if ($compiler->isClass($className)) { $classDefinition = $compiler->getClassDefinition($className); } else { if ($compiler->isInternalClass($className)) { $classDefinition = $compiler->getInternalClassDefinition($className); } else { throw new CompilerException("Class name: " . $className . " does not exist", $expression); } } } else { foreach ($className as $singleClass) { $className = $compilationContext->getFullName($singleClass); if ($compiler->isClass($singleClass)) { $classDefinition = $compiler->getClassDefinition($singleClass); } else { throw new CompilerException("Class name: " . $className . " does not exist", $expression); } } } } else { if ($className == 'self') { $classDefinition = $compilationContext->classDefinition; } else { if ($className == 'parent') { $classDefinition = $compilationContext->classDefinition; $extendsClass = $classDefinition->getExtendsClass(); if (!$extendsClass) { throw new CompilerException('Cannot call method "' . $methodName . '" on parent because class ' . $classDefinition->getCompleteName() . ' does not extend any class', $expression); } $currentClassDefinition = $classDefinition; $classDefinition = $classDefinition->getExtendsClassDefinition(); } } } } /** * Check if the class implements the method */ if (!$dynamicMethod && !$dynamicClass) { if (!$classDefinition->hasMethod($methodName)) { throw new CompilerException("Class '" . $classDefinition->getCompleteName() . "' does not implement static method: '" . $expression['name'] . "'", $expression); } else { $method = $classDefinition->getMethod($methodName); if ($method->isPrivate() && $method->getClassDefinition() != $compilationContext->classDefinition) { throw new CompilerException("Cannot call private method '" . $methodName . "' out of its scope", $expression); } if ($className != 'parent' && $className != 'self') { if (!$method->isStatic()) { throw new CompilerException("Cannot call non-static method '" . $methodName . "' in a static way", $expression); } } if (!$classDefinition->hasMethod("__callStatic")) { if ($method instanceof ClassMethod && !$method->isInternal()) { /** * Try to produce an exception if method is called with a wrong number of parameters */ if (isset($expression['parameters'])) { $callNumberParameters = count($expression['parameters']); } else { $callNumberParameters = 0; } $classMethod = $classDefinition->getMethod($methodName); $expectedNumberParameters = $classMethod->getNumberOfRequiredParameters(); if (!$expectedNumberParameters && $callNumberParameters > 0) { $numberParameters = $classMethod->getNumberOfParameters(); if ($callNumberParameters > $numberParameters) { throw new CompilerException("Method '" . $classDefinition->getCompleteName() . "::" . $expression['name'] . "' called with a wrong number of parameters, the method has: " . $expectedNumberParameters . ", passed: " . $callNumberParameters, $expression); } } if ($callNumberParameters < $expectedNumberParameters) { throw new CompilerException("Method '" . $classDefinition->getCompleteName() . "::" . $expression['name'] . "' called with a wrong number of parameters, the method has: " . $expectedNumberParameters . ", passed: " . $callNumberParameters, $expression); } } } else { if (!isset($method)) { $method = $classDefinition->getMethod("__callStatic"); if ($method->isPrivate() && $method->getClassDefinition() != $compilationContext->classDefinition) { throw new CompilerException("Cannot call private magic method '__call' out of its scope", $expression); } } } } } /** * Call static methods in the same class, use the special context 'self' * Call static methods in the 'self' context */ if (!$dynamicMethod) { if ($dynamicClass) { $this->callFromDynamicClass($methodName, $expression, $symbolVariable, $mustInit, $isExpecting, $compilationContext); } else { if ($className == 'self' || $classDefinition == $compilationContext->classDefinition) { $this->callSelf($methodName, $expression, $symbolVariable, $mustInit, $isExpecting, $classDefinition, $compilationContext, isset($method) ? $method : null); } else { if ($className == 'parent') { $this->callParent($methodName, $expression, $symbolVariable, $mustInit, $isExpecting, $currentClassDefinition, $compilationContext, isset($method) ? $method : null); } else { $this->callFromClass($methodName, $expression, $symbolVariable, $mustInit, $isExpecting, $classDefinition, $compilationContext, isset($method) ? $method : null); } } } } else { if ($dynamicClass) { $this->callFromDynamicClassDynamicMethod($expression, $symbolVariable, $mustInit, $isExpecting, $compilationContext); } } /** * Add the last call status to the current symbol table */ $this->addCallStatusFlag($compilationContext); /** * Transfer the return type-hint to the returned variable */ if ($isExpecting) { if (isset($method)) { if ($method instanceof ClassMethod) { $returnClassTypes = $method->getReturnClassTypes(); if ($returnClassTypes !== null) { $symbolVariable->setDynamicTypes('object'); foreach ($returnClassTypes as $classType) { $symbolVariable->setClassTypes($compilationContext->getFullName($classType)); } } $returnTypes = $method->getReturnTypes(); if ($returnTypes !== null) { foreach ($returnTypes as $dataType => $returnType) { $symbolVariable->setDynamicTypes($dataType); } } } } } /** * We can mark temporary variables generated as idle here */ foreach ($this->getTemporalVariables() as $tempVariable) { $tempVariable->setIdle(true); } if ($isExpecting) { return new CompiledExpression('variable', $symbolVariable->getRealName(), $expression); } return new CompiledExpression('null', null, $expression); }
/** * @param CompilationContext $compilationContext */ public function compile(CompilationContext $compilationContext) { $exprRaw = $this->_statement['expr']; $expr = new EvalExpression(); $condition = $expr->optimize($exprRaw, $compilationContext); /** * This pass tries to move dynamic variable initialization out of the if/else branch */ if (isset($this->_statement['statements']) && (isset($this->_statement['else_statements']) || isset($this->_statement['elseif_statements']))) { $readDetector = new ReadDetector(); $skipVariantInit = new SkipVariantInit(); $skipVariantInit->setVariablesToSkip(0, $expr->getUsedVariables()); $skipVariantInit->pass(0, new StatementsBlock($this->_statement['statements'])); $lastBranchId = 0; if (isset($this->_statement['else_statements'])) { ++$lastBranchId; $skipVariantInit->setVariablesToSkip($lastBranchId, $expr->getUsedVariables()); $skipVariantInit->pass($lastBranchId, new StatementsBlock($this->_statement['else_statements'])); } if (isset($this->_statement['elseif_statements'])) { foreach ($this->_statement['elseif_statements'] as $key => $statement) { $this->_statement['elseif_statements'][$key]['condition'] = $expr->optimize($statement['expr'], $compilationContext); $lastBranchId++; $skipVariantInit->setVariablesToSkip($lastBranchId, $expr->getUsedVariables()); if (!isset($statement['statements'])) { continue; } $skipVariantInit->pass($lastBranchId, new StatementsBlock($statement)); } } $symbolTable = $compilationContext->symbolTable; foreach ($skipVariantInit->getVariables() as $variable) { if ($symbolTable->hasVariable($variable)) { $symbolVariable = $symbolTable->getVariable($variable); if ($symbolVariable->getType() == 'variable') { if (!$readDetector->detect($variable, $exprRaw)) { $symbolVariable->initVariant($compilationContext); $symbolVariable->skipInitVariant(2); } } } } } $compilationContext->codePrinter->output('if (' . $condition . ') {'); $this->_evalExpression = $expr; /** * Try to mark latest temporary variable used as idle */ $evalVariable = $expr->getEvalVariable(); if (is_object($evalVariable)) { if ($evalVariable->isTemporal()) { $evalVariable->setIdle(true); } } /** * Compile statements in the 'if' block */ if (isset($this->_statement['statements'])) { $st = new StatementsBlock($this->_statement['statements']); $branch = $st->compile($compilationContext, $expr->isUnreachable(), Branch::TYPE_CONDITIONAL_TRUE); $branch->setRelatedStatement($this); } /** * Compile statements in the 'elseif' block */ if (isset($this->_statement['elseif_statements'])) { foreach ($this->_statement['elseif_statements'] as $key => $statement) { if (!isset($statement['statements'])) { continue; } $st = new StatementsBlock($statement['statements']); $compilationContext->codePrinter->output('} else if (' . $statement['condition'] . ') {'); $branch = $st->compile($compilationContext, $expr->isUnreachable(), Branch::TYPE_CONDITIONAL_TRUE); $branch->setRelatedStatement($this); } } /** * Compile statements in the 'else' block */ if (isset($this->_statement['else_statements'])) { $compilationContext->codePrinter->output('} else {'); $st = new StatementsBlock($this->_statement['else_statements']); $branch = $st->compile($compilationContext, $expr->isUnreachableElse(), Branch::TYPE_CONDITIONAL_FALSE); $branch->setRelatedStatement($this); } $compilationContext->codePrinter->output('}'); }
/** * Compiles foo = {expr} * Changes the value of a mutable variable * * @param $variable * @param Variable $symbolVariable * @param CompiledExpression $resolvedExpr * @param ReadDetector $readDetector * @param CompilationContext $compilationContext * @param $statement * @throws CompilerException */ public function assign($variable, ZephirVariable $symbolVariable, CompiledExpression $resolvedExpr, ReadDetector $readDetector, CompilationContext $compilationContext, $statement) { if ($symbolVariable->isReadOnly()) { throw new CompilerException("Cannot mutate variable '" . $variable . "' because it is read only", $statement); } $codePrinter = $compilationContext->codePrinter; /** * Only initialize variables if it's direct assignment */ if ($statement['operator'] == 'assign') { $symbolVariable->setIsInitialized(true, $compilationContext, $statement); } else { if (!$symbolVariable->isInitialized()) { throw new CompilerException("Cannot mutate variable '" . $variable . "' because it is not initialized", $statement); } } /** * Set the assigned value to the variable as a CompiledExpression * We could use this expression for further analysis */ $symbolVariable->setPossibleValue($resolvedExpr, $compilationContext); $type = $symbolVariable->getType(); switch ($type) { case 'int': case 'uint': case 'long': case 'ulong': case 'char': case 'uchar': switch ($resolvedExpr->getType()) { case 'null': switch ($statement['operator']) { case 'assign': $codePrinter->output($variable . ' = 0;'); break; case 'add-assign': $codePrinter->output($variable . ' += 0;'); break; case 'sub-assign': $codePrinter->output($variable . ' -= 0;'); break; case 'mul-assign': $codePrinter->output($variable . ' *= 0;'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: int", $statement); } break; case 'int': case 'uint': case 'long': case 'ulong': switch ($statement['operator']) { case 'assign': $codePrinter->output($variable . ' = ' . $resolvedExpr->getCode() . ';'); break; case 'add-assign': $codePrinter->output($variable . ' += ' . $resolvedExpr->getCode() . ';'); break; case 'sub-assign': $codePrinter->output($variable . ' -= ' . $resolvedExpr->getCode() . ';'); break; case 'mul-assign': $codePrinter->output($variable . ' *= ' . $resolvedExpr->getCode() . ';'); break; case 'div-assign': $codePrinter->output($variable . ' /= ' . $resolvedExpr->getCode() . ';'); break; case 'mod-assign': $codePrinter->output($variable . ' %= ' . $resolvedExpr->getCode() . ';'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: int", $statement); } break; case 'char': case 'uchar': switch ($statement['operator']) { case 'assign': $codePrinter->output($variable . ' = \'' . $resolvedExpr->getCode() . '\';'); break; case 'add-assign': $codePrinter->output($variable . ' += \'' . $resolvedExpr->getCode() . '\';'); break; case 'sub-assign': $codePrinter->output($variable . ' -= \'' . $resolvedExpr->getCode() . '\';'); break; case 'mul-assign': $codePrinter->output($variable . ' *= \'' . $resolvedExpr->getCode() . '\';'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: int", $statement); } break; case 'double': switch ($statement['operator']) { case 'assign': $codePrinter->output($variable . ' = (long) (' . $resolvedExpr->getCode() . ');'); break; case 'add-assign': $codePrinter->output($variable . ' += (long) (' . $resolvedExpr->getCode() . ');'); break; case 'sub-assign': $codePrinter->output($variable . ' -= (long) (' . $resolvedExpr->getCode() . ');'); break; case 'mul-assign': $codePrinter->output($variable . ' *= (long) (' . $resolvedExpr->getCode() . ');'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: int", $statement); } break; case 'bool': switch ($statement['operator']) { case 'assign': $codePrinter->output($variable . ' = ' . $resolvedExpr->getBooleanCode() . ';'); break; case 'add-assign': $codePrinter->output($variable . ' += ' . $resolvedExpr->getBooleanCode() . ';'); break; case 'sub-assign': $codePrinter->output($variable . ' -= ' . $resolvedExpr->getBooleanCode() . ';'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: int", $statement); } break; case 'variable': $itemVariable = $compilationContext->symbolTable->getVariableForRead($resolvedExpr->getCode(), $compilationContext, $statement); switch ($itemVariable->getType()) { case 'int': case 'uint': case 'long': case 'ulong': case 'bool': case 'char': case 'uchar': switch ($statement['operator']) { case 'assign': $codePrinter->output($variable . ' = ' . $itemVariable->getName() . ';'); break; case 'add-assign': $codePrinter->output($variable . ' += ' . $itemVariable->getName() . ';'); break; case 'sub-assign': $codePrinter->output($variable . ' -= ' . $itemVariable->getName() . ';'); break; case 'mul-assign': $codePrinter->output($variable . ' *= ' . $itemVariable->getName() . ';'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: int", $statement); } break; case 'double': switch ($statement['operator']) { case 'assign': $codePrinter->output($variable . ' = (long) ' . $itemVariable->getName() . ';'); break; case 'add-assign': $codePrinter->output($variable . ' += (long) ' . $itemVariable->getName() . ';'); break; case 'sub-assign': $codePrinter->output($variable . ' -= (long) ' . $itemVariable->getName() . ';'); break; case 'mul-assign': $codePrinter->output($variable . ' *= (long) ' . $itemVariable->getName() . ';'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: int", $statement); } break; case 'variable': $compilationContext->headersManager->add('kernel/operators'); switch ($statement['operator']) { case 'assign': $codePrinter->output($variable . ' = zephir_get_numberval(' . $resolvedExpr->resolve(null, $compilationContext) . ');'); break; case 'add-assign': $codePrinter->output($variable . ' += zephir_get_numberval(' . $resolvedExpr->resolve(null, $compilationContext) . ');'); break; case 'sub-assign': $codePrinter->output($variable . ' -= zephir_get_numberval(' . $resolvedExpr->resolve(null, $compilationContext) . ');'); break; case 'mul-assign': $codePrinter->output($variable . ' *= zephir_get_numberval(' . $resolvedExpr->resolve(null, $compilationContext) . ');'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: int", $statement); } break; default: throw new CompilerException("Unknown type: " . $itemVariable->getType(), $statement); } break; default: throw new CompilerException("Value type '" . $resolvedExpr->getType() . "' cannot be assigned to variable: int", $statement); } break; case 'double': switch ($resolvedExpr->getType()) { case 'null': switch ($statement['operator']) { case 'assign': $codePrinter->output($variable . ' = 0.0;'); break; case 'add-assign': $codePrinter->output($variable . ' += 0.0;'); break; case 'sub-assign': $codePrinter->output($variable . ' -= 0.0;'); break; case 'mul-assign': $codePrinter->output($variable . ' *= 0.0;'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: double", $statement); } break; case 'int': case 'uint': case 'long': case 'ulong': switch ($statement['operator']) { case 'assign': $codePrinter->output($variable . ' = (double) (' . $resolvedExpr->getCode() . ');'); break; case 'add-assign': $codePrinter->output($variable . ' += (double) (' . $resolvedExpr->getCode() . ');'); break; case 'sub-assign': $codePrinter->output($variable . ' -= (double) (' . $resolvedExpr->getCode() . ');'); break; case 'mul-assign': $codePrinter->output($variable . ' *= (double) (' . $resolvedExpr->getCode() . ');'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: double", $statement); } break; case 'double': switch ($statement['operator']) { case 'assign': $codePrinter->output($variable . ' = ' . $resolvedExpr->getCode() . ';'); break; case 'add-assign': $codePrinter->output($variable . ' += ' . $resolvedExpr->getCode() . ';'); break; case 'sub-assign': $codePrinter->output($variable . ' -= ' . $resolvedExpr->getCode() . ';'); break; case 'mul-assign': $codePrinter->output($variable . ' *= ' . $resolvedExpr->getCode() . ';'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: double", $statement); } break; case 'bool': switch ($statement['operator']) { case 'assign': $codePrinter->output($variable . ' = ' . $resolvedExpr->getBooleanCode() . ';'); break; case 'add-assign': $codePrinter->output($variable . ' += ' . $resolvedExpr->getBooleanCode() . ';'); break; case 'sub-assign': $codePrinter->output($variable . ' -= ' . $resolvedExpr->getBooleanCode() . ';'); break; case 'mul-assign': $codePrinter->output($variable . ' *= ' . $resolvedExpr->getBooleanCode() . ';'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: double", $statement); } break; case 'variable': $itemVariable = $compilationContext->symbolTable->getVariableForRead($resolvedExpr->getCode(), $compilationContext, $statement); switch ($itemVariable->getType()) { case 'int': case 'uint': case 'long': case 'ulong': case 'bool': switch ($statement['operator']) { case 'assign': $codePrinter->output($variable . ' = (double) ' . $itemVariable->getName() . ';'); break; case 'add-assign': $codePrinter->output($variable . ' += (double) ' . $itemVariable->getName() . ';'); break; case 'sub-assign': $codePrinter->output($variable . ' -= (double) ' . $itemVariable->getName() . ';'); break; case 'mul-assign': $codePrinter->output($variable . ' *= (double) ' . $itemVariable->getName() . ';'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: double", $statement); } break; case 'double': switch ($statement['operator']) { case 'assign': $codePrinter->output($variable . ' = ' . $itemVariable->getName() . ';'); break; case 'add-assign': $codePrinter->output($variable . ' += ' . $itemVariable->getName() . ';'); break; case 'sub-assign': $codePrinter->output($variable . ' -= ' . $itemVariable->getName() . ';'); break; case 'mul-assign': $codePrinter->output($variable . ' *= ' . $itemVariable->getName() . ';'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: double", $statement); } break; case 'variable': $compilationContext->headersManager->add('kernel/operators'); switch ($statement['operator']) { case 'assign': $codePrinter->output($variable . ' = zephir_get_numberval(' . $itemVariable->getName() . ');'); break; case 'add-assign': $codePrinter->output($variable . ' += zephir_get_numberval(' . $itemVariable->getName() . ');'); break; case 'sub-assign': $codePrinter->output($variable . ' -= zephir_get_numberval(' . $itemVariable->getName() . ');'); break; case 'mul-assign': $codePrinter->output($variable . ' *= zephir_get_numberval(' . $itemVariable->getName() . ');'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: double", $statement); } break; default: throw new CompilerException("Unknown type: " . $itemVariable->getType(), $statement); } break; default: throw new CompilerException("Unknown type " . $resolvedExpr->getType(), $statement); } break; case 'array': switch ($resolvedExpr->getType()) { case 'variable': case 'array': switch ($statement['operator']) { case 'assign': if ($variable != $resolvedExpr->getCode()) { $symbolVariable->setMustInitNull(true); $compilationContext->symbolTable->mustGrownStack(true); /* Inherit the dynamic type data from the assigned value */ $symbolVariable->setDynamicTypes('array'); $codePrinter->output('ZEPHIR_CPY_WRT(' . $variable . ', ' . $resolvedExpr->getCode() . ');'); } break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: " . $resolvedExpr->getType(), $resolvedExpr->getOriginal()); } break; default: throw new CompilerException("You cannot {$statement['operator']} {$resolvedExpr->getType()} for array type", $resolvedExpr->getOriginal()); } break; case 'string': switch ($resolvedExpr->getType()) { case 'null': switch ($statement['operator']) { case 'assign': $symbolVariable->initVariant($compilationContext); $codePrinter->output('ZVAL_EMPTY_STRING(' . $variable . ');'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: string", $statement); } break; case 'string': switch ($statement['operator']) { case 'assign': $symbolVariable->initVariant($compilationContext); if ($resolvedExpr->getCode()) { $codePrinter->output('ZVAL_STRING(' . $variable . ', "' . $resolvedExpr->getCode() . '", 1);'); } else { $codePrinter->output('ZVAL_EMPTY_STRING(' . $variable . ');'); } break; case 'concat-assign': $codePrinter->output('zephir_concat_self_str(&' . $variable . ', "' . $resolvedExpr->getCode() . '", sizeof("' . $resolvedExpr->getCode() . '")-1 TSRMLS_CC);'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: string", $statement); } break; case 'char': case 'uchar': switch ($statement['operator']) { case 'assign': $symbolVariable->initVariant($compilationContext); if ($resolvedExpr->getCode()) { $codePrinter->output('ZVAL_STRING(' . $variable . ', "' . $resolvedExpr->getCode() . '", 1);'); } else { $codePrinter->output('ZVAL_EMPTY_STRING(' . $variable . ');'); } break; case 'concat-assign': $compilationContext->headersManager->add('kernel/operators'); $codePrinter->output('zephir_concat_self_str(&' . $variable . ', "' . $resolvedExpr->getCode() . '", sizeof("' . $resolvedExpr->getCode() . '")-1 TSRMLS_CC);'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: string", $statement); } break; case 'variable': $itemVariable = $compilationContext->symbolTable->getVariableForRead($resolvedExpr->getCode(), $compilationContext, $statement); switch ($itemVariable->getType()) { case 'int': case 'uint': case 'long': case 'ulong': switch ($statement['operator']) { case 'assign': $symbolVariable->initVariant($compilationContext); $compilationContext->headersManager->add('kernel/string'); $codePrinter->output('Z_STRLEN_P(' . $variable . ') = zephir_spprintf(&Z_STRVAL_P(' . $variable . '), 0, "%ld", ' . $itemVariable->getName() . ');'); $codePrinter->output('Z_TYPE_P(' . $variable . ') = IS_STRING;'); break; case 'concat-assign': $compilationContext->headersManager->add('kernel/operators'); $codePrinter->output('zephir_concat_self_long(&' . $variable . ', ' . $itemVariable->getName() . ' TSRMLS_CC);'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: string", $statement); } break; case 'char': case 'uchar': switch ($statement['operator']) { case 'assign': $symbolVariable->initVariant($compilationContext); $compilationContext->headersManager->add('kernel/string'); $codePrinter->output('Z_STRLEN_P(' . $variable . ') = zephir_spprintf(&Z_STRVAL_P(' . $variable . '), 0, "%c", ' . $itemVariable->getName() . ');'); $codePrinter->output('Z_TYPE_P(' . $variable . ') = IS_STRING;'); break; case 'concat-assign': $compilationContext->headersManager->add('kernel/operators'); $codePrinter->output('zephir_concat_self_char(&' . $variable . ', ' . $itemVariable->getName() . ' TSRMLS_CC);'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: string", $statement); } break; case 'string': switch ($statement['operator']) { case 'assign': $symbolVariable->setMustInitNull(true); $compilationContext->symbolTable->mustGrownStack(true); if ($variable != $itemVariable->getName()) { $codePrinter->output('ZEPHIR_CPY_WRT(' . $variable . ', ' . $itemVariable->getName() . ');'); } break; case 'concat-assign': $compilationContext->headersManager->add('kernel/operators'); $codePrinter->output('zephir_concat_self(&' . $variable . ', ' . $itemVariable->getName() . ' TSRMLS_CC);'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: string", $statement); } break; case 'variable': switch ($statement['operator']) { case 'assign': $symbolVariable->setMustInitNull(true); $compilationContext->symbolTable->mustGrownStack(true); $compilationContext->headersManager->add('kernel/operators'); $codePrinter->output('zephir_get_strval(' . $variable . ', ' . $itemVariable->getName() . ');'); break; case 'concat-assign': $compilationContext->headersManager->add('kernel/operators'); $codePrinter->output('zephir_concat_self(&' . $variable . ', ' . $itemVariable->getName() . ' TSRMLS_CC);'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: " . $itemVariable->getType(), $statement); } break; default: throw new CompilerException("Unknown type: " . $itemVariable->getType(), $statement); } break; default: throw new CompilerException("Unknown type " . $resolvedExpr->getType(), $statement); } break; case 'bool': switch ($resolvedExpr->getType()) { case 'null': switch ($statement['operator']) { case 'assign': $codePrinter->output($variable . ' = 0;'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: null", $statement); } break; case 'int': case 'uint': case 'long': case 'ulong': case 'double': switch ($statement['operator']) { case 'assign': $codePrinter->output($variable . ' = (' . $resolvedExpr->getCode() . ') ? 1 : 0;'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: " . $resolvedExpr->getType(), $statement); } break; case 'char': case 'uchar': switch ($statement['operator']) { case 'assign': $codePrinter->output($variable . ' = (\'' . $resolvedExpr->getCode() . '\') ? 1 : 0;'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: " . $resolvedExpr->getType(), $statement); } break; case 'bool': switch ($statement['operator']) { case 'assign': $codePrinter->output($variable . ' = ' . $resolvedExpr->getBooleanCode() . ';'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: " . $resolvedExpr->getType(), $statement); } break; case 'variable': $itemVariable = $compilationContext->symbolTable->getVariableForRead($resolvedExpr->getCode(), $compilationContext, $statement); switch ($itemVariable->getType()) { case 'int': case 'uint': case 'long': case 'ulong': case 'double': switch ($statement['operator']) { case 'assign': $codePrinter->output($variable . ' = (' . $itemVariable->getName() . ') ? 1 : 0;'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: " . $itemVariable->getType(), $statement); } break; case 'bool': switch ($statement['operator']) { case 'assign': $codePrinter->output($variable . ' = ' . $itemVariable->getName() . ';'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: " . $itemVariable->getType(), $statement); } break; case 'variable': case 'string': case 'array': switch ($statement['operator']) { case 'assign': $codePrinter->output($variable . ' = zephir_is_true(' . $itemVariable->getName() . ');'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: " . $itemVariable->getType(), $statement); } break; default: throw new CompilerException("Cannot assign variable: " . $itemVariable->getType(), $statement); } break; default: throw new CompilerException("Unknown type: " . $resolvedExpr->getType(), $statement); } break; case 'variable': switch ($resolvedExpr->getType()) { case 'null': switch ($statement['operator']) { case 'assign': $symbolVariable->initVariant($compilationContext); $symbolVariable->setDynamicTypes('null'); if ($symbolVariable->isLocalOnly()) { $codePrinter->output('ZVAL_NULL(&' . $variable . ');'); } else { $codePrinter->output('ZVAL_NULL(' . $variable . ');'); } break; } break; case 'int': case 'uint': case 'long': case 'ulong': if ($symbolVariable->isLocalOnly()) { $symbol = '&' . $variable; } else { $symbol = $variable; } switch ($statement['operator']) { case 'mul-assign': case 'sub-assign': case 'add-assign': switch ($statement['operator']) { case 'mul-assign': $functionName = 'ZEPHIR_MUL_ASSIGN'; break; case 'sub-assign': $functionName = 'ZEPHIR_SUB_ASSIGN'; break; case 'add-assign': $functionName = 'ZEPHIR_ADD_ASSIGN'; break; } $tempVariable = $compilationContext->symbolTable->getTempVariableForWrite('variable', $compilationContext); $codePrinter->output('ZVAL_LONG(' . $tempVariable->getName() . ', ' . $resolvedExpr->getCode() . ');'); $compilationContext->symbolTable->mustGrownStack(true); $compilationContext->headersManager->add('kernel/operators'); $codePrinter->output($functionName . '(' . $symbol . ', ' . $tempVariable->getName() . ');'); break; case 'assign': $symbolVariable->setDynamicTypes('long'); if ($readDetector->detect($variable, $resolvedExpr->getOriginal())) { $tempVariable = $compilationContext->symbolTable->getTempVariableForWrite('int', $compilationContext); $codePrinter->output($tempVariable->getName() . ' = ' . $resolvedExpr->getCode() . ';'); $symbolVariable->initVariant($compilationContext); $codePrinter->output('ZVAL_LONG(' . $symbol . ', ' . $tempVariable->getName() . ');'); } else { $symbolVariable->initVariant($compilationContext); $codePrinter->output('ZVAL_LONG(' . $symbol . ', ' . $resolvedExpr->getCode() . ');'); } break; case 'div-assign': $symbolVariable->setDynamicTypes('double'); if ($readDetector->detect($variable, $resolvedExpr->getOriginal())) { $tempVariable = $compilationContext->symbolTable->getTempVariableForWrite('double', $compilationContext); $codePrinter->output($tempVariable->getName() . ' = ' . $resolvedExpr->getCode() . ';'); $symbolVariable->initVariant($compilationContext); $codePrinter->output('ZVAL_DOUBLE(' . $symbol . ', ' . $tempVariable->getName() . ');'); } else { $symbolVariable->initVariant($compilationContext); $codePrinter->output('ZVAL_DOUBLE(' . $symbol . ', ' . $resolvedExpr->getCode() . ');'); } break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: " . $resolvedExpr->getType(), $resolvedExpr->getOriginal()); } break; case 'char': case 'uchar': if ($symbolVariable->isLocalOnly()) { $symbol = '&' . $variable; } else { $symbol = $variable; } switch ($statement['operator']) { case 'assign': $symbolVariable->setDynamicTypes('long'); if ($readDetector->detect($variable, $resolvedExpr->getOriginal())) { $tempVariable = $compilationContext->symbolTable->getTempVariableForWrite('char', $compilationContext); $codePrinter->output($tempVariable->getName() . ' = ' . $resolvedExpr->getCode() . ';'); $symbolVariable->initVariant($compilationContext); $codePrinter->output('ZVAL_LONG(' . $symbol . ', ' . $tempVariable->getName() . ');'); } else { $symbolVariable->initVariant($compilationContext); $codePrinter->output('ZVAL_LONG(' . $symbol . ', \'' . $resolvedExpr->getCode() . '\');'); } break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: " . $resolvedExpr->getType(), $resolvedExpr->getOriginal()); } break; case 'double': if ($symbolVariable->isLocalOnly()) { $symbol = '&' . $variable; } else { $symbol = $variable; } switch ($statement['operator']) { case 'assign': $symbolVariable->setDynamicTypes('double'); if ($readDetector->detect($variable, $resolvedExpr->getOriginal())) { $tempVariable = $compilationContext->symbolTable->getTempVariableForWrite('double', $compilationContext); $codePrinter->output($tempVariable->getName() . ' = ' . $resolvedExpr->getCode() . ';'); $symbolVariable->initVariant($compilationContext); $codePrinter->output('ZVAL_DOUBLE(' . $symbol . ', ' . $tempVariable->getName() . ');'); } else { $symbolVariable->initVariant($compilationContext); $codePrinter->output('ZVAL_DOUBLE(' . $symbol . ', ' . $resolvedExpr->getCode() . ');'); } break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: " . $resolvedExpr->getType(), $resolvedExpr->getOriginal()); } break; case 'bool': if ($symbolVariable->isLocalOnly()) { $symbol = '&' . $variable; } else { $symbol = $variable; } switch ($statement['operator']) { case 'assign': $symbolVariable->setDynamicTypes('bool'); if ($resolvedExpr->getCode() == 'true') { $symbolVariable->initVariant($compilationContext); $codePrinter->output('ZVAL_BOOL(' . $symbol . ', 1);'); } else { if ($resolvedExpr->getCode() == 'false') { $symbolVariable->initVariant($compilationContext); $codePrinter->output('ZVAL_BOOL(' . $symbol . ', 0);'); } else { if ($readDetector->detect($variable, $resolvedExpr->getOriginal())) { $tempVariable = $compilationContext->symbolTable->getTempVariableForWrite('double', $compilationContext); $codePrinter->output($tempVariable->getName() . ' = ' . $resolvedExpr->getBooleanCode() . ';'); $symbolVariable->initVariant($compilationContext); $codePrinter->output('ZVAL_BOOL(' . $symbol . ', ' . $tempVariable->getName() . ');'); } else { $symbolVariable->initVariant($compilationContext); $codePrinter->output('ZVAL_BOOL(' . $symbol . ', ' . $resolvedExpr->getBooleanCode() . ');'); } } } break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: " . $resolvedExpr->getType(), $resolvedExpr->getOriginal()); } break; case 'string': switch ($statement['operator']) { case 'assign': $symbolVariable->initVariant($compilationContext); $symbolVariable->setDynamicTypes('string'); if ($symbolVariable->isLocalOnly()) { $codePrinter->output('ZVAL_STRING(&' . $variable . ', "' . $resolvedExpr->getCode() . '", 1);'); } else { $codePrinter->output('ZVAL_STRING(' . $variable . ', "' . $resolvedExpr->getCode() . '", 1);'); } break; case 'concat-assign': $compilationContext->headersManager->add('kernel/operators'); $codePrinter->output('zephir_concat_self_str(&' . $variable . ', SL("' . $resolvedExpr->getCode() . '") TSRMLS_CC);'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: " . $resolvedExpr->getType(), $resolvedExpr->getOriginal()); } break; case 'array': switch ($statement['operator']) { case 'assign': if ($variable != $resolvedExpr->getCode()) { $symbolVariable->setMustInitNull(true); $compilationContext->symbolTable->mustGrownStack(true); /* Inherit the dynamic type data from the assigned value */ $symbolVariable->setDynamicTypes('array'); $codePrinter->output('ZEPHIR_CPY_WRT(' . $variable . ', ' . $resolvedExpr->getCode() . ');'); } break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: " . $resolvedExpr->getType(), $resolvedExpr->getOriginal()); } break; case 'variable': $itemVariable = $compilationContext->symbolTable->getVariableForRead($resolvedExpr->getCode(), $compilationContext, $resolvedExpr->getOriginal()); switch ($itemVariable->getType()) { case 'int': case 'uint': case 'long': case 'ulong': case 'char': case 'uchar': switch ($statement['operator']) { case 'assign': $symbolVariable->initVariant($compilationContext); $symbolVariable->setDynamicTypes('long'); if ($symbolVariable->isLocalOnly()) { $codePrinter->output('ZVAL_LONG(&' . $variable . ', ' . $itemVariable->getName() . ');'); } else { $codePrinter->output('ZVAL_LONG(' . $variable . ', ' . $itemVariable->getName() . ');'); } break; case 'add-assign': $compilationContext->headersManager->add('kernel/operators'); $symbolVariable->initVariant($compilationContext); $symbolVariable->setDynamicTypes('long'); $tempVariable = $compilationContext->symbolTable->getTempVariableForWrite($itemVariable->getType(), $compilationContext); if ($symbolVariable->isLocalOnly()) { $codePrinter->output($tempVariable->getName() . ' = zephir_get_numberval(&' . $variable . ');'); $codePrinter->output('ZVAL_LONG(&' . $variable . ', ' . $tempVariable->getName() . ' + ' . $itemVariable->getName() . ');'); } else { $codePrinter->output($tempVariable->getName() . ' = zephir_get_numberval(' . $variable . ');'); $codePrinter->output('ZVAL_LONG(' . $variable . ', ' . $tempVariable->getName() . ' + ' . $itemVariable->getName() . ');'); } break; case 'sub-assign': $compilationContext->headersManager->add('kernel/operators'); $symbolVariable->initVariant($compilationContext); $symbolVariable->setDynamicTypes('long'); $tempVariable = $compilationContext->symbolTable->getTempVariableForWrite($itemVariable->getType(), $compilationContext); if ($symbolVariable->isLocalOnly()) { $codePrinter->output($tempVariable->getName() . ' = zephir_get_numberval(&' . $variable . ');'); $codePrinter->output('ZVAL_LONG(&' . $variable . ', ' . $tempVariable->getName() . ' - ' . $itemVariable->getName() . ');'); } else { $codePrinter->output($tempVariable->getName() . ' = zephir_get_numberval(' . $variable . ');'); $codePrinter->output('ZVAL_LONG(' . $variable . ', ' . $tempVariable->getName() . ' - ' . $itemVariable->getName() . ');'); } break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: " . $itemVariable->getType(), $statement); } break; case 'double': switch ($statement['operator']) { case 'assign': $symbolVariable->initVariant($compilationContext); $symbolVariable->setDynamicTypes('double'); if ($symbolVariable->isLocalOnly()) { $codePrinter->output('ZVAL_DOUBLE(&' . $variable . ', ' . $itemVariable->getName() . ');'); } else { $codePrinter->output('ZVAL_DOUBLE(' . $variable . ', ' . $itemVariable->getName() . ');'); } break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: " . $itemVariable->getType(), $statement); } break; case 'bool': switch ($statement['operator']) { case 'assign': $symbolVariable->initVariant($compilationContext); $symbolVariable->setDynamicTypes('bool'); if ($symbolVariable->isLocalOnly()) { $codePrinter->output('ZVAL_BOOL(&' . $variable . ', ' . $itemVariable->getName() . ');'); } else { $codePrinter->output('ZVAL_BOOL(' . $variable . ', ' . $itemVariable->getName() . ');'); } break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: " . $itemVariable->getType(), $statement); } break; case 'array': switch ($statement['operator']) { case 'assign': if ($variable != $resolvedExpr->getCode()) { $symbolVariable->setMustInitNull(true); $compilationContext->symbolTable->mustGrownStack(true); /* Inherit the dynamic type data from the assigned value */ $symbolVariable->setDynamicTypes('array'); $codePrinter->output('ZEPHIR_CPY_WRT(' . $variable . ', ' . $resolvedExpr->getCode() . ');'); } break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: " . $resolvedExpr->getType(), $resolvedExpr->getOriginal()); } break; case 'variable': switch ($statement['operator']) { case 'assign': if ($itemVariable->getName() != $variable) { $symbolVariable->setMustInitNull(true); $compilationContext->symbolTable->mustGrownStack(true); /* Inherit the dynamic type data from the assigned value */ $symbolVariable->setDynamicTypes($itemVariable->getDynamicTypes()); $symbolVariable->setClassTypes($itemVariable->getClassTypes()); $codePrinter->output('ZEPHIR_CPY_WRT(' . $variable . ', ' . $itemVariable->getName() . ');'); if ($itemVariable->isTemporal()) { $itemVariable->setIdle(true); } } break; case 'concat-assign': $compilationContext->headersManager->add('kernel/operators'); $codePrinter->output('zephir_concat_self(&' . $variable . ', ' . $itemVariable->getName() . ' TSRMLS_CC);'); break; case 'add-assign': $compilationContext->symbolTable->mustGrownStack(true); $compilationContext->headersManager->add('kernel/operators'); $codePrinter->output('ZEPHIR_ADD_ASSIGN(' . $variable . ', ' . $itemVariable->getName() . ');'); break; case 'sub-assign': $compilationContext->symbolTable->mustGrownStack(true); $compilationContext->headersManager->add('kernel/operators'); $codePrinter->output('ZEPHIR_SUB_ASSIGN(' . $variable . ', ' . $itemVariable->getName() . ');'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: " . $itemVariable->getType(), $statement); } break; case 'string': switch ($statement['operator']) { case 'assign': if ($itemVariable->getName() != $variable) { $symbolVariable->setMustInitNull(true); $compilationContext->symbolTable->mustGrownStack(true); /* Inherit the dynamic type data from the assigned value */ $symbolVariable->setDynamicTypes($itemVariable->getDynamicTypes()); $symbolVariable->setClassTypes($itemVariable->getClassTypes()); $codePrinter->output('ZEPHIR_CPY_WRT(' . $variable . ', ' . $itemVariable->getName() . ');'); if ($itemVariable->isTemporal()) { $itemVariable->setIdle(true); } } break; case 'concat-assign': $compilationContext->headersManager->add('kernel/operators'); $codePrinter->output('zephir_concat_self(&' . $variable . ', ' . $itemVariable->getName() . ' TSRMLS_CC);'); break; default: throw new CompilerException("Operator '" . $statement['operator'] . "' is not supported for variable type: " . $itemVariable->getType(), $statement); } break; default: throw new CompilerException("Unknown type: " . $itemVariable->getType(), $resolvedExpr->getOriginal()); } break; default: throw new CompilerException("Unknown type: " . $resolvedExpr->getType(), $resolvedExpr->getOriginal()); } break; default: throw new CompilerException("Unknown type: " . $type, $statement); } }
/** * @param CompilationContext $compilationContext * @throws CompilerException */ public function compile(CompilationContext $compilationContext) { $readDetector = new ReadDetector(); $statement = $this->_statement; foreach ($statement['assignments'] as $assignment) { $variable = $assignment['variable']; /** * Get the symbol from the symbol table if necessary */ switch ($assignment['assign-type']) { case 'static-property': case 'static-property-append': case 'static-property-array-index': case 'static-property-array-index-append': case 'dynamic-variable-string': $symbolVariable = null; break; case 'array-index': case 'variable-append': case 'object-property': case 'array-index-append': case 'string-dynamic-object-property': case 'variable-dynamic-object-property': $symbolVariable = $compilationContext->symbolTable->getVariableForUpdate($variable, $compilationContext, $assignment); break; default: $symbolVariable = $compilationContext->symbolTable->getVariableForWrite($variable, $compilationContext, $assignment); break; } /** * Incr/Decr assignments don't require an expression */ if (isset($assignment['expr'])) { $expr = new Expression($assignment['expr']); switch ($assignment['assign-type']) { case 'variable': if (!$readDetector->detect($variable, $assignment['expr'])) { if (isset($assignment['operator'])) { if ($assignment['operator'] == 'assign') { $expr->setExpectReturn(true, $symbolVariable); } } else { $expr->setExpectReturn(true, $symbolVariable); } } else { if (isset($assignment['operator'])) { if ($assignment['operator'] == 'assign') { $expr->setExpectReturn(true); } } else { $expr->setExpectReturn(true); } } break; } switch ($assignment['expr']['type']) { case 'property-access': case 'array-access': case 'type-hint': $expr->setReadOnly(true); break; } $resolvedExpr = $expr->compile($compilationContext); /** * Bad implemented operators could return values different than objects */ if (!is_object($resolvedExpr)) { throw new CompilerException("Resolved expression is not valid", $assignment['expr']); } } /** * There are four types of assignments */ switch ($assignment['assign-type']) { case 'variable': $let = new LetVariable(); $let->assign($variable, $symbolVariable, $resolvedExpr, $readDetector, $compilationContext, $assignment); break; case 'variable-append': $let = new LetVariableAppend(); $let->assign($variable, $symbolVariable, $resolvedExpr, $compilationContext, $assignment); break; case 'object-property': $let = new LetObjectProperty(); $let->assign($variable, $symbolVariable, $resolvedExpr, $compilationContext, $assignment); break; case 'variable-dynamic-object-property': $let = new LetObjectDynamicProperty(); $let->assign($variable, $symbolVariable, $resolvedExpr, $compilationContext, $assignment); break; case 'string-dynamic-object-property': $let = new LetObjectDynamicStringProperty(); $let->assign($variable, $symbolVariable, $resolvedExpr, $compilationContext, $assignment); break; case 'static-property': $let = new LetStaticProperty(); $let->assignStatic($variable, $assignment['property'], $resolvedExpr, $compilationContext, $assignment); break; case 'static-property-append': $let = new LetStaticPropertyAppend(); $let->assignStatic($variable, $assignment['property'], $resolvedExpr, $compilationContext, $assignment); break; case 'static-property-array-index': $let = new LetStaticPropertyArrayIndex(); $let->assignStatic($variable, $assignment['property'], $resolvedExpr, $compilationContext, $assignment); break; case 'static-property-array-index-append': $let = new LetStaticPropertyArrayIndexAppend(); $let->assignStatic($variable, $assignment['property'], $resolvedExpr, $compilationContext, $assignment); break; case 'array-index': $let = new LetArrayIndex(); $let->assign($variable, $symbolVariable, $resolvedExpr, $compilationContext, $assignment); break; case 'array-index-append': $let = new LetArrayIndexAppend(); $let->assign($variable, $symbolVariable, $resolvedExpr, $compilationContext, $assignment); break; case 'object-property-append': $let = new LetObjectPropertyAppend(); $let->assign($variable, $symbolVariable, $resolvedExpr, $compilationContext, $assignment); break; case 'object-property-array-index': $let = new LetObjectPropertyArrayIndex(); $let->assign($variable, $symbolVariable, $resolvedExpr, $compilationContext, $assignment); break; case 'object-property-array-index-append': $let = new LetObjectPropertyArrayIndexAppend(); $let->assign($variable, $symbolVariable, $resolvedExpr, $compilationContext, $assignment); break; case 'incr': $let = new LetIncr(); $let->assign($variable, $symbolVariable, $compilationContext, $assignment); break; case 'decr': $let = new LetDecr(); $let->assign($variable, $symbolVariable, $compilationContext, $assignment); break; case 'object-property-incr': $let = new LetObjectPropertyIncr(); $let->assign($variable, $assignment['property'], $symbolVariable, $compilationContext, $assignment); break; case 'object-property-decr': $let = new LetObjectPropertyDecr(); $let->assign($variable, $assignment['property'], $symbolVariable, $compilationContext, $assignment); break; case 'dynamic-variable': $let = new LetExportSymbol(); $let->assign($variable, $symbolVariable, $resolvedExpr, $compilationContext, $assignment); break; case 'dynamic-variable-string': $let = new LetExportSymbolString(); $let->assign($variable, $symbolVariable, $resolvedExpr, $compilationContext, $assignment); break; default: throw new CompilerException("Unknown assignment: " . $assignment['assign-type'], $assignment); } } }