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

ControlFlowGraph::passExpr()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 27
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
cc 7
eloc 17
nc 7
nop 1
dl 0
loc 27
ccs 0
cts 18
cp 0
crap 56
rs 6.7272
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;
10
11
class ControlFlowGraph
12
{
13
    /**
14
     * @var int
15
     */
16
    protected $lastBlockId = 1;
17
18
    /**
19
     * @var Block
20
     */
21
    protected $root;
22
23
    /**
24
     * @param $statement
25
     */
26 6
    public function __construct($statement)
27
    {
28 6
        $this->root = new Block($this->lastBlockId++);
29
30 6
        if ($statement instanceof Function_) {
31 6
            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...
32 5
                $this->passNodes($statement->stmts, $this->root);
33 5
            }
34 6
        }
35 6
    }
36
37
    /**
38
     * @param array $nodes
39
     * @param Block $block
40
     */
41 5
    protected function passNodes(array $nodes, Block $block)
42
    {
43 5
        foreach ($nodes as $stmt) {
44 5
            switch (get_class($stmt)) {
45 5
                case \PhpParser\Node\Expr\Assign::class:
46
                    $this->passAssign($stmt, $block);
47
                    break;
48 5
                case \PhpParser\Node\Stmt\Return_::class:
49 4
                    $this->passReturn($stmt, $block);
50 4
                    break;
51 1
                case \PhpParser\Node\Stmt\For_::class:
52
                    $block = $this->passFor($stmt, $block);
53
                    break;
54 1
                case \PhpParser\Node\Stmt\If_::class:
55
                    $block = $this->passIf($stmt, $block);
56
                    break;
57 1
                case \PhpParser\Node\Stmt\While_::class:
58
                    $block = $this->passWhile($stmt, $block);
59
                    break;
60 1
                case \PhpParser\Node\Stmt\Do_::class:
61
                    $block = $this->passDo($stmt, $block);
62
                    break;
63 1
                case \PhpParser\Node\Stmt\Throw_::class:
64
                    $this->passThrow($stmt, $block);
65
                    break;
66 1
                case \PhpParser\Node\Expr\Exit_::class:
67
                    $block->addChildren(new Node\ExitNode());
68
                    break;
69 1
                case \PhpParser\Node\Stmt\Label::class:
70
                    $block = $this->createNewBlockIfNeeded($block);
71
                    $block->label = $stmt->name;
72
                    break;
73 1
                case \PhpParser\Node\Stmt\TryCatch::class:
74
                    $block = $this->passTryCatch($stmt, $block);
75
                    break;
76 1
                case \PhpParser\Node\Stmt\Nop::class:
77
                    // ignore commented code
78
                    break;
79 1
                default:
80 1
                    echo 'Unimplemented ' . get_class($stmt) . PHP_EOL;
81 1
                    break;
82 5
            }
83 5
        }
84 5
    }
85
86
    /**
87
     * If current block is not empty, lets create a new one
88
     *
89
     * @param Block $block
90
     * @return Block
91
     */
92
    protected function createNewBlockIfNeeded(Block $block)
93
    {
94
        if ($block->getChildrens()) {
95
            $block->setExit(
96
                $block = new Block($this->lastBlockId++)
97
            );
98
        }
99
100
        return $block;
101
    }
102
103
    /**
104
     * @param \PhpParser\Node\Expr $expr
105
     * @return Node\AbstractNode
106
     */
107
    protected function passExpr(\PhpParser\Node\Expr $expr)
108
    {
109
        switch (get_class($expr)) {
110
            case \PhpParser\Node\Expr\BinaryOp\Equal::class:
111
                return new Node\Expr\BinaryOp\Equal();
112
113
            case \PhpParser\Node\Expr\BinaryOp\Smaller::class:
114
                return new Node\Expr\BinaryOp\Smaller();
115
116
            case \PhpParser\Node\Expr\BinaryOp\SmallerOrEqual::class:
117
                return new Node\Expr\BinaryOp\SmallerOrEqual();
118
119
            case \PhpParser\Node\Expr\BinaryOp\Greater::class:
120
                return new Node\Expr\BinaryOp\Greater();
121
122
            case \PhpParser\Node\Expr\BinaryOp\GreaterOrEqual::class:
123
                return new Node\Expr\BinaryOp\GreaterOrEqual();
124
125
            case \PhpParser\Node\Expr\Instanceof_::class:
126
                return new Node\Expr\InstanceOfExpr();
127
128
            default:
129
                echo 'Unimplemented ' . get_class($expr) . PHP_EOL;
130
        }
131
132
        return new Node\UnknownNode();
133
    }
134
135
    /**
136
     * @param \PhpParser\Node\Stmt\If_ $if
137
     * @param Block $block
138
     * @return Block
139
     */
140
    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...
