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 ( dd33d8...ccf16e )
by Tomasz
02:05
created

RegularParser::prepareLexer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 28

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 9.472
c 0
b 0
f 0
cc 1
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 $nameRegex;
17
    private $tokens;
18
    private $tokensCount;
19
    private $position;
20
    /** @var int[] */
21
    private $backtracks;
22
    private $lastBacktrack;
23
24
    const TOKEN_OPEN = 1;
25
    const TOKEN_CLOSE = 2;
26
    const TOKEN_MARKER = 3;
27
    const TOKEN_SEPARATOR = 4;
28
    const TOKEN_DELIMITER = 5;
29
    const TOKEN_STRING = 6;
30
    const TOKEN_WS = 7;
31
32 14
    public function __construct(SyntaxInterface $syntax = null)
33
    {
34 14
        $this->lexerRegex = $this->prepareLexer($syntax ?: new CommonSyntax());
35 14
        $this->nameRegex = '~^'.RegexBuilderUtility::buildNameRegex().'$~us';
36 14
    }
37
38
    /**
39
     * @param string $text
40
     *
41
     * @return ParsedShortcode[]
42
     */
43 57
    public function parse($text)
44
    {
45 57
        $nestingLevel = ini_set('xdebug.max_nesting_level', -1);
46 57
        $this->tokens = $this->tokenize($text);
47 57
        $this->backtracks = array();
48 57
        $this->lastBacktrack = 0;
49 57
        $this->position = 0;
50 57
        $this->tokensCount = \count($this->tokens);
51
52 57
        $shortcodes = array();
53 57
        while($this->position < $this->tokensCount) {
54 56
            while($this->position < $this->tokensCount && false === $this->lookahead(self::TOKEN_OPEN)) {
55 24
                $this->position++;
56
            }
57 56
            $names = array();
58 56
            $this->beginBacktrack();
59 56
            $matches = $this->shortcode($names);
60 56
            if(\is_array($matches)) {
61 48
                foreach($matches as $shortcode) {
62 48
                    $shortcodes[] = $shortcode;
63
                }
64
            }
65
        }
66 57
        ini_set('xdebug.max_nesting_level', $nestingLevel);
67
68 57
        return $shortcodes;
69
    }
70
71 48
    private function getObject($name, $parameters, $bbCode, $offset, $content, $text)
72
    {
73 48
        return new ParsedShortcode(new Shortcode($name, $parameters, $content, $bbCode), $text, $offset);
74
    }
75
76
    /* --- RULES ----------------------------------------------------------- */
77
78 56
    private function shortcode(array &$names)
