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:45
created

RegularParser::lookaheadN()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 21
Code Lines 12

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