Completed
Pull Request — master (#293)
by Дмитрий
03:06
created

ControlFlowGraph::__construct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 1
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Patsura Dmitry https://github.com/ovr <[email protected]>
4
 */
5
6
namespace PHPSA\ControlFlow;
7
8
use PhpParser\Node\Stmt\Function_;
9
use PHPSA\ControlFlow\Node\Assign;
10
use PHPSA\ControlFlow\Node\Exit_;
11
use PHPSA\ControlFlow\Node\Jump;
12
use PHPSA\ControlFlow\Node\JumpIf;
13
use PHPSA\ControlFlow\Node\Return_;
14
use PHPSA\ControlFlow\Node\Throw_;
15
16
class ControlFlowGraph
17
{
18
    protected $lastBlockId = 1;
19
20
    /**
21
     * @var Block
22
     */
23
    protected $root;
24
25
    public function __construct($statement)
26
    {
27
        $this->root = new Block($this->lastBlockId++);
28
29
        if ($statement instanceof Function_) {
30
            if ($statement->stmts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $statement->stmts of type PhpParser\Node[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
31
                $this->passNodes($statement->stmts, $this->root);
32
            }
33
        }
34
    }
35
36
    protected function passNodes(array $nodes, Block $block)
37
    {
38
        foreach ($nodes as $stmt) {
39
            switch (get_class($stmt)) {
40
                case \PhpParser\Node\Expr\Assign::class:
41
                    $this->passAssign($stmt, $block);
42
                    break;
43
                case \PhpParser\Node\Stmt\Return_::class:
44
                    $this->passReturn($stmt, $block);
45
                    break;
46
                case \PhpParser\Node\Stmt\For_::class:
47
                    $block = $this->passFor($stmt, $block);
48
                    break;
49
                case \PhpParser\Node\Stmt\If_::class:
50
                    $block = $this->passIf($stmt, $block);
51
                    break;
52
                case \PhpParser\Node\Stmt\While_::class:
53
                    $block = $this->passWhile($stmt, $block);
54
                    break;
55
                case \PhpParser\Node\Stmt\Throw_::class:
56
                    $this->passThrow($stmt, $block);
57
                    break;
58
                case \PhpParser\Node\Expr\Exit_::class:
59
                    $block->addChildren(new Exit_());
60
                    break;
61
                default:
62
                    echo 'Unimplemented ' . get_class($stmt) . PHP_EOL;
63
                    break;
64
            }
65
        }
66
    }
67
68
    protected function passIf(\PhpParser\Node\Stmt\If_ $if, Block $block)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $if. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
69
    {
70
        $trueBlock = new Block($this->lastBlockId++);
71
        $this->passNodes($if->stmts, $trueBlock);
72
73
        $jumpIf = new JumpIf($trueBlock);
74
75
        $elseBlock = null;
76
77
        if ($if->else) {
78
            if ($if->else->stmts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $if->else->stmts of type PhpParser\Node[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
79
                $elseBlock = new Block($this->lastBlockId++);
80
                $this->passNodes($if->else->stmts, $elseBlock);
81
82
                $jumpIf->setElse($elseBlock);
83
            }
84
        }
85
86
        $block->addChildren(
87
            $jumpIf
88
        );
89
90
        $exitBlock = new Block($this->lastBlockId++);
91
        $trueBlock->setExit($exitBlock);
92
93
        if ($elseBlock) {
94
            $elseBlock->setExit($exitBlock);
95
        }
96
97
        return $exitBlock;
98
    }
99
100
    protected function passFor(\PhpParser\Node\Stmt\For_ $for, Block $block)
101
    {
102
        $this->passNodes($for->init, $block);
103
104
        $block->setExit(
105
            $loop = new Block($this->lastBlockId++)
106
        );
107
        $this->passNodes($for->stmts, $loop);
108
109
        $loop->setExit(
110
            $after = new Block($this->lastBlockId++)
111
        );
112
        return $after;
113
    }
114
115
    protected function passWhile(\PhpParser\Node\Stmt\While_ $while, Block $block)
116
    {
117
        $cond = new Block($this->lastBlockId++);
118
        $block->setExit(
119
            $cond
120
        );
121
122
        $loop = new Block($this->lastBlockId++);
123
124
        $jumpIf = new JumpIf($loop);
125
        $cond->addChildren($jumpIf);
126
127
        $this->passNodes($while->stmts, $loop);
128
129
        $loop->addChildren(new Jump($cond));
130
        //$loop->setExit($cond);
131
132
        $after = new Block($this->lastBlockId++);
133
        $jumpIf->setElse($after);
134
135
        return $after;
136
    }
137
138
    protected function passThrow(\PhpParser\Node\Stmt\Throw_ $throw_, Block $block)
0 ignored issues
show
Unused Code introduced by
The parameter $throw_ is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
139
    {
140
        $block->addChildren(new Throw_());
141
    }
142
143
    protected function passAssign(\PhpParser\Node\Expr\Assign $assign, Block $block)
0 ignored issues
show
Unused Code introduced by
The parameter $assign is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
144
    {
145
        $block->addChildren(new Assign());
146
    }
147
148
    protected function passReturn(\PhpParser\Node\Stmt\Return_ $return_, Block $block)
0 ignored issues
show
Unused Code introduced by
The parameter $return_ is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
149
    {
150
        $block->addChildren(new Return_());
151
    }
152
153
    /**
154
     * @return Block
155
     */
156
    public function getRoot()
157
    {
158
        return $this->root;
159
    }
160
}
161