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
02:27
created

RegularParser::lookaheadN()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 9
cts 9
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 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 && false === $this->lookahead(self::TOKEN_OPEN)) {
49 24
                $this->position++;
50
            }
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
                }
58
            }
59
        }
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)) { 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, true);
128 21
            }
129
            $isShortcode = $this->lookaheadN(array(self::TOKEN_OPEN, self::TOKEN_STRING));
130
            $isMarker = $this->lookaheadN(array(self::TOKEN_OPEN, self::TOKEN_MARKER));
131 25
            if(false === ($isShortcode || $isMarker)) {
132 25
                $this->match(null, $appendContent, true);
133 25
                continue;
134 6
            }
135 6
136
            $this->beginBacktrack();
137 25
            $matchedShortcodes = $this->shortcode($names);
138 13
            if(is_string($matchedShortcodes)) {
139 13
                $closingName = $matchedShortcodes;
140
                break;
141 20
            }
142
            if(false !== $matchedShortcodes) {
143 20
                $shortcodes = array_merge($shortcodes, $matchedShortcodes);
144 20
                continue;
145 17
            }
146 17
            $this->backtrack();
147 17
148 17
            $this->beginBacktrack();
149
            if(false !== ($closingName = $this->close($names))) {
150 7
                if(null === $content) { $content = ''; }
151 7
                $this->backtrack();
152 7
                $shortcodes = array();
153 1
                break;
154 1
            }
155
            $closingName = null;
156
            $this->backtrack();
157 6
            if($this->position < $this->tokensCount) {
158
                $shortcodes = array();
159
                break;
160 34
            }
161
162
            $this->match(null, $appendContent);
163 20
        }
164
165 20
        return array($this->position < $this->tokensCount ? $content : false, $shortcodes, $closingName);
166
    }
167
168 20
    private function close(array &$names)
169 18
    {
170 18
        $closingName = null;
171 18
        $setName = function(array $token) use(&$closingName) { $closingName = $token[1]; };
172
173 18
        if(!$this->match(self::TOKEN_OPEN, null, true)) { return false; }
174
        if(!$this->match(self::TOKEN_MARKER, null, true)) { return false; }
175
        if(!$this->match(self::TOKEN_STRING, $setName, true)) { return false; }
176 49
        if(!$this->match(self::TOKEN_CLOSE)) { return false; }
177
178 49
        return in_array($closingName, $names) ? $closingName : false;
179
    }
180
181 48
    private function bbCode()
182
    {
183 48
        return $this->match(self::TOKEN_SEPARATOR, null, true) ? $this->value() : null;
184
    }
185
186 48
    private function parameters()
187 48
    {
188
        $parameters = array();
189 48
        $setName = function(array $token) use(&$name) { $name = $token[1]; };
190 48
191 25
        while(true) {
192 24
            $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 23
194 22
            $this->match(self::TOKEN_WS);
195
            if($this->lookahead(self::TOKEN_MARKER) || $this->lookahead(self::TOKEN_CLOSE)) { break; }
196 22
            if(!$this->match(self::TOKEN_STRING, $setName, true)) { return false; }
197
            if(!$this->match(self::TOKEN_SEPARATOR, null, true)) { $parameters[$name] = null; continue; }
198
            if(false === ($value = $this->value())) { return false; }
199 46
            $this->match(self::TOKEN_WS);
200
201
            $parameters[$name] = $value;
202 25
        }
203
204 25
        return $parameters;
205
    }
206
207 25
    private function value()
208 19
    {
209 19
        $value = '';
210
        $appendValue = function(array $token) use(&$value) { $value .= $token[1]; };
211
212 19
        if($this->match(self::TOKEN_DELIMITER)) {
213 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
                $this->match(null, $appendValue);
215 12
            }
216 11
217 2
            return $this->match(self::TOKEN_DELIMITER) ? $value : false;
218
        }
219
220 11
        if($this->match(self::TOKEN_STRING, $appendValue)) {
221
            while($this->match(self::TOKEN_STRING, $appendValue)) {
222
                continue;
223 1
            }
224
225
            return $value;
226
        }
