Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
422 changes: 0 additions & 422 deletions lib/PHPCfg/Printer.php

This file was deleted.

47 changes: 1 addition & 46 deletions lib/PHPCfg/Printer/GraphViz.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
namespace PHPCfg\Printer;

use PHPCfg\Func;
use PHPCfg\Printer;
use PHPCfg\Script;
use phpDocumentor\GraphViz\Edge;
use phpDocumentor\GraphViz\Graph;
Expand Down Expand Up @@ -46,7 +45,7 @@ public function printScript(Script $script): string
$this->printFuncWithHeader($func, $graph, 'func_' . ++$i . '_');
}

return (string) $graph;
return (string) $graph . "\n";
}

public function printFunc(Func $func): string
Expand All @@ -57,50 +56,6 @@ public function printFunc(Func $func): string
return (string) $graph;
}

public function printVars(Func $func): string
{
$graph = Graph::create('vars');
foreach ($this->options['graph'] as $name => $value) {
$setter = 'set' . $name;
$graph->{$setter}($value);
}
$rendered = $this->render($func->cfg);
$nodes = new SplObjectStorage();
foreach ($rendered['varIds'] as $var) {
if (empty($var->ops) && empty($var->usages)) {
continue;
}
$id = $rendered['varIds'][$var];
$output = $this->renderOperand($var);
$nodes[$var] = $this->createNode('var_' . $id, $output);
$graph->setNode($nodes[$var]);
}
foreach ($rendered['varIds'] as $var) {
foreach ($var->ops as $write) {
$b = $write->getAttribute('block');
foreach ($write->getVariableNames() as $varName => $vs) {
if (! is_array($vs)) {
$vs = [$vs];
}
foreach ($vs as $v) {
if (! $v || $write->isWriteVariable($varName) || ! $nodes->contains($v)) {
continue;
}
$edge = $this->createEdge($nodes[$v], $nodes[$var]);
if ($b) {
$edge->setlabel('Block<' . $rendered['blockIds'][$b] . '>' . $write->getType() . ':' . $varName);
} else {
$edge->setlabel($write->getType() . ':' . $varName);
}
$graph->link($edge);
}
}
}
}

return (string) $graph;
}

protected function printFuncWithHeader(Func $func, Graph $graph, $prefix): void
{
$name = $func->getScopedName();
Expand Down
245 changes: 245 additions & 0 deletions lib/PHPCfg/Printer/Printer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
<?php

declare(strict_types=1);

/**
* This file is part of PHP-CFG, a Control flow graph implementation for PHP
*
* @copyright 2015 Anthony Ferrara. All rights reserved
* @license MIT See LICENSE at the root of the project for more info
*/

namespace PHPCfg\Printer;

use LogicException;
use PHPCfg\Block;
use PHPCfg\Func;
use PHPCfg\Op;
use PHPCfg\Operand;
use PHPCfg\Script;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SplObjectStorage;
use SplQueue;

abstract class Printer
{
public const MODE_DEFAULT = 0b00000;
public const MODE_RENDER_ATTRIBUTES = 0b00001;

private SplQueue $blockQueue;

private SplObjectStorage $blocks;

public bool $renderAttributes = false;

protected array $renderers = [];

public function __construct(int $mode = self::MODE_DEFAULT)
{
if ($mode & self::MODE_RENDER_ATTRIBUTES) {
$this->renderAttributes = true;
}
$this->loadRenderers();
$this->reset();

}

public function addRenderer(Renderer $renderer, bool $prepend = false): void
{
if ($prepend) {
array_unshift($this->renderers, $renderer);
} else {
$this->renderers[] = $renderer;
}
}

protected function loadRenderers(): void
{
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
__DIR__ . '/Renderer/',
RecursiveIteratorIterator::LEAVES_ONLY
)
);
$handlers = [];
foreach ($it as $file) {
if (!$file->isFile() || $file->getExtension() !== 'php') {
continue;
}
$class = str_replace(__DIR__, '', $file->getPathname());
$class = __NAMESPACE__ . str_replace("/", "\\", $class);
$class = substr($class, 0, -4);

if (!class_exists($class)) {
continue;
}

$obj = new $class($this);
$this->addRenderer($obj);
}
}

abstract public function printScript(Script $script): string;

abstract public function printFunc(Func $func): string;

protected function reset(): void
{
$this->blocks = new SplObjectStorage();
$this->blockQueue = new SplQueue();
foreach ($this->renderers as $renderer) {
$renderer->reset();
}
}

