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 (#66)
by Tomasz
01:31
created

RegularParser::lookaheadN()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5.1158

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 10
cts 12
cp 0.8333
rs 9.2728
c 0
b 0
f 0
cc 5
nc 6
nop 1
crap 5.1158
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 $nameRegex;
17
    private $tokens;
18
    private $tokensCount;
19
    private $position;
20
    /** @var int[] */
21
    private $backtracks;
22
    private $lastBacktrack;
23
24
    const TOKEN_OPEN = 1;
25
    const TOKEN_CLOSE = 2;
26
    const TOKEN_MARKER = 3;
27
    const TOKEN_SEPARATOR = 4;
28
    const TOKEN_DELIMITER = 5;
29
    const TOKEN_STRING = 6;
30
    const TOKEN_WS = 7;
31
32 14
    public function __construct(SyntaxInterface $syntax = null)
33
    {
34 14
        $this->lexerRegex = $this->prepareLexer($syntax ?: new CommonSyntax());
35 14
        $this->nameRegex = '~^'.RegexBuilderUtility::buildNameRegex().'$~us';
36 14
    }
37
38
    /**
39
     * @param string $text
40
     *
41
     * @return ParsedShortcode[]
42
     */
43 58
    public function parse($text)
44
    {
45 58
        $nestingLevel = ini_set('xdebug.max_nesting_level', -1);
46 58
        $this->tokens = $this->tokenize($text);
47 58
        $this->backtracks = array();
48 58
        $this->lastBacktrack = 0;
49 58
        $this->position = 0;
50 58
        $this->tokensCount = \count($this->tokens);
51
52 58
        $shortcodes = array();
53 58
        while($this->position < $this->tokensCount) {
54 57
            while($this->position < $this->tokensCount && false === $this->lookahead(self::TOKEN_OPEN)) {
55 25
                $this->position++;
56
            }
57 57
            $names = array();
58 57
            $this->beginBacktrack();
59 57
            $matches = $this->shortcode($names);
60 57
            if(\is_array($matches)) {
61 49
                foreach($matches as $shortcode) {
62 49
                    $shortcodes[] = $shortcode;
63
                }
64
            }
65
        }
66 58
        ini_set('xdebug.max_nesting_level', $nestingLevel);
67
68 58
        return $shortcodes;
69
    }
70
71 49
    private function getObject($name, $parameters, $bbCode, $offset, $content, $text)
72
    {
73 49
        return new ParsedShortcode(new Shortcode($name, $parameters, $content, $bbCode), $text, $offset);
74
    }
75
76
    /* --- RULES ----------------------------------------------------------- */
77
78 57
    private function shortcode(array &$names)
79
    {
80 57
        if(!$this->match(self::TOKEN_OPEN, false)) { return false; }
81 57
        $offset = $this->tokens[$this->position - 1][2];
82 57
        $this->match(self::TOKEN_WS, false);
83 57
        if('' === $name = $this->match(self::TOKEN_STRING, false)) { return false; }
84 54
        if($this->lookahead(self::TOKEN_STRING)) { return false; }
85 54
        if(1 !== preg_match($this->nameRegex, $name, $matches)) { return false; }
86 53
        $this->match(self::TOKEN_WS, false);
87
        // bbCode
88 53
        $bbCode = $this->match(self::TOKEN_SEPARATOR, true) ? $this->value() : null;
89 53
        if(false === $bbCode) { return false; }
90
        // parameters
91 52
        if(false === ($parameters = $this->parameters())) { return false; }
92
93
        // self-closing
94 50
        if($this->match(self::TOKEN_MARKER, true)) {
95 17
            if(!$this->match(self::TOKEN_CLOSE, false)) { return false; }
96
97 16
            return array($this->getObject($name, $parameters, $bbCode, $offset, null, $this->getBacktrack()));
98
        }
99
100
        // just-closed or with-content
101 38
        if(!$this->match(self::TOKEN_CLOSE, false)) { return false; }
102 38
        $this->beginBacktrack();
103 38
        $names[] = $name;
104
105
        // begin inlined content()
106 38
        $content = '';
107 38
        $shortcodes = array();
108 38
        $closingName = null;
109
110 38
        while($this->position < $this->tokensCount) {
111 28 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...
112 24
                $content .= $this->match(null, true);
113
            }
114
115 28
            $this->beginBacktrack();
116 28
            $contentMatchedShortcodes = $this->shortcode($names);
117 28
            if(\is_string($contentMatchedShortcodes)) {
118 6
                $closingName = $contentMatchedShortcodes;
119 6
                break;
120
            }
121 28
            if(\is_array($contentMatchedShortcodes)) {
122 15
                foreach($contentMatchedShortcodes as $matchedShortcode) {
123 15
                    $shortcodes[] = $matchedShortcode;
124
                }
125 15
                continue;
126
            }
127 22
            $this->backtrack();
128
129 22
            $this->beginBacktrack();
130 22
            if(false !== ($closingName = $this->close($names))) {
131 19
                if(null === $content) { $content = ''; }
132 19
                $this->backtrack();
133 19
                $shortcodes = array();
134 19
                break;
135
            }
136 8
            $closingName = null;
137 8
            $this->backtrack();
138
139 8
            $content .= $this->match(null, false);
140
        }
141 38
        $content = $this->position < $this->tokensCount ? $content : false;
142
        // end inlined content()
143
144 38
        if(null !== $closingName && $closingName !== $name) {
145 6
            array_pop($names);
146 6
            array_pop($this->backtracks);
147 6
            array_pop($this->backtracks);
148
149 6
            return $closingName;
150
        }
151 38
        if(false === $content || $closingName !== $name) {
152 25
            $this->backtrack(false);
153 25
            $text = $this->backtrack(false);
154
155 25
            return array_merge(array($this->getObject($name, $parameters, $bbCode, $offset, null, $text)), $shortcodes);
156
        }
157 19
        $content = $this->getBacktrack();
158 19
        if(!$this->close($names)) { return false; }
159
160 19
        return array($this->getObject($name, $parameters, $bbCode, $offset, $content, $this->getBacktrack()));
161
    }