79
    {
80 56
        if(!$this->match(self::TOKEN_OPEN, false)) { return false; }
81 56
        $offset = $this->tokens[$this->position - 1][2];
82 56
        $this->match(self::TOKEN_WS, false);
83 56
        if('' === $name = $this->match(self::TOKEN_STRING, false)) { return false; }
84 53
        if($this->lookahead(self::TOKEN_STRING)) { return false; }
85 53
        if(1 !== preg_match($this->nameRegex, $name, $matches)) { return false; }
86 52
        $this->match(self::TOKEN_WS, false);
87
        // bbCode
88 52
        $bbCode = $this->match(self::TOKEN_SEPARATOR, true) ? $this->value() : null;
89 52
        if(false === $bbCode) { return false; }
90
        // parameters
91 51
        if(false === ($parameters = $this->parameters())) { return false; }
92
93
        // self-closing
94 49
        if($this->match(self::TOKEN_MARKER, true)) {
95 16
            if(!$this->match(self::TOKEN_CLOSE, false)) { return false; }
96
97 15
            return array($this->getObject($name, $parameters, $bbCode, $offset, null, $this->getBacktrack()));
98
        }
99
100
        // just-closed or with-content
101 37
        if(!$this->match(self::TOKEN_CLOSE, false)) { return false; }
102 37
        $this->beginBacktrack();
103 37
        $names[] = $name;
104
105
        // begin inlined content()
106 37
        $content = '';
107 37
        $shortcodes = array();
108 37
        $closingName = null;
109
110 37
        while($this->position < $this->tokensCount) {
111 28 View Code Duplication
            while($this->position < $this->tokensCount && false === $this->lookahead(self::TOKEN_OPEN)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
112 24
                $content .= $this->match(null, true);
113
            }
114
115 28
            $this->beginBacktrack();
116 28
            $contentMatchedShortcodes = $this->shortcode($names);
117 28
            if(\is_string($contentMatchedShortcodes)) {
118 6
                $closingName = $contentMatchedShortcodes;
119 6
                break;
120
            }
121 28
            if(\is_array($contentMatchedShortcodes)) {
122 15
                foreach($contentMatchedShortcodes as $matchedShortcode) {
123 15
                    $shortcodes[] = $matchedShortcode;
124
                }
125 15
                continue;
126
            }
127 22
            $this->backtrack();
128
129 22
            $this->beginBacktrack();
130 22
            if(false !== ($closingName = $this->close($names))) {
131 19
                if(null === $content) { $content = ''; }
132 19
                $this->backtrack();
133 19
                $shortcodes = array();
134 19
                break;
135
            }
136 8
            $closingName = null;
137 8
            $this->backtrack();
138
139 8
            $content .= $this->match(null, false);
140
        }
141 37
        $content = $this->position < $this->tokensCount ? $content : false;
142
        // end inlined content()
143
144 37
        if(null !== $closingName && $closingName !== $name) {
145 6
            array_pop($names);
146 6
            array_pop($this->backtracks);
147 6
            array_pop($this->backtracks);
148
149 6
            return $closingName;
150
        }
151 37
        if(false === $content || $closingName !== $name) {
152 24
            $this->backtrack(false);
153 24
            $text = $this->backtrack(false);
154
155 24
            return array_merge(array($this->getObject($name, $parameters, $bbCode, $offset, null, $text)), $shortcodes);
156
        }
157 19
        $content = $this->getBacktrack();
158 19
        if(!$this->close($names)) { return false; }
159
160 19
        return array($this->getObject($name, $parameters, $bbCode, $offset, $content, $this->getBacktrack()));
161
    }
162
163 22
    private function close(array &$names)
164
    {
165 22
        if(!$this->match(self::TOKEN_OPEN, true)) { return false; }
166 20
        if(!$this->match(self::TOKEN_MARKER, true)) { return false; }
167 20
        if(!$closingName = $this->match(self::TOKEN_STRING, true)) { return false; }
168 20
        if(!$this->match(self::TOKEN_CLOSE, false)) { return false; }
169
170 20
        return \in_array($closingName, $names, true) ? $closingName : false;
171
    }
172
173 51
    private function parameters()
174
    {
175 51
        $parameters = array();
176
177 51
        while(true) {
178 51
            $this->match(self::TOKEN_WS, false);
179 51
            if($this->lookahead(self::TOKEN_MARKER) || $this->lookahead(self::TOKEN_CLOSE)) { break; }
180 27
            if(!$name = $this->match(self::TOKEN_STRING, true)) { return false; }
181 26
            if(!$this->match(self::TOKEN_SEPARATOR, true)) { $parameters[$name] = null; continue; }
182 25
            if(false === ($value = $this->value())) { return false; }
183 24
            $this->match(self::TOKEN_WS, false);
184
185 24
            $parameters[$name] = $value;
186
        }
187
188 49
        return $parameters;
189
    }
190
191 27
    private function value()
192
    {
193 27
        $value = '';
194
195 27
        if($this->match(self::TOKEN_DELIMITER, false)) {
196 19 View Code Duplication
            while($this->position < $this->tokensCount && false === $this->lookahead(self::TOKEN_DELIMITER)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
197 19
                $value .= $this->match(null, false);
198
            }
199
200 19
            return $this->match(self::TOKEN_DELIMITER, false) ? $value : false;
201
        }
202
203 14
        if($tmp = $this->match(self::TOKEN_STRING, false)) {
204 13
            $value .= $tmp;
205 13
            while($tmp = $this->match(self::TOKEN_STRING, false)) {
206
                $value .= $tmp;
207
            }
208
209 13
            return $value;
210
        }
211
212 1
        return false;
213
    }
214
215
    /* --- PARSER ---------------------------------------------------------- */
216
217 56
    private function beginBacktrack()
218
    {
219 56
        $this->backtracks[] = $this->position;
220 56
        $this->lastBacktrack = $this->position;
221 56
    }
222
223 30
    private function getBacktrack()
