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 (#60)
by Tomasz
05:32
created

RegularParser::lookaheadN()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5

Importance

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