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 ( a69988...dd33d8 )
by Tomasz
12s
created

RegularParser::value()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 23
Code Lines 12

Duplication

Lines 3
Ratio 13.04 %

Code Coverage

Tests 11
CRAP Score 7.0283

Importance

Changes 0
Metric Value
dl 3
loc 23
ccs 11
cts 12
cp 0.9167
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 12
nc 7
nop 0
crap 7.0283
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 57
    public function parse($text)
41
    {
42 57
        $this->tokens = $this->tokenize($text);
43 57
        $this->backtracks = array();
44 57
        $this->position = 0;
45 57
        $this->tokensCount = count($this->tokens);
46
47 57
        $shortcodes = array();
48 57
        while($this->position < $this->tokensCount) {
49 56
            while($this->position < $this->tokensCount && false === $this->lookahead(self::TOKEN_OPEN)) {
50 24
                $this->position++;
51 24
            }
52 56
            $names = array();
53 56
            $this->beginBacktrack();
54 56
            $matches = $this->shortcode($names);
55 56
            if(is_array($matches)) {
56 48
                foreach($matches as $shortcode) {
57 48
                    $shortcodes[] = $shortcode;
58 48
                }
59 48
            }
60 56
        }
61
62 57
        return $shortcodes;
63
    }
64
65 48
    private function getObject($name, $parameters, $bbCode, $offset, $content, $text)
66
    {
67 48
        return new ParsedShortcode(new Shortcode($name, $parameters, $content, $bbCode), $text, $offset);
68
    }
69
70
    /* --- RULES ----------------------------------------------------------- */
71
72 56
    private function shortcode(array &$names)
73
    {
74 56
        $name = null;
75 56
        $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 56
        if(!$this->match(self::TOKEN_OPEN, $setOffset, true)) { return false; }
81 56
        if(!$this->match(self::TOKEN_STRING, $setName, false)) { return false; }
82 53
        if($this->lookahead(self::TOKEN_STRING)) { return false; }
83 53
        if(!preg_match_all('~^'.RegexBuilderUtility::buildNameRegex().'$~us', $name, $matches)) { return false; }
84 52
        $this->match(self::TOKEN_WS);
85 52
        if(false === ($bbCode = $this->bbCode())) { return false; }
86 51
        if(false === ($parameters = $this->parameters())) { return false; }
87
88
        // self-closing
89 49
        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 37
        if(!$this->match(self::TOKEN_CLOSE)) { return false; }
97 37
        $this->beginBacktrack();
98 37
        $names[] = $name;
99 37
        list($content, $shortcodes, $closingName) = $this->content($names);
100 37
        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 37
        if(false === $content || $closingName !== $name) {
108 24
            $this->backtrack(false);
109 24
            $text = $this->backtrack(false);
110
111 24
            return array_merge(array($this->getObject($name, $parameters, $bbCode, $offset, null, $text)), $shortcodes);
112
        }
113 19
        $content = $this->getBacktrack();
114 19
        if(!$this->close($names)) { return false; }
115
116 19
        return array($this->getObject($name, $parameters, $bbCode, $offset, $content, $this->getBacktrack()));
117
    }
118
119 37
    private function content(array &$names)
120
    {
121 37
        $content = null;
122 37
        $shortcodes = array();
123 37
        $closingName = null;
124
        $appendContent = function(array $token) use(&$content) { $content .= $token[1]; };
125
126 37
        while($this->position < $this->tokensCount) {
127 28 View Code Duplication
            while($this->position < $this->tokensCount && false === $this->lookahead(self::TOKEN_OPEN)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
128 24
                $this->match(null, $appendContent, true);
129 24
            }
130
131 28
            $this->beginBacktrack();
132 28
            $matchedShortcodes = $this->shortcode($names);
133 28
            if(is_string($matchedShortcodes)) {
134 6
                $closingName = $matchedShortcodes;
135 6
                break;
136
            }
137 28
            if(is_array($matchedShortcodes)) {
138 15
                foreach($matchedShortcodes as $matchedShortcode) {
139 15
                    $shortcodes[] = $matchedShortcode;
140 15
                }
141 15
                continue;
142
            }
143 22
            $this->backtrack();
144
145 22
            $this->beginBacktrack();
146 22
            if(false !== ($closingName = $this->close($names))) {
147 19
                if(null === $content) { $content = ''; }
148 19
                $this->backtrack();
149 19
                $shortcodes = array();
150 19
                break;
151
            }
152 8
            $closingName = null;
153 8
            $this->backtrack();
154
155 8
            $this->match(null, $appendContent);
156 8
        }
157
158 37
        return array($this->position < $this->tokensCount ? $content : false, $shortcodes, $closingName);
159
    }
160
161 22
    private function close(array &$names)