224
    {
225 30
        $position = array_pop($this->backtracks);
226 30
        $backtrack = '';
227 30 View Code Duplication
        for($i = $position; $i < $this->position; $i++) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
228 30
            $backtrack .= $this->tokens[$i][1];
229
        }
230
231 30
        return $backtrack;
232
    }
233
234 37
    private function backtrack($modifyPosition = true)
235
    {
236 37
        $position = array_pop($this->backtracks);
237 37
        if($modifyPosition) {
238 22
            $this->position = $position;
239
        }
240
241 37
        $backtrack = '';
242 37 View Code Duplication
        for($i = $position; $i < $this->lastBacktrack; $i++) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
243 24
            $backtrack .= $this->tokens[$i][1];
244
        }
245 37
        $this->lastBacktrack = $position;
246
247 37
        return $backtrack;
248
    }
249
250 56
    private function lookahead($type)
251
    {
252 56
        return $this->position < $this->tokensCount && $this->tokens[$this->position][0] === $type;
253
    }
254
255 56
    private function match($type, $ws)
256
    {
257 56
        if($this->position >= $this->tokensCount) {
258 20
            return '';
259
        }
260
261 56
        $token = $this->tokens[$this->position];
262 56
        if(!empty($type) && $token[0] !== $type) {
263 56
            return '';
264
        }
265
266 56
        $this->position++;
267 56
        if($ws && $this->position < $this->tokensCount && $this->tokens[$this->position][0] === self::TOKEN_WS) {
268 17
            $this->position++;
269
        }
270
271 56
        return $token[1];
272
    }
273
274
    /* --- LEXER ----------------------------------------------------------- */
275
276 57
    private function tokenize($text)
277
    {
278 57
        $count = preg_match_all($this->lexerRegex, $text, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
279 57
        if(false === $count || preg_last_error() !== PREG_NO_ERROR) {
280
            throw new \RuntimeException(sprintf('PCRE failure `%s`.', preg_last_error()));
281
        }
282
283 57
        $tokens = array();
284 57
        $position = 0;
285
286 57
        foreach($matches as $match) {
287
            switch(true) {
288 56 View Code Duplication
                case -1 !== $match['string'][1]: { $token = $match['string'][0]; $type = self::TOKEN_STRING; break; }
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of -1 (integer) and $match['string'][1] (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
289 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...
290 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...
291 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...
292 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...
293 56 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...
294 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...
295
                default: { throw new \RuntimeException(sprintf('Invalid token.')); }
0 ignored issues
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

As per the PSR-2 coding standard, default statements should not be wrapped in curly braces.

switch ($expr) {
    default: { //wrong
        doSomething();
        break;
    }
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
296
            }
297 56
            $tokens[] = array($type, $token, $position);
298 56
            $position += mb_strlen($token, 'utf-8');
299
        }
300
301 57
        return $tokens;
302
    }
303
304
    private function prepareLexer(SyntaxInterface $syntax)
305
    {
306 14
        $group = function($text, $group) {
307 14
            return '(?<'.$group.'>'.preg_replace('/(.)/us', '\\\\$0', $text).')';
308 14
        };
309 14
        $quote = function($text) {
310 14
            return preg_replace('/(.)/us', '\\\\$0', $text);
311 14
        };
312
313
        $rules = array(
314 14
            '(?<string>\\\\.|(?:(?!'.implode('|', array(
315 14
                $quote($syntax->getOpeningTag()),
316 14
                $quote($syntax->getClosingTag()),
317 14
                $quote($syntax->getClosingTagMarker()),
318 14
                $quote($syntax->getParameterValueSeparator()),
319 14
                $quote($syntax->getParameterValueDelimiter()),
320 14
                '\s+',
321 14
            )).').)+)',
322 14
            '(?<ws>\s+)',
323 14
            $group($syntax->getClosingTagMarker(), 'marker'),
324 14
            $group($syntax->getParameterValueDelimiter(), 'delimiter'),
325 14
            $group($syntax->getParameterValueSeparator(), 'separator'),
326 14
            $group($syntax->getOpeningTag(), 'open'),
327 14
            $group($syntax->getClosingTag(), 'close'),
328
        );
329
330 14
        return '~('.implode('|', $rules).')~us';
331
    }
332
}
333