227
228 53
        return false;
229
    }
230 53
231 53
    /* --- PARSER ---------------------------------------------------------- */
232
233
    private function beginBacktrack()
234
    {
235
        $this->backtracks[] = array();
236
    }
237
238
    private function getBacktrack()
239 34
    {
240
        // switch from array_map() to array_column() when dropping support for PHP <5.5
241 34
        return implode('', array_map(function(array $token) { return $token[1]; }, array_pop($this->backtracks)));
242 34
    }
243 34
244 20
    private function backtrack($modifyPosition = true)
245
    {
246
        $tokens = array_pop($this->backtracks);
247 34
        $count = count($tokens);
248
        if($modifyPosition) {
249
            $this->position -= $count;
250 34
        }
251 25
252
        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
            for($i = 0; $i < $count; $i++) {
256
                array_pop($backtrack);
257
            }
258 53
        }
259
260 53
        return implode('', array_map(function(array $token) { return $token[1]; }, $tokens));
261 1
    }
262
263
    private function lookaheadN(array $types)
264 53
    {
265 53
        $position = $this->position;
266 53
        foreach($types as $type) {
267 52
            if($position >= $this->tokensCount) {
268
                return false;
269
            }
270
            if($this->tokens[$position][0] === self::TOKEN_WS) {
271 53
                $position++;
272
            }
273 53
            if($this->tokens[$position][0] !== $type) {
274
                return false;
275
            }
276 53
            $position++;
277
        }
278 53
279 20
        return true;
280
    }
281
282 53
    private function lookahead($type)
283 53
    {
284 53
        return $this->position < $this->tokensCount && (empty($type) || $this->tokens[$this->position][0] === $type);
285 53
    }
286
287 53
    private function match($type, $callbacks = null, $ws = false)
288 53
    {
289
        if($this->position >= $this->tokensCount) {
290
            return false;
291 53
        }
292 53
293 53
        $type = (array)$type;
294
        $token = $this->tokens[$this->position];
295
        if(!empty($type) && !in_array($token[0], $type)) {
296 53
            return false;
297
        }
298 53
        foreach($this->backtracks as &$backtrack) {
299
            $backtrack[] = $token;
300
        }
301
302
        $this->position++;
303 54
        foreach((array)$callbacks as $callback) {
304
            $callback($token);
305 54
        }
306 54
307 54
        $ws && $this->match(self::TOKEN_WS);
308
309 54
        return true;
310
    }
311 53
312 53
    /* --- LEXER ----------------------------------------------------------- */
313 52
314 52
    private function tokenize($text)
315 52
    {
316 52
        preg_match_all($this->lexerRegex, $text, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
317 52
        $tokens = array();
318
        $position = 0;
319 53
320 53
        foreach($matches as $match) {
321
            switch(true) {
322 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 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 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 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 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 14
                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 14
            }
330 14
            $tokens[] = array($type, $token, $position);
331
            $position += mb_strlen($token, 'utf-8');
332
        }
333 14
334 14
        return $tokens;
335 14
    }
336 14
337 14
    private function getTokenizerRegex(SyntaxInterface $syntax)
338 14
    {
339 14
        $quote = function($text, $group) {
340
            return '(?<'.$group.'>'.preg_replace('/(.)/us', '\\\\$0', $text).')';
341
        };
342 14
343
        $rules = array(
344
            $quote($syntax->getOpeningTag(), 'open'),
345
            $quote($syntax->getClosingTag(), 'close'),
346
            $quote($syntax->getClosingTagMarker(), 'marker'),
347
            $quote($syntax->getParameterValueSeparator(), 'separator'),
348
            $quote($syntax->getParameterValueDelimiter(), 'delimiter'),
349
            '(?<ws>\s+)',
350
            '(?<string>[\w-]+|\\\\.|.)',
351
        );
352
353
        return '~('.implode('|', $rules).')~us';
354
    }
355
}
356