protected function getBlockId(Block $block): int
{
return $this->blocks[$block];
}

public function renderOperand(Operand $var): string
{
foreach ($this->renderers as $renderer) {
$result = $renderer->renderOperand($var);
if ($result !== null) {
$kind = $result['kind'];
$type = $result['type'];
unset($result['kind'], $result['type']);
return strtoupper($kind) . $type . '(' . trim(implode(" ", $result)) . ')';
}
}
return 'UNKNOWN';
}

public function renderOp(Op $op): array
{
foreach ($this->renderers as $renderer) {
$result = $renderer->renderOp($op);
if ($result !== null) {
$kind = $result['kind'];
$childblocks = $result['childblocks'];
return [
'op' => $op,
'label' => $this->renderOpLabel($result),
'childBlocks' => $childblocks,
];
}
}

return [
'op' => $op,
'label' => 'UNKNOWN',
'childBlocks' => $childBlocks,
];
}

protected function indent($str, $levels = 1): string
{
if ($levels > 1) {
$str = $this->indent($str, $levels - 1);
}

return str_replace("\n", "\n ", $str);
}

public function enqueueBlock(Block $block): void
{
if (! $this->blocks->contains($block)) {
$this->blocks[$block] = count($this->blocks) + 1;
$this->blockQueue->enqueue($block);
}
}

protected function render(Func $func)
{
if (null !== $func->cfg) {
$this->enqueueBlock($func->cfg);
}

$renderedOps = new SplObjectStorage();
$renderedBlocks = new SplObjectStorage();
while ($this->blockQueue->count() > 0) {
$block = $this->blockQueue->dequeue();
$ops = [];
foreach ($block->phi as $phi) {
$result = $this->indent($this->renderOperand($phi->result) . ' = Phi(');
$result .= implode(', ', array_map([$this, 'renderOperand'], $phi->vars));
$result .= ')';
$renderedOps[$phi] = $ops[] = [
'op' => $phi,
'label' => $result,
'childBlocks' => [],
];
}
foreach ($block->children as $child) {
$renderedOps[$child] = $ops[] = $this->renderOp($child);
}
$renderedBlocks[$block] = $ops;
}

//$varIds = $this->varIds;
$blockIds = $this->blocks;
$this->reset();

return [
'blocks' => $renderedBlocks,
'ops' => $renderedOps,
//'varIds' => $varIds,
'blockIds' => $blockIds,
];
}

public function renderType(?Op\Type $type): string
{
if ($type instanceof Op\Type\Mixed_) {
return 'mixed';
}
if ($type instanceof Op\Type\Void_) {
return 'void';
}
if ($type instanceof Op\Type\Nullable) {
return '?' . $this->renderType($type->subtype);
}
if ($type instanceof Op\Type\Union || $type instanceof Op\Type\Intersection) {
$i = 1;
$strTypes = "";
$sep = $type instanceof Op\Type\Union ? '|' : '&';
foreach ($type->subtypes as $subtype) {
$strTypes .= $this->renderType($subtype);
if ($i < count($type->subtypes)) {
$strTypes .= $sep;
}
$i++;
}
return $strTypes;
}
if ($type instanceof Op\Type\Literal) {
return $type->name;
}
if (is_null($type)) {
return '';
}
throw new LogicException("Unknown type rendering: " . get_class($type));
}

public function renderOpLabel(array $desc): string
{
$result = "{$desc['kind']}";
unset($desc['kind'], $desc['childblocks']);
foreach ($desc as $name => $val) {
if (is_array($val)) {
foreach ($val as $v) {
if (is_array($v)) {
$result .= $this->indent("\n" . implode("\n", $v));
} else {
$result .= $this->indent("\n{$v}");
}
}
} else {
$result .= $this->indent("\n{$val}");
}
}
return $result;
}
}
25 changes: 25 additions & 0 deletions lib/PHPCfg/Printer/Renderer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

/**
* This file is part of PHP-CFG, a Control flow graph implementation for PHP
*
* @copyright 2015 Anthony Ferrara. All rights reserved
* @license MIT See LICENSE at the root of the project for more info
*/

namespace PHPCfg\Printer;

use PHPCfg\Op;
use PHPCfg\Operand;

interface Renderer
{
public function reset(): void;

public function renderOp(Op $op): ?array;

public function renderOperand(Operand $operand): ?array;

}
Loading