<?php
declare(strict_types=1);
/**
* Hoa
*
*
* @license
*
* New BSD License
*
* Copyright © 2007-2017, Hoa community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Hoa nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace Kitab\Compiler\IntermediateRepresentation;
use Kitab\Compiler\Parser;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
use PhpParser\PrettyPrinter;
/**
* A visitor to transform an Abstract Syntax Tree (AST) into an Intermediate
* Representation (IR).
*
* This visitor implements the API from `PhpParser\NodeVisitorAbstract`. It is
* applied when leaving a node of the AST to allow other visitors to transform
* the currently visited node and its children. For instance, the name
* resolver visitor modifies the AST when entering a node. This visitor needs
* the information from the name resolver visitor, so it must executes
* after. The best way to get all these information for a node and its
* children is to run each node when the visitor leaves a node.
*
* # Examples
*
* See [the examples of the current
* namespace](kitab/compiler/intermediaterepresentation/index.html).
*/
class Into extends NodeVisitorAbstract
{
/**
* Name of the file where the AST comes from.
*/
protected $_file = null;
/**
* Pretty print visitor to transform the AST into its PHP representation.
*
* It is used for example to get the PHP representation of a default value
* for an attribute.
*
* This is a reference to the PHP pretty printer allocated in the
* `Kitab\Compiler\Parser` class.
*/
private $_prettyPrinter = null;
/**
* Allocate a new visitor. The only mandatory information is the name of
* the file where the AST comes from.
*/
public function __construct(string $filename)
{
$this->_file = new File($filename);
$this->_prettyPrinter = Parser::getPhpPrettyPrinter();
}
/**
* Transform a node of the AST into an IR.
*
* This method returns nothing because it is called several times by the
* traverser API. To get the final result, see the `collect` method.
*/
public function leaveNode(Node $node)
{
if ($node instanceof Node\Stmt\Class_) {
$classNode = $node;
$class = new Class_($classNode->namespacedName->toString());
$class->lineStart = $classNode->getAttribute('startLine');
$class->lineEnd = $classNode->getAttribute('endLine');
$class->documentation = new Documentation(Parser::extractFromComment($classNode->getDocComment()));
$class->constants = $this->intoConstants($classNode);
$class->attributes = $this->intoAttributes($classNode);
$class->methods = $this->intoMethods($classNode);
if ($classNode->flags & Node\Stmt\Class_::MODIFIER_ABSTRACT) {
$class->abstract = true;
}
if ($classNode->flags & Node\Stmt\Class_::MODIFIER_FINAL) {
$class->final = true;
}
if (null !== $classNode->extends) {
$class->parent = $classNode->extends->toString();
}
foreach ($classNode->implements as $interfaceNameNode) {
$class->interfaces[] = $interfaceNameNode->toString();
}
$this->_file[] = $class;
} elseif ($node instanceof Node\Stmt\Interface_) {
$interfaceNode = $node;
$interface = new Interface_($interfaceNode->namespacedName->toString());
$interface->lineStart = $interfaceNode->getAttribute('startLine');
$interface->lineEnd = $interfaceNode->getAttribute('endLine');
$interface->documentation = new Documentation(Parser::extractFromComment($interfaceNode->getDocComment()));
$interface->constants = $this->intoConstants($interfaceNode);
$interface->methods = $this->intoMethods($interfaceNode);
if (!empty($interfaceNode->extends)) {
$interface->parents = array_map(
function ($nodeName) {
return $nodeName->toString();
},
$interfaceNode->extends
);
}
$this->_file[] = $interface;
} elseif ($node instanceof Node\Stmt\Trait_) {
$traitNode = $node;
$trait = new Trait_($traitNode->namespacedName->toString());
$trait->lineStart = $traitNode->getAttribute('startLine');
$trait->lineEnd = $traitNode->getAttribute('endLine');
$trait->documentation = new Documentation(Parser::extractFromComment($traitNode->getDocComment()));
$trait->methods = $this->intoMethods($traitNode);
$this->_file[] = $trait;
} elseif ($node instanceof Node\Stmt\Function_) {
$functionNode = $node;
$function = new Function_($functionNode->namespacedName->toString());
$function->lineStart = $functionNode->getAttribute('startLine');
$function->lineEnd = $functionNode->getAttribute('endLine');
$function->documentation = new Documentation(Parser::extractFromComment($functionNode->getDocComment()));
$function->inputs = $this->intoInputs($functionNode);
$function->output = $this->intoOutput($functionNode);
$this->_file[] = $function;
}
}
/**
* Extract constant nodes and transform them into a collection of
* `Kitab\Compiler\IntermediateRepresentation\Constant` objects.
*
* It supports both declaration forms:
*
* * `public const FOO = 42; public const BAR = 153;`, and
* * `public const FOO = 42, BAR = 153;`.
*
* The resulting IR will be the same and will correspond to the former form.
*/
protected function intoConstants(Node\Stmt\ClassLike $node): array
{
$constants = [];
foreach ($node->stmts as $statement) {
if (!($statement instanceof Node\Stmt\ClassConst)) {
continue;
}
$defaultDocumentation = Parser::extractFromComment($statement->getDocComment());
if (true === $statement->isPublic()) {
$visibility = Constant::VISIBILITY_PUBLIC;
} elseif (true === $statement->isProtected()) {
$visibility = Constant::VISIBILITY_PROTECTED;
} else {
$visibility = Constant::VISIBILITY_PRIVATE;
}
foreach ($statement->consts as $constantNode) {
$constant = new Constant($constantNode->name);
$constant->visibility = $visibility;
$constant->value = $this->_prettyPrinter->prettyPrint([$constantNode->value]);
$documentation = Parser::extractFromComment($constantNode->getDocComment());
if (empty($documentation)) {
$constant->documentation = new Documentation($defaultDocumentation);
} else {
$constant->documentation = new Documentation($documentation);
}
$constants[] = $constant;
}
}
return $constants;
}
/**
* Extract attribute nodes and transform them into a collection of
* `Kitab\Compiler\IntermediateRepresentation\Attribute` objects.
*
* It supports both declaration forms:
*
* * `public $foo = 42; public $bar = 153;`, and
* * `public $foo = 42, $bar = 153;`.
*
* The resulting IR will be the same and will correspond to the former form.
*/
protected function intoAttributes(Node\Stmt\ClassLike $node): array
{
$attributes = [];
foreach ($node->stmts as $statement) {
if (!($statement instanceof Node\Stmt\Property)) {
continue;
}
$defaultDocumentation = Parser::extractFromComment($statement->getDocComment());
if (true === $statement->isPublic()) {
$visibility = Attribute::VISIBILITY_PUBLIC;
} elseif (true === $statement->isProtected()) {
$visibility = Attribute::VISIBILITY_PROTECTED;
} else {
$visibility = Attribute::VISIBILITY_PRIVATE;
}
$static = $statement->isStatic();
foreach ($statement->props as $attributeNode) {
$attribute = new Attribute($attributeNode->name);
$attribute->visibility = $visibility;
$attribute->static = $static;
if (null !== $attributeNode->default) {
$attribute->default = $this->_prettyPrinter->prettyPrint([$attributeNode->default]);
}
$documentation = Parser::extractFromComment($attributeNode->getDocComment());
if (empty($documentation)) {
$attribute->documentation = new Documentation($defaultDocumentation);
} else {
$attribute->documentation = new Documentation($documentation);
}
$attributes[] = $attribute;
}
}
return $attributes;
}
/**
* Extract method nodes and transform them into a collection of
* `Kitab\Compiler\IntermediateRepresentation\Method` objects.
*/
protected function intoMethods(Node\Stmt\ClassLike $node): array
{
$methods = [];
foreach ($node->getMethods() as $methodNode) {
$method = new Method($methodNode->name);
$method->lineStart = $methodNode->getAttribute('startLine');
$method->lineEnd = $methodNode->getAttribute('endLine');
// Documentation.
$method->documentation = new Documentation(Parser::extractFromComment($methodNode->getDocComment()));
// Visibility, scope, and abstract.
if (true === $methodNode->isPublic()) {
$method->visibility = $method::VISIBILITY_PUBLIC;
} elseif (true === $methodNode->isProtected()) {
$method->visibility = $method::VISIBILITY_PROTECTED;
} else {
$method->visibility = $method::VISIBILITY_PRIVATE;
}
$method->static = $methodNode->isStatic();
$method->abstract = $methodNode->isAbstract();
$method->final = $methodNode->isFinal();
$method->inputs = $this->intoInputs($methodNode);
$method->output = $this->intoOutput($methodNode);
$methods[] = $method;
}
return $methods;
}
/**
* Extract nodes representing parameters of a function and transform them
* into a collection of
* `Kitab\Compiler\IntermediateRepresentation\Parameter` objects.
*/
protected function intoInputs($node): array
{
$inputs = [];
$parametersNode = $node->params;
foreach ($parametersNode as $parameterNode) {
$parameter = new Parameter($parameterNode->name);
$parameter->type = $this->intoType($parameterNode->type);
$parameter->type->reference = $parameterNode->byRef;
$parameter->variadic = $parameterNode->variadic;
$inputs[] = $parameter;
}
return $inputs;
}
/**
* Extract node representing the output of a function and transform it
* into a `Kitab\Compiler\IntermediateRepresentation\Type` object.
*/
protected function intoOutput($node): Type
{
$output = $this->intoType($node->returnType);
$output->reference = $node->byRef;
return $output;
}
/**
* Extract node representing a type and transform it into a
* `Kitab\Compiler\IntermediateRepresentation\Type` object.
*/
protected function intoType($node): Type
{
$type = new Type();
if ($node instanceof Node\Name) {
$type->name = $node->toString();
} elseif ($node instanceof Node\NullableType) {
$type->nullable = true;
$nullableNode = $node->type;
if ($nullableNode instanceof Node\Name) {
$type->name = $nullableNode->toString();
} else {
$type->name = $nullableNode;
}
} else {
$type->name = $node;
}
return $type;
}
/**
* Because the visitor runs for every node in the AST, the only way to
* collect the resulting IR is to call this method.
*
* This method can be called at any time but it is best to call it when
* the traverser returns. It means the transformation will be complete.
*/
public function collect(): File
{
return $this->_file;
}
}