141
    {
142
        $trueBlock = new Block($this->lastBlockId++);
143
        $this->passNodes($if->stmts, $trueBlock);
144
145
        $jumpIf = new Node\JumpIfNode($this->passExpr($if->cond), $trueBlock);
146
147
        $elseBlock = null;
148
149
        if ($if->else) {
150
            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...
151
                $elseBlock = new Block($this->lastBlockId++);
152
                $this->passNodes($if->else->stmts, $elseBlock);
153
154
                $jumpIf->setElse($elseBlock);
155
            }
156
        }
157
158
        $block->addChildren(
159
            $jumpIf
160
        );
161
162
        $exitBlock = new Block($this->lastBlockId++);
163
        $trueBlock->setExit($exitBlock);
164
165
        if ($elseBlock) {
166
            $elseBlock->setExit($exitBlock);
167
        }
168
169
        return $exitBlock;
170
    }
171
172
    /**
173
     * @param \PhpParser\Node\Stmt\For_ $for
174
     * @param Block $block
175
     * @return Block
176
     */
177
    protected function passFor(\PhpParser\Node\Stmt\For_ $for, Block $block)
178
    {
179
        $this->passNodes($for->init, $block);
180
181
        $block->setExit(
182
            $loop = new Block($this->lastBlockId++)
183
        );
184
        $this->passNodes($for->stmts, $loop);
185
186
        $loop->setExit(
187
            $after = new Block($this->lastBlockId++)
188
        );
189
        return $after;
190
    }
191
192
    /**
193
     * @param \PhpParser\Node\Stmt\Do_ $do
194
     * @param Block $block
195
     * @return Block
196
     */
197
    protected function passDo(\PhpParser\Node\Stmt\Do_ $do, Block $block)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $do. 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...
198
    {
199
        $loop = new Block($this->lastBlockId++);
200
        $this->passNodes($do->stmts, $loop);
201
202
        $block->setExit($loop);
203
204
        $cond = new Block($this->lastBlockId++);
205
        $loop->setExit($cond);
206
207
        $jumpIf = new Node\JumpIfNode($this->passExpr($do->cond), $loop);
208
        $cond->addChildren($jumpIf);
209
210
        $exitBlock = new Block($this->lastBlockId++);
211
        $jumpIf->setElse($exitBlock);
212
213
        return $exitBlock;
214
    }
215
216
    /**
217
     * @param \PhpParser\Node\Stmt\While_ $while
218
     * @param Block $block
219
     * @return Block
220
     */
221
    protected function passWhile(\PhpParser\Node\Stmt\While_ $while, Block $block)
222
    {
223
        $cond = new Block($this->lastBlockId++);
224
        $block->setExit(
225
            $cond
226
        );
227
228
        $loop = new Block($this->lastBlockId++);
229
230
        $jumpIf = new Node\JumpIfNode($this->passExpr($while->cond), $loop);
231
        $cond->addChildren($jumpIf);
232
233
        $this->passNodes($while->stmts, $loop);
234
235
        $loop->addChildren(new Node\JumpNode($cond));
236
        //$loop->setExit($cond);
237
238
        $after = new Block($this->lastBlockId++);
239
        $jumpIf->setElse($after);
240
241
        return $after;
242
    }
243
244
    /**
245
     * @param \PhpParser\Node\Stmt\Throw_ $throw_
246
     * @param Block $block
247
     */
248
    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...
249
    {
250
        $block->addChildren(new Node\ThrowNode());
251
    }
252
253
    /**
254
     * @param \PhpParser\Node\Expr\Assign $assign
255
     * @param Block $block
256
     */
257
    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...
258
    {
259
        $block->addChildren(new Node\AssignNode());
260
    }
261
262
    /**
263
     * @param \PhpParser\Node\Stmt\Return_ $return_
264
     * @param Block $block
265
     */
266 4
    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...
267
    {
268 4
        $block->addChildren(new Node\ReturnNode());
269 4
    }
270
271
    /**
272
     * @param \PhpParser\Node\Stmt\TryCatch $stmt
273
     * @param Block $block
274
     * @return Block
275
     */
276
    protected function passTryCatch(\PhpParser\Node\Stmt\TryCatch $stmt, Block $block)
277
    {
278
        $try = new Block($this->lastBlockId++);
279
        $this->passNodes($stmt->stmts, $try);
280
281
        $block->setExit($try);
282
283
        if ($stmt->finally) {
284
            $finally = new Block($this->lastBlockId++);
285
            $this->passNodes($stmt->finally->stmts, $finally);
286
287
            $try->setExit($finally);
288
            return $finally;
289
        }
290
291
        return $try;
292
    }
293
294
    /**
295
     * @return Block
296
     */
297
    public function getRoot()
298
    {
299
        return $this->root;
300
    }
301
}
302