162
    {
163 22
        $closingName = null;
164
        $setName = function(array $token) use(&$closingName) { $closingName = $token[1]; };
165
166 22
        if(!$this->match(self::TOKEN_OPEN, null, true)) { return false; }
167 20
        if(!$this->match(self::TOKEN_MARKER, null, true)) { return false; }
168 20
        if(!$this->match(self::TOKEN_STRING, $setName, true)) { return false; }
169 20
        if(!$this->match(self::TOKEN_CLOSE)) { return false; }
170
171 20
        return in_array($closingName, $names, true) ? $closingName : false;
172
    }
173
174 52
    private function bbCode()
175
    {
176 52
        return $this->match(self::TOKEN_SEPARATOR, null, true) ? $this->value() : null;
177
    }
178
179 51
    private function parameters()
180
    {
181 51
        $parameters = array();
182
        $setName = function(array $token) use(&$name) { $name = $token[1]; };
183
184 51
        while(true) {
185 51
            $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 51
            $this->match(self::TOKEN_WS);
188 51
            if($this->lookahead(self::TOKEN_MARKER) || $this->lookahead(self::TOKEN_CLOSE)) { break; }
189 27
            if(!$this->match(self::TOKEN_STRING, $setName, true)) { return false; }
190 26
            if(!$this->match(self::TOKEN_SEPARATOR, null, true)) { $parameters[$name] = null; continue; }
191 25
            if(false === ($value = $this->value())) { return false; }
192 24
            $this->match(self::TOKEN_WS);
193
194 24
            $parameters[$name] = $value;
195 24
        }
196
197 49
        return $parameters;
198
    }
199
200 27
    private function value()
201
    {
202 27
        $value = '';
203
        $appendValue = function(array $token) use(&$value) { $value .= $token[1]; };
204
205 27
        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 19
            }
209
210 19
            return $this->match(self::TOKEN_DELIMITER) ? $value : false;
211
        }
212
213 14
        if($this->match(self::TOKEN_STRING, $appendValue)) {
214 13
            while($this->match(self::TOKEN_STRING, $appendValue)) {
215
                continue;
216
            }
217
218 13
            return $value;
219
        }
220
221 1
        return false;
222
    }
223
224
    /* --- PARSER ---------------------------------------------------------- */
225
226 56
    private function beginBacktrack()
227
    {
228 56
        $this->backtracks[] = array();
229 56
    }
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 37
    private function backtrack($modifyPosition = true)
238
    {
239 37
        $tokens = array_pop($this->backtracks);
240 37
        $count = count($tokens);
241 37
        if($modifyPosition) {
242 22
            $this->position -= $count;
243 22
        }
244
245 37
        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 37
            for($i = 0; $i < $count; $i++) {
249 28
                array_pop($backtrack);
250 28
            }
251 37
        }
252
253
        return implode('', array_map(function(array $token) { return $token[1]; }, $tokens));
254
    }
255
256 56
    private function lookahead($type)
257
    {
258 56
        return $this->position < $this->tokensCount && (empty($type) || $this->tokens[$this->position][0] === $type);
259
    }
260
261 56
    private function match($type, $callback = null, $ws = false)
262
    {
263 56
        if($this->position >= $this->tokensCount) {
264 20
            return false;
265
        }
266
267 56
        $token = $this->tokens[$this->position];
268 56
        if(!empty($type) && $token[0] !== $type) {
269 53
            return false;
270
        }
271 56
        foreach($this->backtracks as &$backtrack) {
272 56
            $backtrack[] = $token;
273 56
        }
274 56
        unset($backtrack);
275
276 56
        $callback && $callback($token);
277 56
        $this->position++;
278
279 56
        if($ws && $this->position < $this->tokensCount && $this->tokens[$this->position][0] === self::TOKEN_WS) {
280 17
            $token = $this->tokens[$this->position];
281 17
            $this->position++;
282 17
            foreach($this->backtracks as &$backtrack) {
283 17
                $backtrack[] = $token;
284 17
            }
285 17
        }
286
287 56
        return true;
288
    }
289
290
    /* --- LEXER ----------------------------------------------------------- */
291
292 57
    private function tokenize($text)
293
    {
294 57
        preg_match_all($this->lexerRegex, $text, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
295 57
        $tokens = array();
296 57
        $position = 0;
297
298 57
        foreach($matches as $match) {
299 56
            switch(true) {
300 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...
301 56 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 55 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 55 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 55 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 55 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 55
                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 55
            }
308 56
            $tokens[] = array($type, $token, $position);
309 56
            $position += mb_strlen($token, 'utf-8');
310 57
        }
311
312 57
        return $tokens;
313
    }
314
315 14
    private function getTokenizerRegex(SyntaxInterface $syntax)
316
    {
317
        $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 14
        );
340
341 14
        return '~('.implode('|', $rules).')~us';
342
    }
343
}
344