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 (#72)
by Tomasz
01:35
created

RegularParser::tokenize()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5.0113

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 12
cts 13
cp 0.9231
rs 9.3222
c 0
b 0
f 0
cc 5
nc 6
nop 1
crap 5.0113
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
    private $tokenMap;
24
25
    const TOKEN_OPEN = 1;
26
    const TOKEN_CLOSE = 2;
27
    const TOKEN_MARKER = 3;
28
    const TOKEN_SEPARATOR = 4;
29
    const TOKEN_DELIMITER = 5;
30
    const TOKEN_STRING = 6;
31
    const TOKEN_WS = 7;
32
33 14
    public function __construct(SyntaxInterface $syntax = null)
34
    {
35 14
        $this->lexerRegex = $this->prepareLexer($syntax ?: new CommonSyntax());
36 14
        $this->nameRegex = '~^'.RegexBuilderUtility::buildNameRegex().'$~us';
37 14
    }
38
39
    /**
40
     * @param string $text
41
     *
42
     * @return ParsedShortcode[]
43
     */
44 57
    public function parse($text)
45
    {
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
67 57
        return $shortcodes;
68
    }
69
70 48
    private function getObject($name, $parameters, $bbCode, $offset, $content, $text)
71
    {
72 48
        return new ParsedShortcode(new Shortcode($name, $parameters, $content, $bbCode), $text, $offset);
73
    }
74
75
    /* --- RULES ----------------------------------------------------------- */
76
77 56
    private function shortcode(array &$names)
78
    {
79 56
        $name = null;
0 ignored issues
show
Unused Code introduced by
$name is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
80 56
        $offset = null;
0 ignored issues
show
Unused Code introduced by
$offset is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
81
82 56
        if(!$this->match(self::TOKEN_OPEN, false)) { return false; }
83 56
        $offset = $this->tokens[$this->position - 1][2];
84 56
        $this->match(self::TOKEN_WS, false);
85 56
        if('' === $name = $this->match(self::TOKEN_STRING, false)) { return false; }
86 53
        if($this->lookahead(self::TOKEN_STRING)) { return false; }
87 53
        if(1 !== preg_match($this->nameRegex, $name, $matches)) { return false; }
88 52
        $this->match(self::TOKEN_WS, false);
89
        // bbCode
90 52
        $bbCode = $this->match(self::TOKEN_SEPARATOR, true) ? $this->value() : null;
91 52
        if(false === $bbCode) { return false; }
92
        // parameters
93 51
        if(false === ($parameters = $this->parameters())) { return false; }
94
95
        // self-closing
96 49
        if($this->match(self::TOKEN_MARKER, true)) {
97 16
            if(!$this->match(self::TOKEN_CLOSE, false)) { return false; }
98
99 15
            return array($this->getObject($name, $parameters, $bbCode, $offset, null, $this->getBacktrack()));
100
        }
101
102
        // just-closed or with-content
103 37
        if(!$this->match(self::TOKEN_CLOSE, false)) { return false; }
104 37
        $this->beginBacktrack();
105 37
        $names[] = $name;
106 37
        list($content, $shortcodes, $closingName) = $this->content($names);
107 37
        if(null !== $closingName && $closingName !== $name) {
108 6
            array_pop($names);
109 6
            array_pop($this->backtracks);
110 6
            array_pop($this->backtracks);
111
112 6
            return $closingName;
113
        }
114 37
        if(false === $content || $closingName !== $name) {
115 24
            $this->backtrack(false);
116 24
            $text = $this->backtrack(false);
117
118 24
            return array_merge(array($this->getObject($name, $parameters, $bbCode, $offset, null, $text)), $shortcodes);
119
        }
120 19
        $content = $this->getBacktrack();
121 19
        if(!$this->close($names)) { return false; }
122
123 19
        return array($this->getObject($name, $parameters, $bbCode, $offset, $content, $this->getBacktrack()));
124
    }
125
126 37
    private function content(array &$names)
127
    {
128 37
        $content = '';
129 37
        $shortcodes = array();
130 37
        $closingName = null;
131
132 37
        while($this->position < $this->tokensCount) {
133 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...
134 24
                $content .= $this->match(null, true);
135
            }
136
137 28
            $this->beginBacktrack();
138 28
            $matchedShortcodes = $this->shortcode($names);
139 28
            if(\is_string($matchedShortcodes)) {
140 6
                $closingName = $matchedShortcodes;
141 6
                break;
142
            }
143 28
            if(\is_array($matchedShortcodes)) {
144 15
                foreach($matchedShortcodes as $matchedShortcode) {
145 15
                    $shortcodes[] = $matchedShortcode;
146
                }
147 15
                continue;
148
            }
149 22
            $this->backtrack();
150
151 22
            $this->beginBacktrack();
152 22
            if(false !== ($closingName = $this->close($names))) {
153 19
                if(null === $content) { $content = ''; }
154 19
                $this->backtrack();
155 19
                $shortcodes = array();
156 19
                break;
157
            }
158 8
            $closingName = null;
159 8
            $this->backtrack();
160
161 8
            $content .= $this->match(null, false);
162
        }
163
164 37
        return array($this->position < $this->tokensCount ? $content : false, $shortcodes, $closingName);
165
    }
166
167 22
    private function close(array &$names)
