Completed
Push — master ( 475c0f...a498f5 )
by brian
03:00
created

LogicalOperatorProcessor   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 144
Duplicated Lines 0 %

Coupling/Cohesion

Dependencies 3

Test Coverage

Coverage 98.08%

Importance

Changes 0
Metric Value
wmc 17
cbo 3
dl 0
loc 144
ccs 51
cts 52
cp 0.9808
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A run() 0 6 1
B buildRanges() 0 28 4
C shuntingYard() 0 38 8
A hasOperator() 0 4 1
A hasLowerPrecedence() 0 10 3
1
<?php
2
3
/**
4
 * @copyright   (c) 2014-2017 brian ridley
5
 * @author      brian ridley <[email protected]>
6
 * @license     http://opensource.org/licenses/MIT MIT
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace ptlis\SemanticVersion\Parse;
13
14
use ptlis\SemanticVersion\VersionRange\LogicalAnd;
15
use ptlis\SemanticVersion\VersionRange\LogicalOr;
16
use ptlis\SemanticVersion\VersionRange\VersionRangeInterface;
17
18
/**
19
 * Build version ranges from stream of ComparatorVersions & LogicalOperators
20
 *
21
 * Basic (and limited) implementation of the shunting algorithm for logical AND / OR.
22
 */
23
final class LogicalOperatorProcessor
24
{
25
    /**
26
     * @var array Of operator precedences & associativity.
27
     */
28
    private $operatorPrecedence = [
29
        Token::LOGICAL_OR => [
30
            'precedence' => 0,
31
            'associativity' => 'left'
32
        ],
33
        Token::LOGICAL_AND => [
34
            'precedence' => 1,
35
            'associativity' => 'left'
36
        ]
37
    ];
38
39
    /**
40
     * Accepts an array of VersionRanges & logical operator tokens & returns a single version range implementing those
41
     *  constraints.
42
     *
43
     * @param array $tokenList
44
     *
45
     * @return VersionRangeInterface
46
     */
47 3
    public function run(array $tokenList)
48
    {
49 3
        return $this->buildRanges(
50 3
            $this->shuntingYard($tokenList)
51 3
        );
52
    }
53
54
    /**
55
     * Accepts an array of ComparatorVersions & logical operators in reverse polish notation & returns a single
56
     *  instance of a class implementing VersionRangeInterface.
57
     *
58
     * @param array $resultList
59
     *
60
     * @return VersionRangeInterface
61
     */
62 3
    private function buildRanges(array $resultList)
63
    {
64 3
        $stack = new \SplStack();
65
66 3
        foreach ($resultList as $result) {
67 3
            if ($result instanceof VersionRangeInterface) {
68 3
                $stack->push($result);
69 3
            } else {
70 3
                $operator1 = $stack->pop();
71 3
                $operator2 = $stack->pop();
72
73
                /** @var Token $result */
74 3
                if (Token::LOGICAL_AND === $result->getType()) {
75 2
                    $stack->push(new LogicalAnd(
76 2
                        $operator2,
77
                        $operator1
78 2
                    ));
79 2
                } else {
80 2
                    $stack->push(new LogicalOr(
81 2
                        $operator2,
82
                        $operator1
83 2
                    ));
84
                }
85
            }
86 3
        }
87
88 3
        return $stack->pop();
89
    }
90
91
    /**
92
     * Re-order token stream into reverse polish notation.
93
     *
94
     * @param array $tokenList
95
     *
96
     * @return array of ComparatorVersions and logical operator tokens
97
     */
98 3
    private function shuntingYard(array $tokenList)
99
    {
100 3
        $operatorStack = new \SplStack();
101 3
        $output = new \SplQueue();
102
103 3
        foreach ($tokenList as $token) {
1 ignored issue
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
104
105
            // Accumulate Versions & Comparators
106 3
            if ($token instanceof VersionRangeInterface) {
107 3
                $output->enqueue($token);
108
109
            // Handle operators
110 3
            } elseif ($token instanceof Token) {
1 ignored issue
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
111
112
                // Loop while the current token has higher precedence then the stack token
113 3
                $operator1 = $token;
114
                while (
115 3
                    $this->hasOperator($operatorStack)
116 3
                    && ($operator2 = $operatorStack->top())
117 3
                    && $this->hasLowerPrecedence($operator1, $operator2)
118 3
                ) {
119 1
                    $output->enqueue($operatorStack->pop());
120 1
                }
121
122 3
                $operatorStack->push($operator1);
123
1 ignored issue
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
124 3
            } else {
125
                throw new \RuntimeException('Invalid version number');
126
            }
127 3
        }
128
129
        // Merge remaining operators onto output list
130 3
        while ($this->hasOperator($operatorStack)) {
131 3
            $output->enqueue($operatorStack->pop());
132 3
        }
133
134 3
        return iterator_to_array($output);
135
    }
136
137
    /**
138
     * Returns true if we have an operator on the stack.
139
     *
140
     * @param \SplStack $stack
141
     * @return bool
142
     */
143 3
    private function hasOperator(\SplStack $stack)
144
    {
145 3
        return count($stack) > 0;
146
    }
147
148
    /**
149
     * Returns true if the first operator has lower precedence than the second.
150
     *
151
     * @param Token $operator1
152
     * @param Token $operator2
153
     *
154
     * @return bool
155
     */
156 1
    private function hasLowerPrecedence(Token $operator1, Token $operator2)
157
    {
158 1
        $associativity1 = $this->operatorPrecedence[$operator1->getType()]['associativity'];
159
160 1
        $precedence1 = $this->operatorPrecedence[$operator1->getType()]['precedence'];
161 1
        $precedence2 = $this->operatorPrecedence[$operator2->getType()]['precedence'];
162
163 1
        return ('left' === $associativity1 && $precedence1 === $precedence2)
164 1
            || $precedence1 < $precedence2;
165
    }
166
}
167