/** * Compiles the file * * @param Compiler $compiler * @param StringsManager $stringsManager */ public function compile(Compiler $compiler, StringsManager $stringsManager) { if (!$this->_ir) { throw new CompilerException('IR related to compiled file is missing'); } /** * External classes should not be compiled as part of the extension */ if ($this->_external) { return; } /** * Compilation context stores common objects required by compilation entities */ $compilationContext = new CompilationContext(); /** * Set global compiler in the compilation context */ $compilationContext->compiler = $compiler; /** * Set global config in the compilation context */ $compilationContext->config = $this->_config; /** * Set global logger in the compilation context */ $compilationContext->logger = $this->_logger; /** * Set global strings manager */ $compilationContext->stringsManager = $stringsManager; /** * Headers manager */ $headersManager = new HeadersManager(); $compilationContext->headersManager = $headersManager; /** * Main code-printer for the file */ $codePrinter = new CodePrinter(); $compilationContext->codePrinter = $codePrinter; /** * Alias manager */ $compilationContext->aliasManager = $this->_aliasManager; $codePrinter->outputBlankLine(); $class = false; $interface = false; foreach ($this->_ir as $topStatement) { switch ($topStatement['type']) { case 'class': if ($interface || $class) { throw new CompilerException("More than one class defined in the same file", $topStatement); } $class = true; $this->compileClass($compilationContext, $this->_namespace, $topStatement); break; case 'interface': if ($interface || $class) { throw new CompilerException("More than one class defined in the same file", $topStatement); } $class = true; $this->compileClass($compilationContext, $this->_namespace, $topStatement); break; case 'comment': $this->compileComment($compilationContext, $topStatement); break; } } /* ensure functions are handled last */ foreach ($this->_functionDefinitions as $funcDef) { $this->compileFunction($compilationContext, $funcDef); } /* apply headers */ $this->applyClassHeaders($compilationContext); $classDefinition = $this->_classDefinition; if (!$classDefinition) { $this->_ir = null; return; } $classDefinition->setOriginalNode($this->_originalNode); $completeName = $classDefinition->getCompleteName(); $path = str_replace('\\', DIRECTORY_SEPARATOR, strtolower($completeName)); $filePath = 'ext/' . $path . '.zep.c'; $filePathHeader = 'ext/' . $path . '.zep.h'; if (strpos($path, DIRECTORY_SEPARATOR)) { $dirname = dirname($filePath); if (!is_dir($dirname)) { mkdir($dirname, 0755, true); } } if ($codePrinter) { /** * If the file does not exists we create it for the first time */ if (!file_exists($filePath)) { file_put_contents($filePath, $codePrinter->getOutput()); if ($compilationContext->headerPrinter) { file_put_contents($filePathHeader, $compilationContext->headerPrinter->getOutput()); } } else { $fileSystem = $compiler->getFileSystem(); /** * Use md5 hash to avoid rewrite the file again and again when it hasn't changed * thus avoiding unnecessary recompilations */ $output = $codePrinter->getOutput(); $hash = $fileSystem->getHashFile('md5', $filePath, true); if (md5($output) != $hash) { file_put_contents($filePath, $output); } if ($compilationContext->headerPrinter) { $output = $compilationContext->headerPrinter->getOutput(); $hash = $fileSystem->getHashFile('md5', $filePathHeader, true); if (md5($output) != $hash) { file_put_contents($filePathHeader, $output); } } } } /** * Add to file compiled */ $this->_compiledFile = $path . '.c'; $this->_ir = null; }
/** * Compiles a class/interface * * @param CompilationContext $compilationContext * @throws CompilerException */ public function compile(CompilationContext $compilationContext) { /** * Sets the current object as global class definition */ $compilationContext->classDefinition = $this; /** * Get the global codePrinter */ $codePrinter = $compilationContext->codePrinter; /** * The ZEPHIR_INIT_CLASS defines properties and constants exported by the class */ $codePrinter->output('ZEPHIR_INIT_CLASS(' . $this->getCNamespace() . '_' . $this->getName() . ') {'); $codePrinter->outputBlankLine(); $codePrinter->increaseLevel(); /** * Method entry */ $methods =& $this->methods; if (count($methods)) { $methodEntry = strtolower($this->getCNamespace()) . '_' . strtolower($this->getName()) . '_method_entry'; } else { $methodEntry = 'NULL'; } $namespace = str_replace('\\', '_', $compilationContext->config->get('namespace')); $abstractFlag = '0'; if ($this->isAbstract()) { $abstractFlag = 'ZEND_ACC_EXPLICIT_ABSTRACT_CLASS'; } /** * Register the class with extends + interfaces */ $classExtendsDefinition = null; if ($this->extendsClass) { $classExtendsDefinition = $this->extendsClassDefinition; if (!$classExtendsDefinition->isInternal()) { $classEntry = $classExtendsDefinition->getClassEntry(); } else { $classEntry = $this->getClassEntryByClassName($classExtendsDefinition->getName(), $compilationContext); } if ($this->getType() == 'class') { $codePrinter->output('ZEPHIR_REGISTER_CLASS_EX(' . $this->getNCNamespace() . ', ' . $this->getName() . ', ' . $namespace . ', ' . strtolower($this->getSCName($namespace)) . ', ' . $classEntry . ', ' . $methodEntry . ', ' . $abstractFlag . ');'); $codePrinter->outputBlankLine(); } else { $codePrinter->output('ZEPHIR_REGISTER_INTERFACE_EX(' . $this->getNCNamespace() . ', ' . $this->getName() . ', ' . $namespace . ', ' . strtolower($this->getSCName($namespace)) . ', ' . $classEntry . ', ' . $methodEntry . ');'); $codePrinter->outputBlankLine(); } } else { if ($this->getType() == 'class') { $codePrinter->output('ZEPHIR_REGISTER_CLASS(' . $this->getNCNamespace() . ', ' . $this->getName() . ', ' . $namespace . ', ' . strtolower($this->getSCName($namespace)) . ', ' . $methodEntry . ', ' . $abstractFlag . ');'); } else { $codePrinter->output('ZEPHIR_REGISTER_INTERFACE(' . $this->getNCNamespace() . ', ' . $this->getName() . ', ' . $namespace . ', ' . strtolower($this->getSCName($namespace)) . ', ' . $methodEntry . ');'); } $codePrinter->outputBlankLine(); } $needBreak = true; /** * @todo Remove after removing support for php 5.3 */ $currentClassHref =& $this; $this->eventsManager->listen('setMethod', function (ClassMethod $method) use(&$methods, &$currentClassHref, $compilationContext, &$needBreak) { $needBreak = false; $methods[$method->getName()] = $method; $compilationContext->classDefinition->setMethods($methods); }); /** * Compile properties * @var $property ClassProperty */ foreach ($this->getProperties() as $property) { $docBlock = $property->getDocBlock(); if ($docBlock) { $codePrinter->outputDocBlock($docBlock, true); } $property->compile($compilationContext); $codePrinter->outputBlankLine(); } if (!$needBreak) { $compilationContext->codePrinter->clear(); $currentClassHref->compile($compilationContext); return; } /** * Compile constants * @var $constant ClassConstant */ foreach ($this->getConstants() as $constant) { $docBlock = $constant->getDocBlock(); if ($docBlock) { $codePrinter->outputDocBlock($docBlock, true); } $constant->compile($compilationContext); $codePrinter->outputBlankLine(); } /** * Implemented interfaces */ $interfaces = $this->interfaces; $compiler = $compilationContext->compiler; if (is_array($interfaces)) { $codePrinter->outputBlankLine(true); foreach ($interfaces as $interface) { /** * Try to find the interface */ $classEntry = false; if ($compiler->isInterface($interface)) { $classInterfaceDefinition = $compiler->getClassDefinition($interface); $classEntry = $classInterfaceDefinition->getClassEntry(); } else { if ($compiler->isInternalInterface($interface)) { $classInterfaceDefinition = $compiler->getInternalClassDefinition($interface); $classEntry = $this->getClassEntryByClassName($classInterfaceDefinition->getName(), $compilationContext); } } if (!$classEntry) { if ($compiler->isClass($interface)) { throw new CompilerException("Cannot locate interface " . $interface . " when implementing interfaces on " . $this->getCompleteName() . '. ' . $interface . ' is currently a class', $this->originalNode); } else { throw new CompilerException("Cannot locate interface " . $interface . " when implementing interfaces on " . $this->getCompleteName(), $this->originalNode); } } /** * We don't check if abstract classes implement the methods in their interfaces */ if (!$this->isAbstract()) { $this->checkInterfaceImplements($this, $classInterfaceDefinition); } $codePrinter->output('zend_class_implements(' . $this->getClassEntry() . ' TSRMLS_CC, 1, ' . $classEntry . ');'); } } if (!$this->isAbstract()) { /** * Interfaces in extended classes may have */ if ($classExtendsDefinition) { if (!$classExtendsDefinition->isInternal()) { $interfaces = $classExtendsDefinition->getImplementedInterfaces(); if (is_array($interfaces)) { foreach ($interfaces as $interface) { $classInterfaceDefinition = null; if ($compiler->isInterface($interface)) { $classInterfaceDefinition = $compiler->getClassDefinition($interface); } else { if ($compiler->isInternalInterface($interface)) { $classInterfaceDefinition = $compiler->getInternalClassDefinition($interface); } } if ($classInterfaceDefinition) { $this->checkInterfaceImplements($this, $classInterfaceDefinition); } } } } } } $codePrinter->output('return SUCCESS;'); $codePrinter->outputBlankLine(); $codePrinter->decreaseLevel(); $codePrinter->output('}'); $codePrinter->outputBlankLine(); /** * Compile methods */ foreach ($methods as $method) { $docBlock = $method->getDocBlock(); if ($docBlock) { $codePrinter->outputDocBlock($docBlock); } if ($this->getType() == 'class') { $codePrinter->output('PHP_METHOD(' . $this->getCNamespace() . '_' . $this->getName() . ', ' . $method->getName() . ') {'); $codePrinter->outputBlankLine(); if (!$method->isAbstract()) { $method->compile($compilationContext); } $codePrinter->output('}'); $codePrinter->outputBlankLine(); } else { $codePrinter->output('ZEPHIR_DOC_METHOD(' . $this->getCNamespace() . '_' . $this->getName() . ', ' . $method->getName() . ');'); $codePrinter->outputBlankLine(); } } /** * Create a code printer for the header file */ $codePrinter = new CodePrinter(); $codePrinter->outputBlankLine(); $codePrinter->output('extern zend_class_entry *' . $this->getClassEntry() . ';'); $codePrinter->outputBlankLine(); $codePrinter->output('ZEPHIR_INIT_CLASS(' . $this->getCNamespace() . '_' . $this->getName() . ');'); $codePrinter->outputBlankLine(); if ($this->getType() == 'class') { if (count($methods)) { foreach ($methods as $method) { $codePrinter->output('PHP_METHOD(' . $this->getCNamespace() . '_' . $this->getName() . ', ' . $method->getName() . ');'); } $codePrinter->outputBlankLine(); } } /** * Create argument info */ foreach ($methods as $method) { $parameters = $method->getParameters(); if (count($parameters)) { $codePrinter->output('ZEND_BEGIN_ARG_INFO_EX(arginfo_' . strtolower($this->getCNamespace() . '_' . $this->getName() . '_' . $method->getName()) . ', 0, 0, ' . $method->getNumberOfRequiredParameters() . ')'); foreach ($parameters->getParameters() as $parameter) { switch ($parameter['data-type']) { case 'array': $codePrinter->output("\t" . 'ZEND_ARG_ARRAY_INFO(0, ' . $parameter['name'] . ', ' . (isset($parameter['default']) ? 1 : 0) . ')'); break; case 'variable': if (isset($parameter['cast'])) { switch ($parameter['cast']['type']) { case 'variable': $value = $parameter['cast']['value']; $codePrinter->output("\t" . 'ZEND_ARG_OBJ_INFO(0, ' . $parameter['name'] . ', ' . Utils::escapeClassName($compilationContext->getFullName($value)) . ', ' . (isset($parameter['default']) ? 1 : 0) . ')'); break; default: throw new Exception('Unexpected exception'); } } else { $codePrinter->output("\t" . 'ZEND_ARG_INFO(0, ' . $parameter['name'] . ')'); } break; default: $codePrinter->output("\t" . 'ZEND_ARG_INFO(0, ' . $parameter['name'] . ')'); break; } } $codePrinter->output('ZEND_END_ARG_INFO()'); $codePrinter->outputBlankLine(); } } if (count($methods)) { $codePrinter->output('ZEPHIR_INIT_FUNCS(' . strtolower($this->getCNamespace() . '_' . $this->getName()) . '_method_entry) {'); foreach ($methods as $method) { $parameters = $method->getParameters(); if ($this->getType() == 'class') { if (count($parameters)) { $codePrinter->output("\t" . 'PHP_ME(' . $this->getCNamespace() . '_' . $this->getName() . ', ' . $method->getName() . ', arginfo_' . strtolower($this->getCNamespace() . '_' . $this->getName() . '_' . $method->getName()) . ', ' . $method->getModifiers() . ')'); } else { $codePrinter->output("\t" . 'PHP_ME(' . $this->getCNamespace() . '_' . $this->getName() . ', ' . $method->getName() . ', NULL, ' . $method->getModifiers() . ')'); } } else { if ($method->isStatic()) { if (count($parameters)) { $codePrinter->output("\t" . 'ZEND_FENTRY(' . $method->getName() . ', NULL, arginfo_' . strtolower($this->getCNamespace() . '_' . $this->getName() . '_' . $method->getName()) . ', ZEND_ACC_STATIC|ZEND_ACC_ABSTRACT|ZEND_ACC_PUBLIC)'); } else { $codePrinter->output("\t" . 'ZEND_FENTRY(' . $method->getName() . ', NULL, NULL, ZEND_ACC_STATIC|ZEND_ACC_ABSTRACT|ZEND_ACC_PUBLIC)'); } } else { if (count($parameters)) { $codePrinter->output("\t" . 'PHP_ABSTRACT_ME(' . $this->getCNamespace() . '_' . $this->getName() . ', ' . $method->getName() . ', arginfo_' . strtolower($this->getCNamespace() . '_' . $this->getName() . '_' . $method->getName()) . ')'); } else { $codePrinter->output("\t" . 'PHP_ABSTRACT_ME(' . $this->getCNamespace() . '_' . $this->getName() . ', ' . $method->getName() . ', NULL)'); } } } } $codePrinter->output(' PHP_FE_END'); $codePrinter->output('};'); } $compilationContext->headerPrinter = $codePrinter; }