168
    {
169 22
        if(!$this->match(self::TOKEN_OPEN, true)) { return false; }
170 20
        if(!$this->match(self::TOKEN_MARKER, true)) { return false; }
171 20
        if(!$closingName = $this->match(self::TOKEN_STRING, true)) { return false; }
172 20
        if(!$this->match(self::TOKEN_CLOSE, false)) { return false; }
173
174 20
        return \in_array($closingName, $names, true) ? $closingName : false;
175
    }
176
177 51
    private function parameters()
178
    {
179 51
        $parameters = array();
180
181 51
        while(true) {
182 51
            $name = null;
0 ignored issues
show
Unused Code introduced by
$name is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
183
184 51
            $this->match(self::TOKEN_WS, false);
185 51
            if($this->lookahead(self::TOKEN_MARKER) || $this->lookahead(self::TOKEN_CLOSE)) { break; }
186 27
            if(!$name = $this->match(self::TOKEN_STRING, true)) { return false; }
187 26
            if(!$this->match(self::TOKEN_SEPARATOR, true)) { $parameters[$name] = null; continue; }
188 25
            if(false === ($value = $this->value())) { return false; }
189 24
            $this->match(self::TOKEN_WS, false);
190
191 24
            $parameters[$name] = $value;
192
        }
193
194 49
        return $parameters;
195
    }
196
197 27
    private function value()
198
    {
199 27
        $value = '';
200
201 27
        if($this->match(self::TOKEN_DELIMITER, false)) {
202 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...
203 19
                $value .= $this->match(null, false);
204
            }
205
206 19
            return $this->match(self::TOKEN_DELIMITER, false) ? $value : false;
207
        }
208
209 14
        if($tmp = $this->match(self::TOKEN_STRING, false)) {
210 13
            $value .= $tmp;
211 13
            while($tmp = $this->match(self::TOKEN_STRING, false)) {
212 2
                $value .= $tmp;
213
            }
214
215 13
            return $value;
216
        }
217
218 1
        return false;
219
    }
220
221
    /* --- PARSER ---------------------------------------------------------- */
222
223 56
    private function beginBacktrack()
224
    {
225 56
        $this->backtracks[] = $this->position;
226 56
        $this->lastBacktrack = $this->position;
227 56
    }
228
229 30
    private function getBacktrack()
230
    {
231 30
        $position = array_pop($this->backtracks);
232 30
        $backtrack = '';
233 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...
234 30
            $backtrack .= $this->tokens[$i][1];
235
        }
236
237 30
        return $backtrack;
238
    }
239
240 37
    private function backtrack($modifyPosition = true)
241
    {
242 37
        $position = array_pop($this->backtracks);
243 37
        if($modifyPosition) {
244 22
            $this->position = $position;
245
        }
246
247 37
        $backtrack = '';
248 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...
249 24
            $backtrack .= $this->tokens[$i][1];
250
        }
251 37
        $this->lastBacktrack = $position;
252
253 37
        return $backtrack;
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, $ws)
262
    {
263 56
        if($this->position >= $this->tokensCount) {
264 20
            return '';
265
        }
266
267 56
        $token = $this->tokens[$this->position];
268 56
        if(!empty($type) && $token[0] !== $type) {
269 56
            return '';
270
        }
271
272 56
        $this->position++;
273 56
        if($ws && $this->position < $this->tokensCount && $this->tokens[$this->position][0] === self::TOKEN_WS) {
274 17
            $this->position++;
275
        }
276
277 56
        return $token[1];
278
    }
279
280
    /* --- LEXER ----------------------------------------------------------- */
281
282 57
    private function tokenize($text)
283
    {
284 57
        preg_match_all($this->lexerRegex, $text, $matches, PREG_OFFSET_CAPTURE);
285 57
        if(preg_last_error() !== PREG_NO_ERROR) {
286
            throw new \RuntimeException(sprintf('PCRE failure `%s`.', preg_last_error()));
287
        }
288
289 57
        $tokens = array();
290 57
        $position = 0;
291 57
        foreach($matches[0] as $match) {
292 56
            $type = isset($this->tokenMap[$match[0]])
293 56
                ? $this->tokenMap[$match[0]]
294 56
                : (ctype_space($match[0]) ? self::TOKEN_WS : self::TOKEN_STRING);
295 56
            $tokens[] = array($type, $match[0], $position);
296 56
            $position += mb_strlen($match[0], 'utf-8'); // FIXME match[1]
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
297
        }
298
299 57
        return $tokens;
300
    }
301
302 14
    private function prepareLexer(SyntaxInterface $syntax)
303
    {
304 14
        $this->tokenMap = array(
305 14
            $syntax->getOpeningTag() => self::TOKEN_OPEN,
306 14
            $syntax->getClosingTag() => self::TOKEN_CLOSE,
307 14
            $syntax->getClosingTagMarker() => self::TOKEN_MARKER,
308 14
            $syntax->getParameterValueSeparator() => self::TOKEN_SEPARATOR,
309 14
            $syntax->getParameterValueDelimiter() => self::TOKEN_DELIMITER,
310
        );
311
312 14
        $quote = function($text) {
313 14
            return preg_replace('/(.)/us', '\\\\$0', $text);
314 14
        };
315 14
        $symbols = array_map($quote, array_keys($this->tokenMap));
316
317 14
        return '~('.implode('|', $symbols).'|\s+|\\\\.|[\w-]+|.)~us';
318
    }
319
}
320