ParserInput   C
last analyzed

Complexity

Total Complexity 57

Size/Duplication

Total Lines 330
Duplicated Lines 0 %

Coupling/Cohesion

Dependencies 1

Importance

Changes 0
Metric Value
wmc 57
cbo 1
dl 0
loc 330
rs 5.1724
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A save() 0 9 1
B restore() 0 12 5
A forget() 0 4 1
A isWhiteSpace() 0 7 1
B re() 0 21 5
A char() 0 10 2
A str() 0 13 3
C quoted() 0 29 8
C skipWhitespace() 0 62 15
A peek() 0 11 3
A peekReg() 0 6 2
A peekChar() 0 4 1
A currentChar() 0 4 1
A getInput() 0 4 1
A peekNotNumeric() 0 7 4
A start() 0 8 1
A end() 0 17 2

How to fix   Complexity   

Complex Class

Complex classes like ParserInput often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ParserInput, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the ILess
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
namespace ILess\Parser;
11
12
use ILess\Exception\ParserException;
13
use stdClass;
14
15
/**
16
 * Parser input utility.
17
 */
18
final class ParserInput
19
{
20
    const CHARCODE_SPACE = 32;
21
    const CHARCODE_TAB = 9;
22
    const CHARCODE_LF = 10;
23
    const CHARCODE_CR = 13;
24
    const CHARCODE_PLUS = 43;
25
    const CHARCODE_COMMA = 44;
26
    const CHARCODE_FORWARD_SLASH = 47;
27
    const CHARCODE_9 = 57;
28
29
    /**
30
     * Current index.
31
     *
32
     * @var int
33
     */
34
    public $i = 0;
35
36
    /**
37
     * LeSS input string.
38
     *
39
     * @var string
40
     */
41
    private $input;
42
43
    /**
44
     * Current chunk.
45
     *
46
     * @var
47
     */
48
    private $j = 0;
49
    private $saveStack = [];
50
    private $furthest;
51
    private $furthestPossibleErrorMessage;
52
    private $chunks;
53
    private $current;
54
    private $currentPos;
55
56
    /**
57
     * @var bool
58
     */
59
    public $autoCommentAbsorb = true;
60
61
    /**
62
     * @var array
63
     */
64
    public $commentStore = [];
65
66
    /**
67
     * @var bool
68
     */
69
    public $finished = false;
70
71
    /**
72
     * Constructor.
73
     */
74
    public function __construct()
75
    {
76
    }
77
78
    public function save()
79
    {
80
        $this->currentPos = $this->i;
81
        array_push($this->saveStack, (object) [
82
            'current' => $this->current,
83
            'i' => $this->i,
84
            'j' => $this->j,
85
        ]);
86
    }
87
88
    /**
89
     * @param null $possibleErrorMessage
90
     */
91
    public function restore($possibleErrorMessage = null)
92
    {
93
        if ($this->i > $this->furthest || ($this->i === $this->furthest && $possibleErrorMessage && !$this->furthestPossibleErrorMessage)) {
94
            $this->furthest = $this->i;
95
            $this->furthestPossibleErrorMessage = $possibleErrorMessage;
96
        }
97
98
        $state = array_pop($this->saveStack);
99
        $this->current = $state->current;
100
        $this->currentPos = $this->i = $state->i;
101
        $this->j = $state->i;
102
    }
103
104
    public function forget()
105
    {
106
        array_pop($this->saveStack);
107
    }
108
109
    /**
110
     * @param $offset
111
     *
112
     * @return bool
113
     */
114
    public function isWhiteSpace($offset = 0)
115
    {
116
        $pos = $this->i + ($offset);
117
118
        // return preg_match('/\s/', @$this->input[$pos]);
119
        return ctype_space(@$this->input[$pos]);
120
    }
121
122
    public function re($tok)
123
    {
124
        if ($this->i > $this->currentPos) {
125
            $this->current = substr($this->current, $this->i - $this->currentPos);
126
            $this->currentPos = $this->i;
127
        }
128
129
        $m = preg_match($tok, $this->current, $matches);
130
131
        if (preg_last_error() !== PREG_NO_ERROR) {
132
            throw new ParserException("Error in processing expression $tok!");
133
        }
134
135
        if (!$m) {
136
            return;
137
        }
138
139
        $this->skipWhitespace(strlen($matches[0]));
140
141
        return count($matches) === 1 ? $matches[0] : $matches;
142
    }
143
144
    public function char($tok)
145
    {
146
        if (@$this->input[$this->i] !== $tok) {
147
            return;
148
        }
149
150
        $this->skipWhitespace(1);
151
152
        return $tok;
153
    }
154
155
    public function str($tok)
156
    {
157
        $tokLength = strlen($tok);
158
        for ($i = 0; $i < $tokLength; ++$i) {
159
            if (@$this->input[$this->i + $i] !== $tok[$i]) {
160
                return;
161
            }
162
        }
163
164
        $this->skipWhitespace($tokLength);
165
166
        return $tok;
167
    }
168
169
    /**
170
     * @return null|string
171
     */
172
    public function quoted()
173
    {
174
        $startChar = $this->input[$this->i];
175
176
        if ($startChar !== "'" && $startChar !== '"') {
177
            return;
178
        }
179
180
        $length = strlen($this->input);
181
        $currentPosition = $this->i;
182
        for ($i = 1; $i + $currentPosition < $length; ++$i) {
183
            $nextChar = $this->input[$i + $currentPosition];
184
            switch ($nextChar) {
185
                case '\\':
186
                    $i++;
187
                    continue;
188
                case "\r":
189
                case "\n":
190
                    break;
191
                case $startChar:
192
                    $str = substr($this->input, $currentPosition, $i + 1);
193
                    $this->skipWhitespace($i + 1);
194
195
                    return $str;
196
            }
197
        }
198
199
        return;
200
    }
201
202
    private function skipWhitespace($length)
203
    {
204
        $oldi = $this->i;
205
        $oldj = $this->j;
206
        $curr = $this->i - $this->currentPos;
207
        $endIndex = $this->i + strlen($this->current) - $curr;
208
        $mem = ($this->i += $length);
209
        $inp = $this->input;
210
211
        for (; $this->i < $endIndex; ++$this->i) {
212
            $c = ord($inp[$this->i]);
213
            if ($this->autoCommentAbsorb && $c === self::CHARCODE_FORWARD_SLASH) {
214
                $nextChar = $inp[$this->i + 1];
215
                if ($nextChar === '/') {
216
                    $comment = [
217
                        'index' => $this->i,
218
                        'isLineComment' => true,
219
                    ];
220
                    $nextNewLine = strpos($inp, "\n", $this->i + 2);
221
                    if ($nextNewLine === false) {
222
                        $nextNewLine = $endIndex;
223
                    }
224
                    $this->i = $nextNewLine;
225
                    $comment['text'] = substr($inp, $comment['index'], $this->i - $comment['index']);
226
                    $this->commentStore[] = $comment;
227
228
                    continue;
229
                } elseif ($nextChar === '*') {
230
                    $nextStarSlash = strpos($inp, '*/', $this->i + 2);
231
                    if ($nextStarSlash >= 0) {
232
                        $comment = [
233
                            'index' => $this->i,
234
                            'text' => substr($inp, $this->i, $nextStarSlash + 2 - $this->i),
235
                        ];
236
                        $this->i += strlen($comment['text']) - 1;
237
                        $this->commentStore[] = $comment;
238
                        continue;
239
                    }
240
                }
241
                break;
242
            }
243
244
            if ($c !== self::CHARCODE_SPACE && ($c !== self::CHARCODE_LF) && ($c !== self::CHARCODE_TAB) && ($c !== self::CHARCODE_CR)) {
245
                break;
246
            }
247
        }
248
249
        $this->current = substr($this->current, $length + $this->i - $mem + $curr);
250
        $this->currentPos = $this->i;
251
252
        if (!$this->current) {
253
            if ($this->j < count($this->chunks) - 1) {
254
                $this->current = $this->chunks[++$this->j];
255
                $this->skipWhitespace(0);
256
257
                return true;
258
            }
259
            $this->finished = true;
260
        }
261
262
        return $oldi !== $this->i || $oldj !== $this->j;
263
    }
264
265
    /**
266
     * Original less.js function handles string and regexp here, I have to create
267
     * additional method for regexp, see `peekReg`.
268
     *
269
     * @param $tok
270
     *
271
     * @return bool
272
     */
273
    public function peek($tok)
274
    {
275
        $tokLength = strlen($tok);
276
        for ($i = 0; $i < $tokLength; ++$i) {
277
            if (@$this->input[$this->i + $i] !== $tok[$i]) {
278
                return false;
279
            }
280
        }
281
282
        return true;
283
    }
284
285
    public function peekReg($regexp)
286
    {
287
        if (preg_match($regexp, $this->current, $matches)) {
288
            return $matches;
289
        }
290
    }
291
292
    public function peekChar($tok)
293
    {
294
        return $this->input[$this->i] === $tok;
295
    }
296
297
    public function currentChar()
298
    {
299
        return @$this->input[$this->i];
300
    }
301
302
    public function getInput()
303
    {
304
        return $this->input;
305
    }
306
307
    public function peekNotNumeric()
308
    {
309
        $c = ord($this->input[$this->i]);
310
311
        // Is the first char of the dimension 0-9, '.', '+' or '-'
312
        return ($c > self::CHARCODE_9 || $c < self::CHARCODE_PLUS) || $c === self::CHARCODE_FORWARD_SLASH || $c === self::CHARCODE_COMMA;
313
    }
314
315
    /**
316
     * @param $string
317
     */
318
    public function start($string)
319
    {
320
        $this->input = $string;
321
        $this->i = $this->j = $this->currentPos = $this->furthest = 0;
322
        $this->chunks = [$string];
323
        $this->current = $this->chunks[0];
324
        $this->skipWhitespace(0);
325
    }
326
327
    /**
328
     * @return stdClass
329
     */
330
    public function end()
331
    {
332
        $isFinished = $this->i >= strlen($this->input);
333
        $message = null;
334
        if ($this->i < $this->furthest) {
335
            $message = $this->furthestPossibleErrorMessage;
336
            $this->i = $this->furthest;
337
        }
338
339
        return (object) [
340
            'isFinished' => $isFinished,
341
            'furthest' => $this->i,
342
            'furthestPossibleErrorMessage' => $message,
343
            'furthestReachedEnd' => $this->i >= strlen($this->input) - 1,
344
            'furthestChar' => @$this->input[$this->i],
345
        ];
346
    }
347
}
348