GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#44)
by Tomasz
03:31
created

RegularParser::value()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 23
Code Lines 12

Duplication

Lines 3
Ratio 13.04 %

Code Coverage

Tests 12
CRAP Score 7

Importance

Changes 0
Metric Value
dl 3
loc 23
ccs 12
cts 12
cp 1
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 12
nc 7
nop 0
crap 7
1
<?php
2
namespace Thunder\Shortcode\Parser;
3
4
use Thunder\Shortcode\Shortcode\ParsedShortcode;
5
use Thunder\Shortcode\Shortcode\Shortcode;
6
use Thunder\Shortcode\Syntax\CommonSyntax;
7
use Thunder\Shortcode\Syntax\SyntaxInterface;
8
use Thunder\Shortcode\Utility\RegexBuilderUtility;
9
10
/**
11
 * @author Tomasz Kowalczyk <[email protected]>
12
 */
13
final class RegularParser implements ParserInterface
14
{
15
    private $lexerRegex;
16
    private $tokens;
17
    private $tokensCount;
18
    private $position;
19
    private $backtracks;
20
21
    const TOKEN_OPEN = 1;
22
    const TOKEN_CLOSE = 2;
23
    const TOKEN_MARKER = 3;
24
    const TOKEN_SEPARATOR = 4;
25
    const TOKEN_DELIMITER = 5;
26
    const TOKEN_STRING = 6;
27
    const TOKEN_WS = 7;
28
29 14
    public function __construct(SyntaxInterface $syntax = null)
30
    {
31 14
        $this->lexerRegex = $this->getTokenizerRegex($syntax ?: new CommonSyntax());
32 14
    }
33
34
    /**
35
     * @param string $text
36
     *
37
     * @return ParsedShortcode[]
38
     */
39 54
    public function parse($text)
40
    {
41 54
        $this->tokens = $this->tokenize($text);
42 54
        $this->backtracks = array();
43 54
        $this->position = 0;
44 54
        $this->tokensCount = count($this->tokens);
45
46 54
        $shortcodes = array();
47 54
        while($this->position < $this->tokensCount) {
48 53
            while($this->position < $this->tokensCount && !$this->lookahead(self::TOKEN_OPEN)) {
49 24
                $this->position++;
50 24
            }
51 53
            $names = array();
52 53
            $this->beginBacktrack();
53 53
            $matches = $this->shortcode($names);
54 53
            if(is_array($matches)) {
55 45
                foreach($matches as $shortcode) {
56 45
                    $shortcodes[] = $shortcode;
57 45
                }
58 45
            }
59 53
        }
60
61 54
        return $shortcodes;
62
    }
63
64 45
    private function getObject($name, $parameters, $bbCode, $offset, $content, $text)
65
    {
66 45
        return new ParsedShortcode(new Shortcode($name, $parameters, $content, $bbCode), $text, $offset);
67
    }
68
69
    /* --- RULES ----------------------------------------------------------- */
70
71 53
    private function shortcode(array &$names)
72
    {
73 53
        $name = null;
74 53
        $offset = null;
75
76
        $setName = function(array $token) use(&$name) { $name = $token[1]; };
77
        $setOffset = function(array $token) use(&$offset) { $offset = $token[2]; };
78
79 53
        if(!$this->match(self::TOKEN_OPEN, $setOffset, true)) { return false; }
80 53
        if(!$this->match(self::TOKEN_STRING, $setName, false)) { return false; }
81 50
        if($this->lookahead(self::TOKEN_STRING, null)) { return false; }
82 50
        if(!preg_match_all('~^'.RegexBuilderUtility::buildNameRegex().'$~us', $name, $matches)) { return false; }
83 49
        $this->match(self::TOKEN_WS);
84 49
        if(false === ($bbCode = $this->bbCode())) { return false; }
85 48
        if(false === ($parameters = $this->parameters())) { return false; }
86
87
        // self-closing
88 46
        if($this->match(self::TOKEN_MARKER, null, true)) {
89 16
            if(!$this->match(self::TOKEN_CLOSE)) { return false; }
90
91 15
            return array($this->getObject($name, $parameters, $bbCode, $offset, null, $this->getBacktrack()));
92
        }
93
94
        // just-closed or with-content
95 34
        if(!$this->match(self::TOKEN_CLOSE)) { return false; }
96 34
        $this->beginBacktrack();
97 34
        $names[] = $name;
98 34
        list($content, $shortcodes, $closingName) = $this->content($names);
99 34
        if(null !== $closingName && $closingName !== $name) {
100 6
            array_pop($names);
101 6
            array_pop($this->backtracks);
102 6
            array_pop($this->backtracks);
103
104 6
            return $closingName;
105
        }
106 34
        if(false === $content || $closingName !== $name) {
107 23
            $this->backtrack(false);
108 23
            $text = $this->backtrack(false);
109
110 23
            return array_merge(array($this->getObject($name, $parameters, $bbCode, $offset, null, $text)), $shortcodes);
111
        }
112 17
        $content = $this->getBacktrack();
113 17
        if(!$this->close($names)) { return false; }
114
115 17
        return array($this->getObject($name, $parameters, $bbCode, $offset, $content, $this->getBacktrack()));
116
    }
117
118 34
    private function content(array &$names)
119
    {
120 34
        $content = null;
121 34
        $shortcodes = array();
122 34
        $closingName = null;
123
        $appendContent = function(array $token) use(&$content) { $content .= $token[1]; };
124
125 34
        while($this->position < $this->tokensCount) {
126 25 View Code Duplication
            while($this->position < $this->tokensCount && false === $this->lookahead(self::TOKEN_OPEN)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
127 21
                $this->match(null, $appendContent);
128 21
                continue;
129
            }
130
131 25
            $this->beginBacktrack();
132 25
            $matchedShortcodes = $this->shortcode($names);
133 25
            if(is_string($matchedShortcodes)) {
134 6
                $closingName = $matchedShortcodes;
135 6
                break;
136
            }
137 25
            if(false !== $matchedShortcodes) {
138 13
                $shortcodes = array_merge($shortcodes, $matchedShortcodes);
139 13
                continue;
140
            }
141 20
            $this->backtrack();
142
143 20
            $this->beginBacktrack();
144 20
            if(false !== ($closingName = $this->close($names))) {
145 17
                if(null === $content) { $content = ''; }
146 17
                $this->backtrack();
147 17
                $shortcodes = array();
148 17
                break;
149
            }
150 7
            $closingName = null;
151 7
            $this->backtrack();
152 7
            if($this->position < $this->tokensCount) {
153 1
                $shortcodes = array();
154 1
                break;
155
            }
156
157 6
            $this->match(null, $appendContent);
158 6
        }
159
160 34
        return array($this->position < $this->tokensCount ? $content : false, $shortcodes, $closingName);
161
    }
162
163 20
    private function close(array &$names)
164
    {
165 20
        $closingName = null;
166
        $setName = function(array $token) use(&$closingName) { $closingName = $token[1]; };
167
168 20
        if(!$this->match(self::TOKEN_OPEN, null, true)) { return false; }
169 18
        if(!$this->match(self::TOKEN_MARKER, null, true)) { return false; }
170 18
        if(!$this->match(self::TOKEN_STRING, $setName, true)) { return false; }
171 18
        if(!$this->match(self::TOKEN_CLOSE)) { return false; }
172
173 18
        return in_array($closingName, $names) ? $closingName : false;
174
    }
175
176 49
    private function bbCode()
177
    {
178 49
        return $this->match(self::TOKEN_SEPARATOR, null, true) ? $this->value() : null;
179
    }
180
181 48
    private function parameters()
182
    {
183 48
        $parameters = array();
184
        $setName = function(array $token) use(&$name) { $name = $token[1]; };
185
186 48
        while(true) {
187 48
            $name = null;
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $name, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
188
189 48
            $this->match(self::TOKEN_WS);
190 48
            if($this->lookahead(array(self::TOKEN_MARKER, self::TOKEN_CLOSE))) { break; }
191 25
            if(!$this->match(self::TOKEN_STRING, $setName, true)) { return false; }
192 24
            if(!$this->match(self::TOKEN_SEPARATOR, null, true)) { $parameters[$name] = null; continue; }
193 23
            if(false === ($value = $this->value())) { return false; }
194 22
            $this->match(self::TOKEN_WS);
195
196 22
            $parameters[$name] = $value;
197 22
        }
198
199 46
        return $parameters;
200
    }
201
202 25
    private function value()
203
    {
204 25
        $value = '';
205
        $appendValue = function(array $token) use(&$value) { $value .= $token[1]; };
206
207 25
        if($this->match(self::TOKEN_DELIMITER)) {
208 19 View Code Duplication
            while($this->position < $this->tokensCount && !$this->lookahead(self::TOKEN_DELIMITER)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
209 19
                $this->match(null, $appendValue);
210 19
            }
211
212 19
            return $this->match(self::TOKEN_DELIMITER) ? $value : false;
213
        }
214
215 12
        if($this->match(self::TOKEN_STRING, $appendValue)) {
216 11
            while($this->match(self::TOKEN_STRING, $appendValue)) {
217 2
                continue;
218
            }
219
220 11
            return $value;
221
        }
222
223 1
        return false;
224
    }
225
226
    /* --- PARSER ---------------------------------------------------------- */
227
228 53
    private function beginBacktrack()
229
    {
230 53
        $this->backtracks[] = array();
231 53
    }
232
233
    private function getBacktrack()
234
    {
235
        // switch from array_map() to array_column() when dropping support for PHP <5.5
236
        return implode('', array_map(function(array $token) { return $token[1]; }, array_pop($this->backtracks)));
237
    }
238
239 34
    private function backtrack($modifyPosition = true)
240
    {
241 34
        $tokens = array_pop($this->backtracks);
242 34
        $count = count($tokens);
243 34
        if($modifyPosition) {
244 20
            $this->position -= $count;
245 20
        }
246
247 34
        foreach($this->backtracks as &$backtrack) {
248
            // array_pop() in loop is much faster than array_slice() because
249
            // it operates directly on the passed array
250 34
            for($i = 0; $i < $count; $i++) {
251 25
                array_pop($backtrack);
252 25
            }
253 34
        }
254
255
        return implode('', array_map(function(array $token) { return $token[1]; }, $tokens));
256
    }
257
258 53
    private function lookahead($type, $callback = null)
259
    {
260 53
        if($this->position >= $this->tokensCount) {
261 1
            return false;
262
        }
263
264 53
        $type = (array)$type;
265 53
        $token = $this->tokens[$this->position];
266 53
        if(!empty($type) && !in_array($token[0], $type)) {
267 52
            return false;
268
        }
269
270
        /** @var $callback callable */
271 53
        $callback && $callback($token);
272
273 53
        return true;
274
    }
275
276 53
    private function match($type, $callbacks = null, $ws = false)
277
    {
278 53
        if($this->position >= $this->tokensCount) {
279 20
            return false;
280
        }
281
282 53
        $type = (array)$type;
283 53
        $token = $this->tokens[$this->position];
284 53
        if(!empty($type) && !in_array($token[0], $type)) {
285 53
            return false;
286
        }
287 53
        foreach($this->backtracks as &$backtrack) {
288 53
            $backtrack[] = $token;
289 53
        }
290
291 53
        $this->position++;
292 53
        foreach((array)$callbacks as $callback) {
293 53
            $callback($token);
294 53
        }
295
296 53
        $ws && $this->match(self::TOKEN_WS);
297
298 53
        return true;
299
    }
300
301
    /* --- LEXER ----------------------------------------------------------- */
302
303 54
    private function tokenize($text)
304
    {
305 54
        preg_match_all($this->lexerRegex, $text, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
306 54
        $tokens = array();
307 54
        $position = 0;
308
309 54
        foreach($matches as $match) {
310 53
            switch(true) {
311 53 View Code Duplication
                case -1 !== $match['open'][1]: { $token = $match['open'][0]; $type = self::TOKEN_OPEN; break; }
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of -1 (integer) and $match['open'][1] (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
312 53 View Code Duplication
                case -1 !== $match['close'][1]: { $token = $match['close'][0]; $type = self::TOKEN_CLOSE; break; }
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of -1 (integer) and $match['close'][1] (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
313 52 View Code Duplication
                case -1 !== $match['marker'][1]: { $token = $match['marker'][0]; $type = self::TOKEN_MARKER; break; }
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of -1 (integer) and $match['marker'][1] (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
314 52 View Code Duplication
                case -1 !== $match['separator'][1]: { $token = $match['separator'][0]; $type = self::TOKEN_SEPARATOR; break; }
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of -1 (integer) and $match['separator'][1] (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
315 52 View Code Duplication
                case -1 !== $match['delimiter'][1]: { $token = $match['delimiter'][0]; $type = self::TOKEN_DELIMITER; break; }
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of -1 (integer) and $match['delimiter'][1] (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
316 52 View Code Duplication
                case -1 !== $match['ws'][1]: { $token = $match['ws'][0]; $type = self::TOKEN_WS; break; }
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of -1 (integer) and $match['ws'][1] (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
317 52
                default: { $token = $match['string'][0]; $type = self::TOKEN_STRING; }
0 ignored issues
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

As per the PSR-2 coding standard, default statements should not be wrapped in curly braces.

switch ($expr) {
    default: { //wrong
        doSomething();
        break;
    }
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
318 52
            }
319 53
            $tokens[] = array($type, $token, $position);
320 53
            $position += mb_strlen($token, 'utf-8');
321 54
        }
322
323 54
        return $tokens;
324
    }
325
326
    private function getTokenizerRegex(SyntaxInterface $syntax)
327
    {
328 14
        $quote = function($text, $group) {
329 14
            return '(?<'.$group.'>'.preg_replace('/(.)/us', '\\\\$0', $text).')';
330 14
        };
331
332
        $rules = array(
333 14
            $quote($syntax->getOpeningTag(), 'open'),
334 14
            $quote($syntax->getClosingTag(), 'close'),
335 14
            $quote($syntax->getClosingTagMarker(), 'marker'),
336 14
            $quote($syntax->getParameterValueSeparator(), 'separator'),
337 14
            $quote($syntax->getParameterValueDelimiter(), 'delimiter'),
338 14
            '(?<ws>\s+)',
339 14
            '(?<string>[\w-]+|\\\\.|.)',
340 14
        );
341
342 14
        return '~('.implode('|', $rules).')~us';
343
    }
344
}
345