162
163 22
    private function close(array &$names)
164
    {
165 22
        if(!$this->match(self::TOKEN_OPEN, true)) { return false; }
166 20
        if(!$this->match(self::TOKEN_MARKER, true)) { return false; }
167 20
        if(!$closingName = $this->match(self::TOKEN_STRING, true)) { return false; }
168 20
        if(!$this->match(self::TOKEN_CLOSE, false)) { return false; }
169
170 20
        return \in_array($closingName, $names, true) ? $closingName : false;
171
    }
172
173 52
    private function parameters()
174
    {
175 52
        $parameters = array();
176
177 52
        while(true) {
178 52
            $this->match(self::TOKEN_WS, false);
179 52
            if($this->lookahead(self::TOKEN_MARKER) || $this->lookahead(self::TOKEN_CLOSE)) { break; }
180 28
            if(!$name = $this->match(self::TOKEN_STRING, true)) { return false; }
181 27
            if(!$this->match(self::TOKEN_SEPARATOR, true)) { $parameters[$name] = null; continue; }
182 26
            if(false === ($value = $this->value())) { return false; }
183 25
            $this->match(self::TOKEN_WS, false);
184
185 25
            $parameters[$name] = $value;
186
        }
187
188 50
        return $parameters;
189
    }
190
191 28
    private function value()
192
    {
193 28
        $value = '';
194
195 28
        if($this->match(self::TOKEN_DELIMITER, false)) {
196 19 View Code Duplication
            while($this->position < $this->tokensCount && false === $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...
197 19
                $value .= $this->match(null, false);
198
            }
199
200 19
            return $this->match(self::TOKEN_DELIMITER, false) ? $value : false;
201
        }
202
203 15
        if($this->lookahead(self::TOKEN_STRING) || $this->lookahead(self::TOKEN_MARKER)) {
204 14
            while(false === ($this->lookahead(self::TOKEN_WS) || $this->lookahead(self::TOKEN_CLOSE) || $this->lookaheadN(array(self::TOKEN_MARKER, self::TOKEN_CLOSE)))) {
205 14
                $value .= $this->match(null, false);
206
            }
207
208 14
            return $value;
209
        }
210
211 1
        return false;
212
    }
213
214
    /* --- PARSER ---------------------------------------------------------- */
215
216 57
    private function beginBacktrack()
217
    {
218 57
        $this->backtracks[] = $this->position;
219 57
        $this->lastBacktrack = $this->position;
220 57
    }
221
222 31
    private function getBacktrack()
223
    {
224 31
        $position = array_pop($this->backtracks);
225 31
        $backtrack = '';
226 31 View Code Duplication
        for($i = $position; $i < $this->position; $i++) {
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...
227 31
            $backtrack .= $this->tokens[$i][1];
228
        }
229
230 31
        return $backtrack;
231
    }
232
233 38
    private function backtrack($modifyPosition = true)
234
    {
235 38
        $position = array_pop($this->backtracks);
236 38
        if($modifyPosition) {
237 22
            $this->position = $position;
238
        }
239
240 38
        $backtrack = '';
241 38 View Code Duplication
        for($i = $position; $i < $this->lastBacktrack; $i++) {
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...
242 25
            $backtrack .= $this->tokens[$i][1];
243
        }
244 38
        $this->lastBacktrack = $position;
245
246 38
        return $backtrack;
247
    }
248
249 57
    private function lookahead($type)
250
    {
251 57
        return $this->position < $this->tokensCount && $this->tokens[$this->position][0] === $type;
252
    }
