Completed
Pull Request — master (#293)
by Дмитрий
05:14 queued 02:19
created

ControlFlowGraph::passThrow()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 4
rs 10
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
                case \PhpParser\Node\Stmt\Label::class:
62
                    $block->setExit(
63
                        $block = new Block($this->lastBlockId++)
64
                    );
65
                    $block->label = $stmt->name;
66
                    break;
67
                default:
68
                    echo 'Unimplemented ' . get_class($stmt) . PHP_EOL;
69
                    break;
70
            }
71
        }
72
    }
73
74
    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...
75
    {
76
        $trueBlock = new Block($this->lastBlockId++);
77
        $this->passNodes($if->stmts, $trueBlock);
78
79
        $jumpIf = new JumpIf($trueBlock);
80
81
        $elseBlock = null;
82
83
        if ($if->else) {
84
            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...
85
                $elseBlock = new Block($this->lastBlockId++);
86
                $this->passNodes($if->else->stmts, $elseBlock);
87
88
                $jumpIf->setElse($elseBlock);
89
            }
90
        }
91
92
        $block->addChildren(
93
            $jumpIf
94
        );
95
96
        $exitBlock = new Block($this->lastBlockId++);
97
        $trueBlock->setExit($exitBlock);
98
99
        if ($elseBlock) {
100
            $elseBlock->setExit($exitBlock);
101
        }
102
103
        return $exitBlock;
104
    }
105
106
    protected function passFor(\PhpParser\Node\Stmt\For_ $for, Block $block)
107
    {
108
        $this->passNodes($for->init, $block);
109
110
        $block->setExit(
111
            $loop = new Block($this->lastBlockId++)
112
        );
113
        $this->passNodes($for->stmts, $loop);
114
115
        $loop->setExit(
116
            $after = new Block($this->lastBlockId++)
117
        );
118
        return $after;
119
    }
120
121
    protected function passWhile(\PhpParser\Node\Stmt\While_ $while, Block $block)
122
    {
123
        $cond = new Block($this->lastBlockId++);
124
        $block->setExit(
125
            $cond
126
        );
127
128
        $loop = new Block($this->lastBlockId++);
129
130
        $jumpIf = new JumpIf($loop);
131
        $cond->addChildren($jumpIf);
132
133
        $this->passNodes($while->stmts, $loop);
134
135
        $loop->addChildren(new Jump($cond));
136
        //$loop->setExit($cond);
137
138
        $after = new Block($this->lastBlockId++);
139
        $jumpIf->setElse($after);
140
141
        return $after;
142
    }
143
144
    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...
145
    {
146
        $block->addChildren(new Throw_());
147
    }
148
149
    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...
150
    {
151
        $block->addChildren(new Assign());
152
    }
153
154
    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...
155
    {
156
        $block->addChildren(new Return_());
157
    }
158
159
    /**
160
     * @return Block
161
     */
162
    public function getRoot()
163
    {
164
        return $this->root;
165
    }
166
}
167