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
Push — master ( af0648...a69988 )
by Tomasz
10s
created

RegularParser::getTokenizerRegex()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 28
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 21
cts 21
cp 1
rs 8.8571
c 0
b 0
f 0
cc 1
eloc 21
nc 1
nop 1
crap 1
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 55
    public function parse($text)
41
    {
42 55
        $this->tokens = $this->tokenize($text);
43 55
        $this->backtracks = array();
44 55
        $this->position = 0;
45 55
        $this->tokensCount = count($this->tokens);
46
47 55
        $shortcodes = array();
48 55
        while($this->position < $this->tokensCount) {
49 54
            while($this->position < $this->tokensCount && false === $this->lookahead(self::TOKEN_OPEN)) {
50 24
                $this->position++;
51
            }
52 54
            $names = array();
53 54
            $this->beginBacktrack();
54 54
            $matches = $this->shortcode($names);
55 54
            if(is_array($matches)) {
56 46
                foreach($matches as $shortcode) {
57 46
                    $shortcodes[] = $shortcode;
58
                }
59
            }
60
        }
61
62 55
        return $shortcodes;
63
    }
64
65 46
    private function getObject($name, $parameters, $bbCode, $offset, $content, $text)
66
    {
67 46
        return new ParsedShortcode(new Shortcode($name, $parameters, $content, $bbCode), $text, $offset);
68
    }
69
70
    /* --- RULES ----------------------------------------------------------- */
71
72 54
    private function shortcode(array &$names)
73
    {
74 54
        $name = null;
75 54
        $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 54
        if(!$this->match(self::TOKEN_OPEN, $setOffset, true)) { return false; }
81 54
        if(!$this->match(self::TOKEN_STRING, $setName, false)) { return false; }
82 51
        if($this->lookahead(self::TOKEN_STRING)) { return false; }
83 51
        if(!preg_match_all('~^'.RegexBuilderUtility::buildNameRegex().'$~us', $name, $matches)) { return false; }
84 50
        $this->match(self::TOKEN_WS);
85 50
        if(false === ($bbCode = $this->bbCode())) { return false; }
86 49
        if(false === ($parameters = $this->parameters())) { return false; }
87
88
        // self-closing
89 47
        if($this->match(self::TOKEN_MARKER, null, true)) {
90 16
            if(!$this->match(self::TOKEN_CLOSE)) { return false; }
91
92 15
            return array($this->getObject($name, $parameters, $bbCode, $offset, null, $this->getBacktrack()));
93
        }
94
95
        // just-closed or with-content
96 35
        if(!$this->match(self::TOKEN_CLOSE)) { return false; }
97 35
        $this->beginBacktrack();
98 35
        $names[] = $name;
99 35
        list($content, $shortcodes, $closingName) = $this->content($names);
100 35
        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 35
        if(false === $content || $closingName !== $name) {
108 23
            $this->backtrack(false);
109 23
            $text = $this->backtrack(false);
110
111 23
            return array_merge(array($this->getObject($name, $parameters, $bbCode, $offset, null, $text)), $shortcodes);
112
        }
113 18
        $content = $this->getBacktrack();
114 18
        if(!$this->close($names)) { return false; }
115
116 18
        return array($this->getObject($name, $parameters, $bbCode, $offset, $content, $this->getBacktrack()));
117
    }
118
119 35
    private function content(array &$names)
120
    {
121 35
        $content = null;
122 35
        $shortcodes = array();
123 35
        $closingName = null;
124
        $appendContent = function(array $token) use(&$content) { $content .= $token[1]; };
125
126 35
        while($this->position < $this->tokensCount) {
127 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...
128 22
                $this->match(null, $appendContent, true);
129
            }
130
131 26
            $this->beginBacktrack();
132 26
            $matchedShortcodes = $this->shortcode($names);
133 26
            if(is_string($matchedShortcodes)) {
134 6
                $closingName = $matchedShortcodes;
135 6
                break;
136
            }
137 26
            if(is_array($matchedShortcodes)) {
138 14
                foreach($matchedShortcodes as $matchedShortcode) {
139 14
                    $shortcodes[] = $matchedShortcode;
140
                }
141 14
                continue;
142
            }
143 21
            $this->backtrack();
144
145 21
            $this->beginBacktrack();
146 21
            if(false !== ($closingName = $this->close($names))) {
147 18
                if(null === $content) { $content = ''; }
148 18
                $this->backtrack();
149 18
                $shortcodes = array();
150 18
                break;
151
            }
152 8
            $closingName = null;
153 8
            $this->backtrack();
154
155 8
            $this->match(null, $appendContent);
156
        }
157
158 35
        return array($this->position < $this->tokensCount ? $content : false, $shortcodes, $closingName);
159
    }
160
161 21
    private function close(array &$names)
162
    {
163 21
        $closingName = null;
164
        $setName = function(array $token) use(&$closingName) { $closingName = $token[1]; };
165
166 21
        if(!$this->match(self::TOKEN_OPEN, null, true)) { return false; }
167 19
        if(!$this->match(self::TOKEN_MARKER, null, true)) { return false; }
168 19
        if(!$this->match(self::TOKEN_STRING, $setName, true)) { return false; }
169 19
        if(!$this->match(self::TOKEN_CLOSE)) { return false; }
170
171 19
        return in_array($closingName, $names, true) ? $closingName : false;
172
    }
173
174 50
    private function bbCode()