253
254 14
    private function lookaheadN(array $types)
255
    {
256 14
        $count = count($types);
257 14
        if($this->position + $count > $this->tokensCount) {
258
            return false;
259
        }
260
261 14
        $position = $this->position;
262 14
        foreach($types as $type) {
263
            // note: automatically skips whitespace tokens
264 14
            if($this->tokens[$position][0] === self::TOKEN_WS) {
265
                $position++;
266
            }
267 14
            if($type !== $this->tokens[$position][0]) {
268 14
                return false;
269
            }
270 2
            $position++;
271
        }
272
273 2
        return true;
274
    }
275
276 57
    private function match($type, $ws)
277
    {
278 57
        if($this->position >= $this->tokensCount) {
279 20
            return '';
280
        }
281
282 57
        $token = $this->tokens[$this->position];
283 57
        if(!empty($type) && $token[0] !== $type) {
284 57
            return '';
285
        }
286
287 57
        $this->position++;
288 57
        if($ws && $this->position < $this->tokensCount && $this->tokens[$this->position][0] === self::TOKEN_WS) {
289 17
            $this->position++;
290
        }
291
292 57
        return $token[1];
293
    }
294
295
    /* --- LEXER ----------------------------------------------------------- */
296
297 58
    private function tokenize($text)
298
    {
299 58
        $count = preg_match_all($this->lexerRegex, $text, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
300 58
        if(false === $count || preg_last_error() !== PREG_NO_ERROR) {
301
            throw new \RuntimeException(sprintf('PCRE failure `%s`.', preg_last_error()));
302
        }
303
304 58
        $tokens = array();
305 58
        $position = 0;
306
307 58
        foreach($matches as $match) {
308
            switch(true) {
309 57 View Code Duplication
                case -1 !== $match['string'][1]: { $token = $match['string'][0]; $type = self::TOKEN_STRING; break; }
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of -1 (integer) and $match['string'][1] (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
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...
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...
310 57 View Code Duplication
                case -1 !== $match['ws'][1]: { $token = $match['ws'][0]; $type = self::TOKEN_WS; break; }
0 ignored issues
show
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...
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...
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...
311 57 View Code Duplication
                case -1 !== $match['marker'][1]: { $token = $match['marker'][0]; $type = self::TOKEN_MARKER; break; }
0 ignored issues
show
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...
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...
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 57 View Code Duplication
                case -1 !== $match['delimiter'][1]: { $token = $match['delimiter'][0]; $type = self::TOKEN_DELIMITER; break; }
0 ignored issues
show
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...
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...
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 57 View Code Duplication
                case -1 !== $match['separator'][1]: { $token = $match['separator'][0]; $type = self::TOKEN_SEPARATOR; break; }
0 ignored issues
show
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...
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...
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 57 View Code Duplication
                case -1 !== $match['open'][1]: { $token = $match['open'][0]; $type = self::TOKEN_OPEN; break; }
0 ignored issues
show
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...
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...
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 55 View Code Duplication
                case -1 !== $match['close'][1]: { $token = $match['close'][0]; $type = self::TOKEN_CLOSE; break; }
0 ignored issues
show
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...
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...
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
                default: { throw new \RuntimeException(sprintf('Invalid token.')); }
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...
317
            }
318 57
            $tokens[] = array($type, $token, $position);
319 57
            $position += mb_strlen($token, 'utf-8');
320
        }
321
322 58
        return $tokens;
323
    }
324
325
    private function prepareLexer(SyntaxInterface $syntax)
326
    {
327 14
        $group = function($text, $group) {
328 14
            return '(?<'.$group.'>'.preg_replace('/(.)/us', '\\\\$0', $text).')';
329 14
        };
330 14
        $quote = function($text) {
331 14
            return preg_replace('/(.)/us', '\\\\$0', $text);
332 14
        };
333
334
        $rules = array(
335 14
            '(?<string>\\\\.|(?:(?!'.implode('|', array(
336 14
                $quote($syntax->getOpeningTag()),
337 14
                $quote($syntax->getClosingTag()),
338 14
                $quote($syntax->getClosingTagMarker()),
339 14
                $quote($syntax->getParameterValueSeparator()),
340 14
                $quote($syntax->getParameterValueDelimiter()),
341 14
                '\s+',
342 14
            )).').)+)',
343 14
            '(?<ws>\s+)',
344 14
            $group($syntax->getClosingTagMarker(), 'marker'),
345 14
            $group($syntax->getParameterValueDelimiter(), 'delimiter'),
346 14
            $group($syntax->getParameterValueSeparator(), 'separator'),
347 14
            $group($syntax->getOpeningTag(), 'open'),
348 14
            $group($syntax->getClosingTag(), 'close'),
349
        );
350
351 14
        return '~('.implode('|', $rules).')~us';
352
    }
353
}
354