175
    {
176 50
        return $this->match(self::TOKEN_SEPARATOR, null, true) ? $this->value() : null;
177
    }
178
179 49
    private function parameters()
180
    {
181 49
        $parameters = array();
182
        $setName = function(array $token) use(&$name) { $name = $token[1]; };
183
184 49
        while(true) {
185 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...
186
187 49
            $this->match(self::TOKEN_WS);
188 49
            if($this->lookahead(self::TOKEN_MARKER) || $this->lookahead(self::TOKEN_CLOSE)) { break; }
189 25
            if(!$this->match(self::TOKEN_STRING, $setName, true)) { return false; }
190 24
            if(!$this->match(self::TOKEN_SEPARATOR, null, true)) { $parameters[$name] = null; continue; }
191 23
            if(false === ($value = $this->value())) { return false; }
192 22
            $this->match(self::TOKEN_WS);
193
194 22
            $parameters[$name] = $value;
195
        }
196
197 47
        return $parameters;
198
    }
199
200 25
    private function value()
201
    {
202 25
        $value = '';
203
        $appendValue = function(array $token) use(&$value) { $value .= $token[1]; };
204
205 25
        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 12
        if($this->match(self::TOKEN_STRING, $appendValue)) {
214 11
            while($this->match(self::TOKEN_STRING, $appendValue)) {
215
                continue;
216
            }
217
218 11
            return $value;
219
        }
220
221 1
        return false;
222
    }
223
224
    /* --- PARSER ---------------------------------------------------------- */
225
226 54
    private function beginBacktrack()
227
    {
228 54
        $this->backtracks[] = array();
229 54
    }
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 35
    private function backtrack($modifyPosition = true)
238
    {
239 35
        $tokens = array_pop($this->backtracks);
240 35
        $count = count($tokens);
241 35
        if($modifyPosition) {
242 21
            $this->position -= $count;
243
        }
244
245 35
        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 35
            for($i = 0; $i < $count; $i++) {
249 26
                array_pop($backtrack);
250
            }
251
        }
252
253
        return implode('', array_map(function(array $token) { return $token[1]; }, $tokens));
254
    }
255
256 54
    private function lookahead($type)
257
    {
258 54
        return $this->position < $this->tokensCount && (empty($type) || $this->tokens[$this->position][0] === $type);
259
    }
260
261 54
    private function match($type, $callback = null, $ws = false)
262
    {
263 54
        if($this->position >= $this->tokensCount) {
264 20
            return false;
265
        }
266
267 54
        $token = $this->tokens[$this->position];
268 54
        if(!empty($type) && $token[0] !== $type) {
269 51
            return false;
270
        }
271 54
        foreach($this->backtracks as &$backtrack) {
272 54
            $backtrack[] = $token;
273
        }
274 54
        unset($backtrack);
275
276 54
        $callback && $callback($token);
277 54
        $this->position++;
278
279 54
        if($ws && $this->position < $this->tokensCount && $this->tokens[$this->position][0] === self::TOKEN_WS) {
280 16
            $token = $this->tokens[$this->position];
281 16
            $this->position++;
282 16
            foreach($this->backtracks as &$backtrack) {
283 16
                $backtrack[] = $token;
284
            }
285
        }
286
287 54
        return true;
288
    }
289
290
    /* --- LEXER ----------------------------------------------------------- */
291
292 55
    private function tokenize($text)
293
    {
294 55
        preg_match_all($this->lexerRegex, $text, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
295 55
        $tokens = array();
296 55
        $position = 0;
297
298 55
        foreach($matches as $match) {
299
            switch(true) {
300 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...
301 54 View Code Duplication
                case -1 !== $match['close'][1]: { $token = $match['close'][0]; $type = self::TOKEN_CLOSE; break; }
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
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...
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...
302 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...
303 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...
304 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...
305 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...
306 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...
307
            }
308 54
            $tokens[] = array($type, $token, $position);
309 54
            $position += mb_strlen($token, 'utf-8');
310
        }
311
312 55
        return $tokens;
313
    }
314
315
    private function getTokenizerRegex(SyntaxInterface $syntax)
316
    {
317 14
        $group = function($text, $group) {
318 14
            return '(?<'.$group.'>'.preg_replace('/(.)/us', '\\\\$0', $text).')';
319 14
        };
320 14
        $quote = function($text) {
321 14
            return preg_replace('/(.)/us', '\\\\$0', $text);
322 14
        };
323
324
        $rules = array(
325 14
            $group($syntax->getOpeningTag(), 'open'),
326 14
            $group($syntax->getClosingTag(), 'close'),
327 14
            $group($syntax->getClosingTagMarker(), 'marker'),
328 14
            $group($syntax->getParameterValueSeparator(), 'separator'),
329 14
            $group($syntax->getParameterValueDelimiter(), 'delimiter'),
330 14
            '(?<ws>\s+)',
331 14
            '(?<string>\\\\.|(?:(?!'.implode('|', array(
332 14
                $quote($syntax->getOpeningTag()),
333 14
                $quote($syntax->getClosingTag()),
334 14
                $quote($syntax->getClosingTagMarker()),
335 14
                $quote($syntax->getParameterValueSeparator()),
336 14
                $quote($syntax->getParameterValueDelimiter()),
337 14
                '\s+',
338 14
            )).').)+)',
339
        );
340
341 14
        return '~('.implode('|', $rules).')~us';
342
    }
343
}
344