Passed
Push — master ( d4a329...711fef )
by Jeroen De
03:36
created
SCSS/vendor/scssphp/scssphp/scss.inc.php 2 patches
Indentation   +29 added lines, -29 removed lines patch added patch discarded remove patch
@@ -1,36 +1,36 @@
 block discarded – undo
1 1
 <?php
2 2
 
3 3
 if (version_compare(PHP_VERSION, '5.6') < 0) {
4
-    throw new \Exception('scssphp requires PHP 5.6 or above');
4
+	throw new \Exception('scssphp requires PHP 5.6 or above');
5 5
 }
6 6
 
7 7
 if (! class_exists('ScssPhp\ScssPhp\Version', false)) {
8
-    include_once __DIR__ . '/src/Base/Range.php';
9
-    include_once __DIR__ . '/src/Block.php';
10
-    include_once __DIR__ . '/src/Cache.php';
11
-    include_once __DIR__ . '/src/Colors.php';
12
-    include_once __DIR__ . '/src/Compiler.php';
13
-    include_once __DIR__ . '/src/Compiler/Environment.php';
14
-    include_once __DIR__ . '/src/Exception/SassException.php';
15
-    include_once __DIR__ . '/src/Exception/CompilerException.php';
16
-    include_once __DIR__ . '/src/Exception/ParserException.php';
17
-    include_once __DIR__ . '/src/Exception/RangeException.php';
18
-    include_once __DIR__ . '/src/Exception/ServerException.php';
19
-    include_once __DIR__ . '/src/Formatter.php';
20
-    include_once __DIR__ . '/src/Formatter/Compact.php';
21
-    include_once __DIR__ . '/src/Formatter/Compressed.php';
22
-    include_once __DIR__ . '/src/Formatter/Crunched.php';
23
-    include_once __DIR__ . '/src/Formatter/Debug.php';
24
-    include_once __DIR__ . '/src/Formatter/Expanded.php';
25
-    include_once __DIR__ . '/src/Formatter/Nested.php';
26
-    include_once __DIR__ . '/src/Formatter/OutputBlock.php';
27
-    include_once __DIR__ . '/src/Node.php';
28
-    include_once __DIR__ . '/src/Node/Number.php';
29
-    include_once __DIR__ . '/src/Parser.php';
30
-    include_once __DIR__ . '/src/SourceMap/Base64.php';
31
-    include_once __DIR__ . '/src/SourceMap/Base64VLQ.php';
32
-    include_once __DIR__ . '/src/SourceMap/SourceMapGenerator.php';
33
-    include_once __DIR__ . '/src/Type.php';
34
-    include_once __DIR__ . '/src/Util.php';
35
-    include_once __DIR__ . '/src/Version.php';
8
+	include_once __DIR__ . '/src/Base/Range.php';
9
+	include_once __DIR__ . '/src/Block.php';
10
+	include_once __DIR__ . '/src/Cache.php';
11
+	include_once __DIR__ . '/src/Colors.php';
12
+	include_once __DIR__ . '/src/Compiler.php';
13
+	include_once __DIR__ . '/src/Compiler/Environment.php';
14
+	include_once __DIR__ . '/src/Exception/SassException.php';
15
+	include_once __DIR__ . '/src/Exception/CompilerException.php';
16
+	include_once __DIR__ . '/src/Exception/ParserException.php';
17
+	include_once __DIR__ . '/src/Exception/RangeException.php';
18
+	include_once __DIR__ . '/src/Exception/ServerException.php';
19
+	include_once __DIR__ . '/src/Formatter.php';
20
+	include_once __DIR__ . '/src/Formatter/Compact.php';
21
+	include_once __DIR__ . '/src/Formatter/Compressed.php';
22
+	include_once __DIR__ . '/src/Formatter/Crunched.php';
23
+	include_once __DIR__ . '/src/Formatter/Debug.php';
24
+	include_once __DIR__ . '/src/Formatter/Expanded.php';
25
+	include_once __DIR__ . '/src/Formatter/Nested.php';
26
+	include_once __DIR__ . '/src/Formatter/OutputBlock.php';
27
+	include_once __DIR__ . '/src/Node.php';
28
+	include_once __DIR__ . '/src/Node/Number.php';
29
+	include_once __DIR__ . '/src/Parser.php';
30
+	include_once __DIR__ . '/src/SourceMap/Base64.php';
31
+	include_once __DIR__ . '/src/SourceMap/Base64VLQ.php';
32
+	include_once __DIR__ . '/src/SourceMap/SourceMapGenerator.php';
33
+	include_once __DIR__ . '/src/Type.php';
34
+	include_once __DIR__ . '/src/Util.php';
35
+	include_once __DIR__ . '/src/Version.php';
36 36
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -4,7 +4,7 @@
 block discarded – undo
4 4
     throw new \Exception('scssphp requires PHP 5.6 or above');
5 5
 }
6 6
 
7
-if (! class_exists('ScssPhp\ScssPhp\Version', false)) {
7
+if (!class_exists('ScssPhp\ScssPhp\Version', false)) {
8 8
     include_once __DIR__ . '/src/Base/Range.php';
9 9
     include_once __DIR__ . '/src/Block.php';
10 10
     include_once __DIR__ . '/src/Cache.php';
Please login to merge, or discard this patch.
SCSS/vendor/scssphp/scssphp/src/Block.php 1 patch
Indentation   +40 added lines, -40 removed lines patch added patch discarded remove patch
@@ -19,53 +19,53 @@
 block discarded – undo
19 19
  */
20 20
 class Block
21 21
 {
22
-    /**
23
-     * @var string
24
-     */
25
-    public $type;
22
+	/**
23
+	 * @var string
24
+	 */
25
+	public $type;
26 26
 
27
-    /**
28
-     * @var \ScssPhp\ScssPhp\Block
29
-     */
30
-    public $parent;
27
+	/**
28
+	 * @var \ScssPhp\ScssPhp\Block
29
+	 */
30
+	public $parent;
31 31
 
32
-    /**
33
-     * @var string
34
-     */
35
-    public $sourceName;
32
+	/**
33
+	 * @var string
34
+	 */
35
+	public $sourceName;
36 36
 
37
-    /**
38
-     * @var integer
39
-     */
40
-    public $sourceIndex;
37
+	/**
38
+	 * @var integer
39
+	 */
40
+	public $sourceIndex;
41 41
 
42
-    /**
43
-     * @var integer
44
-     */
45
-    public $sourceLine;
42
+	/**
43
+	 * @var integer
44
+	 */
45
+	public $sourceLine;
46 46
 
47
-    /**
48
-     * @var integer
49
-     */
50
-    public $sourceColumn;
47
+	/**
48
+	 * @var integer
49
+	 */
50
+	public $sourceColumn;
51 51
 
52
-    /**
53
-     * @var array
54
-     */
55
-    public $selectors;
52
+	/**
53
+	 * @var array
54
+	 */
55
+	public $selectors;
56 56
 
57
-    /**
58
-     * @var array
59
-     */
60
-    public $comments;
57
+	/**
58
+	 * @var array
59
+	 */
60
+	public $comments;
61 61
 
62
-    /**
63
-     * @var array
64
-     */
65
-    public $children;
62
+	/**
63
+	 * @var array
64
+	 */
65
+	public $children;
66 66
 
67
-    /**
68
-     * @var \ScssPhp\ScssPhp\Block
69
-     */
70
-    public $selfParent;
67
+	/**
68
+	 * @var \ScssPhp\ScssPhp\Block
69
+	 */
70
+	public $selfParent;
71 71
 }
Please login to merge, or discard this patch.
SCSS/vendor/scssphp/scssphp/src/Base/Range.php 1 patch
Indentation   +24 added lines, -24 removed lines patch added patch discarded remove patch
@@ -19,30 +19,30 @@
 block discarded – undo
19 19
  */
20 20
 class Range
21 21
 {
22
-    public $first;
23
-    public $last;
22
+	public $first;
23
+	public $last;
24 24
 
25
-    /**
26
-     * Initialize range
27
-     *
28
-     * @param integer|float $first
29
-     * @param integer|float $last
30
-     */
31
-    public function __construct($first, $last)
32
-    {
33
-        $this->first = $first;
34
-        $this->last = $last;
35
-    }
25
+	/**
26
+	 * Initialize range
27
+	 *
28
+	 * @param integer|float $first
29
+	 * @param integer|float $last
30
+	 */
31
+	public function __construct($first, $last)
32
+	{
33
+		$this->first = $first;
34
+		$this->last = $last;
35
+	}
36 36
 
37
-    /**
38
-     * Test for inclusion in range
39
-     *
40
-     * @param integer|float $value
41
-     *
42
-     * @return boolean
43
-     */
44
-    public function includes($value)
45
-    {
46
-        return $value >= $this->first && $value <= $this->last;
47
-    }
37
+	/**
38
+	 * Test for inclusion in range
39
+	 *
40
+	 * @param integer|float $value
41
+	 *
42
+	 * @return boolean
43
+	 */
44
+	public function includes($value)
45
+	{
46
+		return $value >= $this->first && $value <= $this->last;
47
+	}
48 48
 }
Please login to merge, or discard this patch.
SCSS/vendor/scssphp/scssphp/src/Compiler/Environment.php 1 patch
Indentation   +20 added lines, -20 removed lines patch added patch discarded remove patch
@@ -19,28 +19,28 @@
 block discarded – undo
19 19
  */
20 20
 class Environment
21 21
 {
22
-    /**
23
-     * @var \ScssPhp\ScssPhp\Block
24
-     */
25
-    public $block;
22
+	/**
23
+	 * @var \ScssPhp\ScssPhp\Block
24
+	 */
25
+	public $block;
26 26
 
27
-    /**
28
-     * @var \ScssPhp\ScssPhp\Compiler\Environment
29
-     */
30
-    public $parent;
27
+	/**
28
+	 * @var \ScssPhp\ScssPhp\Compiler\Environment
29
+	 */
30
+	public $parent;
31 31
 
32
-    /**
33
-     * @var array
34
-     */
35
-    public $store;
32
+	/**
33
+	 * @var array
34
+	 */
35
+	public $store;
36 36
 
37
-    /**
38
-     * @var array
39
-     */
40
-    public $storeUnreduced;
37
+	/**
38
+	 * @var array
39
+	 */
40
+	public $storeUnreduced;
41 41
 
42
-    /**
43
-     * @var integer
44
-     */
45
-    public $depth;
42
+	/**
43
+	 * @var integer
44
+	 */
45
+	public $depth;
46 46
 }
Please login to merge, or discard this patch.
SCSS/vendor/scssphp/scssphp/src/Parser.php 2 patches
Indentation   +3585 added lines, -3585 removed lines patch added patch discarded remove patch
@@ -26,3633 +26,3633 @@
 block discarded – undo
26 26
  */
27 27
 class Parser
28 28
 {
29
-    const SOURCE_INDEX  = -1;
30
-    const SOURCE_LINE   = -2;
31
-    const SOURCE_COLUMN = -3;
32
-
33
-    /**
34
-     * @var array
35
-     */
36
-    protected static $precedence = [
37
-        '='   => 0,
38
-        'or'  => 1,
39
-        'and' => 2,
40
-        '=='  => 3,
41
-        '!='  => 3,
42
-        '<=>' => 3,
43
-        '<='  => 4,
44
-        '>='  => 4,
45
-        '<'   => 4,
46
-        '>'   => 4,
47
-        '+'   => 5,
48
-        '-'   => 5,
49
-        '*'   => 6,
50
-        '/'   => 6,
51
-        '%'   => 6,
52
-    ];
53
-
54
-    protected static $commentPattern;
55
-    protected static $operatorPattern;
56
-    protected static $whitePattern;
57
-
58
-    protected $cache;
59
-
60
-    private $sourceName;
61
-    private $sourceIndex;
62
-    private $sourcePositions;
63
-    private $charset;
64
-    private $count;
65
-    private $env;
66
-    private $inParens;
67
-    private $eatWhiteDefault;
68
-    private $discardComments;
69
-    private $allowVars;
70
-    private $buffer;
71
-    private $utf8;
72
-    private $encoding;
73
-    private $patternModifiers;
74
-    private $commentsSeen;
75
-
76
-    private $cssOnly;
77
-
78
-    /**
79
-     * Constructor
80
-     *
81
-     * @api
82
-     *
83
-     * @param string                 $sourceName
84
-     * @param integer                $sourceIndex
85
-     * @param string                 $encoding
86
-     * @param \ScssPhp\ScssPhp\Cache $cache
87
-     */
88
-    public function __construct($sourceName, $sourceIndex = 0, $encoding = 'utf-8', $cache = null, $cssOnly = false)
89
-    {
90
-        $this->sourceName       = $sourceName ?: '(stdin)';
91
-        $this->sourceIndex      = $sourceIndex;
92
-        $this->charset          = null;
93
-        $this->utf8             = ! $encoding || strtolower($encoding) === 'utf-8';
94
-        $this->patternModifiers = $this->utf8 ? 'Aisu' : 'Ais';
95
-        $this->commentsSeen     = [];
96
-        $this->commentsSeen     = [];
97
-        $this->allowVars        = true;
98
-        $this->cssOnly          = $cssOnly;
99
-
100
-        if (empty(static::$operatorPattern)) {
101
-            static::$operatorPattern = '([*\/%+-]|[!=]\=|\>\=?|\<\=\>|\<\=?|and|or)';
102
-
103
-            $commentSingle      = '\/\/';
104
-            $commentMultiLeft   = '\/\*';
105
-            $commentMultiRight  = '\*\/';
106
-
107
-            static::$commentPattern = $commentMultiLeft . '.*?' . $commentMultiRight;
108
-            static::$whitePattern = $this->utf8
109
-                ? '/' . $commentSingle . '[^\n]*\s*|(' . static::$commentPattern . ')\s*|\s+/AisuS'
110
-                : '/' . $commentSingle . '[^\n]*\s*|(' . static::$commentPattern . ')\s*|\s+/AisS';
111
-        }
112
-
113
-        if ($cache) {
114
-            $this->cache = $cache;
115
-        }
116
-    }
117
-
118
-    /**
119
-     * Get source file name
120
-     *
121
-     * @api
122
-     *
123
-     * @return string
124
-     */
125
-    public function getSourceName()
126
-    {
127
-        return $this->sourceName;
128
-    }
129
-
130
-    /**
131
-     * Throw parser error
132
-     *
133
-     * @api
134
-     *
135
-     * @param string $msg
136
-     *
137
-     * @throws \ScssPhp\ScssPhp\Exception\ParserException
138
-     */
139
-    public function throwParseError($msg = 'parse error')
140
-    {
141
-        list($line, $column) = $this->getSourcePosition($this->count);
142
-
143
-        $loc = empty($this->sourceName)
144
-             ? "line: $line, column: $column"
145
-             : "$this->sourceName on line $line, at column $column";
146
-
147
-        if ($this->peek('(.*?)(\n|$)', $m, $this->count)) {
148
-            $this->restoreEncoding();
149
-
150
-            $e = new ParserException("$msg: failed at `$m[1]` $loc");
151
-            $e->setSourcePosition([$this->sourceName, $line, $column]);
152
-
153
-            throw $e;
154
-        }
155
-
156
-        $this->restoreEncoding();
157
-
158
-        $e = new ParserException("$msg: $loc");
159
-        $e->setSourcePosition([$this->sourceName, $line, $column]);
160
-
161
-        throw $e;
162
-    }
163
-
164
-    /**
165
-     * Parser buffer
166
-     *
167
-     * @api
168
-     *
169
-     * @param string $buffer
170
-     *
171
-     * @return \ScssPhp\ScssPhp\Block
172
-     */
173
-    public function parse($buffer)
174
-    {
175
-        if ($this->cache) {
176
-            $cacheKey = $this->sourceName . ':' . md5($buffer);
177
-            $parseOptions = [
178
-                'charset' => $this->charset,
179
-                'utf8' => $this->utf8,
180
-            ];
181
-            $v = $this->cache->getCache('parse', $cacheKey, $parseOptions);
182
-
183
-            if (! \is_null($v)) {
184
-                return $v;
185
-            }
186
-        }
187
-
188
-        // strip BOM (byte order marker)
189
-        if (substr($buffer, 0, 3) === "\xef\xbb\xbf") {
190
-            $buffer = substr($buffer, 3);
191
-        }
192
-
193
-        $this->buffer          = rtrim($buffer, "\x00..\x1f");
194
-        $this->count           = 0;
195
-        $this->env             = null;
196
-        $this->inParens        = false;
197
-        $this->eatWhiteDefault = true;
198
-
199
-        $this->saveEncoding();
200
-        $this->extractLineNumbers($buffer);
201
-
202
-        $this->pushBlock(null); // root block
203
-        $this->whitespace();
204
-        $this->pushBlock(null);
205
-        $this->popBlock();
206
-
207
-        while ($this->parseChunk()) {
208
-            ;
209
-        }
210
-
211
-        if ($this->count !== \strlen($this->buffer)) {
212
-            $this->throwParseError();
213
-        }
214
-
215
-        if (! empty($this->env->parent)) {
216
-            $this->throwParseError('unclosed block');
217
-        }
218
-
219
-        if ($this->charset) {
220
-            array_unshift($this->env->children, $this->charset);
221
-        }
222
-
223
-        $this->restoreEncoding();
224
-
225
-        if ($this->cache) {
226
-            $this->cache->setCache('parse', $cacheKey, $this->env, $parseOptions);
227
-        }
228
-
229
-        return $this->env;
230
-    }
231
-
232
-    /**
233
-     * Parse a value or value list
234
-     *
235
-     * @api
236
-     *
237
-     * @param string       $buffer
238
-     * @param string|array $out
239
-     *
240
-     * @return boolean
241
-     */
242
-    public function parseValue($buffer, &$out)
243
-    {
244
-        $this->count           = 0;
245
-        $this->env             = null;
246
-        $this->inParens        = false;
247
-        $this->eatWhiteDefault = true;
248
-        $this->buffer          = (string) $buffer;
249
-
250
-        $this->saveEncoding();
251
-
252
-        $list = $this->valueList($out);
253
-
254
-        $this->restoreEncoding();
255
-
256
-        return $list;
257
-    }
258
-
259
-    /**
260
-     * Parse a selector or selector list
261
-     *
262
-     * @api
263
-     *
264
-     * @param string       $buffer
265
-     * @param string|array $out
266
-     *
267
-     * @return boolean
268
-     */
269
-    public function parseSelector($buffer, &$out)
270
-    {
271
-        $this->count           = 0;
272
-        $this->env             = null;
273
-        $this->inParens        = false;
274
-        $this->eatWhiteDefault = true;
275
-        $this->buffer          = (string) $buffer;
276
-
277
-        $this->saveEncoding();
278
-
279
-        $selector = $this->selectors($out);
280
-
281
-        $this->restoreEncoding();
282
-
283
-        return $selector;
284
-    }
285
-
286
-    /**
287
-     * Parse a media Query
288
-     *
289
-     * @api
290
-     *
291
-     * @param string       $buffer
292
-     * @param string|array $out
293
-     *
294
-     * @return boolean
295
-     */
296
-    public function parseMediaQueryList($buffer, &$out)
297
-    {
298
-        $this->count           = 0;
299
-        $this->env             = null;
300
-        $this->inParens        = false;
301
-        $this->eatWhiteDefault = true;
302
-        $this->buffer          = (string) $buffer;
303
-
304
-        $this->saveEncoding();
305
-
306
-        $isMediaQuery = $this->mediaQueryList($out);
307
-
308
-        $this->restoreEncoding();
309
-
310
-        return $isMediaQuery;
311
-    }
312
-
313
-    /**
314
-     * Parse a single chunk off the head of the buffer and append it to the
315
-     * current parse environment.
316
-     *
317
-     * Returns false when the buffer is empty, or when there is an error.
318
-     *
319
-     * This function is called repeatedly until the entire document is
320
-     * parsed.
321
-     *
322
-     * This parser is most similar to a recursive descent parser. Single
323
-     * functions represent discrete grammatical rules for the language, and
324
-     * they are able to capture the text that represents those rules.
325
-     *
326
-     * Consider the function Compiler::keyword(). (All parse functions are
327
-     * structured the same.)
328
-     *
329
-     * The function takes a single reference argument. When calling the
330
-     * function it will attempt to match a keyword on the head of the buffer.
331
-     * If it is successful, it will place the keyword in the referenced
332
-     * argument, advance the position in the buffer, and return true. If it
333
-     * fails then it won't advance the buffer and it will return false.
334
-     *
335
-     * All of these parse functions are powered by Compiler::match(), which behaves
336
-     * the same way, but takes a literal regular expression. Sometimes it is
337
-     * more convenient to use match instead of creating a new function.
338
-     *
339
-     * Because of the format of the functions, to parse an entire string of
340
-     * grammatical rules, you can chain them together using &&.
341
-     *
342
-     * But, if some of the rules in the chain succeed before one fails, then
343
-     * the buffer position will be left at an invalid state. In order to
344
-     * avoid this, Compiler::seek() is used to remember and set buffer positions.
345
-     *
346
-     * Before parsing a chain, use $s = $this->count to remember the current
347
-     * position into $s. Then if a chain fails, use $this->seek($s) to
348
-     * go back where we started.
349
-     *
350
-     * @return boolean
351
-     */
352
-    protected function parseChunk()
353
-    {
354
-        $s = $this->count;
355
-
356
-        // the directives
357
-        if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] === '@') {
358
-            if (
359
-                $this->literal('@at-root', 8) &&
360
-                ($this->selectors($selector) || true) &&
361
-                ($this->map($with) || true) &&
362
-                (($this->matchChar('(') &&
363
-                    $this->interpolation($with) &&
364
-                    $this->matchChar(')')) || true) &&
365
-                $this->matchChar('{', false)
366
-            ) {
367
-                if ($this->cssOnly) {
368
-                    $this->throwParseError('SCSS syntax not allowed in CSS file');
369
-                }
370
-
371
-                $atRoot = $this->pushSpecialBlock(Type::T_AT_ROOT, $s);
372
-                $atRoot->selector = $selector;
373
-                $atRoot->with     = $with;
374
-
375
-                return true;
376
-            }
377
-
378
-            $this->seek($s);
379
-
380
-            if (
381
-                $this->literal('@media', 6) &&
382
-                $this->mediaQueryList($mediaQueryList) &&
383
-                $this->matchChar('{', false)
384
-            ) {
385
-                $media = $this->pushSpecialBlock(Type::T_MEDIA, $s);
386
-                $media->queryList = $mediaQueryList[2];
387
-
388
-                return true;
389
-            }
390
-
391
-            $this->seek($s);
392
-
393
-            if (
394
-                $this->literal('@mixin', 6) &&
395
-                $this->keyword($mixinName) &&
396
-                ($this->argumentDef($args) || true) &&
397
-                $this->matchChar('{', false)
398
-            ) {
399
-                if ($this->cssOnly) {
400
-                    $this->throwParseError('SCSS syntax not allowed in CSS file');
401
-                }
402
-
403
-                $mixin = $this->pushSpecialBlock(Type::T_MIXIN, $s);
404
-                $mixin->name = $mixinName;
405
-                $mixin->args = $args;
406
-
407
-                return true;
408
-            }
409
-
410
-            $this->seek($s);
411
-
412
-            if (
413
-                ($this->literal('@include', 8) &&
414
-                    $this->keyword($mixinName) &&
415
-                    ($this->matchChar('(') &&
416
-                    ($this->argValues($argValues) || true) &&
417
-                    $this->matchChar(')') || true) &&
418
-                    ($this->end()) ||
419
-                ($this->literal('using', 5) &&
420
-                    $this->argumentDef($argUsing) &&
421
-                    ($this->end() || $this->matchChar('{') && $hasBlock = true)) ||
422
-                $this->matchChar('{') && $hasBlock = true)
423
-            ) {
424
-                if ($this->cssOnly) {
425
-                    $this->throwParseError('SCSS syntax not allowed in CSS file');
426
-                }
427
-
428
-                $child = [
429
-                    Type::T_INCLUDE,
430
-                    $mixinName,
431
-                    isset($argValues) ? $argValues : null,
432
-                    null,
433
-                    isset($argUsing) ? $argUsing : null
434
-                ];
435
-
436
-                if (! empty($hasBlock)) {
437
-                    $include = $this->pushSpecialBlock(Type::T_INCLUDE, $s);
438
-                    $include->child = $child;
439
-                } else {
440
-                    $this->append($child, $s);
441
-                }
442
-
443
-                return true;
444
-            }
445
-
446
-            $this->seek($s);
447
-
448
-            if (
449
-                $this->literal('@scssphp-import-once', 20) &&
450
-                $this->valueList($importPath) &&
451
-                $this->end()
452
-            ) {
453
-                if ($this->cssOnly) {
454
-                    $this->throwParseError('SCSS syntax not allowed in CSS file');
455
-                }
456
-
457
-                $this->append([Type::T_SCSSPHP_IMPORT_ONCE, $importPath], $s);
458
-
459
-                return true;
460
-            }
461
-
462
-            $this->seek($s);
463
-
464
-            if (
465
-                $this->literal('@import', 7) &&
466
-                $this->valueList($importPath) &&
467
-                $importPath[0] !== Type::T_FUNCTION_CALL &&
468
-                $this->end()
469
-            ) {
470
-                $this->append([Type::T_IMPORT, $importPath], $s);
471
-
472
-                return true;
473
-            }
474
-
475
-            $this->seek($s);
476
-
477
-            if (
478
-                $this->literal('@import', 7) &&
479
-                $this->url($importPath) &&
480
-                $this->end()
481
-            ) {
482
-                if ($this->cssOnly) {
483
-                    $this->throwParseError('SCSS syntax not allowed in CSS file');
484
-                }
485
-
486
-                $this->append([Type::T_IMPORT, $importPath], $s);
487
-
488
-                return true;
489
-            }
490
-
491
-            $this->seek($s);
492
-
493
-            if (
494
-                $this->literal('@extend', 7) &&
495
-                $this->selectors($selectors) &&
496
-                $this->end()
497
-            ) {
498
-                if ($this->cssOnly) {
499
-                    $this->throwParseError('SCSS syntax not allowed in CSS file');
500
-                }
501
-
502
-                // check for '!flag'
503
-                $optional = $this->stripOptionalFlag($selectors);
504
-                $this->append([Type::T_EXTEND, $selectors, $optional], $s);
505
-
506
-                return true;
507
-            }
508
-
509
-            $this->seek($s);
510
-
511
-            if (
512
-                $this->literal('@function', 9) &&
513
-                $this->keyword($fnName) &&
514
-                $this->argumentDef($args) &&
515
-                $this->matchChar('{', false)
516
-            ) {
517
-                if ($this->cssOnly) {
518
-                    $this->throwParseError('SCSS syntax not allowed in CSS file');
519
-                }
520
-
521
-                $func = $this->pushSpecialBlock(Type::T_FUNCTION, $s);
522
-                $func->name = $fnName;
523
-                $func->args = $args;
524
-
525
-                return true;
526
-            }
527
-
528
-            $this->seek($s);
529
-
530
-            if (
531
-                $this->literal('@break', 6) &&
532
-                $this->end()
533
-            ) {
534
-                if ($this->cssOnly) {
535
-                    $this->throwParseError('SCSS syntax not allowed in CSS file');
536
-                }
537
-
538
-                $this->append([Type::T_BREAK], $s);
539
-
540
-                return true;
541
-            }
542
-
543
-            $this->seek($s);
544
-
545
-            if (
546
-                $this->literal('@continue', 9) &&
547
-                $this->end()
548
-            ) {
549
-                if ($this->cssOnly) {
550
-                    $this->throwParseError('SCSS syntax not allowed in CSS file');
551
-                }
552
-
553
-                $this->append([Type::T_CONTINUE], $s);
554
-
555
-                return true;
556
-            }
557
-
558
-            $this->seek($s);
559
-
560
-            if (
561
-                $this->literal('@return', 7) &&
562
-                ($this->valueList($retVal) || true) &&
563
-                $this->end()
564
-            ) {
565
-                if ($this->cssOnly) {
566
-                    $this->throwParseError('SCSS syntax not allowed in CSS file');
567
-                }
568
-
569
-                $this->append([Type::T_RETURN, isset($retVal) ? $retVal : [Type::T_NULL]], $s);
570
-
571
-                return true;
572
-            }
573
-
574
-            $this->seek($s);
575
-
576
-            if (
577
-                $this->literal('@each', 5) &&
578
-                $this->genericList($varNames, 'variable', ',', false) &&
579
-                $this->literal('in', 2) &&
580
-                $this->valueList($list) &&
581
-                $this->matchChar('{', false)
582
-            ) {
583
-                if ($this->cssOnly) {
584
-                    $this->throwParseError('SCSS syntax not allowed in CSS file');
585
-                }
586
-
587
-                $each = $this->pushSpecialBlock(Type::T_EACH, $s);
588
-
589
-                foreach ($varNames[2] as $varName) {
590
-                    $each->vars[] = $varName[1];
591
-                }
592
-
593
-                $each->list = $list;
594
-
595
-                return true;
596
-            }
597
-
598
-            $this->seek($s);
599
-
600
-            if (
601
-                $this->literal('@while', 6) &&
602
-                $this->expression($cond) &&
603
-                $this->matchChar('{', false)
604
-            ) {
605
-                if ($this->cssOnly) {
606
-                    $this->throwParseError('SCSS syntax not allowed in CSS file');
607
-                }
608
-
609
-                while (
610
-                    $cond[0] === Type::T_LIST &&
611
-                    ! empty($cond['enclosing']) &&
612
-                    $cond['enclosing'] === 'parent' &&
613
-                    \count($cond[2]) == 1
614
-                ) {
615
-                    $cond = reset($cond[2]);
616
-                }
617
-
618
-                $while = $this->pushSpecialBlock(Type::T_WHILE, $s);
619
-                $while->cond = $cond;
620
-
621
-                return true;
622
-            }
623
-
624
-            $this->seek($s);
625
-
626
-            if (
627
-                $this->literal('@for', 4) &&
628
-                $this->variable($varName) &&
629
-                $this->literal('from', 4) &&
630
-                $this->expression($start) &&
631
-                ($this->literal('through', 7) ||
632
-                    ($forUntil = true && $this->literal('to', 2))) &&
633
-                $this->expression($end) &&
634
-                $this->matchChar('{', false)
635
-            ) {
636
-                if ($this->cssOnly) {
637
-                    $this->throwParseError('SCSS syntax not allowed in CSS file');
638
-                }
639
-
640
-                $for = $this->pushSpecialBlock(Type::T_FOR, $s);
641
-                $for->var   = $varName[1];
642
-                $for->start = $start;
643
-                $for->end   = $end;
644
-                $for->until = isset($forUntil);
645
-
646
-                return true;
647
-            }
648
-
649
-            $this->seek($s);
650
-
651
-            if (
652
-                $this->literal('@if', 3) &&
653
-                $this->functionCallArgumentsList($cond, false) &&
654
-                $this->matchChar('{', false)
655
-            ) {
656
-                if ($this->cssOnly) {
657
-                    $this->throwParseError('SCSS syntax not allowed in CSS file');
658
-                }
659
-
660
-                $if = $this->pushSpecialBlock(Type::T_IF, $s);
661
-
662
-                while (
663
-                    $cond[0] === Type::T_LIST &&
664
-                    ! empty($cond['enclosing']) &&
665
-                    $cond['enclosing'] === 'parent' &&
666
-                    \count($cond[2]) == 1
667
-                ) {
668
-                    $cond = reset($cond[2]);
669
-                }
670
-
671
-                $if->cond  = $cond;
672
-                $if->cases = [];
673
-
674
-                return true;
675
-            }
676
-
677
-            $this->seek($s);
678
-
679
-            if (
680
-                $this->literal('@debug', 6) &&
681
-                $this->functionCallArgumentsList($value, false) &&
682
-                $this->end()
683
-            ) {
684
-                if ($this->cssOnly) {
685
-                    $this->throwParseError('SCSS syntax not allowed in CSS file');
686
-                }
687
-
688
-                $this->append([Type::T_DEBUG, $value], $s);
689
-
690
-                return true;
691
-            }
692
-
693
-            $this->seek($s);
694
-
695
-            if (
696
-                $this->literal('@warn', 5) &&
697
-                $this->functionCallArgumentsList($value, false) &&
698
-                $this->end()
699
-            ) {
700
-                if ($this->cssOnly) {
701
-                    $this->throwParseError('SCSS syntax not allowed in CSS file');
702
-                }
703
-
704
-                $this->append([Type::T_WARN, $value], $s);
705
-
706
-                return true;
707
-            }
708
-
709
-            $this->seek($s);
710
-
711
-            if (
712
-                $this->literal('@error', 6) &&
713
-                $this->functionCallArgumentsList($value, false) &&
714
-                $this->end()
715
-            ) {
716
-                if ($this->cssOnly) {
717
-                    $this->throwParseError('SCSS syntax not allowed in CSS file');
718
-                }
719
-
720
-                $this->append([Type::T_ERROR, $value], $s);
721
-
722
-                return true;
723
-            }
724
-
725
-            $this->seek($s);
726
-
727
-            if (
728
-                $this->literal('@content', 8) &&
729
-                ($this->end() ||
730
-                    $this->matchChar('(') &&
731
-                    $this->argValues($argContent) &&
732
-                    $this->matchChar(')') &&
733
-                    $this->end())
734
-            ) {
735
-                if ($this->cssOnly) {
736
-                    $this->throwParseError('SCSS syntax not allowed in CSS file');
737
-                }
738
-
739
-                $this->append([Type::T_MIXIN_CONTENT, isset($argContent) ? $argContent : null], $s);
740
-
741
-                return true;
742
-            }
743
-
744
-            $this->seek($s);
745
-
746
-            $last = $this->last();
747
-
748
-            if (isset($last) && $last[0] === Type::T_IF) {
749
-                list(, $if) = $last;
750
-
751
-                if ($this->literal('@else', 5)) {
752
-                    if ($this->matchChar('{', false)) {
753
-                        $else = $this->pushSpecialBlock(Type::T_ELSE, $s);
754
-                    } elseif (
755
-                        $this->literal('if', 2) &&
756
-                        $this->valueList($cond) &&
757
-                        $this->matchChar('{', false)
758
-                    ) {
759
-                        $else = $this->pushSpecialBlock(Type::T_ELSEIF, $s);
760
-                        $else->cond = $cond;
761
-                    }
762
-
763
-                    if (isset($else)) {
764
-                        $else->dontAppend = true;
765
-                        $if->cases[] = $else;
766
-
767
-                        return true;
768
-                    }
769
-                }
770
-
771
-                $this->seek($s);
772
-            }
773
-
774
-            // only retain the first @charset directive encountered
775
-            if (
776
-                $this->literal('@charset', 8) &&
777
-                $this->valueList($charset) &&
778
-                $this->end()
779
-            ) {
780
-                if (! isset($this->charset)) {
781
-                    $statement = [Type::T_CHARSET, $charset];
782
-
783
-                    list($line, $column) = $this->getSourcePosition($s);
784
-
785
-                    $statement[static::SOURCE_LINE]   = $line;
786
-                    $statement[static::SOURCE_COLUMN] = $column;
787
-                    $statement[static::SOURCE_INDEX]  = $this->sourceIndex;
788
-
789
-                    $this->charset = $statement;
790
-                }
791
-
792
-                return true;
793
-            }
794
-
795
-            $this->seek($s);
796
-
797
-            if (
798
-                $this->literal('@supports', 9) &&
799
-                ($t1 = $this->supportsQuery($supportQuery)) &&
800
-                ($t2 = $this->matchChar('{', false))
801
-            ) {
802
-                $directive = $this->pushSpecialBlock(Type::T_DIRECTIVE, $s);
803
-                $directive->name  = 'supports';
804
-                $directive->value = $supportQuery;
805
-
806
-                return true;
807
-            }
808
-
809
-            $this->seek($s);
810
-
811
-            // doesn't match built in directive, do generic one
812
-            if (
813
-                $this->matchChar('@', false) &&
814
-                $this->keyword($dirName) &&
815
-                $this->directiveValue($dirValue, '{')
816
-            ) {
817
-                if ($dirName === 'media') {
818
-                    $directive = $this->pushSpecialBlock(Type::T_MEDIA, $s);
819
-                } else {
820
-                    $directive = $this->pushSpecialBlock(Type::T_DIRECTIVE, $s);
821
-                    $directive->name = $dirName;
822
-                }
823
-
824
-                if (isset($dirValue)) {
825
-                    $directive->value = $dirValue;
826
-                }
827
-
828
-                return true;
829
-            }
830
-
831
-            $this->seek($s);
832
-
833
-            // maybe it's a generic blockless directive
834
-            if (
835
-                $this->matchChar('@', false) &&
836
-                $this->keyword($dirName) &&
837
-                $this->directiveValue($dirValue) &&
838
-                $this->end()
839
-            ) {
840
-                $this->append([Type::T_DIRECTIVE, [$dirName, $dirValue]], $s);
841
-
842
-                return true;
843
-            }
844
-
845
-            $this->seek($s);
846
-
847
-            return false;
848
-        }
849
-
850
-        $inCssSelector = null;
851
-        if ($this->cssOnly) {
852
-            $inCssSelector = (! empty($this->env->parent) &&
853
-                ! in_array($this->env->type, [Type::T_DIRECTIVE, Type::T_MEDIA]));
854
-        }
855
-        // custom properties : right part is static
856
-        if (
857
-            ($this->customProperty($name) ||
858
-                ($inCssSelector && $this->propertyName($name))) &&
859
-            $this->matchChar(':', false)
860
-        ) {
861
-            $start = $this->count;
862
-
863
-            // but can be complex and finish with ; or }
864
-            foreach ([';','}'] as $ending) {
865
-                if (
866
-                    $this->openString($ending, $stringValue, '(', ')', false) &&
867
-                    $this->end()
868
-                ) {
869
-                    $end = $this->count;
870
-                    $value = $stringValue;
871
-
872
-                    // check if we have only a partial value due to nested [] or { } to take in account
873
-                    $nestingPairs = [['[', ']'], ['{', '}']];
874
-
875
-                    foreach ($nestingPairs as $nestingPair) {
876
-                        $p = strpos($this->buffer, $nestingPair[0], $start);
877
-
878
-                        if ($p && $p < $end) {
879
-                            $this->seek($start);
880
-
881
-                            if (
882
-                                $this->openString($ending, $stringValue, $nestingPair[0], $nestingPair[1], false) &&
883
-                                $this->end() &&
884
-                                $this->count > $end
885
-                            ) {
886
-                                $end = $this->count;
887
-                                $value = $stringValue;
888
-                            }
889
-                        }
890
-                    }
891
-
892
-                    $this->seek($end);
893
-                    $this->append([Type::T_CUSTOM_PROPERTY, $name, $value], $s);
894
-
895
-                    return true;
896
-                }
897
-            }
898
-
899
-            // TODO: output an error here if nothing found according to sass spec
900
-        }
901
-
902
-        $this->seek($s);
903
-
904
-        // property shortcut
905
-        // captures most properties before having to parse a selector
906
-        if (
907
-            $this->keyword($name, false) &&
908
-            $this->literal(': ', 2) &&
909
-            $this->valueList($value) &&
910
-            $this->end()
911
-        ) {
912
-            $name = [Type::T_STRING, '', [$name]];
913
-            $this->append([Type::T_ASSIGN, $name, $value], $s);
914
-
915
-            return true;
916
-        }
917
-
918
-        $this->seek($s);
919
-
920
-        // variable assigns
921
-        if (
922
-            $this->variable($name) &&
923
-            $this->matchChar(':') &&
924
-            $this->valueList($value) &&
925
-            $this->end()
926
-        ) {
927
-            if ($this->cssOnly) {
928
-                $this->throwParseError('SCSS syntax not allowed in CSS file');
929
-            }
930
-
931
-            // check for '!flag'
932
-            $assignmentFlags = $this->stripAssignmentFlags($value);
933
-            $this->append([Type::T_ASSIGN, $name, $value, $assignmentFlags], $s);
934
-
935
-            return true;
936
-        }
937
-
938
-        $this->seek($s);
939
-
940
-        // misc
941
-        if ($this->literal('-->', 3)) {
942
-            return true;
943
-        }
944
-
945
-        // opening css block
946
-        if (
947
-            $this->selectors($selectors) &&
948
-            $this->matchChar('{', false)
949
-        ) {
950
-            if ($this->cssOnly) {
951
-                if ($inCssSelector) {
952
-                    $this->throwParseError('SCSS syntax not allowed in CSS file');
953
-                }
954
-            }
955
-
956
-            $this->pushBlock($selectors, $s);
957
-
958
-            if ($this->eatWhiteDefault) {
959
-                $this->whitespace();
960
-                $this->append(null); // collect comments at the beginning if needed
961
-            }
962
-
963
-            return true;
964
-        }
965
-
966
-        $this->seek($s);
967
-
968
-        // property assign, or nested assign
969
-        if (
970
-            $this->propertyName($name) &&
971
-            $this->matchChar(':')
972
-        ) {
973
-            $foundSomething = false;
974
-
975
-            if ($this->valueList($value)) {
976
-                if (empty($this->env->parent)) {
977
-                    $this->throwParseError('expected "{"');
978
-                }
979
-
980
-                $this->append([Type::T_ASSIGN, $name, $value], $s);
981
-                $foundSomething = true;
982
-            }
983
-
984
-            if ($this->matchChar('{', false)) {
985
-                if ($this->cssOnly) {
986
-                    $this->throwParseError('SCSS syntax not allowed in CSS file');
987
-                }
988
-
989
-                $propBlock = $this->pushSpecialBlock(Type::T_NESTED_PROPERTY, $s);
990
-                $propBlock->prefix = $name;
991
-                $propBlock->hasValue = $foundSomething;
992
-
993
-                $foundSomething = true;
994
-            } elseif ($foundSomething) {
995
-                $foundSomething = $this->end();
996
-            }
997
-
998
-            if ($foundSomething) {
999
-                return true;
1000
-            }
1001
-        }
1002
-
1003
-        $this->seek($s);
1004
-
1005
-        // closing a block
1006
-        if ($this->matchChar('}', false)) {
1007
-            $block = $this->popBlock();
1008
-
1009
-            if (! isset($block->type) || $block->type !== Type::T_IF) {
1010
-                if ($this->env->parent) {
1011
-                    $this->append(null); // collect comments before next statement if needed
1012
-                }
1013
-            }
1014
-
1015
-            if (isset($block->type) && $block->type === Type::T_INCLUDE) {
1016
-                $include = $block->child;
1017
-                unset($block->child);
1018
-                $include[3] = $block;
1019
-                $this->append($include, $s);
1020
-            } elseif (empty($block->dontAppend)) {
1021
-                $type = isset($block->type) ? $block->type : Type::T_BLOCK;
1022
-                $this->append([$type, $block], $s);
1023
-            }
1024
-
1025
-            // collect comments just after the block closing if needed
1026
-            if ($this->eatWhiteDefault) {
1027
-                $this->whitespace();
1028
-
1029
-                if ($this->env->comments) {
1030
-                    $this->append(null);
1031
-                }
1032
-            }
1033
-
1034
-            return true;
1035
-        }
1036
-
1037
-        // extra stuff
1038
-        if (
1039
-            $this->matchChar(';') ||
1040
-            $this->literal('<!--', 4)
1041
-        ) {
1042
-            return true;
1043
-        }
1044
-
1045
-        return false;
1046
-    }
1047
-
1048
-    /**
1049
-     * Push block onto parse tree
1050
-     *
1051
-     * @param array   $selectors
1052
-     * @param integer $pos
1053
-     *
1054
-     * @return \ScssPhp\ScssPhp\Block
1055
-     */
1056
-    protected function pushBlock($selectors, $pos = 0)
1057
-    {
1058
-        list($line, $column) = $this->getSourcePosition($pos);
1059
-
1060
-        $b = new Block();
1061
-        $b->sourceName   = $this->sourceName;
1062
-        $b->sourceLine   = $line;
1063
-        $b->sourceColumn = $column;
1064
-        $b->sourceIndex  = $this->sourceIndex;
1065
-        $b->selectors    = $selectors;
1066
-        $b->comments     = [];
1067
-        $b->parent       = $this->env;
1068
-
1069
-        if (! $this->env) {
1070
-            $b->children = [];
1071
-        } elseif (empty($this->env->children)) {
1072
-            $this->env->children = $this->env->comments;
1073
-            $b->children = [];
1074
-            $this->env->comments = [];
1075
-        } else {
1076
-            $b->children = $this->env->comments;
1077
-            $this->env->comments = [];
1078
-        }
1079
-
1080
-        $this->env = $b;
1081
-
1082
-        // collect comments at the beginning of a block if needed
1083
-        if ($this->eatWhiteDefault) {
1084
-            $this->whitespace();
1085
-
1086
-            if ($this->env->comments) {
1087
-                $this->append(null);
1088
-            }
1089
-        }
1090
-
1091
-        return $b;
1092
-    }
1093
-
1094
-    /**
1095
-     * Push special (named) block onto parse tree
1096
-     *
1097
-     * @param string  $type
1098
-     * @param integer $pos
1099
-     *
1100
-     * @return \ScssPhp\ScssPhp\Block
1101
-     */
1102
-    protected function pushSpecialBlock($type, $pos)
1103
-    {
1104
-        $block = $this->pushBlock(null, $pos);
1105
-        $block->type = $type;
1106
-
1107
-        return $block;
1108
-    }
1109
-
1110
-    /**
1111
-     * Pop scope and return last block
1112
-     *
1113
-     * @return \ScssPhp\ScssPhp\Block
1114
-     *
1115
-     * @throws \Exception
1116
-     */
1117
-    protected function popBlock()
1118
-    {
1119
-
1120
-        // collect comments ending just before of a block closing
1121
-        if ($this->env->comments) {
1122
-            $this->append(null);
1123
-        }
1124
-
1125
-        // pop the block
1126
-        $block = $this->env;
1127
-
1128
-        if (empty($block->parent)) {
1129
-            $this->throwParseError('unexpected }');
1130
-        }
1131
-
1132
-        if ($block->type == Type::T_AT_ROOT) {
1133
-            // keeps the parent in case of self selector &
1134
-            $block->selfParent = $block->parent;
1135
-        }
1136
-
1137
-        $this->env = $block->parent;
1138
-
1139
-        unset($block->parent);
1140
-
1141
-        return $block;
1142
-    }
1143
-
1144
-    /**
1145
-     * Peek input stream
1146
-     *
1147
-     * @param string  $regex
1148
-     * @param array   $out
1149
-     * @param integer $from
1150
-     *
1151
-     * @return integer
1152
-     */
1153
-    protected function peek($regex, &$out, $from = null)
1154
-    {
1155
-        if (! isset($from)) {
1156
-            $from = $this->count;
1157
-        }
1158
-
1159
-        $r = '/' . $regex . '/' . $this->patternModifiers;
1160
-        $result = preg_match($r, $this->buffer, $out, null, $from);
1161
-
1162
-        return $result;
1163
-    }
1164
-
1165
-    /**
1166
-     * Seek to position in input stream (or return current position in input stream)
1167
-     *
1168
-     * @param integer $where
1169
-     */
1170
-    protected function seek($where)
1171
-    {
1172
-        $this->count = $where;
1173
-    }
1174
-
1175
-    /**
1176
-     * Match string looking for either ending delim, escape, or string interpolation
1177
-     *
1178
-     * {@internal This is a workaround for preg_match's 250K string match limit. }}
1179
-     *
1180
-     * @param array  $m     Matches (passed by reference)
1181
-     * @param string $delim Delimiter
1182
-     *
1183
-     * @return boolean True if match; false otherwise
1184
-     */
1185
-    protected function matchString(&$m, $delim)
1186
-    {
1187
-        $token = null;
1188
-
1189
-        $end = \strlen($this->buffer);
1190
-
1191
-        // look for either ending delim, escape, or string interpolation
1192
-        foreach (['#{', '\\', "\r", $delim] as $lookahead) {
1193
-            $pos = strpos($this->buffer, $lookahead, $this->count);
1194
-
1195
-            if ($pos !== false && $pos < $end) {
1196
-                $end = $pos;
1197
-                $token = $lookahead;
1198
-            }
1199
-        }
1200
-
1201
-        if (! isset($token)) {
1202
-            return false;
1203
-        }
1204
-
1205
-        $match = substr($this->buffer, $this->count, $end - $this->count);
1206
-        $m = [
1207
-            $match . $token,
1208
-            $match,
1209
-            $token
1210
-        ];
1211
-        $this->count = $end + \strlen($token);
1212
-
1213
-        return true;
1214
-    }
1215
-
1216
-    /**
1217
-     * Try to match something on head of buffer
1218
-     *
1219
-     * @param string  $regex
1220
-     * @param array   $out
1221
-     * @param boolean $eatWhitespace
1222
-     *
1223
-     * @return boolean
1224
-     */
1225
-    protected function match($regex, &$out, $eatWhitespace = null)
1226
-    {
1227
-        $r = '/' . $regex . '/' . $this->patternModifiers;
1228
-
1229
-        if (! preg_match($r, $this->buffer, $out, null, $this->count)) {
1230
-            return false;
1231
-        }
1232
-
1233
-        $this->count += \strlen($out[0]);
1234
-
1235
-        if (! isset($eatWhitespace)) {
1236
-            $eatWhitespace = $this->eatWhiteDefault;
1237
-        }
1238
-
1239
-        if ($eatWhitespace) {
1240
-            $this->whitespace();
1241
-        }
1242
-
1243
-        return true;
1244
-    }
1245
-
1246
-    /**
1247
-     * Match a single string
1248
-     *
1249
-     * @param string  $char
1250
-     * @param boolean $eatWhitespace
1251
-     *
1252
-     * @return boolean
1253
-     */
1254
-    protected function matchChar($char, $eatWhitespace = null)
1255
-    {
1256
-        if (! isset($this->buffer[$this->count]) || $this->buffer[$this->count] !== $char) {
1257
-            return false;
1258
-        }
1259
-
1260
-        $this->count++;
1261
-
1262
-        if (! isset($eatWhitespace)) {
1263
-            $eatWhitespace = $this->eatWhiteDefault;
1264
-        }
1265
-
1266
-        if ($eatWhitespace) {
1267
-            $this->whitespace();
1268
-        }
1269
-
1270
-        return true;
1271
-    }
1272
-
1273
-    /**
1274
-     * Match literal string
1275
-     *
1276
-     * @param string  $what
1277
-     * @param integer $len
1278
-     * @param boolean $eatWhitespace
1279
-     *
1280
-     * @return boolean
1281
-     */
1282
-    protected function literal($what, $len, $eatWhitespace = null)
1283
-    {
1284
-        if (strcasecmp(substr($this->buffer, $this->count, $len), $what) !== 0) {
1285
-            return false;
1286
-        }
1287
-
1288
-        $this->count += $len;
1289
-
1290
-        if (! isset($eatWhitespace)) {
1291
-            $eatWhitespace = $this->eatWhiteDefault;
1292
-        }
1293
-
1294
-        if ($eatWhitespace) {
1295
-            $this->whitespace();
1296
-        }
1297
-
1298
-        return true;
1299
-    }
1300
-
1301
-    /**
1302
-     * Match some whitespace
1303
-     *
1304
-     * @return boolean
1305
-     */
1306
-    protected function whitespace()
1307
-    {
1308
-        $gotWhite = false;
1309
-
1310
-        while (preg_match(static::$whitePattern, $this->buffer, $m, null, $this->count)) {
1311
-            if (isset($m[1]) && empty($this->commentsSeen[$this->count])) {
1312
-                // comment that are kept in the output CSS
1313
-                $comment = [];
1314
-                $startCommentCount = $this->count;
1315
-                $endCommentCount = $this->count + \strlen($m[1]);
1316
-
1317
-                // find interpolations in comment
1318
-                $p = strpos($this->buffer, '#{', $this->count);
1319
-
1320
-                while ($p !== false && $p < $endCommentCount) {
1321
-                    $c           = substr($this->buffer, $this->count, $p - $this->count);
1322
-                    $comment[]   = $c;
1323
-                    $this->count = $p;
1324
-                    $out         = null;
1325
-
1326
-                    if ($this->interpolation($out)) {
1327
-                        // keep right spaces in the following string part
1328
-                        if ($out[3]) {
1329
-                            while ($this->buffer[$this->count - 1] !== '}') {
1330
-                                $this->count--;
1331
-                            }
1332
-
1333
-                            $out[3] = '';
1334
-                        }
1335
-
1336
-                        $comment[] = [Type::T_COMMENT, substr($this->buffer, $p, $this->count - $p), $out];
1337
-                    } else {
1338
-                        $comment[] = substr($this->buffer, $this->count, 2);
1339
-
1340
-                        $this->count += 2;
1341
-                    }
1342
-
1343
-                    $p = strpos($this->buffer, '#{', $this->count);
1344
-                }
1345
-
1346
-                // remaining part
1347
-                $c = substr($this->buffer, $this->count, $endCommentCount - $this->count);
1348
-
1349
-                if (! $comment) {
1350
-                    // single part static comment
1351
-                    $this->appendComment([Type::T_COMMENT, $c]);
1352
-                } else {
1353
-                    $comment[] = $c;
1354
-                    $staticComment = substr($this->buffer, $startCommentCount, $endCommentCount - $startCommentCount);
1355
-                    $this->appendComment([Type::T_COMMENT, $staticComment, [Type::T_STRING, '', $comment]]);
1356
-                }
1357
-
1358
-                $this->commentsSeen[$startCommentCount] = true;
1359
-                $this->count = $endCommentCount;
1360
-            } else {
1361
-                // comment that are ignored and not kept in the output css
1362
-                $this->count += \strlen($m[0]);
1363
-            }
1364
-
1365
-            $gotWhite = true;
1366
-        }
1367
-
1368
-        return $gotWhite;
1369
-    }
1370
-
1371
-    /**
1372
-     * Append comment to current block
1373
-     *
1374
-     * @param array $comment
1375
-     */
1376
-    protected function appendComment($comment)
1377
-    {
1378
-        if (! $this->discardComments) {
1379
-            $this->env->comments[] = $comment;
1380
-        }
1381
-    }
1382
-
1383
-    /**
1384
-     * Append statement to current block
1385
-     *
1386
-     * @param array   $statement
1387
-     * @param integer $pos
1388
-     */
1389
-    protected function append($statement, $pos = null)
1390
-    {
1391
-        if (! \is_null($statement)) {
1392
-            if (! \is_null($pos)) {
1393
-                list($line, $column) = $this->getSourcePosition($pos);
1394
-
1395
-                $statement[static::SOURCE_LINE]   = $line;
1396
-                $statement[static::SOURCE_COLUMN] = $column;
1397
-                $statement[static::SOURCE_INDEX]  = $this->sourceIndex;
1398
-            }
1399
-
1400
-            $this->env->children[] = $statement;
1401
-        }
1402
-
1403
-        $comments = $this->env->comments;
1404
-
1405
-        if ($comments) {
1406
-            $this->env->children = array_merge($this->env->children, $comments);
1407
-            $this->env->comments = [];
1408
-        }
1409
-    }
1410
-
1411
-    /**
1412
-     * Returns last child was appended
1413
-     *
1414
-     * @return array|null
1415
-     */
1416
-    protected function last()
1417
-    {
1418
-        $i = \count($this->env->children) - 1;
1419
-
1420
-        if (isset($this->env->children[$i])) {
1421
-            return $this->env->children[$i];
1422
-        }
1423
-    }
1424
-
1425
-    /**
1426
-     * Parse media query list
1427
-     *
1428
-     * @param array $out
1429
-     *
1430
-     * @return boolean
1431
-     */
1432
-    protected function mediaQueryList(&$out)
1433
-    {
1434
-        return $this->genericList($out, 'mediaQuery', ',', false);
1435
-    }
1436
-
1437
-    /**
1438
-     * Parse media query
1439
-     *
1440
-     * @param array $out
1441
-     *
1442
-     * @return boolean
1443
-     */
1444
-    protected function mediaQuery(&$out)
1445
-    {
1446
-        $expressions = null;
1447
-        $parts = [];
1448
-
1449
-        if (
1450
-            ($this->literal('only', 4) && ($only = true) ||
1451
-            $this->literal('not', 3) && ($not = true) || true) &&
1452
-            $this->mixedKeyword($mediaType)
1453
-        ) {
1454
-            $prop = [Type::T_MEDIA_TYPE];
1455
-
1456
-            if (isset($only)) {
1457
-                $prop[] = [Type::T_KEYWORD, 'only'];
1458
-            }
1459
-
1460
-            if (isset($not)) {
1461
-                $prop[] = [Type::T_KEYWORD, 'not'];
1462
-            }
1463
-
1464
-            $media = [Type::T_LIST, '', []];
1465
-
1466
-            foreach ((array) $mediaType as $type) {
1467
-                if (\is_array($type)) {
1468
-                    $media[2][] = $type;
1469
-                } else {
1470
-                    $media[2][] = [Type::T_KEYWORD, $type];
1471
-                }
1472
-            }
1473
-
1474
-            $prop[]  = $media;
1475
-            $parts[] = $prop;
1476
-        }
1477
-
1478
-        if (empty($parts) || $this->literal('and', 3)) {
1479
-            $this->genericList($expressions, 'mediaExpression', 'and', false);
1480
-
1481
-            if (\is_array($expressions)) {
1482
-                $parts = array_merge($parts, $expressions[2]);
1483
-            }
1484
-        }
1485
-
1486
-        $out = $parts;
1487
-
1488
-        return true;
1489
-    }
1490
-
1491
-    /**
1492
-     * Parse supports query
1493
-     *
1494
-     * @param array $out
1495
-     *
1496
-     * @return boolean
1497
-     */
1498
-    protected function supportsQuery(&$out)
1499
-    {
1500
-        $expressions = null;
1501
-        $parts = [];
1502
-
1503
-        $s = $this->count;
1504
-
1505
-        $not = false;
1506
-
1507
-        if (
1508
-            ($this->literal('not', 3) && ($not = true) || true) &&
1509
-            $this->matchChar('(') &&
1510
-            ($this->expression($property)) &&
1511
-            $this->literal(': ', 2) &&
1512
-            $this->valueList($value) &&
1513
-            $this->matchChar(')')
1514
-        ) {
1515
-            $support = [Type::T_STRING, '', [[Type::T_KEYWORD, ($not ? 'not ' : '') . '(']]];
1516
-            $support[2][] = $property;
1517
-            $support[2][] = [Type::T_KEYWORD, ': '];
1518
-            $support[2][] = $value;
1519
-            $support[2][] = [Type::T_KEYWORD, ')'];
1520
-
1521
-            $parts[] = $support;
1522
-            $s = $this->count;
1523
-        } else {
1524
-            $this->seek($s);
1525
-        }
1526
-
1527
-        if (
1528
-            $this->matchChar('(') &&
1529
-            $this->supportsQuery($subQuery) &&
1530
-            $this->matchChar(')')
1531
-        ) {
1532
-            $parts[] = [Type::T_STRING, '', [[Type::T_KEYWORD, '('], $subQuery, [Type::T_KEYWORD, ')']]];
1533
-            $s = $this->count;
1534
-        } else {
1535
-            $this->seek($s);
1536
-        }
1537
-
1538
-        if (
1539
-            $this->literal('not', 3) &&
1540
-            $this->supportsQuery($subQuery)
1541
-        ) {
1542
-            $parts[] = [Type::T_STRING, '', [[Type::T_KEYWORD, 'not '], $subQuery]];
1543
-            $s = $this->count;
1544
-        } else {
1545
-            $this->seek($s);
1546
-        }
1547
-
1548
-        if (
1549
-            $this->literal('selector(', 9) &&
1550
-            $this->selector($selector) &&
1551
-            $this->matchChar(')')
1552
-        ) {
1553
-            $support = [Type::T_STRING, '', [[Type::T_KEYWORD, 'selector(']]];
1554
-
1555
-            $selectorList = [Type::T_LIST, '', []];
1556
-
1557
-            foreach ($selector as $sc) {
1558
-                $compound = [Type::T_STRING, '', []];
1559
-
1560
-                foreach ($sc as $scp) {
1561
-                    if (\is_array($scp)) {
1562
-                        $compound[2][] = $scp;
1563
-                    } else {
1564
-                        $compound[2][] = [Type::T_KEYWORD, $scp];
1565
-                    }
1566
-                }
1567
-
1568
-                $selectorList[2][] = $compound;
1569
-            }
1570
-
1571
-            $support[2][] = $selectorList;
1572
-            $support[2][] = [Type::T_KEYWORD, ')'];
1573
-            $parts[] = $support;
1574
-            $s = $this->count;
1575
-        } else {
1576
-            $this->seek($s);
1577
-        }
1578
-
1579
-        if ($this->variable($var) or $this->interpolation($var)) {
1580
-            $parts[] = $var;
1581
-            $s = $this->count;
1582
-        } else {
1583
-            $this->seek($s);
1584
-        }
1585
-
1586
-        if (
1587
-            $this->literal('and', 3) &&
1588
-            $this->genericList($expressions, 'supportsQuery', ' and', false)
1589
-        ) {
1590
-            array_unshift($expressions[2], [Type::T_STRING, '', $parts]);
1591
-
1592
-            $parts = [$expressions];
1593
-            $s = $this->count;
1594
-        } else {
1595
-            $this->seek($s);
1596
-        }
1597
-
1598
-        if (
1599
-            $this->literal('or', 2) &&
1600
-            $this->genericList($expressions, 'supportsQuery', ' or', false)
1601
-        ) {
1602
-            array_unshift($expressions[2], [Type::T_STRING, '', $parts]);
1603
-
1604
-            $parts = [$expressions];
1605
-            $s = $this->count;
1606
-        } else {
1607
-            $this->seek($s);
1608
-        }
1609
-
1610
-        if (\count($parts)) {
1611
-            if ($this->eatWhiteDefault) {
1612
-                $this->whitespace();
1613
-            }
1614
-
1615
-            $out = [Type::T_STRING, '', $parts];
1616
-
1617
-            return true;
1618
-        }
1619
-
1620
-        return false;
1621
-    }
1622
-
1623
-
1624
-    /**
1625
-     * Parse media expression
1626
-     *
1627
-     * @param array $out
1628
-     *
1629
-     * @return boolean
1630
-     */
1631
-    protected function mediaExpression(&$out)
1632
-    {
1633
-        $s = $this->count;
1634
-        $value = null;
1635
-
1636
-        if (
1637
-            $this->matchChar('(') &&
1638
-            $this->expression($feature) &&
1639
-            ($this->matchChar(':') &&
1640
-                $this->expression($value) || true) &&
1641
-            $this->matchChar(')')
1642
-        ) {
1643
-            $out = [Type::T_MEDIA_EXPRESSION, $feature];
1644
-
1645
-            if ($value) {
1646
-                $out[] = $value;
1647
-            }
1648
-
1649
-            return true;
1650
-        }
1651
-
1652
-        $this->seek($s);
1653
-
1654
-        return false;
1655
-    }
1656
-
1657
-    /**
1658
-     * Parse argument values
1659
-     *
1660
-     * @param array $out
1661
-     *
1662
-     * @return boolean
1663
-     */
1664
-    protected function argValues(&$out)
1665
-    {
1666
-        $discardComments = $this->discardComments;
1667
-        $this->discardComments = true;
1668
-
1669
-        if ($this->genericList($list, 'argValue', ',', false)) {
1670
-            $out = $list[2];
1671
-
1672
-            $this->discardComments = $discardComments;
1673
-
1674
-            return true;
1675
-        }
1676
-
1677
-        $this->discardComments = $discardComments;
1678
-
1679
-        return false;
1680
-    }
1681
-
1682
-    /**
1683
-     * Parse argument value
1684
-     *
1685
-     * @param array $out
1686
-     *
1687
-     * @return boolean
1688
-     */
1689
-    protected function argValue(&$out)
1690
-    {
1691
-        $s = $this->count;
1692
-
1693
-        $keyword = null;
1694
-
1695
-        if (! $this->variable($keyword) || ! $this->matchChar(':')) {
1696
-            $this->seek($s);
1697
-
1698
-            $keyword = null;
1699
-        }
1700
-
1701
-        if ($this->genericList($value, 'expression', '', true)) {
1702
-            $out = [$keyword, $value, false];
1703
-            $s = $this->count;
1704
-
1705
-            if ($this->literal('...', 3)) {
1706
-                $out[2] = true;
1707
-            } else {
1708
-                $this->seek($s);
1709
-            }
1710
-
1711
-            return true;
1712
-        }
1713
-
1714
-        return false;
1715
-    }
1716
-
1717
-    /**
1718
-     * Parse directive value list that considers $vars as keyword
1719
-     *
1720
-     * @param array          $out
1721
-     * @param boolean|string $endChar
1722
-     *
1723
-     * @return boolean
1724
-     */
1725
-    protected function directiveValue(&$out, $endChar = false)
1726
-    {
1727
-        $s = $this->count;
1728
-
1729
-        if ($this->variable($out)) {
1730
-            if ($endChar && $this->matchChar($endChar, false)) {
1731
-                return true;
1732
-            }
1733
-
1734
-            if (! $endChar && $this->end()) {
1735
-                return true;
1736
-            }
1737
-        }
1738
-
1739
-        $this->seek($s);
1740
-
1741
-        if ($endChar && $this->openString($endChar, $out)) {
1742
-            if ($this->matchChar($endChar, false)) {
1743
-                return true;
1744
-            }
1745
-        }
1746
-
1747
-        $this->seek($s);
1748
-
1749
-        $allowVars = $this->allowVars;
1750
-        $this->allowVars = false;
1751
-
1752
-        $res = $this->genericList($out, 'spaceList', ',');
1753
-        $this->allowVars = $allowVars;
1754
-
1755
-        if ($res) {
1756
-            if ($endChar && $this->matchChar($endChar, false)) {
1757
-                return true;
1758
-            }
1759
-
1760
-            if (! $endChar && $this->end()) {
1761
-                return true;
1762
-            }
1763
-        }
1764
-
1765
-        $this->seek($s);
1766
-
1767
-        if ($endChar && $this->matchChar($endChar, false)) {
1768
-            return true;
1769
-        }
1770
-
1771
-        return false;
1772
-    }
1773
-
1774
-    /**
1775
-     * Parse comma separated value list
1776
-     *
1777
-     * @param array $out
1778
-     *
1779
-     * @return boolean
1780
-     */
1781
-    protected function valueList(&$out)
1782
-    {
1783
-        $discardComments = $this->discardComments;
1784
-        $this->discardComments = true;
1785
-        $res = $this->genericList($out, 'spaceList', ',');
1786
-        $this->discardComments = $discardComments;
1787
-
1788
-        return $res;
1789
-    }
1790
-
1791
-    /**
1792
-     * Parse a function call, where externals () are part of the call
1793
-     * and not of the value list
1794
-     *
1795
-     * @param $out
1796
-     * @param bool $mandatoryParenthesis
1797
-     * @return bool
1798
-     */
1799
-    protected function functionCallArgumentsList(&$out, $mandatoryParenthesis = true)
1800
-    {
1801
-        $s = $this->count;
1802
-
1803
-        if (
1804
-            $this->matchChar('(') &&
1805
-            $this->valueList($out) &&
1806
-            $this->matchChar(')')
1807
-        ) {
1808
-            return true;
1809
-        }
1810
-
1811
-        if (! $mandatoryParenthesis) {
1812
-            $this->seek($s);
1813
-
1814
-            if ($this->valueList($out)) {
1815
-                return true;
1816
-            }
1817
-        }
1818
-
1819
-        $this->seek($s);
1820
-
1821
-        return false;
1822
-    }
1823
-
1824
-    /**
1825
-     * Parse space separated value list
1826
-     *
1827
-     * @param array $out
1828
-     *
1829
-     * @return boolean
1830
-     */
1831
-    protected function spaceList(&$out)
1832
-    {
1833
-        return $this->genericList($out, 'expression');
1834
-    }
1835
-
1836
-    /**
1837
-     * Parse generic list
1838
-     *
1839
-     * @param array    $out
1840
-     * @param callable $parseItem
1841
-     * @param string   $delim
1842
-     * @param boolean  $flatten
1843
-     *
1844
-     * @return boolean
1845
-     */
1846
-    protected function genericList(&$out, $parseItem, $delim = '', $flatten = true)
1847
-    {
1848
-        $s     = $this->count;
1849
-        $items = [];
1850
-        $value = null;
1851
-
1852
-        while ($this->$parseItem($value)) {
1853
-            $trailing_delim = false;
1854
-            $items[] = $value;
1855
-
1856
-            if ($delim) {
1857
-                if (! $this->literal($delim, \strlen($delim))) {
1858
-                    break;
1859
-                }
1860
-
1861
-                $trailing_delim = true;
1862
-            } else {
1863
-                // if no delim watch that a keyword didn't eat the single/double quote
1864
-                // from the following starting string
1865
-                if ($value[0] === Type::T_KEYWORD) {
1866
-                    $word = $value[1];
1867
-
1868
-                    $last_char = substr($word, -1);
1869
-
1870
-                    if (
1871
-                        strlen($word) > 1 &&
1872
-                        in_array($last_char, [ "'", '"']) &&
1873
-                        substr($word, -2, 1) !== '\\'
1874
-                    ) {
1875
-                        // if there is a non escaped opening quote in the keyword, this seems unlikely a mistake
1876
-                        $word = str_replace('\\' . $last_char, '\\\\', $word);
1877
-                        if (strpos($word, $last_char) < strlen($word) - 1) {
1878
-                            continue;
1879
-                        }
1880
-
1881
-                        $currentCount = $this->count;
1882
-
1883
-                        // let's try to rewind to previous char and try a parse
1884
-                        $this->count--;
1885
-                        // in case the keyword also eat spaces
1886
-                        while (substr($this->buffer, $this->count, 1) !== $last_char) {
1887
-                            $this->count--;
1888
-                        }
1889
-
1890
-                        $nextValue = null;
1891
-                        if ($this->$parseItem($nextValue)) {
1892
-                            if ($nextValue[0] === Type::T_KEYWORD && $nextValue[1] === $last_char) {
1893
-                                // bad try, forget it
1894
-                                $this->seek($currentCount);
1895
-                                continue;
1896
-                            }
1897
-                            if ($nextValue[0] !== Type::T_STRING) {
1898
-                                // bad try, forget it
1899
-                                $this->seek($currentCount);
1900
-                                continue;
1901
-                            }
1902
-
1903
-                            // OK it was a good idea
1904
-                            $value[1] = substr($value[1], 0, -1);
1905
-                            array_pop($items);
1906
-                            $items[] = $value;
1907
-                            $items[] = $nextValue;
1908
-                        } else {
1909
-                            // bad try, forget it
1910
-                            $this->seek($currentCount);
1911
-                            continue;
1912
-                        }
1913
-                    }
1914
-                }
1915
-            }
1916
-        }
1917
-
1918
-        if (! $items) {
1919
-            $this->seek($s);
1920
-
1921
-            return false;
1922
-        }
1923
-
1924
-        if ($trailing_delim) {
1925
-            $items[] = [Type::T_NULL];
1926
-        }
1927
-
1928
-        if ($flatten && \count($items) === 1) {
1929
-            $out = $items[0];
1930
-        } else {
1931
-            $out = [Type::T_LIST, $delim, $items];
1932
-        }
1933
-
1934
-        return true;
1935
-    }
1936
-
1937
-    /**
1938
-     * Parse expression
1939
-     *
1940
-     * @param array   $out
1941
-     * @param boolean $listOnly
1942
-     * @param boolean $lookForExp
1943
-     *
1944
-     * @return boolean
1945
-     */
1946
-    protected function expression(&$out, $listOnly = false, $lookForExp = true)
1947
-    {
1948
-        $s = $this->count;
1949
-        $discard = $this->discardComments;
1950
-        $this->discardComments = true;
1951
-        $allowedTypes = ($listOnly ? [Type::T_LIST] : [Type::T_LIST, Type::T_MAP]);
1952
-
1953
-        if ($this->matchChar('(')) {
1954
-            if ($this->enclosedExpression($lhs, $s, ')', $allowedTypes)) {
1955
-                if ($lookForExp) {
1956
-                    $out = $this->expHelper($lhs, 0);
1957
-                } else {
1958
-                    $out = $lhs;
1959
-                }
1960
-
1961
-                $this->discardComments = $discard;
1962
-
1963
-                return true;
1964
-            }
1965
-
1966
-            $this->seek($s);
1967
-        }
1968
-
1969
-        if (\in_array(Type::T_LIST, $allowedTypes) && $this->matchChar('[')) {
1970
-            if ($this->enclosedExpression($lhs, $s, ']', [Type::T_LIST])) {
1971
-                if ($lookForExp) {
1972
-                    $out = $this->expHelper($lhs, 0);
1973
-                } else {
1974
-                    $out = $lhs;
1975
-                }
1976
-
1977
-                $this->discardComments = $discard;
1978
-
1979
-                return true;
1980
-            }
1981
-
1982
-            $this->seek($s);
1983
-        }
1984
-
1985
-        if (! $listOnly && $this->value($lhs)) {
1986
-            if ($lookForExp) {
1987
-                $out = $this->expHelper($lhs, 0);
1988
-            } else {
1989
-                $out = $lhs;
1990
-            }
1991
-
1992
-            $this->discardComments = $discard;
1993
-
1994
-            return true;
1995
-        }
1996
-
1997
-        $this->discardComments = $discard;
1998
-
1999
-        return false;
2000
-    }
2001
-
2002
-    /**
2003
-     * Parse expression specifically checking for lists in parenthesis or brackets
2004
-     *
2005
-     * @param array   $out
2006
-     * @param integer $s
2007
-     * @param string  $closingParen
2008
-     * @param array   $allowedTypes
2009
-     *
2010
-     * @return boolean
2011
-     */
2012
-    protected function enclosedExpression(&$out, $s, $closingParen = ')', $allowedTypes = [Type::T_LIST, Type::T_MAP])
2013
-    {
2014
-        if ($this->matchChar($closingParen) && \in_array(Type::T_LIST, $allowedTypes)) {
2015
-            $out = [Type::T_LIST, '', []];
2016
-
2017
-            switch ($closingParen) {
2018
-                case ')':
2019
-                    $out['enclosing'] = 'parent'; // parenthesis list
2020
-                    break;
2021
-
2022
-                case ']':
2023
-                    $out['enclosing'] = 'bracket'; // bracketed list
2024
-                    break;
2025
-            }
2026
-
2027
-            return true;
2028
-        }
2029
-
2030
-        if (
2031
-            $this->valueList($out) &&
2032
-            $this->matchChar($closingParen) && ! ($closingParen === ')' &&
2033
-            \in_array($out[0], [Type::T_EXPRESSION, Type::T_UNARY])) &&
2034
-            \in_array(Type::T_LIST, $allowedTypes)
2035
-        ) {
2036
-            if ($out[0] !== Type::T_LIST || ! empty($out['enclosing'])) {
2037
-                $out = [Type::T_LIST, '', [$out]];
2038
-            }
2039
-
2040
-            switch ($closingParen) {
2041
-                case ')':
2042
-                    $out['enclosing'] = 'parent'; // parenthesis list
2043
-                    break;
2044
-
2045
-                case ']':
2046
-                    $out['enclosing'] = 'bracket'; // bracketed list
2047
-                    break;
2048
-            }
2049
-
2050
-            return true;
2051
-        }
2052
-
2053
-        $this->seek($s);
2054
-
2055
-        if (\in_array(Type::T_MAP, $allowedTypes) && $this->map($out)) {
2056
-            return true;
2057
-        }
2058
-
2059
-        return false;
2060
-    }
2061
-
2062
-    /**
2063
-     * Parse left-hand side of subexpression
2064
-     *
2065
-     * @param array   $lhs
2066
-     * @param integer $minP
2067
-     *
2068
-     * @return array
2069
-     */
2070
-    protected function expHelper($lhs, $minP)
2071
-    {
2072
-        $operators = static::$operatorPattern;
2073
-
2074
-        $ss = $this->count;
2075
-        $whiteBefore = isset($this->buffer[$this->count - 1]) &&
2076
-            ctype_space($this->buffer[$this->count - 1]);
2077
-
2078
-        while ($this->match($operators, $m, false) && static::$precedence[$m[1]] >= $minP) {
2079
-            $whiteAfter = isset($this->buffer[$this->count]) &&
2080
-                ctype_space($this->buffer[$this->count]);
2081
-            $varAfter = isset($this->buffer[$this->count]) &&
2082
-                $this->buffer[$this->count] === '$';
2083
-
2084
-            $this->whitespace();
2085
-
2086
-            $op = $m[1];
2087
-
2088
-            // don't turn negative numbers into expressions
2089
-            if ($op === '-' && $whiteBefore && ! $whiteAfter && ! $varAfter) {
2090
-                break;
2091
-            }
2092
-
2093
-            if (! $this->value($rhs) && ! $this->expression($rhs, true, false)) {
2094
-                break;
2095
-            }
2096
-
2097
-            // peek and see if rhs belongs to next operator
2098
-            if ($this->peek($operators, $next) && static::$precedence[$next[1]] > static::$precedence[$op]) {
2099
-                $rhs = $this->expHelper($rhs, static::$precedence[$next[1]]);
2100
-            }
2101
-
2102
-            $lhs = [Type::T_EXPRESSION, $op, $lhs, $rhs, $this->inParens, $whiteBefore, $whiteAfter];
2103
-            $ss = $this->count;
2104
-            $whiteBefore = isset($this->buffer[$this->count - 1]) &&
2105
-                ctype_space($this->buffer[$this->count - 1]);
2106
-        }
2107
-
2108
-        $this->seek($ss);
2109
-
2110
-        return $lhs;
2111
-    }
2112
-
2113
-    /**
2114
-     * Parse value
2115
-     *
2116
-     * @param array $out
2117
-     *
2118
-     * @return boolean
2119
-     */
2120
-    protected function value(&$out)
2121
-    {
2122
-        if (! isset($this->buffer[$this->count])) {
2123
-            return false;
2124
-        }
2125
-
2126
-        $s = $this->count;
2127
-        $char = $this->buffer[$this->count];
2128
-
2129
-        if (
2130
-            $this->literal('url(', 4) &&
2131
-            $this->match('data:([a-z]+)\/([a-z0-9.+-]+);base64,', $m, false)
2132
-        ) {
2133
-            $len = strspn(
2134
-                $this->buffer,
2135
-                'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwyxz0123456789+/=',
2136
-                $this->count
2137
-            );
29
+	const SOURCE_INDEX  = -1;
30
+	const SOURCE_LINE   = -2;
31
+	const SOURCE_COLUMN = -3;
32
+
33
+	/**
34
+	 * @var array
35
+	 */
36
+	protected static $precedence = [
37
+		'='   => 0,
38
+		'or'  => 1,
39
+		'and' => 2,
40
+		'=='  => 3,
41
+		'!='  => 3,
42
+		'<=>' => 3,
43
+		'<='  => 4,
44
+		'>='  => 4,
45
+		'<'   => 4,
46
+		'>'   => 4,
47
+		'+'   => 5,
48
+		'-'   => 5,
49
+		'*'   => 6,
50
+		'/'   => 6,
51
+		'%'   => 6,
52
+	];
53
+
54
+	protected static $commentPattern;
55
+	protected static $operatorPattern;
56
+	protected static $whitePattern;
57
+
58
+	protected $cache;
59
+
60
+	private $sourceName;
61
+	private $sourceIndex;
62
+	private $sourcePositions;
63
+	private $charset;
64
+	private $count;
65
+	private $env;
66
+	private $inParens;
67
+	private $eatWhiteDefault;
68
+	private $discardComments;
69
+	private $allowVars;
70
+	private $buffer;
71
+	private $utf8;
72
+	private $encoding;
73
+	private $patternModifiers;
74
+	private $commentsSeen;
75
+
76
+	private $cssOnly;
77
+
78
+	/**
79
+	 * Constructor
80
+	 *
81
+	 * @api
82
+	 *
83
+	 * @param string                 $sourceName
84
+	 * @param integer                $sourceIndex
85
+	 * @param string                 $encoding
86
+	 * @param \ScssPhp\ScssPhp\Cache $cache
87
+	 */
88
+	public function __construct($sourceName, $sourceIndex = 0, $encoding = 'utf-8', $cache = null, $cssOnly = false)
89
+	{
90
+		$this->sourceName       = $sourceName ?: '(stdin)';
91
+		$this->sourceIndex      = $sourceIndex;
92
+		$this->charset          = null;
93
+		$this->utf8             = ! $encoding || strtolower($encoding) === 'utf-8';
94
+		$this->patternModifiers = $this->utf8 ? 'Aisu' : 'Ais';
95
+		$this->commentsSeen     = [];
96
+		$this->commentsSeen     = [];
97
+		$this->allowVars        = true;
98
+		$this->cssOnly          = $cssOnly;
99
+
100
+		if (empty(static::$operatorPattern)) {
101
+			static::$operatorPattern = '([*\/%+-]|[!=]\=|\>\=?|\<\=\>|\<\=?|and|or)';
102
+
103
+			$commentSingle      = '\/\/';
104
+			$commentMultiLeft   = '\/\*';
105
+			$commentMultiRight  = '\*\/';
106
+
107
+			static::$commentPattern = $commentMultiLeft . '.*?' . $commentMultiRight;
108
+			static::$whitePattern = $this->utf8
109
+				? '/' . $commentSingle . '[^\n]*\s*|(' . static::$commentPattern . ')\s*|\s+/AisuS'
110
+				: '/' . $commentSingle . '[^\n]*\s*|(' . static::$commentPattern . ')\s*|\s+/AisS';
111
+		}
112
+
113
+		if ($cache) {
114
+			$this->cache = $cache;
115
+		}
116
+	}
117
+
118
+	/**
119
+	 * Get source file name
120
+	 *
121
+	 * @api
122
+	 *
123
+	 * @return string
124
+	 */
125
+	public function getSourceName()
126
+	{
127
+		return $this->sourceName;
128
+	}
129
+
130
+	/**
131
+	 * Throw parser error
132
+	 *
133
+	 * @api
134
+	 *
135
+	 * @param string $msg
136
+	 *
137
+	 * @throws \ScssPhp\ScssPhp\Exception\ParserException
138
+	 */
139
+	public function throwParseError($msg = 'parse error')
140
+	{
141
+		list($line, $column) = $this->getSourcePosition($this->count);
142
+
143
+		$loc = empty($this->sourceName)
144
+			 ? "line: $line, column: $column"
145
+			 : "$this->sourceName on line $line, at column $column";
146
+
147
+		if ($this->peek('(.*?)(\n|$)', $m, $this->count)) {
148
+			$this->restoreEncoding();
149
+
150
+			$e = new ParserException("$msg: failed at `$m[1]` $loc");
151
+			$e->setSourcePosition([$this->sourceName, $line, $column]);
152
+
153
+			throw $e;
154
+		}
155
+
156
+		$this->restoreEncoding();
157
+
158
+		$e = new ParserException("$msg: $loc");
159
+		$e->setSourcePosition([$this->sourceName, $line, $column]);
160
+
161
+		throw $e;
162
+	}
163
+
164
+	/**
165
+	 * Parser buffer
166
+	 *
167
+	 * @api
168
+	 *
169
+	 * @param string $buffer
170
+	 *
171
+	 * @return \ScssPhp\ScssPhp\Block
172
+	 */
173
+	public function parse($buffer)
174
+	{
175
+		if ($this->cache) {
176
+			$cacheKey = $this->sourceName . ':' . md5($buffer);
177
+			$parseOptions = [
178
+				'charset' => $this->charset,
179
+				'utf8' => $this->utf8,
180
+			];
181
+			$v = $this->cache->getCache('parse', $cacheKey, $parseOptions);
182
+
183
+			if (! \is_null($v)) {
184
+				return $v;
185
+			}
186
+		}
187
+
188
+		// strip BOM (byte order marker)
189
+		if (substr($buffer, 0, 3) === "\xef\xbb\xbf") {
190
+			$buffer = substr($buffer, 3);
191
+		}
192
+
193
+		$this->buffer          = rtrim($buffer, "\x00..\x1f");
194
+		$this->count           = 0;
195
+		$this->env             = null;
196
+		$this->inParens        = false;
197
+		$this->eatWhiteDefault = true;
198
+
199
+		$this->saveEncoding();
200
+		$this->extractLineNumbers($buffer);
201
+
202
+		$this->pushBlock(null); // root block
203
+		$this->whitespace();
204
+		$this->pushBlock(null);
205
+		$this->popBlock();
206
+
207
+		while ($this->parseChunk()) {
208
+			;
209
+		}
210
+
211
+		if ($this->count !== \strlen($this->buffer)) {
212
+			$this->throwParseError();
213
+		}
214
+
215
+		if (! empty($this->env->parent)) {
216
+			$this->throwParseError('unclosed block');
217
+		}
218
+
219
+		if ($this->charset) {
220
+			array_unshift($this->env->children, $this->charset);
221
+		}
222
+
223
+		$this->restoreEncoding();
224
+
225
+		if ($this->cache) {
226
+			$this->cache->setCache('parse', $cacheKey, $this->env, $parseOptions);
227
+		}
228
+
229
+		return $this->env;
230
+	}
231
+
232
+	/**
233
+	 * Parse a value or value list
234
+	 *
235
+	 * @api
236
+	 *
237
+	 * @param string       $buffer
238
+	 * @param string|array $out
239
+	 *
240
+	 * @return boolean
241
+	 */
242
+	public function parseValue($buffer, &$out)
243
+	{
244
+		$this->count           = 0;
245
+		$this->env             = null;
246
+		$this->inParens        = false;
247
+		$this->eatWhiteDefault = true;
248
+		$this->buffer          = (string) $buffer;
249
+
250
+		$this->saveEncoding();
251
+
252
+		$list = $this->valueList($out);
253
+
254
+		$this->restoreEncoding();
255
+
256
+		return $list;
257
+	}
258
+
259
+	/**
260
+	 * Parse a selector or selector list
261
+	 *
262
+	 * @api
263
+	 *
264
+	 * @param string       $buffer
265
+	 * @param string|array $out
266
+	 *
267
+	 * @return boolean
268
+	 */
269
+	public function parseSelector($buffer, &$out)
270
+	{
271
+		$this->count           = 0;
272
+		$this->env             = null;
273
+		$this->inParens        = false;
274
+		$this->eatWhiteDefault = true;
275
+		$this->buffer          = (string) $buffer;
276
+
277
+		$this->saveEncoding();
278
+
279
+		$selector = $this->selectors($out);
280
+
281
+		$this->restoreEncoding();
282
+
283
+		return $selector;
284
+	}
285
+
286
+	/**
287
+	 * Parse a media Query
288
+	 *
289
+	 * @api
290
+	 *
291
+	 * @param string       $buffer
292
+	 * @param string|array $out
293
+	 *
294
+	 * @return boolean
295
+	 */
296
+	public function parseMediaQueryList($buffer, &$out)
297
+	{
298
+		$this->count           = 0;
299
+		$this->env             = null;
300
+		$this->inParens        = false;
301
+		$this->eatWhiteDefault = true;
302
+		$this->buffer          = (string) $buffer;
303
+
304
+		$this->saveEncoding();
305
+
306
+		$isMediaQuery = $this->mediaQueryList($out);
307
+
308
+		$this->restoreEncoding();
309
+
310
+		return $isMediaQuery;
311
+	}
312
+
313
+	/**
314
+	 * Parse a single chunk off the head of the buffer and append it to the
315
+	 * current parse environment.
316
+	 *
317
+	 * Returns false when the buffer is empty, or when there is an error.
318
+	 *
319
+	 * This function is called repeatedly until the entire document is
320
+	 * parsed.
321
+	 *
322
+	 * This parser is most similar to a recursive descent parser. Single
323
+	 * functions represent discrete grammatical rules for the language, and
324
+	 * they are able to capture the text that represents those rules.
325
+	 *
326
+	 * Consider the function Compiler::keyword(). (All parse functions are
327
+	 * structured the same.)
328
+	 *
329
+	 * The function takes a single reference argument. When calling the
330
+	 * function it will attempt to match a keyword on the head of the buffer.
331
+	 * If it is successful, it will place the keyword in the referenced
332
+	 * argument, advance the position in the buffer, and return true. If it
333
+	 * fails then it won't advance the buffer and it will return false.
334
+	 *
335
+	 * All of these parse functions are powered by Compiler::match(), which behaves
336
+	 * the same way, but takes a literal regular expression. Sometimes it is
337
+	 * more convenient to use match instead of creating a new function.
338
+	 *
339
+	 * Because of the format of the functions, to parse an entire string of
340
+	 * grammatical rules, you can chain them together using &&.
341
+	 *
342
+	 * But, if some of the rules in the chain succeed before one fails, then
343
+	 * the buffer position will be left at an invalid state. In order to
344
+	 * avoid this, Compiler::seek() is used to remember and set buffer positions.
345
+	 *
346
+	 * Before parsing a chain, use $s = $this->count to remember the current
347
+	 * position into $s. Then if a chain fails, use $this->seek($s) to
348
+	 * go back where we started.
349
+	 *
350
+	 * @return boolean
351
+	 */
352
+	protected function parseChunk()
353
+	{
354
+		$s = $this->count;
355
+
356
+		// the directives
357
+		if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] === '@') {
358
+			if (
359
+				$this->literal('@at-root', 8) &&
360
+				($this->selectors($selector) || true) &&
361
+				($this->map($with) || true) &&
362
+				(($this->matchChar('(') &&
363
+					$this->interpolation($with) &&
364
+					$this->matchChar(')')) || true) &&
365
+				$this->matchChar('{', false)
366
+			) {
367
+				if ($this->cssOnly) {
368
+					$this->throwParseError('SCSS syntax not allowed in CSS file');
369
+				}
370
+
371
+				$atRoot = $this->pushSpecialBlock(Type::T_AT_ROOT, $s);
372
+				$atRoot->selector = $selector;
373
+				$atRoot->with     = $with;
374
+
375
+				return true;
376
+			}
377
+
378
+			$this->seek($s);
379
+
380
+			if (
381
+				$this->literal('@media', 6) &&
382
+				$this->mediaQueryList($mediaQueryList) &&
383
+				$this->matchChar('{', false)
384
+			) {
385
+				$media = $this->pushSpecialBlock(Type::T_MEDIA, $s);
386
+				$media->queryList = $mediaQueryList[2];
387
+
388
+				return true;
389
+			}
390
+
391
+			$this->seek($s);
392
+
393
+			if (
394
+				$this->literal('@mixin', 6) &&
395
+				$this->keyword($mixinName) &&
396
+				($this->argumentDef($args) || true) &&
397
+				$this->matchChar('{', false)
398
+			) {
399
+				if ($this->cssOnly) {
400
+					$this->throwParseError('SCSS syntax not allowed in CSS file');
401
+				}
402
+
403
+				$mixin = $this->pushSpecialBlock(Type::T_MIXIN, $s);
404
+				$mixin->name = $mixinName;
405
+				$mixin->args = $args;
406
+
407
+				return true;
408
+			}
409
+
410
+			$this->seek($s);
411
+
412
+			if (
413
+				($this->literal('@include', 8) &&
414
+					$this->keyword($mixinName) &&
415
+					($this->matchChar('(') &&
416
+					($this->argValues($argValues) || true) &&
417
+					$this->matchChar(')') || true) &&
418
+					($this->end()) ||
419
+				($this->literal('using', 5) &&
420
+					$this->argumentDef($argUsing) &&
421
+					($this->end() || $this->matchChar('{') && $hasBlock = true)) ||
422
+				$this->matchChar('{') && $hasBlock = true)
423
+			) {
424
+				if ($this->cssOnly) {
425
+					$this->throwParseError('SCSS syntax not allowed in CSS file');
426
+				}
427
+
428
+				$child = [
429
+					Type::T_INCLUDE,
430
+					$mixinName,
431
+					isset($argValues) ? $argValues : null,
432
+					null,
433
+					isset($argUsing) ? $argUsing : null
434
+				];
435
+
436
+				if (! empty($hasBlock)) {
437
+					$include = $this->pushSpecialBlock(Type::T_INCLUDE, $s);
438
+					$include->child = $child;
439
+				} else {
440
+					$this->append($child, $s);
441
+				}
442
+
443
+				return true;
444
+			}
445
+
446
+			$this->seek($s);
447
+
448
+			if (
449
+				$this->literal('@scssphp-import-once', 20) &&
450
+				$this->valueList($importPath) &&
451
+				$this->end()
452
+			) {
453
+				if ($this->cssOnly) {
454
+					$this->throwParseError('SCSS syntax not allowed in CSS file');
455
+				}
456
+
457
+				$this->append([Type::T_SCSSPHP_IMPORT_ONCE, $importPath], $s);
458
+
459
+				return true;
460
+			}
461
+
462
+			$this->seek($s);
463
+
464
+			if (
465
+				$this->literal('@import', 7) &&
466
+				$this->valueList($importPath) &&
467
+				$importPath[0] !== Type::T_FUNCTION_CALL &&
468
+				$this->end()
469
+			) {
470
+				$this->append([Type::T_IMPORT, $importPath], $s);
471
+
472
+				return true;
473
+			}
474
+
475
+			$this->seek($s);
476
+
477
+			if (
478
+				$this->literal('@import', 7) &&
479
+				$this->url($importPath) &&
480
+				$this->end()
481
+			) {
482
+				if ($this->cssOnly) {
483
+					$this->throwParseError('SCSS syntax not allowed in CSS file');
484
+				}
485
+
486
+				$this->append([Type::T_IMPORT, $importPath], $s);
487
+
488
+				return true;
489
+			}
490
+
491
+			$this->seek($s);
492
+
493
+			if (
494
+				$this->literal('@extend', 7) &&
495
+				$this->selectors($selectors) &&
496
+				$this->end()
497
+			) {
498
+				if ($this->cssOnly) {
499
+					$this->throwParseError('SCSS syntax not allowed in CSS file');
500
+				}
501
+
502
+				// check for '!flag'
503
+				$optional = $this->stripOptionalFlag($selectors);
504
+				$this->append([Type::T_EXTEND, $selectors, $optional], $s);
505
+
506
+				return true;
507
+			}
508
+
509
+			$this->seek($s);
510
+
511
+			if (
512
+				$this->literal('@function', 9) &&
513
+				$this->keyword($fnName) &&
514
+				$this->argumentDef($args) &&
515
+				$this->matchChar('{', false)
516
+			) {
517
+				if ($this->cssOnly) {
518
+					$this->throwParseError('SCSS syntax not allowed in CSS file');
519
+				}
520
+
521
+				$func = $this->pushSpecialBlock(Type::T_FUNCTION, $s);
522
+				$func->name = $fnName;
523
+				$func->args = $args;
524
+
525
+				return true;
526
+			}
527
+
528
+			$this->seek($s);
529
+
530
+			if (
531
+				$this->literal('@break', 6) &&
532
+				$this->end()
533
+			) {
534
+				if ($this->cssOnly) {
535
+					$this->throwParseError('SCSS syntax not allowed in CSS file');
536
+				}
537
+
538
+				$this->append([Type::T_BREAK], $s);
539
+
540
+				return true;
541
+			}
542
+
543
+			$this->seek($s);
544
+
545
+			if (
546
+				$this->literal('@continue', 9) &&
547
+				$this->end()
548
+			) {
549
+				if ($this->cssOnly) {
550
+					$this->throwParseError('SCSS syntax not allowed in CSS file');
551
+				}
552
+
553
+				$this->append([Type::T_CONTINUE], $s);
554
+
555
+				return true;
556
+			}
557
+
558
+			$this->seek($s);
559
+
560
+			if (
561
+				$this->literal('@return', 7) &&
562
+				($this->valueList($retVal) || true) &&
563
+				$this->end()
564
+			) {
565
+				if ($this->cssOnly) {
566
+					$this->throwParseError('SCSS syntax not allowed in CSS file');
567
+				}
568
+
569
+				$this->append([Type::T_RETURN, isset($retVal) ? $retVal : [Type::T_NULL]], $s);
570
+
571
+				return true;
572
+			}
573
+
574
+			$this->seek($s);
575
+
576
+			if (
577
+				$this->literal('@each', 5) &&
578
+				$this->genericList($varNames, 'variable', ',', false) &&
579
+				$this->literal('in', 2) &&
580
+				$this->valueList($list) &&
581
+				$this->matchChar('{', false)
582
+			) {
583
+				if ($this->cssOnly) {
584
+					$this->throwParseError('SCSS syntax not allowed in CSS file');
585
+				}
586
+
587
+				$each = $this->pushSpecialBlock(Type::T_EACH, $s);
588
+
589
+				foreach ($varNames[2] as $varName) {
590
+					$each->vars[] = $varName[1];
591
+				}
592
+
593
+				$each->list = $list;
594
+
595
+				return true;
596
+			}
597
+
598
+			$this->seek($s);
599
+
600
+			if (
601
+				$this->literal('@while', 6) &&
602
+				$this->expression($cond) &&
603
+				$this->matchChar('{', false)
604
+			) {
605
+				if ($this->cssOnly) {
606
+					$this->throwParseError('SCSS syntax not allowed in CSS file');
607
+				}
608
+
609
+				while (
610
+					$cond[0] === Type::T_LIST &&
611
+					! empty($cond['enclosing']) &&
612
+					$cond['enclosing'] === 'parent' &&
613
+					\count($cond[2]) == 1
614
+				) {
615
+					$cond = reset($cond[2]);
616
+				}
617
+
618
+				$while = $this->pushSpecialBlock(Type::T_WHILE, $s);
619
+				$while->cond = $cond;
620
+
621
+				return true;
622
+			}
623
+
624
+			$this->seek($s);
625
+
626
+			if (
627
+				$this->literal('@for', 4) &&
628
+				$this->variable($varName) &&
629
+				$this->literal('from', 4) &&
630
+				$this->expression($start) &&
631
+				($this->literal('through', 7) ||
632
+					($forUntil = true && $this->literal('to', 2))) &&
633
+				$this->expression($end) &&
634
+				$this->matchChar('{', false)
635
+			) {
636
+				if ($this->cssOnly) {
637
+					$this->throwParseError('SCSS syntax not allowed in CSS file');
638
+				}
639
+
640
+				$for = $this->pushSpecialBlock(Type::T_FOR, $s);
641
+				$for->var   = $varName[1];
642
+				$for->start = $start;
643
+				$for->end   = $end;
644
+				$for->until = isset($forUntil);
645
+
646
+				return true;
647
+			}
648
+
649
+			$this->seek($s);
650
+
651
+			if (
652
+				$this->literal('@if', 3) &&
653
+				$this->functionCallArgumentsList($cond, false) &&
654
+				$this->matchChar('{', false)
655
+			) {
656
+				if ($this->cssOnly) {
657
+					$this->throwParseError('SCSS syntax not allowed in CSS file');
658
+				}
659
+
660
+				$if = $this->pushSpecialBlock(Type::T_IF, $s);
661
+
662
+				while (
663
+					$cond[0] === Type::T_LIST &&
664
+					! empty($cond['enclosing']) &&
665
+					$cond['enclosing'] === 'parent' &&
666
+					\count($cond[2]) == 1
667
+				) {
668
+					$cond = reset($cond[2]);
669
+				}
670
+
671
+				$if->cond  = $cond;
672
+				$if->cases = [];
673
+
674
+				return true;
675
+			}
676
+
677
+			$this->seek($s);
678
+
679
+			if (
680
+				$this->literal('@debug', 6) &&
681
+				$this->functionCallArgumentsList($value, false) &&
682
+				$this->end()
683
+			) {
684
+				if ($this->cssOnly) {
685
+					$this->throwParseError('SCSS syntax not allowed in CSS file');
686
+				}
687
+
688
+				$this->append([Type::T_DEBUG, $value], $s);
689
+
690
+				return true;
691
+			}
692
+
693
+			$this->seek($s);
694
+
695
+			if (
696
+				$this->literal('@warn', 5) &&
697
+				$this->functionCallArgumentsList($value, false) &&
698
+				$this->end()
699
+			) {
700
+				if ($this->cssOnly) {
701
+					$this->throwParseError('SCSS syntax not allowed in CSS file');
702
+				}
703
+
704
+				$this->append([Type::T_WARN, $value], $s);
705
+
706
+				return true;
707
+			}
708
+
709
+			$this->seek($s);
710
+
711
+			if (
712
+				$this->literal('@error', 6) &&
713
+				$this->functionCallArgumentsList($value, false) &&
714
+				$this->end()
715
+			) {
716
+				if ($this->cssOnly) {
717
+					$this->throwParseError('SCSS syntax not allowed in CSS file');
718
+				}
719
+
720
+				$this->append([Type::T_ERROR, $value], $s);
721
+
722
+				return true;
723
+			}
724
+
725
+			$this->seek($s);
726
+
727
+			if (
728
+				$this->literal('@content', 8) &&
729
+				($this->end() ||
730
+					$this->matchChar('(') &&
731
+					$this->argValues($argContent) &&
732
+					$this->matchChar(')') &&
733
+					$this->end())
734
+			) {
735
+				if ($this->cssOnly) {
736
+					$this->throwParseError('SCSS syntax not allowed in CSS file');
737
+				}
738
+
739
+				$this->append([Type::T_MIXIN_CONTENT, isset($argContent) ? $argContent : null], $s);
740
+
741
+				return true;
742
+			}
743
+
744
+			$this->seek($s);
745
+
746
+			$last = $this->last();
747
+
748
+			if (isset($last) && $last[0] === Type::T_IF) {
749
+				list(, $if) = $last;
750
+
751
+				if ($this->literal('@else', 5)) {
752
+					if ($this->matchChar('{', false)) {
753
+						$else = $this->pushSpecialBlock(Type::T_ELSE, $s);
754
+					} elseif (
755
+						$this->literal('if', 2) &&
756
+						$this->valueList($cond) &&
757
+						$this->matchChar('{', false)
758
+					) {
759
+						$else = $this->pushSpecialBlock(Type::T_ELSEIF, $s);
760
+						$else->cond = $cond;
761
+					}
762
+
763
+					if (isset($else)) {
764
+						$else->dontAppend = true;
765
+						$if->cases[] = $else;
766
+
767
+						return true;
768
+					}
769
+				}
770
+
771
+				$this->seek($s);
772
+			}
773
+
774
+			// only retain the first @charset directive encountered
775
+			if (
776
+				$this->literal('@charset', 8) &&
777
+				$this->valueList($charset) &&
778
+				$this->end()
779
+			) {
780
+				if (! isset($this->charset)) {
781
+					$statement = [Type::T_CHARSET, $charset];
782
+
783
+					list($line, $column) = $this->getSourcePosition($s);
784
+
785
+					$statement[static::SOURCE_LINE]   = $line;
786
+					$statement[static::SOURCE_COLUMN] = $column;
787
+					$statement[static::SOURCE_INDEX]  = $this->sourceIndex;
788
+
789
+					$this->charset = $statement;
790
+				}
791
+
792
+				return true;
793
+			}
794
+
795
+			$this->seek($s);
796
+
797
+			if (
798
+				$this->literal('@supports', 9) &&
799
+				($t1 = $this->supportsQuery($supportQuery)) &&
800
+				($t2 = $this->matchChar('{', false))
801
+			) {
802
+				$directive = $this->pushSpecialBlock(Type::T_DIRECTIVE, $s);
803
+				$directive->name  = 'supports';
804
+				$directive->value = $supportQuery;
805
+
806
+				return true;
807
+			}
808
+
809
+			$this->seek($s);
810
+
811
+			// doesn't match built in directive, do generic one
812
+			if (
813
+				$this->matchChar('@', false) &&
814
+				$this->keyword($dirName) &&
815
+				$this->directiveValue($dirValue, '{')
816
+			) {
817
+				if ($dirName === 'media') {
818
+					$directive = $this->pushSpecialBlock(Type::T_MEDIA, $s);
819
+				} else {
820
+					$directive = $this->pushSpecialBlock(Type::T_DIRECTIVE, $s);
821
+					$directive->name = $dirName;
822
+				}
823
+
824
+				if (isset($dirValue)) {
825
+					$directive->value = $dirValue;
826
+				}
827
+
828
+				return true;
829
+			}
830
+
831
+			$this->seek($s);
832
+
833
+			// maybe it's a generic blockless directive
834
+			if (
835
+				$this->matchChar('@', false) &&
836
+				$this->keyword($dirName) &&
837
+				$this->directiveValue($dirValue) &&
838
+				$this->end()
839
+			) {
840
+				$this->append([Type::T_DIRECTIVE, [$dirName, $dirValue]], $s);
841
+
842
+				return true;
843
+			}
844
+
845
+			$this->seek($s);
846
+
847
+			return false;
848
+		}
849
+
850
+		$inCssSelector = null;
851
+		if ($this->cssOnly) {
852
+			$inCssSelector = (! empty($this->env->parent) &&
853
+				! in_array($this->env->type, [Type::T_DIRECTIVE, Type::T_MEDIA]));
854
+		}
855
+		// custom properties : right part is static
856
+		if (
857
+			($this->customProperty($name) ||
858
+				($inCssSelector && $this->propertyName($name))) &&
859
+			$this->matchChar(':', false)
860
+		) {
861
+			$start = $this->count;
862
+
863
+			// but can be complex and finish with ; or }
864
+			foreach ([';','}'] as $ending) {
865
+				if (
866
+					$this->openString($ending, $stringValue, '(', ')', false) &&
867
+					$this->end()
868
+				) {
869
+					$end = $this->count;
870
+					$value = $stringValue;
871
+
872
+					// check if we have only a partial value due to nested [] or { } to take in account
873
+					$nestingPairs = [['[', ']'], ['{', '}']];
874
+
875
+					foreach ($nestingPairs as $nestingPair) {
876
+						$p = strpos($this->buffer, $nestingPair[0], $start);
877
+
878
+						if ($p && $p < $end) {
879
+							$this->seek($start);
880
+
881
+							if (
882
+								$this->openString($ending, $stringValue, $nestingPair[0], $nestingPair[1], false) &&
883
+								$this->end() &&
884
+								$this->count > $end
885
+							) {
886
+								$end = $this->count;
887
+								$value = $stringValue;
888
+							}
889
+						}
890
+					}
891
+
892
+					$this->seek($end);
893
+					$this->append([Type::T_CUSTOM_PROPERTY, $name, $value], $s);
894
+
895
+					return true;
896
+				}
897
+			}
898
+
899
+			// TODO: output an error here if nothing found according to sass spec
900
+		}
901
+
902
+		$this->seek($s);
903
+
904
+		// property shortcut
905
+		// captures most properties before having to parse a selector
906
+		if (
907
+			$this->keyword($name, false) &&
908
+			$this->literal(': ', 2) &&
909
+			$this->valueList($value) &&
910
+			$this->end()
911
+		) {
912
+			$name = [Type::T_STRING, '', [$name]];
913
+			$this->append([Type::T_ASSIGN, $name, $value], $s);
914
+
915
+			return true;
916
+		}
917
+
918
+		$this->seek($s);
919
+
920
+		// variable assigns
921
+		if (
922
+			$this->variable($name) &&
923
+			$this->matchChar(':') &&
924
+			$this->valueList($value) &&
925
+			$this->end()
926
+		) {
927
+			if ($this->cssOnly) {
928
+				$this->throwParseError('SCSS syntax not allowed in CSS file');
929
+			}
930
+
931
+			// check for '!flag'
932
+			$assignmentFlags = $this->stripAssignmentFlags($value);
933
+			$this->append([Type::T_ASSIGN, $name, $value, $assignmentFlags], $s);
934
+
935
+			return true;
936
+		}
937
+
938
+		$this->seek($s);
939
+
940
+		// misc
941
+		if ($this->literal('-->', 3)) {
942
+			return true;
943
+		}
944
+
945
+		// opening css block
946
+		if (
947
+			$this->selectors($selectors) &&
948
+			$this->matchChar('{', false)
949
+		) {
950
+			if ($this->cssOnly) {
951
+				if ($inCssSelector) {
952
+					$this->throwParseError('SCSS syntax not allowed in CSS file');
953
+				}
954
+			}
955
+
956
+			$this->pushBlock($selectors, $s);
957
+
958
+			if ($this->eatWhiteDefault) {
959
+				$this->whitespace();
960
+				$this->append(null); // collect comments at the beginning if needed
961
+			}
962
+
963
+			return true;
964
+		}
965
+
966
+		$this->seek($s);
967
+
968
+		// property assign, or nested assign
969
+		if (
970
+			$this->propertyName($name) &&
971
+			$this->matchChar(':')
972
+		) {
973
+			$foundSomething = false;
974
+
975
+			if ($this->valueList($value)) {
976
+				if (empty($this->env->parent)) {
977
+					$this->throwParseError('expected "{"');
978
+				}
979
+
980
+				$this->append([Type::T_ASSIGN, $name, $value], $s);
981
+				$foundSomething = true;
982
+			}
983
+
984
+			if ($this->matchChar('{', false)) {
985
+				if ($this->cssOnly) {
986
+					$this->throwParseError('SCSS syntax not allowed in CSS file');
987
+				}
988
+
989
+				$propBlock = $this->pushSpecialBlock(Type::T_NESTED_PROPERTY, $s);
990
+				$propBlock->prefix = $name;
991
+				$propBlock->hasValue = $foundSomething;
992
+
993
+				$foundSomething = true;
994
+			} elseif ($foundSomething) {
995
+				$foundSomething = $this->end();
996
+			}
997
+
998
+			if ($foundSomething) {
999
+				return true;
1000
+			}
1001
+		}
1002
+
1003
+		$this->seek($s);
1004
+
1005
+		// closing a block
1006
+		if ($this->matchChar('}', false)) {
1007
+			$block = $this->popBlock();
1008
+
1009
+			if (! isset($block->type) || $block->type !== Type::T_IF) {
1010
+				if ($this->env->parent) {
1011
+					$this->append(null); // collect comments before next statement if needed
1012
+				}
1013
+			}
1014
+
1015
+			if (isset($block->type) && $block->type === Type::T_INCLUDE) {
1016
+				$include = $block->child;
1017
+				unset($block->child);
1018
+				$include[3] = $block;
1019
+				$this->append($include, $s);
1020
+			} elseif (empty($block->dontAppend)) {
1021
+				$type = isset($block->type) ? $block->type : Type::T_BLOCK;
1022
+				$this->append([$type, $block], $s);
1023
+			}
1024
+
1025
+			// collect comments just after the block closing if needed
1026
+			if ($this->eatWhiteDefault) {
1027
+				$this->whitespace();
1028
+
1029
+				if ($this->env->comments) {
1030
+					$this->append(null);
1031
+				}
1032
+			}
1033
+
1034
+			return true;
1035
+		}
1036
+
1037
+		// extra stuff
1038
+		if (
1039
+			$this->matchChar(';') ||
1040
+			$this->literal('<!--', 4)
1041
+		) {
1042
+			return true;
1043
+		}
1044
+
1045
+		return false;
1046
+	}
1047
+
1048
+	/**
1049
+	 * Push block onto parse tree
1050
+	 *
1051
+	 * @param array   $selectors
1052
+	 * @param integer $pos
1053
+	 *
1054
+	 * @return \ScssPhp\ScssPhp\Block
1055
+	 */
1056
+	protected function pushBlock($selectors, $pos = 0)
1057
+	{
1058
+		list($line, $column) = $this->getSourcePosition($pos);
1059
+
1060
+		$b = new Block();
1061
+		$b->sourceName   = $this->sourceName;
1062
+		$b->sourceLine   = $line;
1063
+		$b->sourceColumn = $column;
1064
+		$b->sourceIndex  = $this->sourceIndex;
1065
+		$b->selectors    = $selectors;
1066
+		$b->comments     = [];
1067
+		$b->parent       = $this->env;
1068
+
1069
+		if (! $this->env) {
1070
+			$b->children = [];
1071
+		} elseif (empty($this->env->children)) {
1072
+			$this->env->children = $this->env->comments;
1073
+			$b->children = [];
1074
+			$this->env->comments = [];
1075
+		} else {
1076
+			$b->children = $this->env->comments;
1077
+			$this->env->comments = [];
1078
+		}
1079
+
1080
+		$this->env = $b;
1081
+
1082
+		// collect comments at the beginning of a block if needed
1083
+		if ($this->eatWhiteDefault) {
1084
+			$this->whitespace();
1085
+
1086
+			if ($this->env->comments) {
1087
+				$this->append(null);
1088
+			}
1089
+		}
1090
+
1091
+		return $b;
1092
+	}
1093
+
1094
+	/**
1095
+	 * Push special (named) block onto parse tree
1096
+	 *
1097
+	 * @param string  $type
1098
+	 * @param integer $pos
1099
+	 *
1100
+	 * @return \ScssPhp\ScssPhp\Block
1101
+	 */
1102
+	protected function pushSpecialBlock($type, $pos)
1103
+	{
1104
+		$block = $this->pushBlock(null, $pos);
1105
+		$block->type = $type;
1106
+
1107
+		return $block;
1108
+	}
1109
+
1110
+	/**
1111
+	 * Pop scope and return last block
1112
+	 *
1113
+	 * @return \ScssPhp\ScssPhp\Block
1114
+	 *
1115
+	 * @throws \Exception
1116
+	 */
1117
+	protected function popBlock()
1118
+	{
1119
+
1120
+		// collect comments ending just before of a block closing
1121
+		if ($this->env->comments) {
1122
+			$this->append(null);
1123
+		}
1124
+
1125
+		// pop the block
1126
+		$block = $this->env;
1127
+
1128
+		if (empty($block->parent)) {
1129
+			$this->throwParseError('unexpected }');
1130
+		}
1131
+
1132
+		if ($block->type == Type::T_AT_ROOT) {
1133
+			// keeps the parent in case of self selector &
1134
+			$block->selfParent = $block->parent;
1135
+		}
1136
+
1137
+		$this->env = $block->parent;
1138
+
1139
+		unset($block->parent);
1140
+
1141
+		return $block;
1142
+	}
1143
+
1144
+	/**
1145
+	 * Peek input stream
1146
+	 *
1147
+	 * @param string  $regex
1148
+	 * @param array   $out
1149
+	 * @param integer $from
1150
+	 *
1151
+	 * @return integer
1152
+	 */
1153
+	protected function peek($regex, &$out, $from = null)
1154
+	{
1155
+		if (! isset($from)) {
1156
+			$from = $this->count;
1157
+		}
1158
+
1159
+		$r = '/' . $regex . '/' . $this->patternModifiers;
1160
+		$result = preg_match($r, $this->buffer, $out, null, $from);
1161
+
1162
+		return $result;
1163
+	}
1164
+
1165
+	/**
1166
+	 * Seek to position in input stream (or return current position in input stream)
1167
+	 *
1168
+	 * @param integer $where
1169
+	 */
1170
+	protected function seek($where)
1171
+	{
1172
+		$this->count = $where;
1173
+	}
1174
+
1175
+	/**
1176
+	 * Match string looking for either ending delim, escape, or string interpolation
1177
+	 *
1178
+	 * {@internal This is a workaround for preg_match's 250K string match limit. }}
1179
+	 *
1180
+	 * @param array  $m     Matches (passed by reference)
1181
+	 * @param string $delim Delimiter
1182
+	 *
1183
+	 * @return boolean True if match; false otherwise
1184
+	 */
1185
+	protected function matchString(&$m, $delim)
1186
+	{
1187
+		$token = null;
1188
+
1189
+		$end = \strlen($this->buffer);
1190
+
1191
+		// look for either ending delim, escape, or string interpolation
1192
+		foreach (['#{', '\\', "\r", $delim] as $lookahead) {
1193
+			$pos = strpos($this->buffer, $lookahead, $this->count);
1194
+
1195
+			if ($pos !== false && $pos < $end) {
1196
+				$end = $pos;
1197
+				$token = $lookahead;
1198
+			}
1199
+		}
1200
+
1201
+		if (! isset($token)) {
1202
+			return false;
1203
+		}
1204
+
1205
+		$match = substr($this->buffer, $this->count, $end - $this->count);
1206
+		$m = [
1207
+			$match . $token,
1208
+			$match,
1209
+			$token
1210
+		];
1211
+		$this->count = $end + \strlen($token);
1212
+
1213
+		return true;
1214
+	}
1215
+
1216
+	/**
1217
+	 * Try to match something on head of buffer
1218
+	 *
1219
+	 * @param string  $regex
1220
+	 * @param array   $out
1221
+	 * @param boolean $eatWhitespace
1222
+	 *
1223
+	 * @return boolean
1224
+	 */
1225
+	protected function match($regex, &$out, $eatWhitespace = null)
1226
+	{
1227
+		$r = '/' . $regex . '/' . $this->patternModifiers;
1228
+
1229
+		if (! preg_match($r, $this->buffer, $out, null, $this->count)) {
1230
+			return false;
1231
+		}
1232
+
1233
+		$this->count += \strlen($out[0]);
1234
+
1235
+		if (! isset($eatWhitespace)) {
1236
+			$eatWhitespace = $this->eatWhiteDefault;
1237
+		}
1238
+
1239
+		if ($eatWhitespace) {
1240
+			$this->whitespace();
1241
+		}
1242
+
1243
+		return true;
1244
+	}
1245
+
1246
+	/**
1247
+	 * Match a single string
1248
+	 *
1249
+	 * @param string  $char
1250
+	 * @param boolean $eatWhitespace
1251
+	 *
1252
+	 * @return boolean
1253
+	 */
1254
+	protected function matchChar($char, $eatWhitespace = null)
1255
+	{
1256
+		if (! isset($this->buffer[$this->count]) || $this->buffer[$this->count] !== $char) {
1257
+			return false;
1258
+		}
1259
+
1260
+		$this->count++;
1261
+
1262
+		if (! isset($eatWhitespace)) {
1263
+			$eatWhitespace = $this->eatWhiteDefault;
1264
+		}
1265
+
1266
+		if ($eatWhitespace) {
1267
+			$this->whitespace();
1268
+		}
1269
+
1270
+		return true;
1271
+	}
1272
+
1273
+	/**
1274
+	 * Match literal string
1275
+	 *
1276
+	 * @param string  $what
1277
+	 * @param integer $len
1278
+	 * @param boolean $eatWhitespace
1279
+	 *
1280
+	 * @return boolean
1281
+	 */
1282
+	protected function literal($what, $len, $eatWhitespace = null)
1283
+	{
1284
+		if (strcasecmp(substr($this->buffer, $this->count, $len), $what) !== 0) {
1285
+			return false;
1286
+		}
1287
+
1288
+		$this->count += $len;
1289
+
1290
+		if (! isset($eatWhitespace)) {
1291
+			$eatWhitespace = $this->eatWhiteDefault;
1292
+		}
1293
+
1294
+		if ($eatWhitespace) {
1295
+			$this->whitespace();
1296
+		}
1297
+
1298
+		return true;
1299
+	}
1300
+
1301
+	/**
1302
+	 * Match some whitespace
1303
+	 *
1304
+	 * @return boolean
1305
+	 */
1306
+	protected function whitespace()
1307
+	{
1308
+		$gotWhite = false;
1309
+
1310
+		while (preg_match(static::$whitePattern, $this->buffer, $m, null, $this->count)) {
1311
+			if (isset($m[1]) && empty($this->commentsSeen[$this->count])) {
1312
+				// comment that are kept in the output CSS
1313
+				$comment = [];
1314
+				$startCommentCount = $this->count;
1315
+				$endCommentCount = $this->count + \strlen($m[1]);
1316
+
1317
+				// find interpolations in comment
1318
+				$p = strpos($this->buffer, '#{', $this->count);
1319
+
1320
+				while ($p !== false && $p < $endCommentCount) {
1321
+					$c           = substr($this->buffer, $this->count, $p - $this->count);
1322
+					$comment[]   = $c;
1323
+					$this->count = $p;
1324
+					$out         = null;
1325
+
1326
+					if ($this->interpolation($out)) {
1327
+						// keep right spaces in the following string part
1328
+						if ($out[3]) {
1329
+							while ($this->buffer[$this->count - 1] !== '}') {
1330
+								$this->count--;
1331
+							}
1332
+
1333
+							$out[3] = '';
1334
+						}
1335
+
1336
+						$comment[] = [Type::T_COMMENT, substr($this->buffer, $p, $this->count - $p), $out];
1337
+					} else {
1338
+						$comment[] = substr($this->buffer, $this->count, 2);
1339
+
1340
+						$this->count += 2;
1341
+					}
1342
+
1343
+					$p = strpos($this->buffer, '#{', $this->count);
1344
+				}
1345
+
1346
+				// remaining part
1347
+				$c = substr($this->buffer, $this->count, $endCommentCount - $this->count);
1348
+
1349
+				if (! $comment) {
1350
+					// single part static comment
1351
+					$this->appendComment([Type::T_COMMENT, $c]);
1352
+				} else {
1353
+					$comment[] = $c;
1354
+					$staticComment = substr($this->buffer, $startCommentCount, $endCommentCount - $startCommentCount);
1355
+					$this->appendComment([Type::T_COMMENT, $staticComment, [Type::T_STRING, '', $comment]]);
1356
+				}
1357
+
1358
+				$this->commentsSeen[$startCommentCount] = true;
1359
+				$this->count = $endCommentCount;
1360
+			} else {
1361
+				// comment that are ignored and not kept in the output css
1362
+				$this->count += \strlen($m[0]);
1363
+			}
1364
+
1365
+			$gotWhite = true;
1366
+		}
1367
+
1368
+		return $gotWhite;
1369
+	}
1370
+
1371
+	/**
1372
+	 * Append comment to current block
1373
+	 *
1374
+	 * @param array $comment
1375
+	 */
1376
+	protected function appendComment($comment)
1377
+	{
1378
+		if (! $this->discardComments) {
1379
+			$this->env->comments[] = $comment;
1380
+		}
1381
+	}
1382
+
1383
+	/**
1384
+	 * Append statement to current block
1385
+	 *
1386
+	 * @param array   $statement
1387
+	 * @param integer $pos
1388
+	 */
1389
+	protected function append($statement, $pos = null)
1390
+	{
1391
+		if (! \is_null($statement)) {
1392
+			if (! \is_null($pos)) {
1393
+				list($line, $column) = $this->getSourcePosition($pos);
1394
+
1395
+				$statement[static::SOURCE_LINE]   = $line;
1396
+				$statement[static::SOURCE_COLUMN] = $column;
1397
+				$statement[static::SOURCE_INDEX]  = $this->sourceIndex;
1398
+			}
1399
+
1400
+			$this->env->children[] = $statement;
1401
+		}
1402
+
1403
+		$comments = $this->env->comments;
1404
+
1405
+		if ($comments) {
1406
+			$this->env->children = array_merge($this->env->children, $comments);
1407
+			$this->env->comments = [];
1408
+		}
1409
+	}
1410
+
1411
+	/**
1412
+	 * Returns last child was appended
1413
+	 *
1414
+	 * @return array|null
1415
+	 */
1416
+	protected function last()
1417
+	{
1418
+		$i = \count($this->env->children) - 1;
1419
+
1420
+		if (isset($this->env->children[$i])) {
1421
+			return $this->env->children[$i];
1422
+		}
1423
+	}
1424
+
1425
+	/**
1426
+	 * Parse media query list
1427
+	 *
1428
+	 * @param array $out
1429
+	 *
1430
+	 * @return boolean
1431
+	 */
1432
+	protected function mediaQueryList(&$out)
1433
+	{
1434
+		return $this->genericList($out, 'mediaQuery', ',', false);
1435
+	}
1436
+
1437
+	/**
1438
+	 * Parse media query
1439
+	 *
1440
+	 * @param array $out
1441
+	 *
1442
+	 * @return boolean
1443
+	 */
1444
+	protected function mediaQuery(&$out)
1445
+	{
1446
+		$expressions = null;
1447
+		$parts = [];
1448
+
1449
+		if (
1450
+			($this->literal('only', 4) && ($only = true) ||
1451
+			$this->literal('not', 3) && ($not = true) || true) &&
1452
+			$this->mixedKeyword($mediaType)
1453
+		) {
1454
+			$prop = [Type::T_MEDIA_TYPE];
1455
+
1456
+			if (isset($only)) {
1457
+				$prop[] = [Type::T_KEYWORD, 'only'];
1458
+			}
1459
+
1460
+			if (isset($not)) {
1461
+				$prop[] = [Type::T_KEYWORD, 'not'];
1462
+			}
1463
+
1464
+			$media = [Type::T_LIST, '', []];
1465
+
1466
+			foreach ((array) $mediaType as $type) {
1467
+				if (\is_array($type)) {
1468
+					$media[2][] = $type;
1469
+				} else {
1470
+					$media[2][] = [Type::T_KEYWORD, $type];
1471
+				}
1472
+			}
1473
+
1474
+			$prop[]  = $media;
1475
+			$parts[] = $prop;
1476
+		}
1477
+
1478
+		if (empty($parts) || $this->literal('and', 3)) {
1479
+			$this->genericList($expressions, 'mediaExpression', 'and', false);
1480
+
1481
+			if (\is_array($expressions)) {
1482
+				$parts = array_merge($parts, $expressions[2]);
1483
+			}
1484
+		}
1485
+
1486
+		$out = $parts;
1487
+
1488
+		return true;
1489
+	}
1490
+
1491
+	/**
1492
+	 * Parse supports query
1493
+	 *
1494
+	 * @param array $out
1495
+	 *
1496
+	 * @return boolean
1497
+	 */
1498
+	protected function supportsQuery(&$out)
1499
+	{
1500
+		$expressions = null;
1501
+		$parts = [];
1502
+
1503
+		$s = $this->count;
1504
+
1505
+		$not = false;
1506
+
1507
+		if (
1508
+			($this->literal('not', 3) && ($not = true) || true) &&
1509
+			$this->matchChar('(') &&
1510
+			($this->expression($property)) &&
1511
+			$this->literal(': ', 2) &&
1512
+			$this->valueList($value) &&
1513
+			$this->matchChar(')')
1514
+		) {
1515
+			$support = [Type::T_STRING, '', [[Type::T_KEYWORD, ($not ? 'not ' : '') . '(']]];
1516
+			$support[2][] = $property;
1517
+			$support[2][] = [Type::T_KEYWORD, ': '];
1518
+			$support[2][] = $value;
1519
+			$support[2][] = [Type::T_KEYWORD, ')'];
1520
+
1521
+			$parts[] = $support;
1522
+			$s = $this->count;
1523
+		} else {
1524
+			$this->seek($s);
1525
+		}
1526
+
1527
+		if (
1528
+			$this->matchChar('(') &&
1529
+			$this->supportsQuery($subQuery) &&
1530
+			$this->matchChar(')')
1531
+		) {
1532
+			$parts[] = [Type::T_STRING, '', [[Type::T_KEYWORD, '('], $subQuery, [Type::T_KEYWORD, ')']]];
1533
+			$s = $this->count;
1534
+		} else {
1535
+			$this->seek($s);
1536
+		}
1537
+
1538
+		if (
1539
+			$this->literal('not', 3) &&
1540
+			$this->supportsQuery($subQuery)
1541
+		) {
1542
+			$parts[] = [Type::T_STRING, '', [[Type::T_KEYWORD, 'not '], $subQuery]];
1543
+			$s = $this->count;
1544
+		} else {
1545
+			$this->seek($s);
1546
+		}
1547
+
1548
+		if (
1549
+			$this->literal('selector(', 9) &&
1550
+			$this->selector($selector) &&
1551
+			$this->matchChar(')')
1552
+		) {
1553
+			$support = [Type::T_STRING, '', [[Type::T_KEYWORD, 'selector(']]];
1554
+
1555
+			$selectorList = [Type::T_LIST, '', []];
1556
+
1557
+			foreach ($selector as $sc) {
1558
+				$compound = [Type::T_STRING, '', []];
1559
+
1560
+				foreach ($sc as $scp) {
1561
+					if (\is_array($scp)) {
1562
+						$compound[2][] = $scp;
1563
+					} else {
1564
+						$compound[2][] = [Type::T_KEYWORD, $scp];
1565
+					}
1566
+				}
1567
+
1568
+				$selectorList[2][] = $compound;
1569
+			}
1570
+
1571
+			$support[2][] = $selectorList;
1572
+			$support[2][] = [Type::T_KEYWORD, ')'];
1573
+			$parts[] = $support;
1574
+			$s = $this->count;
1575
+		} else {
1576
+			$this->seek($s);
1577
+		}
1578
+
1579
+		if ($this->variable($var) or $this->interpolation($var)) {
1580
+			$parts[] = $var;
1581
+			$s = $this->count;
1582
+		} else {
1583
+			$this->seek($s);
1584
+		}
1585
+
1586
+		if (
1587
+			$this->literal('and', 3) &&
1588
+			$this->genericList($expressions, 'supportsQuery', ' and', false)
1589
+		) {
1590
+			array_unshift($expressions[2], [Type::T_STRING, '', $parts]);
1591
+
1592
+			$parts = [$expressions];
1593
+			$s = $this->count;
1594
+		} else {
1595
+			$this->seek($s);
1596
+		}
1597
+
1598
+		if (
1599
+			$this->literal('or', 2) &&
1600
+			$this->genericList($expressions, 'supportsQuery', ' or', false)
1601
+		) {
1602
+			array_unshift($expressions[2], [Type::T_STRING, '', $parts]);
1603
+
1604
+			$parts = [$expressions];
1605
+			$s = $this->count;
1606
+		} else {
1607
+			$this->seek($s);
1608
+		}
1609
+
1610
+		if (\count($parts)) {
1611
+			if ($this->eatWhiteDefault) {
1612
+				$this->whitespace();
1613
+			}
1614
+
1615
+			$out = [Type::T_STRING, '', $parts];
1616
+
1617
+			return true;
1618
+		}
1619
+
1620
+		return false;
1621
+	}
1622
+
1623
+
1624
+	/**
1625
+	 * Parse media expression
1626
+	 *
1627
+	 * @param array $out
1628
+	 *
1629
+	 * @return boolean
1630
+	 */
1631
+	protected function mediaExpression(&$out)
1632
+	{
1633
+		$s = $this->count;
1634
+		$value = null;
1635
+
1636
+		if (
1637
+			$this->matchChar('(') &&
1638
+			$this->expression($feature) &&
1639
+			($this->matchChar(':') &&
1640
+				$this->expression($value) || true) &&
1641
+			$this->matchChar(')')
1642
+		) {
1643
+			$out = [Type::T_MEDIA_EXPRESSION, $feature];
1644
+
1645
+			if ($value) {
1646
+				$out[] = $value;
1647
+			}
1648
+
1649
+			return true;
1650
+		}
1651
+
1652
+		$this->seek($s);
1653
+
1654
+		return false;
1655
+	}
1656
+
1657
+	/**
1658
+	 * Parse argument values
1659
+	 *
1660
+	 * @param array $out
1661
+	 *
1662
+	 * @return boolean
1663
+	 */
1664
+	protected function argValues(&$out)
1665
+	{
1666
+		$discardComments = $this->discardComments;
1667
+		$this->discardComments = true;
1668
+
1669
+		if ($this->genericList($list, 'argValue', ',', false)) {
1670
+			$out = $list[2];
1671
+
1672
+			$this->discardComments = $discardComments;
1673
+
1674
+			return true;
1675
+		}
1676
+
1677
+		$this->discardComments = $discardComments;
1678
+
1679
+		return false;
1680
+	}
1681
+
1682
+	/**
1683
+	 * Parse argument value
1684
+	 *
1685
+	 * @param array $out
1686
+	 *
1687
+	 * @return boolean
1688
+	 */
1689
+	protected function argValue(&$out)
1690
+	{
1691
+		$s = $this->count;
1692
+
1693
+		$keyword = null;
1694
+
1695
+		if (! $this->variable($keyword) || ! $this->matchChar(':')) {
1696
+			$this->seek($s);
1697
+
1698
+			$keyword = null;
1699
+		}
1700
+
1701
+		if ($this->genericList($value, 'expression', '', true)) {
1702
+			$out = [$keyword, $value, false];
1703
+			$s = $this->count;
1704
+
1705
+			if ($this->literal('...', 3)) {
1706
+				$out[2] = true;
1707
+			} else {
1708
+				$this->seek($s);
1709
+			}
1710
+
1711
+			return true;
1712
+		}
1713
+
1714
+		return false;
1715
+	}
1716
+
1717
+	/**
1718
+	 * Parse directive value list that considers $vars as keyword
1719
+	 *
1720
+	 * @param array          $out
1721
+	 * @param boolean|string $endChar
1722
+	 *
1723
+	 * @return boolean
1724
+	 */
1725
+	protected function directiveValue(&$out, $endChar = false)
1726
+	{
1727
+		$s = $this->count;
1728
+
1729
+		if ($this->variable($out)) {
1730
+			if ($endChar && $this->matchChar($endChar, false)) {
1731
+				return true;
1732
+			}
1733
+
1734
+			if (! $endChar && $this->end()) {
1735
+				return true;
1736
+			}
1737
+		}
1738
+
1739
+		$this->seek($s);
1740
+
1741
+		if ($endChar && $this->openString($endChar, $out)) {
1742
+			if ($this->matchChar($endChar, false)) {
1743
+				return true;
1744
+			}
1745
+		}
1746
+
1747
+		$this->seek($s);
1748
+
1749
+		$allowVars = $this->allowVars;
1750
+		$this->allowVars = false;
1751
+
1752
+		$res = $this->genericList($out, 'spaceList', ',');
1753
+		$this->allowVars = $allowVars;
1754
+
1755
+		if ($res) {
1756
+			if ($endChar && $this->matchChar($endChar, false)) {
1757
+				return true;
1758
+			}
1759
+
1760
+			if (! $endChar && $this->end()) {
1761
+				return true;
1762
+			}
1763
+		}
1764
+
1765
+		$this->seek($s);
1766
+
1767
+		if ($endChar && $this->matchChar($endChar, false)) {
1768
+			return true;
1769
+		}
1770
+
1771
+		return false;
1772
+	}
1773
+
1774
+	/**
1775
+	 * Parse comma separated value list
1776
+	 *
1777
+	 * @param array $out
1778
+	 *
1779
+	 * @return boolean
1780
+	 */
1781
+	protected function valueList(&$out)
1782
+	{
1783
+		$discardComments = $this->discardComments;
1784
+		$this->discardComments = true;
1785
+		$res = $this->genericList($out, 'spaceList', ',');
1786
+		$this->discardComments = $discardComments;
1787
+
1788
+		return $res;
1789
+	}
1790
+
1791
+	/**
1792
+	 * Parse a function call, where externals () are part of the call
1793
+	 * and not of the value list
1794
+	 *
1795
+	 * @param $out
1796
+	 * @param bool $mandatoryParenthesis
1797
+	 * @return bool
1798
+	 */
1799
+	protected function functionCallArgumentsList(&$out, $mandatoryParenthesis = true)
1800
+	{
1801
+		$s = $this->count;
1802
+
1803
+		if (
1804
+			$this->matchChar('(') &&
1805
+			$this->valueList($out) &&
1806
+			$this->matchChar(')')
1807
+		) {
1808
+			return true;
1809
+		}
1810
+
1811
+		if (! $mandatoryParenthesis) {
1812
+			$this->seek($s);
1813
+
1814
+			if ($this->valueList($out)) {
1815
+				return true;
1816
+			}
1817
+		}
1818
+
1819
+		$this->seek($s);
1820
+
1821
+		return false;
1822
+	}
1823
+
1824
+	/**
1825
+	 * Parse space separated value list
1826
+	 *
1827
+	 * @param array $out
1828
+	 *
1829
+	 * @return boolean
1830
+	 */
1831
+	protected function spaceList(&$out)
1832
+	{
1833
+		return $this->genericList($out, 'expression');
1834
+	}
1835
+
1836
+	/**
1837
+	 * Parse generic list
1838
+	 *
1839
+	 * @param array    $out
1840
+	 * @param callable $parseItem
1841
+	 * @param string   $delim
1842
+	 * @param boolean  $flatten
1843
+	 *
1844
+	 * @return boolean
1845
+	 */
1846
+	protected function genericList(&$out, $parseItem, $delim = '', $flatten = true)
1847
+	{
1848
+		$s     = $this->count;
1849
+		$items = [];
1850
+		$value = null;
1851
+
1852
+		while ($this->$parseItem($value)) {
1853
+			$trailing_delim = false;
1854
+			$items[] = $value;
1855
+
1856
+			if ($delim) {
1857
+				if (! $this->literal($delim, \strlen($delim))) {
1858
+					break;
1859
+				}
1860
+
1861
+				$trailing_delim = true;
1862
+			} else {
1863
+				// if no delim watch that a keyword didn't eat the single/double quote
1864
+				// from the following starting string
1865
+				if ($value[0] === Type::T_KEYWORD) {
1866
+					$word = $value[1];
1867
+
1868
+					$last_char = substr($word, -1);
1869
+
1870
+					if (
1871
+						strlen($word) > 1 &&
1872
+						in_array($last_char, [ "'", '"']) &&
1873
+						substr($word, -2, 1) !== '\\'
1874
+					) {
1875
+						// if there is a non escaped opening quote in the keyword, this seems unlikely a mistake
1876
+						$word = str_replace('\\' . $last_char, '\\\\', $word);
1877
+						if (strpos($word, $last_char) < strlen($word) - 1) {
1878
+							continue;
1879
+						}
1880
+
1881
+						$currentCount = $this->count;
1882
+
1883
+						// let's try to rewind to previous char and try a parse
1884
+						$this->count--;
1885
+						// in case the keyword also eat spaces
1886
+						while (substr($this->buffer, $this->count, 1) !== $last_char) {
1887
+							$this->count--;
1888
+						}
1889
+
1890
+						$nextValue = null;
1891
+						if ($this->$parseItem($nextValue)) {
1892
+							if ($nextValue[0] === Type::T_KEYWORD && $nextValue[1] === $last_char) {
1893
+								// bad try, forget it
1894
+								$this->seek($currentCount);
1895
+								continue;
1896
+							}
1897
+							if ($nextValue[0] !== Type::T_STRING) {
1898
+								// bad try, forget it
1899
+								$this->seek($currentCount);
1900
+								continue;
1901
+							}
1902
+
1903
+							// OK it was a good idea
1904
+							$value[1] = substr($value[1], 0, -1);
1905
+							array_pop($items);
1906
+							$items[] = $value;
1907
+							$items[] = $nextValue;
1908
+						} else {
1909
+							// bad try, forget it
1910
+							$this->seek($currentCount);
1911
+							continue;
1912
+						}
1913
+					}
1914
+				}
1915
+			}
1916
+		}
1917
+
1918
+		if (! $items) {
1919
+			$this->seek($s);
1920
+
1921
+			return false;
1922
+		}
1923
+
1924
+		if ($trailing_delim) {
1925
+			$items[] = [Type::T_NULL];
1926
+		}
1927
+
1928
+		if ($flatten && \count($items) === 1) {
1929
+			$out = $items[0];
1930
+		} else {
1931
+			$out = [Type::T_LIST, $delim, $items];
1932
+		}
1933
+
1934
+		return true;
1935
+	}
1936
+
1937
+	/**
1938
+	 * Parse expression
1939
+	 *
1940
+	 * @param array   $out
1941
+	 * @param boolean $listOnly
1942
+	 * @param boolean $lookForExp
1943
+	 *
1944
+	 * @return boolean
1945
+	 */
1946
+	protected function expression(&$out, $listOnly = false, $lookForExp = true)
1947
+	{
1948
+		$s = $this->count;
1949
+		$discard = $this->discardComments;
1950
+		$this->discardComments = true;
1951
+		$allowedTypes = ($listOnly ? [Type::T_LIST] : [Type::T_LIST, Type::T_MAP]);
1952
+
1953
+		if ($this->matchChar('(')) {
1954
+			if ($this->enclosedExpression($lhs, $s, ')', $allowedTypes)) {
1955
+				if ($lookForExp) {
1956
+					$out = $this->expHelper($lhs, 0);
1957
+				} else {
1958
+					$out = $lhs;
1959
+				}
1960
+
1961
+				$this->discardComments = $discard;
1962
+
1963
+				return true;
1964
+			}
1965
+
1966
+			$this->seek($s);
1967
+		}
1968
+
1969
+		if (\in_array(Type::T_LIST, $allowedTypes) && $this->matchChar('[')) {
1970
+			if ($this->enclosedExpression($lhs, $s, ']', [Type::T_LIST])) {
1971
+				if ($lookForExp) {
1972
+					$out = $this->expHelper($lhs, 0);
1973
+				} else {
1974
+					$out = $lhs;
1975
+				}
1976
+
1977
+				$this->discardComments = $discard;
1978
+
1979
+				return true;
1980
+			}
1981
+
1982
+			$this->seek($s);
1983
+		}
1984
+
1985
+		if (! $listOnly && $this->value($lhs)) {
1986
+			if ($lookForExp) {
1987
+				$out = $this->expHelper($lhs, 0);
1988
+			} else {
1989
+				$out = $lhs;
1990
+			}
1991
+
1992
+			$this->discardComments = $discard;
1993
+
1994
+			return true;
1995
+		}
1996
+
1997
+		$this->discardComments = $discard;
1998
+
1999
+		return false;
2000
+	}
2001
+
2002
+	/**
2003
+	 * Parse expression specifically checking for lists in parenthesis or brackets
2004
+	 *
2005
+	 * @param array   $out
2006
+	 * @param integer $s
2007
+	 * @param string  $closingParen
2008
+	 * @param array   $allowedTypes
2009
+	 *
2010
+	 * @return boolean
2011
+	 */
2012
+	protected function enclosedExpression(&$out, $s, $closingParen = ')', $allowedTypes = [Type::T_LIST, Type::T_MAP])
2013
+	{
2014
+		if ($this->matchChar($closingParen) && \in_array(Type::T_LIST, $allowedTypes)) {
2015
+			$out = [Type::T_LIST, '', []];
2016
+
2017
+			switch ($closingParen) {
2018
+				case ')':
2019
+					$out['enclosing'] = 'parent'; // parenthesis list
2020
+					break;
2021
+
2022
+				case ']':
2023
+					$out['enclosing'] = 'bracket'; // bracketed list
2024
+					break;
2025
+			}
2026
+
2027
+			return true;
2028
+		}
2029
+
2030
+		if (
2031
+			$this->valueList($out) &&
2032
+			$this->matchChar($closingParen) && ! ($closingParen === ')' &&
2033
+			\in_array($out[0], [Type::T_EXPRESSION, Type::T_UNARY])) &&
2034
+			\in_array(Type::T_LIST, $allowedTypes)
2035
+		) {
2036
+			if ($out[0] !== Type::T_LIST || ! empty($out['enclosing'])) {
2037
+				$out = [Type::T_LIST, '', [$out]];
2038
+			}
2039
+
2040
+			switch ($closingParen) {
2041
+				case ')':
2042
+					$out['enclosing'] = 'parent'; // parenthesis list
2043
+					break;
2044
+
2045
+				case ']':
2046
+					$out['enclosing'] = 'bracket'; // bracketed list
2047
+					break;
2048
+			}
2049
+
2050
+			return true;
2051
+		}
2052
+
2053
+		$this->seek($s);
2054
+
2055
+		if (\in_array(Type::T_MAP, $allowedTypes) && $this->map($out)) {
2056
+			return true;
2057
+		}
2058
+
2059
+		return false;
2060
+	}
2061
+
2062
+	/**
2063
+	 * Parse left-hand side of subexpression
2064
+	 *
2065
+	 * @param array   $lhs
2066
+	 * @param integer $minP
2067
+	 *
2068
+	 * @return array
2069
+	 */
2070
+	protected function expHelper($lhs, $minP)
2071
+	{
2072
+		$operators = static::$operatorPattern;
2073
+
2074
+		$ss = $this->count;
2075
+		$whiteBefore = isset($this->buffer[$this->count - 1]) &&
2076
+			ctype_space($this->buffer[$this->count - 1]);
2077
+
2078
+		while ($this->match($operators, $m, false) && static::$precedence[$m[1]] >= $minP) {
2079
+			$whiteAfter = isset($this->buffer[$this->count]) &&
2080
+				ctype_space($this->buffer[$this->count]);
2081
+			$varAfter = isset($this->buffer[$this->count]) &&
2082
+				$this->buffer[$this->count] === '$';
2083
+
2084
+			$this->whitespace();
2085
+
2086
+			$op = $m[1];
2087
+
2088
+			// don't turn negative numbers into expressions
2089
+			if ($op === '-' && $whiteBefore && ! $whiteAfter && ! $varAfter) {
2090
+				break;
2091
+			}
2092
+
2093
+			if (! $this->value($rhs) && ! $this->expression($rhs, true, false)) {
2094
+				break;
2095
+			}
2096
+
2097
+			// peek and see if rhs belongs to next operator
2098
+			if ($this->peek($operators, $next) && static::$precedence[$next[1]] > static::$precedence[$op]) {
2099
+				$rhs = $this->expHelper($rhs, static::$precedence[$next[1]]);
2100
+			}
2101
+
2102
+			$lhs = [Type::T_EXPRESSION, $op, $lhs, $rhs, $this->inParens, $whiteBefore, $whiteAfter];
2103
+			$ss = $this->count;
2104
+			$whiteBefore = isset($this->buffer[$this->count - 1]) &&
2105
+				ctype_space($this->buffer[$this->count - 1]);
2106
+		}
2107
+
2108
+		$this->seek($ss);
2109
+
2110
+		return $lhs;
2111
+	}
2112
+
2113
+	/**
2114
+	 * Parse value
2115
+	 *
2116
+	 * @param array $out
2117
+	 *
2118
+	 * @return boolean
2119
+	 */
2120
+	protected function value(&$out)
2121
+	{
2122
+		if (! isset($this->buffer[$this->count])) {
2123
+			return false;
2124
+		}
2125
+
2126
+		$s = $this->count;
2127
+		$char = $this->buffer[$this->count];
2128
+
2129
+		if (
2130
+			$this->literal('url(', 4) &&
2131
+			$this->match('data:([a-z]+)\/([a-z0-9.+-]+);base64,', $m, false)
2132
+		) {
2133
+			$len = strspn(
2134
+				$this->buffer,
2135
+				'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwyxz0123456789+/=',
2136
+				$this->count
2137
+			);
2138 2138
 
2139
-            $this->count += $len;
2140
-
2141
-            if ($this->matchChar(')')) {
2142
-                $content = substr($this->buffer, $s, $this->count - $s);
2143
-                $out = [Type::T_KEYWORD, $content];
2139
+			$this->count += $len;
2140
+
2141
+			if ($this->matchChar(')')) {
2142
+				$content = substr($this->buffer, $s, $this->count - $s);
2143
+				$out = [Type::T_KEYWORD, $content];
2144 2144
 
2145
-                return true;
2146
-            }
2147
-        }
2145
+				return true;
2146
+			}
2147
+		}
2148 2148
 
2149
-        $this->seek($s);
2149
+		$this->seek($s);
2150 2150
 
2151
-        if (
2152
-            $this->literal('url(', 4, false) &&
2153
-            $this->match('\s*(\/\/[^\s\)]+)\s*', $m)
2154
-        ) {
2155
-            $content = 'url(' . $m[1];
2151
+		if (
2152
+			$this->literal('url(', 4, false) &&
2153
+			$this->match('\s*(\/\/[^\s\)]+)\s*', $m)
2154
+		) {
2155
+			$content = 'url(' . $m[1];
2156 2156
 
2157
-            if ($this->matchChar(')')) {
2158
-                $content .= ')';
2159
-                $out = [Type::T_KEYWORD, $content];
2157
+			if ($this->matchChar(')')) {
2158
+				$content .= ')';
2159
+				$out = [Type::T_KEYWORD, $content];
2160 2160
 
2161
-                return true;
2162
-            }
2163
-        }
2161
+				return true;
2162
+			}
2163
+		}
2164 2164
 
2165
-        $this->seek($s);
2165
+		$this->seek($s);
2166 2166
 
2167
-        // not
2168
-        if ($char === 'n' && $this->literal('not', 3, false)) {
2169
-            if (
2170
-                $this->whitespace() &&
2171
-                $this->value($inner)
2172
-            ) {
2173
-                $out = [Type::T_UNARY, 'not', $inner, $this->inParens];
2167
+		// not
2168
+		if ($char === 'n' && $this->literal('not', 3, false)) {
2169
+			if (
2170
+				$this->whitespace() &&
2171
+				$this->value($inner)
2172
+			) {
2173
+				$out = [Type::T_UNARY, 'not', $inner, $this->inParens];
2174 2174
 
2175
-                return true;
2176
-            }
2175
+				return true;
2176
+			}
2177 2177
 
2178
-            $this->seek($s);
2178
+			$this->seek($s);
2179 2179
 
2180
-            if ($this->parenValue($inner)) {
2181
-                $out = [Type::T_UNARY, 'not', $inner, $this->inParens];
2180
+			if ($this->parenValue($inner)) {
2181
+				$out = [Type::T_UNARY, 'not', $inner, $this->inParens];
2182 2182
 
2183
-                return true;
2184
-            }
2183
+				return true;
2184
+			}
2185 2185
 
2186
-            $this->seek($s);
2187
-        }
2186
+			$this->seek($s);
2187
+		}
2188 2188
 
2189
-        // addition
2190
-        if ($char === '+') {
2191
-            $this->count++;
2189
+		// addition
2190
+		if ($char === '+') {
2191
+			$this->count++;
2192 2192
 
2193
-            $follow_white = $this->whitespace();
2193
+			$follow_white = $this->whitespace();
2194 2194
 
2195
-            if ($this->value($inner)) {
2196
-                $out = [Type::T_UNARY, '+', $inner, $this->inParens];
2195
+			if ($this->value($inner)) {
2196
+				$out = [Type::T_UNARY, '+', $inner, $this->inParens];
2197 2197
 
2198
-                return true;
2199
-            }
2198
+				return true;
2199
+			}
2200 2200
 
2201
-            if ($follow_white) {
2202
-                $out = [Type::T_KEYWORD, $char];
2203
-                return  true;
2204
-            }
2201
+			if ($follow_white) {
2202
+				$out = [Type::T_KEYWORD, $char];
2203
+				return  true;
2204
+			}
2205 2205
 
2206
-            $this->seek($s);
2206
+			$this->seek($s);
2207 2207
 
2208
-            return false;
2209
-        }
2208
+			return false;
2209
+		}
2210 2210
 
2211
-        // negation
2212
-        if ($char === '-') {
2213
-            if ($this->customProperty($out)) {
2214
-                return true;
2215
-            }
2211
+		// negation
2212
+		if ($char === '-') {
2213
+			if ($this->customProperty($out)) {
2214
+				return true;
2215
+			}
2216 2216
 
2217
-            $this->count++;
2217
+			$this->count++;
2218 2218
 
2219
-            $follow_white = $this->whitespace();
2219
+			$follow_white = $this->whitespace();
2220 2220
 
2221
-            if ($this->variable($inner) || $this->unit($inner) || $this->parenValue($inner)) {
2222
-                $out = [Type::T_UNARY, '-', $inner, $this->inParens];
2221
+			if ($this->variable($inner) || $this->unit($inner) || $this->parenValue($inner)) {
2222
+				$out = [Type::T_UNARY, '-', $inner, $this->inParens];
2223 2223
 
2224
-                return true;
2225
-            }
2224
+				return true;
2225
+			}
2226 2226
 
2227
-            if (
2228
-                $this->keyword($inner) &&
2229
-                ! $this->func($inner, $out)
2230
-            ) {
2231
-                $out = [Type::T_UNARY, '-', $inner, $this->inParens];
2227
+			if (
2228
+				$this->keyword($inner) &&
2229
+				! $this->func($inner, $out)
2230
+			) {
2231
+				$out = [Type::T_UNARY, '-', $inner, $this->inParens];
2232 2232
 
2233
-                return true;
2234
-            }
2233
+				return true;
2234
+			}
2235 2235
 
2236
-            if ($follow_white) {
2237
-                $out = [Type::T_KEYWORD, $char];
2236
+			if ($follow_white) {
2237
+				$out = [Type::T_KEYWORD, $char];
2238 2238
 
2239
-                return  true;
2240
-            }
2239
+				return  true;
2240
+			}
2241 2241
 
2242
-            $this->seek($s);
2243
-        }
2242
+			$this->seek($s);
2243
+		}
2244 2244
 
2245
-        // paren
2246
-        if ($char === '(' && $this->parenValue($out)) {
2247
-            return true;
2248
-        }
2245
+		// paren
2246
+		if ($char === '(' && $this->parenValue($out)) {
2247
+			return true;
2248
+		}
2249 2249
 
2250
-        if ($char === '#') {
2251
-            if ($this->interpolation($out) || $this->color($out)) {
2252
-                return true;
2253
-            }
2250
+		if ($char === '#') {
2251
+			if ($this->interpolation($out) || $this->color($out)) {
2252
+				return true;
2253
+			}
2254 2254
 
2255
-            $this->count++;
2255
+			$this->count++;
2256 2256
 
2257
-            if ($this->keyword($keyword)) {
2258
-                $out = [Type::T_KEYWORD, '#' . $keyword];
2257
+			if ($this->keyword($keyword)) {
2258
+				$out = [Type::T_KEYWORD, '#' . $keyword];
2259 2259
 
2260
-                return true;
2261
-            }
2260
+				return true;
2261
+			}
2262 2262
 
2263
-            $this->count--;
2264
-        }
2263
+			$this->count--;
2264
+		}
2265 2265
 
2266
-        if ($this->matchChar('&', true)) {
2267
-            $out = [Type::T_SELF];
2266
+		if ($this->matchChar('&', true)) {
2267
+			$out = [Type::T_SELF];
2268 2268
 
2269
-            return true;
2270
-        }
2269
+			return true;
2270
+		}
2271 2271
 
2272
-        if ($char === '$' && $this->variable($out)) {
2273
-            return true;
2274
-        }
2272
+		if ($char === '$' && $this->variable($out)) {
2273
+			return true;
2274
+		}
2275 2275
 
2276
-        if ($char === 'p' && $this->progid($out)) {
2277
-            return true;
2278
-        }
2276
+		if ($char === 'p' && $this->progid($out)) {
2277
+			return true;
2278
+		}
2279 2279
 
2280
-        if (($char === '"' || $char === "'") && $this->string($out)) {
2281
-            return true;
2282
-        }
2280
+		if (($char === '"' || $char === "'") && $this->string($out)) {
2281
+			return true;
2282
+		}
2283 2283
 
2284
-        if ($this->unit($out)) {
2285
-            return true;
2286
-        }
2284
+		if ($this->unit($out)) {
2285
+			return true;
2286
+		}
2287 2287
 
2288
-        // unicode range with wildcards
2289
-        if (
2290
-            $this->literal('U+', 2) &&
2291
-            $this->match('([0-9A-F]+\?*)(-([0-9A-F]+))?', $m, false)
2292
-        ) {
2293
-            $out = [Type::T_KEYWORD, 'U+' . $m[0]];
2288
+		// unicode range with wildcards
2289
+		if (
2290
+			$this->literal('U+', 2) &&
2291
+			$this->match('([0-9A-F]+\?*)(-([0-9A-F]+))?', $m, false)
2292
+		) {
2293
+			$out = [Type::T_KEYWORD, 'U+' . $m[0]];
2294 2294
 
2295
-            return true;
2296
-        }
2297
-
2298
-        if ($this->keyword($keyword, false)) {
2299
-            if ($this->func($keyword, $out)) {
2300
-                return true;
2301
-            }
2302
-
2303
-            $this->whitespace();
2304
-
2305
-            if ($keyword === 'null') {
2306
-                $out = [Type::T_NULL];
2307
-            } else {
2308
-                $out = [Type::T_KEYWORD, $keyword];
2309
-            }
2310
-
2311
-            return true;
2312
-        }
2313
-
2314
-        return false;
2315
-    }
2316
-
2317
-    /**
2318
-     * Parse parenthesized value
2319
-     *
2320
-     * @param array $out
2321
-     *
2322
-     * @return boolean
2323
-     */
2324
-    protected function parenValue(&$out)
2325
-    {
2326
-        $s = $this->count;
2327
-
2328
-        $inParens = $this->inParens;
2329
-
2330
-        if ($this->matchChar('(')) {
2331
-            if ($this->matchChar(')')) {
2332
-                $out = [Type::T_LIST, '', []];
2333
-
2334
-                return true;
2335
-            }
2336
-
2337
-            $this->inParens = true;
2338
-
2339
-            if (
2340
-                $this->expression($exp) &&
2341
-                $this->matchChar(')')
2342
-            ) {
2343
-                $out = $exp;
2344
-                $this->inParens = $inParens;
2345
-
2346
-                return true;
2347
-            }
2348
-        }
2349
-
2350
-        $this->inParens = $inParens;
2351
-        $this->seek($s);
2352
-
2353
-        return false;
2354
-    }
2355
-
2356
-    /**
2357
-     * Parse "progid:"
2358
-     *
2359
-     * @param array $out
2360
-     *
2361
-     * @return boolean
2362
-     */
2363
-    protected function progid(&$out)
2364
-    {
2365
-        $s = $this->count;
2366
-
2367
-        if (
2368
-            $this->literal('progid:', 7, false) &&
2369
-            $this->openString('(', $fn) &&
2370
-            $this->matchChar('(')
2371
-        ) {
2372
-            $this->openString(')', $args, '(');
2373
-
2374
-            if ($this->matchChar(')')) {
2375
-                $out = [Type::T_STRING, '', [
2376
-                    'progid:', $fn, '(', $args, ')'
2377
-                ]];
2378
-
2379
-                return true;
2380
-            }
2381
-        }
2382
-
2383
-        $this->seek($s);
2384
-
2385
-        return false;
2386
-    }
2387
-
2388
-    /**
2389
-     * Parse function call
2390
-     *
2391
-     * @param string $name
2392
-     * @param array  $func
2393
-     *
2394
-     * @return boolean
2395
-     */
2396
-    protected function func($name, &$func)
2397
-    {
2398
-        $s = $this->count;
2399
-
2400
-        if ($this->matchChar('(')) {
2401
-            if ($name === 'alpha' && $this->argumentList($args)) {
2402
-                $func = [Type::T_FUNCTION, $name, [Type::T_STRING, '', $args]];
2403
-
2404
-                return true;
2405
-            }
2406
-
2407
-            if ($name !== 'expression' && ! preg_match('/^(-[a-z]+-)?calc$/', $name)) {
2408
-                $ss = $this->count;
2409
-
2410
-                if (
2411
-                    $this->argValues($args) &&
2412
-                    $this->matchChar(')')
2413
-                ) {
2414
-                    $func = [Type::T_FUNCTION_CALL, $name, $args];
2415
-
2416
-                    return true;
2417
-                }
2418
-
2419
-                $this->seek($ss);
2420
-            }
2421
-
2422
-            if (
2423
-                ($this->openString(')', $str, '(') || true) &&
2424
-                $this->matchChar(')')
2425
-            ) {
2426
-                $args = [];
2427
-
2428
-                if (! empty($str)) {
2429
-                    $args[] = [null, [Type::T_STRING, '', [$str]]];
2430
-                }
2431
-
2432
-                $func = [Type::T_FUNCTION_CALL, $name, $args];
2433
-
2434
-                return true;
2435
-            }
2436
-        }
2437
-
2438
-        $this->seek($s);
2439
-
2440
-        return false;
2441
-    }
2442
-
2443
-    /**
2444
-     * Parse function call argument list
2445
-     *
2446
-     * @param array $out
2447
-     *
2448
-     * @return boolean
2449
-     */
2450
-    protected function argumentList(&$out)
2451
-    {
2452
-        $s = $this->count;
2453
-        $this->matchChar('(');
2454
-
2455
-        $args = [];
2456
-
2457
-        while ($this->keyword($var)) {
2458
-            if (
2459
-                $this->matchChar('=') &&
2460
-                $this->expression($exp)
2461
-            ) {
2462
-                $args[] = [Type::T_STRING, '', [$var . '=']];
2463
-                $arg = $exp;
2464
-            } else {
2465
-                break;
2466
-            }
2467
-
2468
-            $args[] = $arg;
2469
-
2470
-            if (! $this->matchChar(',')) {
2471
-                break;
2472
-            }
2473
-
2474
-            $args[] = [Type::T_STRING, '', [', ']];
2475
-        }
2476
-
2477
-        if (! $this->matchChar(')') || ! $args) {
2478
-            $this->seek($s);
2479
-
2480
-            return false;
2481
-        }
2482
-
2483
-        $out = $args;
2484
-
2485
-        return true;
2486
-    }
2487
-
2488
-    /**
2489
-     * Parse mixin/function definition  argument list
2490
-     *
2491
-     * @param array $out
2492
-     *
2493
-     * @return boolean
2494
-     */
2495
-    protected function argumentDef(&$out)
2496
-    {
2497
-        $s = $this->count;
2498
-        $this->matchChar('(');
2499
-
2500
-        $args = [];
2501
-
2502
-        while ($this->variable($var)) {
2503
-            $arg = [$var[1], null, false];
2504
-
2505
-            $ss = $this->count;
2506
-
2507
-            if (
2508
-                $this->matchChar(':') &&
2509
-                $this->genericList($defaultVal, 'expression', '', true)
2510
-            ) {
2511
-                $arg[1] = $defaultVal;
2512
-            } else {
2513
-                $this->seek($ss);
2514
-            }
2515
-
2516
-            $ss = $this->count;
2517
-
2518
-            if ($this->literal('...', 3)) {
2519
-                $sss = $this->count;
2520
-
2521
-                if (! $this->matchChar(')')) {
2522
-                    $this->throwParseError('... has to be after the final argument');
2523
-                }
2524
-
2525
-                $arg[2] = true;
2526
-
2527
-                $this->seek($sss);
2528
-            } else {
2529
-                $this->seek($ss);
2530
-            }
2531
-
2532
-            $args[] = $arg;
2533
-
2534
-            if (! $this->matchChar(',')) {
2535
-                break;
2536
-            }
2537
-        }
2538
-
2539
-        if (! $this->matchChar(')')) {
2540
-            $this->seek($s);
2541
-
2542
-            return false;
2543
-        }
2544
-
2545
-        $out = $args;
2546
-
2547
-        return true;
2548
-    }
2549
-
2550
-    /**
2551
-     * Parse map
2552
-     *
2553
-     * @param array $out
2554
-     *
2555
-     * @return boolean
2556
-     */
2557
-    protected function map(&$out)
2558
-    {
2559
-        $s = $this->count;
2560
-
2561
-        if (! $this->matchChar('(')) {
2562
-            return false;
2563
-        }
2564
-
2565
-        $keys = [];
2566
-        $values = [];
2567
-
2568
-        while (
2569
-            $this->genericList($key, 'expression', '', true) &&
2570
-            $this->matchChar(':') &&
2571
-            $this->genericList($value, 'expression', '', true)
2572
-        ) {
2573
-            $keys[] = $key;
2574
-            $values[] = $value;
2575
-
2576
-            if (! $this->matchChar(',')) {
2577
-                break;
2578
-            }
2579
-        }
2580
-
2581
-        if (! $keys || ! $this->matchChar(')')) {
2582
-            $this->seek($s);
2583
-
2584
-            return false;
2585
-        }
2586
-
2587
-        $out = [Type::T_MAP, $keys, $values];
2588
-
2589
-        return true;
2590
-    }
2591
-
2592
-    /**
2593
-     * Parse color
2594
-     *
2595
-     * @param array $out
2596
-     *
2597
-     * @return boolean
2598
-     */
2599
-    protected function color(&$out)
2600
-    {
2601
-        $s = $this->count;
2602
-
2603
-        if ($this->match('(#([0-9a-f]+)\b)', $m)) {
2604
-            if (\in_array(\strlen($m[2]), [3,4,6,8])) {
2605
-                $out = [Type::T_KEYWORD, $m[0]];
2606
-
2607
-                return true;
2608
-            }
2609
-
2610
-            $this->seek($s);
2611
-
2612
-            return false;
2613
-        }
2614
-
2615
-        return false;
2616
-    }
2617
-
2618
-    /**
2619
-     * Parse number with unit
2620
-     *
2621
-     * @param array $unit
2622
-     *
2623
-     * @return boolean
2624
-     */
2625
-    protected function unit(&$unit)
2626
-    {
2627
-        $s = $this->count;
2628
-
2629
-        if ($this->match('([0-9]*(\.)?[0-9]+)([%a-zA-Z]+)?', $m, false)) {
2630
-            if (\strlen($this->buffer) === $this->count || ! ctype_digit($this->buffer[$this->count])) {
2631
-                $this->whitespace();
2632
-
2633
-                $unit = new Node\Number($m[1], empty($m[3]) ? '' : $m[3]);
2634
-
2635
-                return true;
2636
-            }
2637
-
2638
-            $this->seek($s);
2639
-        }
2640
-
2641
-        return false;
2642
-    }
2643
-
2644
-    /**
2645
-     * Parse string
2646
-     *
2647
-     * @param array $out
2648
-     *
2649
-     * @return boolean
2650
-     */
2651
-    protected function string(&$out)
2652
-    {
2653
-        $s = $this->count;
2654
-
2655
-        if ($this->matchChar('"', false)) {
2656
-            $delim = '"';
2657
-        } elseif ($this->matchChar("'", false)) {
2658
-            $delim = "'";
2659
-        } else {
2660
-            return false;
2661
-        }
2662
-
2663
-        $content = [];
2664
-        $oldWhite = $this->eatWhiteDefault;
2665
-        $this->eatWhiteDefault = false;
2666
-        $hasInterpolation = false;
2667
-
2668
-        while ($this->matchString($m, $delim)) {
2669
-            if ($m[1] !== '') {
2670
-                $content[] = $m[1];
2671
-            }
2672
-
2673
-            if ($m[2] === '#{') {
2674
-                $this->count -= \strlen($m[2]);
2675
-
2676
-                if ($this->interpolation($inter, false)) {
2677
-                    $content[] = $inter;
2678
-                    $hasInterpolation = true;
2679
-                } else {
2680
-                    $this->count += \strlen($m[2]);
2681
-                    $content[] = '#{'; // ignore it
2682
-                }
2683
-            } elseif ($m[2] === "\r") {
2684
-                $content[] = '\\a';
2685
-                // TODO : warning
2686
-                # DEPRECATION WARNING on line x, column y of zzz:
2687
-                # Unescaped multiline strings are deprecated and will be removed in a future version of Sass.
2688
-                # To include a newline in a string, use "\a" or "\a " as in CSS.
2689
-                if ($this->matchChar("\n", false)) {
2690
-                    $content[] = ' ';
2691
-                }
2692
-            } elseif ($m[2] === '\\') {
2693
-                if (
2694
-                    $this->literal("\r\n", 2, false) ||
2695
-                    $this->matchChar("\r", false) ||
2696
-                    $this->matchChar("\n", false) ||
2697
-                    $this->matchChar("\f", false)
2698
-                ) {
2699
-                    // this is a continuation escaping, to be ignored
2700
-                } elseif ($this->matchEscapeCharacter($c)) {
2701
-                    $content[] = $c;
2702
-                } else {
2703
-                    $this->throwParseError('Unterminated escape sequence');
2704
-                }
2705
-            } else {
2706
-                $this->count -= \strlen($delim);
2707
-                break; // delim
2708
-            }
2709
-        }
2710
-
2711
-        $this->eatWhiteDefault = $oldWhite;
2712
-
2713
-        if ($this->literal($delim, \strlen($delim))) {
2714
-            if ($hasInterpolation) {
2715
-                $delim = '"';
2716
-            }
2717
-
2718
-            $out = [Type::T_STRING, $delim, $content];
2719
-
2720
-            return true;
2721
-        }
2722
-
2723
-        $this->seek($s);
2724
-
2725
-        return false;
2726
-    }
2727
-
2728
-    protected function matchEscapeCharacter(&$out)
2729
-    {
2730
-        if ($this->match('[a-f0-9]', $m, false)) {
2731
-            $hex = $m[0];
2732
-
2733
-            for ($i = 5; $i--;) {
2734
-                if ($this->match('[a-f0-9]', $m, false)) {
2735
-                    $hex .= $m[0];
2736
-                } else {
2737
-                    break;
2738
-                }
2739
-            }
2740
-
2741
-            $value = hexdec($hex);
2742
-
2743
-            if ($value == 0 || ($value >= 0xD800 && $value <= 0xDFFF) || $value >= 0x10FFFF) {
2744
-                $out = "\u{FFFD}";
2745
-            } else {
2746
-                $out = Util::mbChr($value);
2747
-            }
2748
-
2749
-            return true;
2750
-        }
2751
-
2752
-        if ($this->match('.', $m, false)) {
2753
-            $out = $m[0];
2754
-
2755
-            return true;
2756
-        }
2757
-
2758
-        return false;
2759
-    }
2760
-
2761
-    /**
2762
-     * Parse keyword or interpolation
2763
-     *
2764
-     * @param array   $out
2765
-     * @param boolean $restricted
2766
-     *
2767
-     * @return boolean
2768
-     */
2769
-    protected function mixedKeyword(&$out, $restricted = false)
2770
-    {
2771
-        $parts = [];
2772
-
2773
-        $oldWhite = $this->eatWhiteDefault;
2774
-        $this->eatWhiteDefault = false;
2775
-
2776
-        for (;;) {
2777
-            if ($restricted ? $this->restrictedKeyword($key) : $this->keyword($key)) {
2778
-                $parts[] = $key;
2779
-                continue;
2780
-            }
2781
-
2782
-            if ($this->interpolation($inter)) {
2783
-                $parts[] = $inter;
2784
-                continue;
2785
-            }
2786
-
2787
-            break;
2788
-        }
2789
-
2790
-        $this->eatWhiteDefault = $oldWhite;
2791
-
2792
-        if (! $parts) {
2793
-            return false;
2794
-        }
2795
-
2796
-        if ($this->eatWhiteDefault) {
2797
-            $this->whitespace();
2798
-        }
2799
-
2800
-        $out = $parts;
2801
-
2802
-        return true;
2803
-    }
2804
-
2805
-    /**
2806
-     * Parse an unbounded string stopped by $end
2807
-     *
2808
-     * @param string  $end
2809
-     * @param array   $out
2810
-     * @param string  $nestingOpen
2811
-     * @param string  $nestingClose
2812
-     * @param boolean $trimEnd
2813
-     *
2814
-     * @return boolean
2815
-     */
2816
-    protected function openString($end, &$out, $nestingOpen = null, $nestingClose = null, $trimEnd = true)
2817
-    {
2818
-        $oldWhite = $this->eatWhiteDefault;
2819
-        $this->eatWhiteDefault = false;
2820
-
2821
-        if ($nestingOpen && ! $nestingClose) {
2822
-            $nestingClose = $end;
2823
-        }
2824
-
2825
-        $patt = '(.*?)([\'"]|#\{|'
2826
-            . $this->pregQuote($end) . '|'
2827
-            . (($nestingClose && $nestingClose !== $end) ? $this->pregQuote($nestingClose) . '|' : '')
2828
-            . static::$commentPattern . ')';
2829
-
2830
-        $nestingLevel = 0;
2831
-
2832
-        $content = [];
2833
-
2834
-        while ($this->match($patt, $m, false)) {
2835
-            if (isset($m[1]) && $m[1] !== '') {
2836
-                $content[] = $m[1];
2837
-
2838
-                if ($nestingOpen) {
2839
-                    $nestingLevel += substr_count($m[1], $nestingOpen);
2840
-                }
2841
-            }
2842
-
2843
-            $tok = $m[2];
2844
-
2845
-            $this->count -= \strlen($tok);
2846
-
2847
-            if ($tok === $end && ! $nestingLevel) {
2848
-                break;
2849
-            }
2850
-
2851
-            if ($tok === $nestingClose) {
2852
-                $nestingLevel--;
2853
-            }
2854
-
2855
-            if (($tok === "'" || $tok === '"') && $this->string($str)) {
2856
-                $content[] = $str;
2857
-                continue;
2858
-            }
2859
-
2860
-            if ($tok === '#{' && $this->interpolation($inter)) {
2861
-                $content[] = $inter;
2862
-                continue;
2863
-            }
2864
-
2865
-            $content[] = $tok;
2866
-            $this->count += \strlen($tok);
2867
-        }
2868
-
2869
-        $this->eatWhiteDefault = $oldWhite;
2870
-
2871
-        if (! $content || $tok !== $end) {
2872
-            return false;
2873
-        }
2874
-
2875
-        // trim the end
2876
-        if ($trimEnd && \is_string(end($content))) {
2877
-            $content[\count($content) - 1] = rtrim(end($content));
2878
-        }
2879
-
2880
-        $out = [Type::T_STRING, '', $content];
2881
-
2882
-        return true;
2883
-    }
2884
-
2885
-    /**
2886
-     * Parser interpolation
2887
-     *
2888
-     * @param string|array $out
2889
-     * @param boolean      $lookWhite save information about whitespace before and after
2890
-     *
2891
-     * @return boolean
2892
-     */
2893
-    protected function interpolation(&$out, $lookWhite = true)
2894
-    {
2895
-        $oldWhite = $this->eatWhiteDefault;
2896
-        $allowVars = $this->allowVars;
2897
-        $this->allowVars = true;
2898
-        $this->eatWhiteDefault = true;
2899
-
2900
-        $s = $this->count;
2901
-
2902
-        if (
2903
-            $this->literal('#{', 2) &&
2904
-            $this->valueList($value) &&
2905
-            $this->matchChar('}', false)
2906
-        ) {
2907
-            if ($value === [Type::T_SELF]) {
2908
-                $out = $value;
2909
-            } else {
2910
-                if ($lookWhite) {
2911
-                    $left = ($s > 0 && preg_match('/\s/', $this->buffer[$s - 1])) ? ' ' : '';
2912
-                    $right = preg_match('/\s/', $this->buffer[$this->count]) ? ' ' : '';
2913
-                } else {
2914
-                    $left = $right = false;
2915
-                }
2916
-
2917
-                $out = [Type::T_INTERPOLATE, $value, $left, $right];
2918
-            }
2919
-
2920
-            $this->eatWhiteDefault = $oldWhite;
2921
-            $this->allowVars = $allowVars;
2922
-
2923
-            if ($this->eatWhiteDefault) {
2924
-                $this->whitespace();
2925
-            }
2926
-
2927
-            return true;
2928
-        }
2929
-
2930
-        $this->seek($s);
2931
-
2932
-        $this->eatWhiteDefault = $oldWhite;
2933
-        $this->allowVars = $allowVars;
2934
-
2935
-        return false;
2936
-    }
2937
-
2938
-    /**
2939
-     * Parse property name (as an array of parts or a string)
2940
-     *
2941
-     * @param array $out
2942
-     *
2943
-     * @return boolean
2944
-     */
2945
-    protected function propertyName(&$out)
2946
-    {
2947
-        $parts = [];
2948
-
2949
-        $oldWhite = $this->eatWhiteDefault;
2950
-        $this->eatWhiteDefault = false;
2951
-
2952
-        for (;;) {
2953
-            if ($this->interpolation($inter)) {
2954
-                $parts[] = $inter;
2955
-                continue;
2956
-            }
2957
-
2958
-            if ($this->keyword($text)) {
2959
-                $parts[] = $text;
2960
-                continue;
2961
-            }
2962
-
2963
-            if (! $parts && $this->match('[:.#]', $m, false)) {
2964
-                // css hacks
2965
-                $parts[] = $m[0];
2966
-                continue;
2967
-            }
2968
-
2969
-            break;
2970
-        }
2971
-
2972
-        $this->eatWhiteDefault = $oldWhite;
2973
-
2974
-        if (! $parts) {
2975
-            return false;
2976
-        }
2977
-
2978
-        // match comment hack
2979
-        if (preg_match(static::$whitePattern, $this->buffer, $m, null, $this->count)) {
2980
-            if (! empty($m[0])) {
2981
-                $parts[] = $m[0];
2982
-                $this->count += \strlen($m[0]);
2983
-            }
2984
-        }
2985
-
2986
-        $this->whitespace(); // get any extra whitespace
2987
-
2988
-        $out = [Type::T_STRING, '', $parts];
2989
-
2990
-        return true;
2991
-    }
2992
-
2993
-    /**
2994
-     * Parse custom property name (as an array of parts or a string)
2995
-     *
2996
-     * @param array $out
2997
-     *
2998
-     * @return boolean
2999
-     */
3000
-    protected function customProperty(&$out)
3001
-    {
3002
-        $s = $this->count;
3003
-
3004
-        if (! $this->literal('--', 2, false)) {
3005
-            return false;
3006
-        }
3007
-
3008
-        $parts = ['--'];
3009
-
3010
-        $oldWhite = $this->eatWhiteDefault;
3011
-        $this->eatWhiteDefault = false;
3012
-
3013
-        for (;;) {
3014
-            if ($this->interpolation($inter)) {
3015
-                $parts[] = $inter;
3016
-                continue;
3017
-            }
3018
-
3019
-            if ($this->matchChar('&', false)) {
3020
-                $parts[] = [Type::T_SELF];
3021
-                continue;
3022
-            }
3023
-
3024
-            if ($this->variable($var)) {
3025
-                $parts[] = $var;
3026
-                continue;
3027
-            }
3028
-
3029
-            if ($this->keyword($text)) {
3030
-                $parts[] = $text;
3031
-                continue;
3032
-            }
3033
-
3034
-            break;
3035
-        }
3036
-
3037
-        $this->eatWhiteDefault = $oldWhite;
3038
-
3039
-        if (\count($parts) == 1) {
3040
-            $this->seek($s);
3041
-
3042
-            return false;
3043
-        }
3044
-
3045
-        $this->whitespace(); // get any extra whitespace
3046
-
3047
-        $out = [Type::T_STRING, '', $parts];
3048
-
3049
-        return true;
3050
-    }
3051
-
3052
-    /**
3053
-     * Parse comma separated selector list
3054
-     *
3055
-     * @param array   $out
3056
-     * @param boolean $subSelector
3057
-     *
3058
-     * @return boolean
3059
-     */
3060
-    protected function selectors(&$out, $subSelector = false)
3061
-    {
3062
-        $s = $this->count;
3063
-        $selectors = [];
3064
-
3065
-        while ($this->selector($sel, $subSelector)) {
3066
-            $selectors[] = $sel;
3067
-
3068
-            if (! $this->matchChar(',', true)) {
3069
-                break;
3070
-            }
3071
-
3072
-            while ($this->matchChar(',', true)) {
3073
-                ; // ignore extra
3074
-            }
3075
-        }
3076
-
3077
-        if (! $selectors) {
3078
-            $this->seek($s);
3079
-
3080
-            return false;
3081
-        }
3082
-
3083
-        $out = $selectors;
3084
-
3085
-        return true;
3086
-    }
3087
-
3088
-    /**
3089
-     * Parse whitespace separated selector list
3090
-     *
3091
-     * @param array   $out
3092
-     * @param boolean $subSelector
3093
-     *
3094
-     * @return boolean
3095
-     */
3096
-    protected function selector(&$out, $subSelector = false)
3097
-    {
3098
-        $selector = [];
3099
-
3100
-        for (;;) {
3101
-            $s = $this->count;
3102
-
3103
-            if ($this->match('[>+~]+', $m, true)) {
3104
-                if (
3105
-                    $subSelector && \is_string($subSelector) && strpos($subSelector, 'nth-') === 0 &&
3106
-                    $m[0] === '+' && $this->match("(\d+|n\b)", $counter)
3107
-                ) {
3108
-                    $this->seek($s);
3109
-                } else {
3110
-                    $selector[] = [$m[0]];
3111
-                    continue;
3112
-                }
3113
-            }
3114
-
3115
-            if ($this->selectorSingle($part, $subSelector)) {
3116
-                $selector[] = $part;
3117
-                $this->match('\s+', $m);
3118
-                continue;
3119
-            }
3120
-
3121
-            if ($this->match('\/[^\/]+\/', $m, true)) {
3122
-                $selector[] = [$m[0]];
3123
-                continue;
3124
-            }
3125
-
3126
-            break;
3127
-        }
3128
-
3129
-        if (! $selector) {
3130
-            return false;
3131
-        }
3132
-
3133
-        $out = $selector;
3134
-
3135
-        return true;
3136
-    }
3137
-
3138
-    /**
3139
-     * Parse the parts that make up a selector
3140
-     *
3141
-     * {@internal
3142
-     *     div[yes=no]#something.hello.world:nth-child(-2n+1)%placeholder
3143
-     * }}
3144
-     *
3145
-     * @param array   $out
3146
-     * @param boolean $subSelector
3147
-     *
3148
-     * @return boolean
3149
-     */
3150
-    protected function selectorSingle(&$out, $subSelector = false)
3151
-    {
3152
-        $oldWhite = $this->eatWhiteDefault;
3153
-        $this->eatWhiteDefault = false;
3154
-
3155
-        $parts = [];
3156
-
3157
-        if ($this->matchChar('*', false)) {
3158
-            $parts[] = '*';
3159
-        }
3160
-
3161
-        for (;;) {
3162
-            if (! isset($this->buffer[$this->count])) {
3163
-                break;
3164
-            }
3165
-
3166
-            $s = $this->count;
3167
-            $char = $this->buffer[$this->count];
3168
-
3169
-            // see if we can stop early
3170
-            if ($char === '{' || $char === ',' || $char === ';' || $char === '}' || $char === '@') {
3171
-                break;
3172
-            }
3173
-
3174
-            // parsing a sub selector in () stop with the closing )
3175
-            if ($subSelector && $char === ')') {
3176
-                break;
3177
-            }
3178
-
3179
-            //self
3180
-            switch ($char) {
3181
-                case '&':
3182
-                    $parts[] = Compiler::$selfSelector;
3183
-                    $this->count++;
3184
-                    continue 2;
3185
-
3186
-                case '.':
3187
-                    $parts[] = '.';
3188
-                    $this->count++;
3189
-                    continue 2;
3190
-
3191
-                case '|':
3192
-                    $parts[] = '|';
3193
-                    $this->count++;
3194
-                    continue 2;
3195
-            }
3196
-
3197
-            if ($char === '\\' && $this->match('\\\\\S', $m)) {
3198
-                $parts[] = $m[0];
3199
-                continue;
3200
-            }
3201
-
3202
-            if ($char === '%') {
3203
-                $this->count++;
3204
-
3205
-                if ($this->placeholder($placeholder)) {
3206
-                    $parts[] = '%';
3207
-                    $parts[] = $placeholder;
3208
-                    continue;
3209
-                }
3210
-
3211
-                break;
3212
-            }
3213
-
3214
-            if ($char === '#') {
3215
-                if ($this->interpolation($inter)) {
3216
-                    $parts[] = $inter;
3217
-                    continue;
3218
-                }
3219
-
3220
-                $parts[] = '#';
3221
-                $this->count++;
3222
-                continue;
3223
-            }
3224
-
3225
-            // a pseudo selector
3226
-            if ($char === ':') {
3227
-                if ($this->buffer[$this->count + 1] === ':') {
3228
-                    $this->count += 2;
3229
-                    $part = '::';
3230
-                } else {
3231
-                    $this->count++;
3232
-                    $part = ':';
3233
-                }
3234
-
3235
-                if ($this->mixedKeyword($nameParts, true)) {
3236
-                    $parts[] = $part;
3237
-
3238
-                    foreach ($nameParts as $sub) {
3239
-                        $parts[] = $sub;
3240
-                    }
3241
-
3242
-                    $ss = $this->count;
3243
-
3244
-                    if (
3245
-                        $nameParts === ['not'] ||
3246
-                        $nameParts === ['is'] ||
3247
-                        $nameParts === ['has'] ||
3248
-                        $nameParts === ['where'] ||
3249
-                        $nameParts === ['slotted'] ||
3250
-                        $nameParts === ['nth-child'] ||
3251
-                        $nameParts === ['nth-last-child'] ||
3252
-                        $nameParts === ['nth-of-type'] ||
3253
-                        $nameParts === ['nth-last-of-type']
3254
-                    ) {
3255
-                        if (
3256
-                            $this->matchChar('(', true) &&
3257
-                            ($this->selectors($subs, reset($nameParts)) || true) &&
3258
-                            $this->matchChar(')')
3259
-                        ) {
3260
-                            $parts[] = '(';
3261
-
3262
-                            while ($sub = array_shift($subs)) {
3263
-                                while ($ps = array_shift($sub)) {
3264
-                                    foreach ($ps as &$p) {
3265
-                                        $parts[] = $p;
3266
-                                    }
3267
-
3268
-                                    if (\count($sub) && reset($sub)) {
3269
-                                        $parts[] = ' ';
3270
-                                    }
3271
-                                }
3272
-
3273
-                                if (\count($subs) && reset($subs)) {
3274
-                                    $parts[] = ', ';
3275
-                                }
3276
-                            }
3277
-
3278
-                            $parts[] = ')';
3279
-                        } else {
3280
-                            $this->seek($ss);
3281
-                        }
3282
-                    } elseif (
3283
-                        $this->matchChar('(') &&
3284
-                        ($this->openString(')', $str, '(') || true) &&
3285
-                        $this->matchChar(')')
3286
-                    ) {
3287
-                        $parts[] = '(';
3288
-
3289
-                        if (! empty($str)) {
3290
-                            $parts[] = $str;
3291
-                        }
3292
-
3293
-                        $parts[] = ')';
3294
-                    } else {
3295
-                        $this->seek($ss);
3296
-                    }
3297
-
3298
-                    continue;
3299
-                }
3300
-            }
3301
-
3302
-            $this->seek($s);
3303
-
3304
-            // 2n+1
3305
-            if ($subSelector && \is_string($subSelector) && strpos($subSelector, 'nth-') === 0) {
3306
-                if ($this->match("(\s*(\+\s*|\-\s*)?(\d+|n|\d+n))+", $counter)) {
3307
-                    $parts[] = $counter[0];
3308
-                    //$parts[] = str_replace(' ', '', $counter[0]);
3309
-                    continue;
3310
-                }
3311
-            }
3312
-
3313
-            $this->seek($s);
3314
-
3315
-            // attribute selector
3316
-            if (
3317
-                $char === '[' &&
3318
-                $this->matchChar('[') &&
3319
-                ($this->openString(']', $str, '[') || true) &&
3320
-                $this->matchChar(']')
3321
-            ) {
3322
-                $parts[] = '[';
3323
-
3324
-                if (! empty($str)) {
3325
-                    $parts[] = $str;
3326
-                }
3327
-
3328
-                $parts[] = ']';
3329
-                continue;
3330
-            }
3331
-
3332
-            $this->seek($s);
3333
-
3334
-            // for keyframes
3335
-            if ($this->unit($unit)) {
3336
-                $parts[] = $unit;
3337
-                continue;
3338
-            }
3339
-
3340
-            if ($this->restrictedKeyword($name)) {
3341
-                $parts[] = $name;
3342
-                continue;
3343
-            }
3344
-
3345
-            break;
3346
-        }
3347
-
3348
-        $this->eatWhiteDefault = $oldWhite;
3349
-
3350
-        if (! $parts) {
3351
-            return false;
3352
-        }
3353
-
3354
-        $out = $parts;
3355
-
3356
-        return true;
3357
-    }
3358
-
3359
-    /**
3360
-     * Parse a variable
3361
-     *
3362
-     * @param array $out
3363
-     *
3364
-     * @return boolean
3365
-     */
3366
-    protected function variable(&$out)
3367
-    {
3368
-        $s = $this->count;
3369
-
3370
-        if (
3371
-            $this->matchChar('$', false) &&
3372
-            $this->keyword($name)
3373
-        ) {
3374
-            if ($this->allowVars) {
3375
-                $out = [Type::T_VARIABLE, $name];
3376
-            } else {
3377
-                $out = [Type::T_KEYWORD, '$' . $name];
3378
-            }
3379
-
3380
-            return true;
3381
-        }
3382
-
3383
-        $this->seek($s);
3384
-
3385
-        return false;
3386
-    }
3387
-
3388
-    /**
3389
-     * Parse a keyword
3390
-     *
3391
-     * @param string  $word
3392
-     * @param boolean $eatWhitespace
3393
-     *
3394
-     * @return boolean
3395
-     */
3396
-    protected function keyword(&$word, $eatWhitespace = null)
3397
-    {
3398
-        $match = $this->match(
3399
-            $this->utf8
3400
-                ? '(([\pL\w\x{00A0}-\x{10FFFF}_\-\*!"\']|[\\\\].)([\pL\w\x{00A0}-\x{10FFFF}\-_"\']|[\\\\].)*)'
3401
-                : '(([\w_\-\*!"\']|[\\\\].)([\w\-_"\']|[\\\\].)*)',
3402
-            $m,
3403
-            $eatWhitespace
3404
-        );
3405
-
3406
-        if ($match) {
3407
-            $word = $m[1];
3408
-
3409
-            return true;
3410
-        }
3411
-
3412
-        return false;
3413
-    }
3414
-
3415
-    /**
3416
-     * Parse a keyword that should not start with a number
3417
-     *
3418
-     * @param string  $word
3419
-     * @param boolean $eatWhitespace
3420
-     *
3421
-     * @return boolean
3422
-     */
3423
-    protected function restrictedKeyword(&$word, $eatWhitespace = null)
3424
-    {
3425
-        $s = $this->count;
3426
-
3427
-        if ($this->keyword($word, $eatWhitespace) && (\ord($word[0]) > 57 || \ord($word[0]) < 48)) {
3428
-            return true;
3429
-        }
3430
-
3431
-        $this->seek($s);
3432
-
3433
-        return false;
3434
-    }
3435
-
3436
-    /**
3437
-     * Parse a placeholder
3438
-     *
3439
-     * @param string|array $placeholder
3440
-     *
3441
-     * @return boolean
3442
-     */
3443
-    protected function placeholder(&$placeholder)
3444
-    {
3445
-        $match = $this->match(
3446
-            $this->utf8
3447
-                ? '([\pL\w\-_]+)'
3448
-                : '([\w\-_]+)',
3449
-            $m
3450
-        );
3451
-
3452
-        if ($match) {
3453
-            $placeholder = $m[1];
3454
-
3455
-            return true;
3456
-        }
3457
-
3458
-        if ($this->interpolation($placeholder)) {
3459
-            return true;
3460
-        }
3461
-
3462
-        return false;
3463
-    }
3464
-
3465
-    /**
3466
-     * Parse a url
3467
-     *
3468
-     * @param array $out
3469
-     *
3470
-     * @return boolean
3471
-     */
3472
-    protected function url(&$out)
3473
-    {
3474
-        //if ($this->match('(url\(\s*(["\']?)([^)]+)\2\s*\))', $m)) {
3475
-        if (
3476
-            $this->literal('url(', 4) &&
3477
-            ($this->string($out) || $this->openString(')', $out)) &&
3478
-            $this->matchChar(')')
3479
-        ) {
3480
-            $out = [Type::T_STRING, '', ['url(', $out, ')']];
3481
-
3482
-            return true;
3483
-        }
3484
-
3485
-        return false;
3486
-    }
3487
-
3488
-    /**
3489
-     * Consume an end of statement delimiter
3490
-     *
3491
-     * @return boolean
3492
-     */
3493
-    protected function end()
3494
-    {
3495
-        if ($this->matchChar(';')) {
3496
-            return true;
3497
-        }
3498
-
3499
-        if ($this->count === \strlen($this->buffer) || $this->buffer[$this->count] === '}') {
3500
-            // if there is end of file or a closing block next then we don't need a ;
3501
-            return true;
3502
-        }
3503
-
3504
-        return false;
3505
-    }
3506
-
3507
-    /**
3508
-     * Strip assignment flag from the list
3509
-     *
3510
-     * @param array $value
3511
-     *
3512
-     * @return array
3513
-     */
3514
-    protected function stripAssignmentFlags(&$value)
3515
-    {
3516
-        $flags = [];
3517
-
3518
-        for ($token = &$value; $token[0] === Type::T_LIST && ($s = \count($token[2])); $token = &$lastNode) {
3519
-            $lastNode = &$token[2][$s - 1];
3520
-
3521
-            while ($lastNode[0] === Type::T_KEYWORD && \in_array($lastNode[1], ['!default', '!global'])) {
3522
-                array_pop($token[2]);
3523
-
3524
-                $node     = end($token[2]);
3525
-                $token    = $this->flattenList($token);
3526
-                $flags[]  = $lastNode[1];
3527
-                $lastNode = $node;
3528
-            }
3529
-        }
3530
-
3531
-        return $flags;
3532
-    }
3533
-
3534
-    /**
3535
-     * Strip optional flag from selector list
3536
-     *
3537
-     * @param array $selectors
3538
-     *
3539
-     * @return string
3540
-     */
3541
-    protected function stripOptionalFlag(&$selectors)
3542
-    {
3543
-        $optional = false;
3544
-        $selector = end($selectors);
3545
-        $part     = end($selector);
3546
-
3547
-        if ($part === ['!optional']) {
3548
-            array_pop($selectors[\count($selectors) - 1]);
3549
-
3550
-            $optional = true;
3551
-        }
3552
-
3553
-        return $optional;
3554
-    }
3555
-
3556
-    /**
3557
-     * Turn list of length 1 into value type
3558
-     *
3559
-     * @param array $value
3560
-     *
3561
-     * @return array
3562
-     */
3563
-    protected function flattenList($value)
3564
-    {
3565
-        if ($value[0] === Type::T_LIST && \count($value[2]) === 1) {
3566
-            return $this->flattenList($value[2][0]);
3567
-        }
3568
-
3569
-        return $value;
3570
-    }
3571
-
3572
-    /**
3573
-     * Quote regular expression
3574
-     *
3575
-     * @param string $what
3576
-     *
3577
-     * @return string
3578
-     */
3579
-    private function pregQuote($what)
3580
-    {
3581
-        return preg_quote($what, '/');
3582
-    }
3583
-
3584
-    /**
3585
-     * Extract line numbers from buffer
3586
-     *
3587
-     * @param string $buffer
3588
-     */
3589
-    private function extractLineNumbers($buffer)
3590
-    {
3591
-        $this->sourcePositions = [0 => 0];
3592
-        $prev = 0;
3593
-
3594
-        while (($pos = strpos($buffer, "\n", $prev)) !== false) {
3595
-            $this->sourcePositions[] = $pos;
3596
-            $prev = $pos + 1;
3597
-        }
3598
-
3599
-        $this->sourcePositions[] = \strlen($buffer);
3600
-
3601
-        if (substr($buffer, -1) !== "\n") {
3602
-            $this->sourcePositions[] = \strlen($buffer) + 1;
3603
-        }
3604
-    }
3605
-
3606
-    /**
3607
-     * Get source line number and column (given character position in the buffer)
3608
-     *
3609
-     * @param integer $pos
3610
-     *
3611
-     * @return array
3612
-     */
3613
-    private function getSourcePosition($pos)
3614
-    {
3615
-        $low = 0;
3616
-        $high = \count($this->sourcePositions);
3617
-
3618
-        while ($low < $high) {
3619
-            $mid = (int) (($high + $low) / 2);
3620
-
3621
-            if ($pos < $this->sourcePositions[$mid]) {
3622
-                $high = $mid - 1;
3623
-                continue;
3624
-            }
3625
-
3626
-            if ($pos >= $this->sourcePositions[$mid + 1]) {
3627
-                $low = $mid + 1;
3628
-                continue;
3629
-            }
3630
-
3631
-            return [$mid + 1, $pos - $this->sourcePositions[$mid]];
3632
-        }
3633
-
3634
-        return [$low + 1, $pos - $this->sourcePositions[$low]];
3635
-    }
3636
-
3637
-    /**
3638
-     * Save internal encoding
3639
-     */
3640
-    private function saveEncoding()
3641
-    {
3642
-        if (\extension_loaded('mbstring')) {
3643
-            $this->encoding = mb_internal_encoding();
3644
-
3645
-            mb_internal_encoding('iso-8859-1');
3646
-        }
3647
-    }
3648
-
3649
-    /**
3650
-     * Restore internal encoding
3651
-     */
3652
-    private function restoreEncoding()
3653
-    {
3654
-        if (\extension_loaded('mbstring') && $this->encoding) {
3655
-            mb_internal_encoding($this->encoding);
3656
-        }
3657
-    }
2295
+			return true;
2296
+		}
2297
+
2298
+		if ($this->keyword($keyword, false)) {
2299
+			if ($this->func($keyword, $out)) {
2300
+				return true;
2301
+			}
2302
+
2303
+			$this->whitespace();
2304
+
2305
+			if ($keyword === 'null') {
2306
+				$out = [Type::T_NULL];
2307
+			} else {
2308
+				$out = [Type::T_KEYWORD, $keyword];
2309
+			}
2310
+
2311
+			return true;
2312
+		}
2313
+
2314
+		return false;
2315
+	}
2316
+
2317
+	/**
2318
+	 * Parse parenthesized value
2319
+	 *
2320
+	 * @param array $out
2321
+	 *
2322
+	 * @return boolean
2323
+	 */
2324
+	protected function parenValue(&$out)
2325
+	{
2326
+		$s = $this->count;
2327
+
2328
+		$inParens = $this->inParens;
2329
+
2330
+		if ($this->matchChar('(')) {
2331
+			if ($this->matchChar(')')) {
2332
+				$out = [Type::T_LIST, '', []];
2333
+
2334
+				return true;
2335
+			}
2336
+
2337
+			$this->inParens = true;
2338
+
2339
+			if (
2340
+				$this->expression($exp) &&
2341
+				$this->matchChar(')')
2342
+			) {
2343
+				$out = $exp;
2344
+				$this->inParens = $inParens;
2345
+
2346
+				return true;
2347
+			}
2348
+		}
2349
+
2350
+		$this->inParens = $inParens;
2351
+		$this->seek($s);
2352
+
2353
+		return false;
2354
+	}
2355
+
2356
+	/**
2357
+	 * Parse "progid:"
2358
+	 *
2359
+	 * @param array $out
2360
+	 *
2361
+	 * @return boolean
2362
+	 */
2363
+	protected function progid(&$out)
2364
+	{
2365
+		$s = $this->count;
2366
+
2367
+		if (
2368
+			$this->literal('progid:', 7, false) &&
2369
+			$this->openString('(', $fn) &&
2370
+			$this->matchChar('(')
2371
+		) {
2372
+			$this->openString(')', $args, '(');
2373
+
2374
+			if ($this->matchChar(')')) {
2375
+				$out = [Type::T_STRING, '', [
2376
+					'progid:', $fn, '(', $args, ')'
2377
+				]];
2378
+
2379
+				return true;
2380
+			}
2381
+		}
2382
+
2383
+		$this->seek($s);
2384
+
2385
+		return false;
2386
+	}
2387
+
2388
+	/**
2389
+	 * Parse function call
2390
+	 *
2391
+	 * @param string $name
2392
+	 * @param array  $func
2393
+	 *
2394
+	 * @return boolean
2395
+	 */
2396
+	protected function func($name, &$func)
2397
+	{
2398
+		$s = $this->count;
2399
+
2400
+		if ($this->matchChar('(')) {
2401
+			if ($name === 'alpha' && $this->argumentList($args)) {
2402
+				$func = [Type::T_FUNCTION, $name, [Type::T_STRING, '', $args]];
2403
+
2404
+				return true;
2405
+			}
2406
+
2407
+			if ($name !== 'expression' && ! preg_match('/^(-[a-z]+-)?calc$/', $name)) {
2408
+				$ss = $this->count;
2409
+
2410
+				if (
2411
+					$this->argValues($args) &&
2412
+					$this->matchChar(')')
2413
+				) {
2414
+					$func = [Type::T_FUNCTION_CALL, $name, $args];
2415
+
2416
+					return true;
2417
+				}
2418
+
2419
+				$this->seek($ss);
2420
+			}
2421
+
2422
+			if (
2423
+				($this->openString(')', $str, '(') || true) &&
2424
+				$this->matchChar(')')
2425
+			) {
2426
+				$args = [];
2427
+
2428
+				if (! empty($str)) {
2429
+					$args[] = [null, [Type::T_STRING, '', [$str]]];
2430
+				}
2431
+
2432
+				$func = [Type::T_FUNCTION_CALL, $name, $args];
2433
+
2434
+				return true;
2435
+			}
2436
+		}
2437
+
2438
+		$this->seek($s);
2439
+
2440
+		return false;
2441
+	}
2442
+
2443
+	/**
2444
+	 * Parse function call argument list
2445
+	 *
2446
+	 * @param array $out
2447
+	 *
2448
+	 * @return boolean
2449
+	 */
2450
+	protected function argumentList(&$out)
2451
+	{
2452
+		$s = $this->count;
2453
+		$this->matchChar('(');
2454
+
2455
+		$args = [];
2456
+
2457
+		while ($this->keyword($var)) {
2458
+			if (
2459
+				$this->matchChar('=') &&
2460
+				$this->expression($exp)
2461
+			) {
2462
+				$args[] = [Type::T_STRING, '', [$var . '=']];
2463
+				$arg = $exp;
2464
+			} else {
2465
+				break;
2466
+			}
2467
+
2468
+			$args[] = $arg;
2469
+
2470
+			if (! $this->matchChar(',')) {
2471
+				break;
2472
+			}
2473
+
2474
+			$args[] = [Type::T_STRING, '', [', ']];
2475
+		}
2476
+
2477
+		if (! $this->matchChar(')') || ! $args) {
2478
+			$this->seek($s);
2479
+
2480
+			return false;
2481
+		}
2482
+
2483
+		$out = $args;
2484
+
2485
+		return true;
2486
+	}
2487
+
2488
+	/**
2489
+	 * Parse mixin/function definition  argument list
2490
+	 *
2491
+	 * @param array $out
2492
+	 *
2493
+	 * @return boolean
2494
+	 */
2495
+	protected function argumentDef(&$out)
2496
+	{
2497
+		$s = $this->count;
2498
+		$this->matchChar('(');
2499
+
2500
+		$args = [];
2501
+
2502
+		while ($this->variable($var)) {
2503
+			$arg = [$var[1], null, false];
2504
+
2505
+			$ss = $this->count;
2506
+
2507
+			if (
2508
+				$this->matchChar(':') &&
2509
+				$this->genericList($defaultVal, 'expression', '', true)
2510
+			) {
2511
+				$arg[1] = $defaultVal;
2512
+			} else {
2513
+				$this->seek($ss);
2514
+			}
2515
+
2516
+			$ss = $this->count;
2517
+
2518
+			if ($this->literal('...', 3)) {
2519
+				$sss = $this->count;
2520
+
2521
+				if (! $this->matchChar(')')) {
2522
+					$this->throwParseError('... has to be after the final argument');
2523
+				}
2524
+
2525
+				$arg[2] = true;
2526
+
2527
+				$this->seek($sss);
2528
+			} else {
2529
+				$this->seek($ss);
2530
+			}
2531
+
2532
+			$args[] = $arg;
2533
+
2534
+			if (! $this->matchChar(',')) {
2535
+				break;
2536
+			}
2537
+		}
2538
+
2539
+		if (! $this->matchChar(')')) {
2540
+			$this->seek($s);
2541
+
2542
+			return false;
2543
+		}
2544
+
2545
+		$out = $args;
2546
+
2547
+		return true;
2548
+	}
2549
+
2550
+	/**
2551
+	 * Parse map
2552
+	 *
2553
+	 * @param array $out
2554
+	 *
2555
+	 * @return boolean
2556
+	 */
2557
+	protected function map(&$out)
2558
+	{
2559
+		$s = $this->count;
2560
+
2561
+		if (! $this->matchChar('(')) {
2562
+			return false;
2563
+		}
2564
+
2565
+		$keys = [];
2566
+		$values = [];
2567
+
2568
+		while (
2569
+			$this->genericList($key, 'expression', '', true) &&
2570
+			$this->matchChar(':') &&
2571
+			$this->genericList($value, 'expression', '', true)
2572
+		) {
2573
+			$keys[] = $key;
2574
+			$values[] = $value;
2575
+
2576
+			if (! $this->matchChar(',')) {
2577
+				break;
2578
+			}
2579
+		}
2580
+
2581
+		if (! $keys || ! $this->matchChar(')')) {
2582
+			$this->seek($s);
2583
+
2584
+			return false;
2585
+		}
2586
+
2587
+		$out = [Type::T_MAP, $keys, $values];
2588
+
2589
+		return true;
2590
+	}
2591
+
2592
+	/**
2593
+	 * Parse color
2594
+	 *
2595
+	 * @param array $out
2596
+	 *
2597
+	 * @return boolean
2598
+	 */
2599
+	protected function color(&$out)
2600
+	{
2601
+		$s = $this->count;
2602
+
2603
+		if ($this->match('(#([0-9a-f]+)\b)', $m)) {
2604
+			if (\in_array(\strlen($m[2]), [3,4,6,8])) {
2605
+				$out = [Type::T_KEYWORD, $m[0]];
2606
+
2607
+				return true;
2608
+			}
2609
+
2610
+			$this->seek($s);
2611
+
2612
+			return false;
2613
+		}
2614
+
2615
+		return false;
2616
+	}
2617
+
2618
+	/**
2619
+	 * Parse number with unit
2620
+	 *
2621
+	 * @param array $unit
2622
+	 *
2623
+	 * @return boolean
2624
+	 */
2625
+	protected function unit(&$unit)
2626
+	{
2627
+		$s = $this->count;
2628
+
2629
+		if ($this->match('([0-9]*(\.)?[0-9]+)([%a-zA-Z]+)?', $m, false)) {
2630
+			if (\strlen($this->buffer) === $this->count || ! ctype_digit($this->buffer[$this->count])) {
2631
+				$this->whitespace();
2632
+
2633
+				$unit = new Node\Number($m[1], empty($m[3]) ? '' : $m[3]);
2634
+
2635
+				return true;
2636
+			}
2637
+
2638
+			$this->seek($s);
2639
+		}
2640
+
2641
+		return false;
2642
+	}
2643
+
2644
+	/**
2645
+	 * Parse string
2646
+	 *
2647
+	 * @param array $out
2648
+	 *
2649
+	 * @return boolean
2650
+	 */
2651
+	protected function string(&$out)
2652
+	{
2653
+		$s = $this->count;
2654
+
2655
+		if ($this->matchChar('"', false)) {
2656
+			$delim = '"';
2657
+		} elseif ($this->matchChar("'", false)) {
2658
+			$delim = "'";
2659
+		} else {
2660
+			return false;
2661
+		}
2662
+
2663
+		$content = [];
2664
+		$oldWhite = $this->eatWhiteDefault;
2665
+		$this->eatWhiteDefault = false;
2666
+		$hasInterpolation = false;
2667
+
2668
+		while ($this->matchString($m, $delim)) {
2669
+			if ($m[1] !== '') {
2670
+				$content[] = $m[1];
2671
+			}
2672
+
2673
+			if ($m[2] === '#{') {
2674
+				$this->count -= \strlen($m[2]);
2675
+
2676
+				if ($this->interpolation($inter, false)) {
2677
+					$content[] = $inter;
2678
+					$hasInterpolation = true;
2679
+				} else {
2680
+					$this->count += \strlen($m[2]);
2681
+					$content[] = '#{'; // ignore it
2682
+				}
2683
+			} elseif ($m[2] === "\r") {
2684
+				$content[] = '\\a';
2685
+				// TODO : warning
2686
+				# DEPRECATION WARNING on line x, column y of zzz:
2687
+				# Unescaped multiline strings are deprecated and will be removed in a future version of Sass.
2688
+				# To include a newline in a string, use "\a" or "\a " as in CSS.
2689
+				if ($this->matchChar("\n", false)) {
2690
+					$content[] = ' ';
2691
+				}
2692
+			} elseif ($m[2] === '\\') {
2693
+				if (
2694
+					$this->literal("\r\n", 2, false) ||
2695
+					$this->matchChar("\r", false) ||
2696
+					$this->matchChar("\n", false) ||
2697
+					$this->matchChar("\f", false)
2698
+				) {
2699
+					// this is a continuation escaping, to be ignored
2700
+				} elseif ($this->matchEscapeCharacter($c)) {
2701
+					$content[] = $c;
2702
+				} else {
2703
+					$this->throwParseError('Unterminated escape sequence');
2704
+				}
2705
+			} else {
2706
+				$this->count -= \strlen($delim);
2707
+				break; // delim
2708
+			}
2709
+		}
2710
+
2711
+		$this->eatWhiteDefault = $oldWhite;
2712
+
2713
+		if ($this->literal($delim, \strlen($delim))) {
2714
+			if ($hasInterpolation) {
2715
+				$delim = '"';
2716
+			}
2717
+
2718
+			$out = [Type::T_STRING, $delim, $content];
2719
+
2720
+			return true;
2721
+		}
2722
+
2723
+		$this->seek($s);
2724
+
2725
+		return false;
2726
+	}
2727
+
2728
+	protected function matchEscapeCharacter(&$out)
2729
+	{
2730
+		if ($this->match('[a-f0-9]', $m, false)) {
2731
+			$hex = $m[0];
2732
+
2733
+			for ($i = 5; $i--;) {
2734
+				if ($this->match('[a-f0-9]', $m, false)) {
2735
+					$hex .= $m[0];
2736
+				} else {
2737
+					break;
2738
+				}
2739
+			}
2740
+
2741
+			$value = hexdec($hex);
2742
+
2743
+			if ($value == 0 || ($value >= 0xD800 && $value <= 0xDFFF) || $value >= 0x10FFFF) {
2744
+				$out = "\u{FFFD}";
2745
+			} else {
2746
+				$out = Util::mbChr($value);
2747
+			}
2748
+
2749
+			return true;
2750
+		}
2751
+
2752
+		if ($this->match('.', $m, false)) {
2753
+			$out = $m[0];
2754
+
2755
+			return true;
2756
+		}
2757
+
2758
+		return false;
2759
+	}
2760
+
2761
+	/**
2762
+	 * Parse keyword or interpolation
2763
+	 *
2764
+	 * @param array   $out
2765
+	 * @param boolean $restricted
2766
+	 *
2767
+	 * @return boolean
2768
+	 */
2769
+	protected function mixedKeyword(&$out, $restricted = false)
2770
+	{
2771
+		$parts = [];
2772
+
2773
+		$oldWhite = $this->eatWhiteDefault;
2774
+		$this->eatWhiteDefault = false;
2775
+
2776
+		for (;;) {
2777
+			if ($restricted ? $this->restrictedKeyword($key) : $this->keyword($key)) {
2778
+				$parts[] = $key;
2779
+				continue;
2780
+			}
2781
+
2782
+			if ($this->interpolation($inter)) {
2783
+				$parts[] = $inter;
2784
+				continue;
2785
+			}
2786
+
2787
+			break;
2788
+		}
2789
+
2790
+		$this->eatWhiteDefault = $oldWhite;
2791
+
2792
+		if (! $parts) {
2793
+			return false;
2794
+		}
2795
+
2796
+		if ($this->eatWhiteDefault) {
2797
+			$this->whitespace();
2798
+		}
2799
+
2800
+		$out = $parts;
2801
+
2802
+		return true;
2803
+	}
2804
+
2805
+	/**
2806
+	 * Parse an unbounded string stopped by $end
2807
+	 *
2808
+	 * @param string  $end
2809
+	 * @param array   $out
2810
+	 * @param string  $nestingOpen
2811
+	 * @param string  $nestingClose
2812
+	 * @param boolean $trimEnd
2813
+	 *
2814
+	 * @return boolean
2815
+	 */
2816
+	protected function openString($end, &$out, $nestingOpen = null, $nestingClose = null, $trimEnd = true)
2817
+	{
2818
+		$oldWhite = $this->eatWhiteDefault;
2819
+		$this->eatWhiteDefault = false;
2820
+
2821
+		if ($nestingOpen && ! $nestingClose) {
2822
+			$nestingClose = $end;
2823
+		}
2824
+
2825
+		$patt = '(.*?)([\'"]|#\{|'
2826
+			. $this->pregQuote($end) . '|'
2827
+			. (($nestingClose && $nestingClose !== $end) ? $this->pregQuote($nestingClose) . '|' : '')
2828
+			. static::$commentPattern . ')';
2829
+
2830
+		$nestingLevel = 0;
2831
+
2832
+		$content = [];
2833
+
2834
+		while ($this->match($patt, $m, false)) {
2835
+			if (isset($m[1]) && $m[1] !== '') {
2836
+				$content[] = $m[1];
2837
+
2838
+				if ($nestingOpen) {
2839
+					$nestingLevel += substr_count($m[1], $nestingOpen);
2840
+				}
2841
+			}
2842
+
2843
+			$tok = $m[2];
2844
+
2845
+			$this->count -= \strlen($tok);
2846
+
2847
+			if ($tok === $end && ! $nestingLevel) {
2848
+				break;
2849
+			}
2850
+
2851
+			if ($tok === $nestingClose) {
2852
+				$nestingLevel--;
2853
+			}
2854
+
2855
+			if (($tok === "'" || $tok === '"') && $this->string($str)) {
2856
+				$content[] = $str;
2857
+				continue;
2858
+			}
2859
+
2860
+			if ($tok === '#{' && $this->interpolation($inter)) {
2861
+				$content[] = $inter;
2862
+				continue;
2863
+			}
2864
+
2865
+			$content[] = $tok;
2866
+			$this->count += \strlen($tok);
2867
+		}
2868
+
2869
+		$this->eatWhiteDefault = $oldWhite;
2870
+
2871
+		if (! $content || $tok !== $end) {
2872
+			return false;
2873
+		}
2874
+
2875
+		// trim the end
2876
+		if ($trimEnd && \is_string(end($content))) {
2877
+			$content[\count($content) - 1] = rtrim(end($content));
2878
+		}
2879
+
2880
+		$out = [Type::T_STRING, '', $content];
2881
+
2882
+		return true;
2883
+	}
2884
+
2885
+	/**
2886
+	 * Parser interpolation
2887
+	 *
2888
+	 * @param string|array $out
2889
+	 * @param boolean      $lookWhite save information about whitespace before and after
2890
+	 *
2891
+	 * @return boolean
2892
+	 */
2893
+	protected function interpolation(&$out, $lookWhite = true)
2894
+	{
2895
+		$oldWhite = $this->eatWhiteDefault;
2896
+		$allowVars = $this->allowVars;
2897
+		$this->allowVars = true;
2898
+		$this->eatWhiteDefault = true;
2899
+
2900
+		$s = $this->count;
2901
+
2902
+		if (
2903
+			$this->literal('#{', 2) &&
2904
+			$this->valueList($value) &&
2905
+			$this->matchChar('}', false)
2906
+		) {
2907
+			if ($value === [Type::T_SELF]) {
2908
+				$out = $value;
2909
+			} else {
2910
+				if ($lookWhite) {
2911
+					$left = ($s > 0 && preg_match('/\s/', $this->buffer[$s - 1])) ? ' ' : '';
2912
+					$right = preg_match('/\s/', $this->buffer[$this->count]) ? ' ' : '';
2913
+				} else {
2914
+					$left = $right = false;
2915
+				}
2916
+
2917
+				$out = [Type::T_INTERPOLATE, $value, $left, $right];
2918
+			}
2919
+
2920
+			$this->eatWhiteDefault = $oldWhite;
2921
+			$this->allowVars = $allowVars;
2922
+
2923
+			if ($this->eatWhiteDefault) {
2924
+				$this->whitespace();
2925
+			}
2926
+
2927
+			return true;
2928
+		}
2929
+
2930
+		$this->seek($s);
2931
+
2932
+		$this->eatWhiteDefault = $oldWhite;
2933
+		$this->allowVars = $allowVars;
2934
+
2935
+		return false;
2936
+	}
2937
+
2938
+	/**
2939
+	 * Parse property name (as an array of parts or a string)
2940
+	 *
2941
+	 * @param array $out
2942
+	 *
2943
+	 * @return boolean
2944
+	 */
2945
+	protected function propertyName(&$out)
2946
+	{
2947
+		$parts = [];
2948
+
2949
+		$oldWhite = $this->eatWhiteDefault;
2950
+		$this->eatWhiteDefault = false;
2951
+
2952
+		for (;;) {
2953
+			if ($this->interpolation($inter)) {
2954
+				$parts[] = $inter;
2955
+				continue;
2956
+			}
2957
+
2958
+			if ($this->keyword($text)) {
2959
+				$parts[] = $text;
2960
+				continue;
2961
+			}
2962
+
2963
+			if (! $parts && $this->match('[:.#]', $m, false)) {
2964
+				// css hacks
2965
+				$parts[] = $m[0];
2966
+				continue;
2967
+			}
2968
+
2969
+			break;
2970
+		}
2971
+
2972
+		$this->eatWhiteDefault = $oldWhite;
2973
+
2974
+		if (! $parts) {
2975
+			return false;
2976
+		}
2977
+
2978
+		// match comment hack
2979
+		if (preg_match(static::$whitePattern, $this->buffer, $m, null, $this->count)) {
2980
+			if (! empty($m[0])) {
2981
+				$parts[] = $m[0];
2982
+				$this->count += \strlen($m[0]);
2983
+			}
2984
+		}
2985
+
2986
+		$this->whitespace(); // get any extra whitespace
2987
+
2988
+		$out = [Type::T_STRING, '', $parts];
2989
+
2990
+		return true;
2991
+	}
2992
+
2993
+	/**
2994
+	 * Parse custom property name (as an array of parts or a string)
2995
+	 *
2996
+	 * @param array $out
2997
+	 *
2998
+	 * @return boolean
2999
+	 */
3000
+	protected function customProperty(&$out)
3001
+	{
3002
+		$s = $this->count;
3003
+
3004
+		if (! $this->literal('--', 2, false)) {
3005
+			return false;
3006
+		}
3007
+
3008
+		$parts = ['--'];
3009
+
3010
+		$oldWhite = $this->eatWhiteDefault;
3011
+		$this->eatWhiteDefault = false;
3012
+
3013
+		for (;;) {
3014
+			if ($this->interpolation($inter)) {
3015
+				$parts[] = $inter;
3016
+				continue;
3017
+			}
3018
+
3019
+			if ($this->matchChar('&', false)) {
3020
+				$parts[] = [Type::T_SELF];
3021
+				continue;
3022
+			}
3023
+
3024
+			if ($this->variable($var)) {
3025
+				$parts[] = $var;
3026
+				continue;
3027
+			}
3028
+
3029
+			if ($this->keyword($text)) {
3030
+				$parts[] = $text;
3031
+				continue;
3032
+			}
3033
+
3034
+			break;
3035
+		}
3036
+
3037
+		$this->eatWhiteDefault = $oldWhite;
3038
+
3039
+		if (\count($parts) == 1) {
3040
+			$this->seek($s);
3041
+
3042
+			return false;
3043
+		}
3044
+
3045
+		$this->whitespace(); // get any extra whitespace
3046
+
3047
+		$out = [Type::T_STRING, '', $parts];
3048
+
3049
+		return true;
3050
+	}
3051
+
3052
+	/**
3053
+	 * Parse comma separated selector list
3054
+	 *
3055
+	 * @param array   $out
3056
+	 * @param boolean $subSelector
3057
+	 *
3058
+	 * @return boolean
3059
+	 */
3060
+	protected function selectors(&$out, $subSelector = false)
3061
+	{
3062
+		$s = $this->count;
3063
+		$selectors = [];
3064
+
3065
+		while ($this->selector($sel, $subSelector)) {
3066
+			$selectors[] = $sel;
3067
+
3068
+			if (! $this->matchChar(',', true)) {
3069
+				break;
3070
+			}
3071
+
3072
+			while ($this->matchChar(',', true)) {
3073
+				; // ignore extra
3074
+			}
3075
+		}
3076
+
3077
+		if (! $selectors) {
3078
+			$this->seek($s);
3079
+
3080
+			return false;
3081
+		}
3082
+
3083
+		$out = $selectors;
3084
+
3085
+		return true;
3086
+	}
3087
+
3088
+	/**
3089
+	 * Parse whitespace separated selector list
3090
+	 *
3091
+	 * @param array   $out
3092
+	 * @param boolean $subSelector
3093
+	 *
3094
+	 * @return boolean
3095
+	 */
3096
+	protected function selector(&$out, $subSelector = false)
3097
+	{
3098
+		$selector = [];
3099
+
3100
+		for (;;) {
3101
+			$s = $this->count;
3102
+
3103
+			if ($this->match('[>+~]+', $m, true)) {
3104
+				if (
3105
+					$subSelector && \is_string($subSelector) && strpos($subSelector, 'nth-') === 0 &&
3106
+					$m[0] === '+' && $this->match("(\d+|n\b)", $counter)
3107
+				) {
3108
+					$this->seek($s);
3109
+				} else {
3110
+					$selector[] = [$m[0]];
3111
+					continue;
3112
+				}
3113
+			}
3114
+
3115
+			if ($this->selectorSingle($part, $subSelector)) {
3116
+				$selector[] = $part;
3117
+				$this->match('\s+', $m);
3118
+				continue;
3119
+			}
3120
+
3121
+			if ($this->match('\/[^\/]+\/', $m, true)) {
3122
+				$selector[] = [$m[0]];
3123
+				continue;
3124
+			}
3125
+
3126
+			break;
3127
+		}
3128
+
3129
+		if (! $selector) {
3130
+			return false;
3131
+		}
3132
+
3133
+		$out = $selector;
3134
+
3135
+		return true;
3136
+	}
3137
+
3138
+	/**
3139
+	 * Parse the parts that make up a selector
3140
+	 *
3141
+	 * {@internal
3142
+	 *     div[yes=no]#something.hello.world:nth-child(-2n+1)%placeholder
3143
+	 * }}
3144
+	 *
3145
+	 * @param array   $out
3146
+	 * @param boolean $subSelector
3147
+	 *
3148
+	 * @return boolean
3149
+	 */
3150
+	protected function selectorSingle(&$out, $subSelector = false)
3151
+	{
3152
+		$oldWhite = $this->eatWhiteDefault;
3153
+		$this->eatWhiteDefault = false;
3154
+
3155
+		$parts = [];
3156
+
3157
+		if ($this->matchChar('*', false)) {
3158
+			$parts[] = '*';
3159
+		}
3160
+
3161
+		for (;;) {
3162
+			if (! isset($this->buffer[$this->count])) {
3163
+				break;
3164
+			}
3165
+
3166
+			$s = $this->count;
3167
+			$char = $this->buffer[$this->count];
3168
+
3169
+			// see if we can stop early
3170
+			if ($char === '{' || $char === ',' || $char === ';' || $char === '}' || $char === '@') {
3171
+				break;
3172
+			}
3173
+
3174
+			// parsing a sub selector in () stop with the closing )
3175
+			if ($subSelector && $char === ')') {
3176
+				break;
3177
+			}
3178
+
3179
+			//self
3180
+			switch ($char) {
3181
+				case '&':
3182
+					$parts[] = Compiler::$selfSelector;
3183
+					$this->count++;
3184
+					continue 2;
3185
+
3186
+				case '.':
3187
+					$parts[] = '.';
3188
+					$this->count++;
3189
+					continue 2;
3190
+
3191
+				case '|':
3192
+					$parts[] = '|';
3193
+					$this->count++;
3194
+					continue 2;
3195
+			}
3196
+
3197
+			if ($char === '\\' && $this->match('\\\\\S', $m)) {
3198
+				$parts[] = $m[0];
3199
+				continue;
3200
+			}
3201
+
3202
+			if ($char === '%') {
3203
+				$this->count++;
3204
+
3205
+				if ($this->placeholder($placeholder)) {
3206
+					$parts[] = '%';
3207
+					$parts[] = $placeholder;
3208
+					continue;
3209
+				}
3210
+
3211
+				break;
3212
+			}
3213
+
3214
+			if ($char === '#') {
3215
+				if ($this->interpolation($inter)) {
3216
+					$parts[] = $inter;
3217
+					continue;
3218
+				}
3219
+
3220
+				$parts[] = '#';
3221
+				$this->count++;
3222
+				continue;
3223
+			}
3224
+
3225
+			// a pseudo selector
3226
+			if ($char === ':') {
3227
+				if ($this->buffer[$this->count + 1] === ':') {
3228
+					$this->count += 2;
3229
+					$part = '::';
3230
+				} else {
3231
+					$this->count++;
3232
+					$part = ':';
3233
+				}
3234
+
3235
+				if ($this->mixedKeyword($nameParts, true)) {
3236
+					$parts[] = $part;
3237
+
3238
+					foreach ($nameParts as $sub) {
3239
+						$parts[] = $sub;
3240
+					}
3241
+
3242
+					$ss = $this->count;
3243
+
3244
+					if (
3245
+						$nameParts === ['not'] ||
3246
+						$nameParts === ['is'] ||
3247
+						$nameParts === ['has'] ||
3248
+						$nameParts === ['where'] ||
3249
+						$nameParts === ['slotted'] ||
3250
+						$nameParts === ['nth-child'] ||
3251
+						$nameParts === ['nth-last-child'] ||
3252
+						$nameParts === ['nth-of-type'] ||
3253
+						$nameParts === ['nth-last-of-type']
3254
+					) {
3255
+						if (
3256
+							$this->matchChar('(', true) &&
3257
+							($this->selectors($subs, reset($nameParts)) || true) &&
3258
+							$this->matchChar(')')
3259
+						) {
3260
+							$parts[] = '(';
3261
+
3262
+							while ($sub = array_shift($subs)) {
3263
+								while ($ps = array_shift($sub)) {
3264
+									foreach ($ps as &$p) {
3265
+										$parts[] = $p;
3266
+									}
3267
+
3268
+									if (\count($sub) && reset($sub)) {
3269
+										$parts[] = ' ';
3270
+									}
3271
+								}
3272
+
3273
+								if (\count($subs) && reset($subs)) {
3274
+									$parts[] = ', ';
3275
+								}
3276
+							}
3277
+
3278
+							$parts[] = ')';
3279
+						} else {
3280
+							$this->seek($ss);
3281
+						}
3282
+					} elseif (
3283
+						$this->matchChar('(') &&
3284
+						($this->openString(')', $str, '(') || true) &&
3285
+						$this->matchChar(')')
3286
+					) {
3287
+						$parts[] = '(';
3288
+
3289
+						if (! empty($str)) {
3290
+							$parts[] = $str;
3291
+						}
3292
+
3293
+						$parts[] = ')';
3294
+					} else {
3295
+						$this->seek($ss);
3296
+					}
3297
+
3298
+					continue;
3299
+				}
3300
+			}
3301
+
3302
+			$this->seek($s);
3303
+
3304
+			// 2n+1
3305
+			if ($subSelector && \is_string($subSelector) && strpos($subSelector, 'nth-') === 0) {
3306
+				if ($this->match("(\s*(\+\s*|\-\s*)?(\d+|n|\d+n))+", $counter)) {
3307
+					$parts[] = $counter[0];
3308
+					//$parts[] = str_replace(' ', '', $counter[0]);
3309
+					continue;
3310
+				}
3311
+			}
3312
+
3313
+			$this->seek($s);
3314
+
3315
+			// attribute selector
3316
+			if (
3317
+				$char === '[' &&
3318
+				$this->matchChar('[') &&
3319
+				($this->openString(']', $str, '[') || true) &&
3320
+				$this->matchChar(']')
3321
+			) {
3322
+				$parts[] = '[';
3323
+
3324
+				if (! empty($str)) {
3325
+					$parts[] = $str;
3326
+				}
3327
+
3328
+				$parts[] = ']';
3329
+				continue;
3330
+			}
3331
+
3332
+			$this->seek($s);
3333
+
3334
+			// for keyframes
3335
+			if ($this->unit($unit)) {
3336
+				$parts[] = $unit;
3337
+				continue;
3338
+			}
3339
+
3340
+			if ($this->restrictedKeyword($name)) {
3341
+				$parts[] = $name;
3342
+				continue;
3343
+			}
3344
+
3345
+			break;
3346
+		}
3347
+
3348
+		$this->eatWhiteDefault = $oldWhite;
3349
+
3350
+		if (! $parts) {
3351
+			return false;
3352
+		}
3353
+
3354
+		$out = $parts;
3355
+
3356
+		return true;
3357
+	}
3358
+
3359
+	/**
3360
+	 * Parse a variable
3361
+	 *
3362
+	 * @param array $out
3363
+	 *
3364
+	 * @return boolean
3365
+	 */
3366
+	protected function variable(&$out)
3367
+	{
3368
+		$s = $this->count;
3369
+
3370
+		if (
3371
+			$this->matchChar('$', false) &&
3372
+			$this->keyword($name)
3373
+		) {
3374
+			if ($this->allowVars) {
3375
+				$out = [Type::T_VARIABLE, $name];
3376
+			} else {
3377
+				$out = [Type::T_KEYWORD, '$' . $name];
3378
+			}
3379
+
3380
+			return true;
3381
+		}
3382
+
3383
+		$this->seek($s);
3384
+
3385
+		return false;
3386
+	}
3387
+
3388
+	/**
3389
+	 * Parse a keyword
3390
+	 *
3391
+	 * @param string  $word
3392
+	 * @param boolean $eatWhitespace
3393
+	 *
3394
+	 * @return boolean
3395
+	 */
3396
+	protected function keyword(&$word, $eatWhitespace = null)
3397
+	{
3398
+		$match = $this->match(
3399
+			$this->utf8
3400
+				? '(([\pL\w\x{00A0}-\x{10FFFF}_\-\*!"\']|[\\\\].)([\pL\w\x{00A0}-\x{10FFFF}\-_"\']|[\\\\].)*)'
3401
+				: '(([\w_\-\*!"\']|[\\\\].)([\w\-_"\']|[\\\\].)*)',
3402
+			$m,
3403
+			$eatWhitespace
3404
+		);
3405
+
3406
+		if ($match) {
3407
+			$word = $m[1];
3408
+
3409
+			return true;
3410
+		}
3411
+
3412
+		return false;
3413
+	}
3414
+
3415
+	/**
3416
+	 * Parse a keyword that should not start with a number
3417
+	 *
3418
+	 * @param string  $word
3419
+	 * @param boolean $eatWhitespace
3420
+	 *
3421
+	 * @return boolean
3422
+	 */
3423
+	protected function restrictedKeyword(&$word, $eatWhitespace = null)
3424
+	{
3425
+		$s = $this->count;
3426
+
3427
+		if ($this->keyword($word, $eatWhitespace) && (\ord($word[0]) > 57 || \ord($word[0]) < 48)) {
3428
+			return true;
3429
+		}
3430
+
3431
+		$this->seek($s);
3432
+
3433
+		return false;
3434
+	}
3435
+
3436
+	/**
3437
+	 * Parse a placeholder
3438
+	 *
3439
+	 * @param string|array $placeholder
3440
+	 *
3441
+	 * @return boolean
3442
+	 */
3443
+	protected function placeholder(&$placeholder)
3444
+	{
3445
+		$match = $this->match(
3446
+			$this->utf8
3447
+				? '([\pL\w\-_]+)'
3448
+				: '([\w\-_]+)',
3449
+			$m
3450
+		);
3451
+
3452
+		if ($match) {
3453
+			$placeholder = $m[1];
3454
+
3455
+			return true;
3456
+		}
3457
+
3458
+		if ($this->interpolation($placeholder)) {
3459
+			return true;
3460
+		}
3461
+
3462
+		return false;
3463
+	}
3464
+
3465
+	/**
3466
+	 * Parse a url
3467
+	 *
3468
+	 * @param array $out
3469
+	 *
3470
+	 * @return boolean
3471
+	 */
3472
+	protected function url(&$out)
3473
+	{
3474
+		//if ($this->match('(url\(\s*(["\']?)([^)]+)\2\s*\))', $m)) {
3475
+		if (
3476
+			$this->literal('url(', 4) &&
3477
+			($this->string($out) || $this->openString(')', $out)) &&
3478
+			$this->matchChar(')')
3479
+		) {
3480
+			$out = [Type::T_STRING, '', ['url(', $out, ')']];
3481
+
3482
+			return true;
3483
+		}
3484
+
3485
+		return false;
3486
+	}
3487
+
3488
+	/**
3489
+	 * Consume an end of statement delimiter
3490
+	 *
3491
+	 * @return boolean
3492
+	 */
3493
+	protected function end()
3494
+	{
3495
+		if ($this->matchChar(';')) {
3496
+			return true;
3497
+		}
3498
+
3499
+		if ($this->count === \strlen($this->buffer) || $this->buffer[$this->count] === '}') {
3500
+			// if there is end of file or a closing block next then we don't need a ;
3501
+			return true;
3502
+		}
3503
+
3504
+		return false;
3505
+	}
3506
+
3507
+	/**
3508
+	 * Strip assignment flag from the list
3509
+	 *
3510
+	 * @param array $value
3511
+	 *
3512
+	 * @return array
3513
+	 */
3514
+	protected function stripAssignmentFlags(&$value)
3515
+	{
3516
+		$flags = [];
3517
+
3518
+		for ($token = &$value; $token[0] === Type::T_LIST && ($s = \count($token[2])); $token = &$lastNode) {
3519
+			$lastNode = &$token[2][$s - 1];
3520
+
3521
+			while ($lastNode[0] === Type::T_KEYWORD && \in_array($lastNode[1], ['!default', '!global'])) {
3522
+				array_pop($token[2]);
3523
+
3524
+				$node     = end($token[2]);
3525
+				$token    = $this->flattenList($token);
3526
+				$flags[]  = $lastNode[1];
3527
+				$lastNode = $node;
3528
+			}
3529
+		}
3530
+
3531
+		return $flags;
3532
+	}
3533
+
3534
+	/**
3535
+	 * Strip optional flag from selector list
3536
+	 *
3537
+	 * @param array $selectors
3538
+	 *
3539
+	 * @return string
3540
+	 */
3541
+	protected function stripOptionalFlag(&$selectors)
3542
+	{
3543
+		$optional = false;
3544
+		$selector = end($selectors);
3545
+		$part     = end($selector);
3546
+
3547
+		if ($part === ['!optional']) {
3548
+			array_pop($selectors[\count($selectors) - 1]);
3549
+
3550
+			$optional = true;
3551
+		}
3552
+
3553
+		return $optional;
3554
+	}
3555
+
3556
+	/**
3557
+	 * Turn list of length 1 into value type
3558
+	 *
3559
+	 * @param array $value
3560
+	 *
3561
+	 * @return array
3562
+	 */
3563
+	protected function flattenList($value)
3564
+	{
3565
+		if ($value[0] === Type::T_LIST && \count($value[2]) === 1) {
3566
+			return $this->flattenList($value[2][0]);
3567
+		}
3568
+
3569
+		return $value;
3570
+	}
3571
+
3572
+	/**
3573
+	 * Quote regular expression
3574
+	 *
3575
+	 * @param string $what
3576
+	 *
3577
+	 * @return string
3578
+	 */
3579
+	private function pregQuote($what)
3580
+	{
3581
+		return preg_quote($what, '/');
3582
+	}
3583
+
3584
+	/**
3585
+	 * Extract line numbers from buffer
3586
+	 *
3587
+	 * @param string $buffer
3588
+	 */
3589
+	private function extractLineNumbers($buffer)
3590
+	{
3591
+		$this->sourcePositions = [0 => 0];
3592
+		$prev = 0;
3593
+
3594
+		while (($pos = strpos($buffer, "\n", $prev)) !== false) {
3595
+			$this->sourcePositions[] = $pos;
3596
+			$prev = $pos + 1;
3597
+		}
3598
+
3599
+		$this->sourcePositions[] = \strlen($buffer);
3600
+
3601
+		if (substr($buffer, -1) !== "\n") {
3602
+			$this->sourcePositions[] = \strlen($buffer) + 1;
3603
+		}
3604
+	}
3605
+
3606
+	/**
3607
+	 * Get source line number and column (given character position in the buffer)
3608
+	 *
3609
+	 * @param integer $pos
3610
+	 *
3611
+	 * @return array
3612
+	 */
3613
+	private function getSourcePosition($pos)
3614
+	{
3615
+		$low = 0;
3616
+		$high = \count($this->sourcePositions);
3617
+
3618
+		while ($low < $high) {
3619
+			$mid = (int) (($high + $low) / 2);
3620
+
3621
+			if ($pos < $this->sourcePositions[$mid]) {
3622
+				$high = $mid - 1;
3623
+				continue;
3624
+			}
3625
+
3626
+			if ($pos >= $this->sourcePositions[$mid + 1]) {
3627
+				$low = $mid + 1;
3628
+				continue;
3629
+			}
3630
+
3631
+			return [$mid + 1, $pos - $this->sourcePositions[$mid]];
3632
+		}
3633
+
3634
+		return [$low + 1, $pos - $this->sourcePositions[$low]];
3635
+	}
3636
+
3637
+	/**
3638
+	 * Save internal encoding
3639
+	 */
3640
+	private function saveEncoding()
3641
+	{
3642
+		if (\extension_loaded('mbstring')) {
3643
+			$this->encoding = mb_internal_encoding();
3644
+
3645
+			mb_internal_encoding('iso-8859-1');
3646
+		}
3647
+	}
3648
+
3649
+	/**
3650
+	 * Restore internal encoding
3651
+	 */
3652
+	private function restoreEncoding()
3653
+	{
3654
+		if (\extension_loaded('mbstring') && $this->encoding) {
3655
+			mb_internal_encoding($this->encoding);
3656
+		}
3657
+	}
3658 3658
 }
Please login to merge, or discard this patch.
Spacing   +64 added lines, -64 removed lines patch added patch discarded remove patch
@@ -90,7 +90,7 @@  discard block
 block discarded – undo
90 90
         $this->sourceName       = $sourceName ?: '(stdin)';
91 91
         $this->sourceIndex      = $sourceIndex;
92 92
         $this->charset          = null;
93
-        $this->utf8             = ! $encoding || strtolower($encoding) === 'utf-8';
93
+        $this->utf8             = !$encoding || strtolower($encoding) === 'utf-8';
94 94
         $this->patternModifiers = $this->utf8 ? 'Aisu' : 'Ais';
95 95
         $this->commentsSeen     = [];
96 96
         $this->commentsSeen     = [];
@@ -180,7 +180,7 @@  discard block
 block discarded – undo
180 180
             ];
181 181
             $v = $this->cache->getCache('parse', $cacheKey, $parseOptions);
182 182
 
183
-            if (! \is_null($v)) {
183
+            if (!\is_null($v)) {
184 184
                 return $v;
185 185
             }
186 186
         }
@@ -212,7 +212,7 @@  discard block
 block discarded – undo
212 212
             $this->throwParseError();
213 213
         }
214 214
 
215
-        if (! empty($this->env->parent)) {
215
+        if (!empty($this->env->parent)) {
216 216
             $this->throwParseError('unclosed block');
217 217
         }
218 218
 
@@ -433,7 +433,7 @@  discard block
 block discarded – undo
433 433
                     isset($argUsing) ? $argUsing : null
434 434
                 ];
435 435
 
436
-                if (! empty($hasBlock)) {
436
+                if (!empty($hasBlock)) {
437 437
                     $include = $this->pushSpecialBlock(Type::T_INCLUDE, $s);
438 438
                     $include->child = $child;
439 439
                 } else {
@@ -608,7 +608,7 @@  discard block
 block discarded – undo
608 608
 
609 609
                 while (
610 610
                     $cond[0] === Type::T_LIST &&
611
-                    ! empty($cond['enclosing']) &&
611
+                    !empty($cond['enclosing']) &&
612 612
                     $cond['enclosing'] === 'parent' &&
613 613
                     \count($cond[2]) == 1
614 614
                 ) {
@@ -661,7 +661,7 @@  discard block
 block discarded – undo
661 661
 
662 662
                 while (
663 663
                     $cond[0] === Type::T_LIST &&
664
-                    ! empty($cond['enclosing']) &&
664
+                    !empty($cond['enclosing']) &&
665 665
                     $cond['enclosing'] === 'parent' &&
666 666
                     \count($cond[2]) == 1
667 667
                 ) {
@@ -777,7 +777,7 @@  discard block
 block discarded – undo
777 777
                 $this->valueList($charset) &&
778 778
                 $this->end()
779 779
             ) {
780
-                if (! isset($this->charset)) {
780
+                if (!isset($this->charset)) {
781 781
                     $statement = [Type::T_CHARSET, $charset];
782 782
 
783 783
                     list($line, $column) = $this->getSourcePosition($s);
@@ -849,8 +849,8 @@  discard block
 block discarded – undo
849 849
 
850 850
         $inCssSelector = null;
851 851
         if ($this->cssOnly) {
852
-            $inCssSelector = (! empty($this->env->parent) &&
853
-                ! in_array($this->env->type, [Type::T_DIRECTIVE, Type::T_MEDIA]));
852
+            $inCssSelector = (!empty($this->env->parent) &&
853
+                !in_array($this->env->type, [Type::T_DIRECTIVE, Type::T_MEDIA]));
854 854
         }
855 855
         // custom properties : right part is static
856 856
         if (
@@ -861,7 +861,7 @@  discard block
 block discarded – undo
861 861
             $start = $this->count;
862 862
 
863 863
             // but can be complex and finish with ; or }
864
-            foreach ([';','}'] as $ending) {
864
+            foreach ([';', '}'] as $ending) {
865 865
                 if (
866 866
                     $this->openString($ending, $stringValue, '(', ')', false) &&
867 867
                     $this->end()
@@ -1006,7 +1006,7 @@  discard block
 block discarded – undo
1006 1006
         if ($this->matchChar('}', false)) {
1007 1007
             $block = $this->popBlock();
1008 1008
 
1009
-            if (! isset($block->type) || $block->type !== Type::T_IF) {
1009
+            if (!isset($block->type) || $block->type !== Type::T_IF) {
1010 1010
                 if ($this->env->parent) {
1011 1011
                     $this->append(null); // collect comments before next statement if needed
1012 1012
                 }
@@ -1066,7 +1066,7 @@  discard block
 block discarded – undo
1066 1066
         $b->comments     = [];
1067 1067
         $b->parent       = $this->env;
1068 1068
 
1069
-        if (! $this->env) {
1069
+        if (!$this->env) {
1070 1070
             $b->children = [];
1071 1071
         } elseif (empty($this->env->children)) {
1072 1072
             $this->env->children = $this->env->comments;
@@ -1152,7 +1152,7 @@  discard block
 block discarded – undo
1152 1152
      */
1153 1153
     protected function peek($regex, &$out, $from = null)
1154 1154
     {
1155
-        if (! isset($from)) {
1155
+        if (!isset($from)) {
1156 1156
             $from = $this->count;
1157 1157
         }
1158 1158
 
@@ -1198,7 +1198,7 @@  discard block
 block discarded – undo
1198 1198
             }
1199 1199
         }
1200 1200
 
1201
-        if (! isset($token)) {
1201
+        if (!isset($token)) {
1202 1202
             return false;
1203 1203
         }
1204 1204
 
@@ -1226,13 +1226,13 @@  discard block
 block discarded – undo
1226 1226
     {
1227 1227
         $r = '/' . $regex . '/' . $this->patternModifiers;
1228 1228
 
1229
-        if (! preg_match($r, $this->buffer, $out, null, $this->count)) {
1229
+        if (!preg_match($r, $this->buffer, $out, null, $this->count)) {
1230 1230
             return false;
1231 1231
         }
1232 1232
 
1233 1233
         $this->count += \strlen($out[0]);
1234 1234
 
1235
-        if (! isset($eatWhitespace)) {
1235
+        if (!isset($eatWhitespace)) {
1236 1236
             $eatWhitespace = $this->eatWhiteDefault;
1237 1237
         }
1238 1238
 
@@ -1253,13 +1253,13 @@  discard block
 block discarded – undo
1253 1253
      */
1254 1254
     protected function matchChar($char, $eatWhitespace = null)
1255 1255
     {
1256
-        if (! isset($this->buffer[$this->count]) || $this->buffer[$this->count] !== $char) {
1256
+        if (!isset($this->buffer[$this->count]) || $this->buffer[$this->count] !== $char) {
1257 1257
             return false;
1258 1258
         }
1259 1259
 
1260 1260
         $this->count++;
1261 1261
 
1262
-        if (! isset($eatWhitespace)) {
1262
+        if (!isset($eatWhitespace)) {
1263 1263
             $eatWhitespace = $this->eatWhiteDefault;
1264 1264
         }
1265 1265
 
@@ -1287,7 +1287,7 @@  discard block
 block discarded – undo
1287 1287
 
1288 1288
         $this->count += $len;
1289 1289
 
1290
-        if (! isset($eatWhitespace)) {
1290
+        if (!isset($eatWhitespace)) {
1291 1291
             $eatWhitespace = $this->eatWhiteDefault;
1292 1292
         }
1293 1293
 
@@ -1346,7 +1346,7 @@  discard block
 block discarded – undo
1346 1346
                 // remaining part
1347 1347
                 $c = substr($this->buffer, $this->count, $endCommentCount - $this->count);
1348 1348
 
1349
-                if (! $comment) {
1349
+                if (!$comment) {
1350 1350
                     // single part static comment
1351 1351
                     $this->appendComment([Type::T_COMMENT, $c]);
1352 1352
                 } else {
@@ -1375,7 +1375,7 @@  discard block
 block discarded – undo
1375 1375
      */
1376 1376
     protected function appendComment($comment)
1377 1377
     {
1378
-        if (! $this->discardComments) {
1378
+        if (!$this->discardComments) {
1379 1379
             $this->env->comments[] = $comment;
1380 1380
         }
1381 1381
     }
@@ -1388,8 +1388,8 @@  discard block
 block discarded – undo
1388 1388
      */
1389 1389
     protected function append($statement, $pos = null)
1390 1390
     {
1391
-        if (! \is_null($statement)) {
1392
-            if (! \is_null($pos)) {
1391
+        if (!\is_null($statement)) {
1392
+            if (!\is_null($pos)) {
1393 1393
                 list($line, $column) = $this->getSourcePosition($pos);
1394 1394
 
1395 1395
                 $statement[static::SOURCE_LINE]   = $line;
@@ -1692,7 +1692,7 @@  discard block
 block discarded – undo
1692 1692
 
1693 1693
         $keyword = null;
1694 1694
 
1695
-        if (! $this->variable($keyword) || ! $this->matchChar(':')) {
1695
+        if (!$this->variable($keyword) || !$this->matchChar(':')) {
1696 1696
             $this->seek($s);
1697 1697
 
1698 1698
             $keyword = null;
@@ -1731,7 +1731,7 @@  discard block
 block discarded – undo
1731 1731
                 return true;
1732 1732
             }
1733 1733
 
1734
-            if (! $endChar && $this->end()) {
1734
+            if (!$endChar && $this->end()) {
1735 1735
                 return true;
1736 1736
             }
1737 1737
         }
@@ -1757,7 +1757,7 @@  discard block
 block discarded – undo
1757 1757
                 return true;
1758 1758
             }
1759 1759
 
1760
-            if (! $endChar && $this->end()) {
1760
+            if (!$endChar && $this->end()) {
1761 1761
                 return true;
1762 1762
             }
1763 1763
         }
@@ -1808,7 +1808,7 @@  discard block
 block discarded – undo
1808 1808
             return true;
1809 1809
         }
1810 1810
 
1811
-        if (! $mandatoryParenthesis) {
1811
+        if (!$mandatoryParenthesis) {
1812 1812
             $this->seek($s);
1813 1813
 
1814 1814
             if ($this->valueList($out)) {
@@ -1854,7 +1854,7 @@  discard block
 block discarded – undo
1854 1854
             $items[] = $value;
1855 1855
 
1856 1856
             if ($delim) {
1857
-                if (! $this->literal($delim, \strlen($delim))) {
1857
+                if (!$this->literal($delim, \strlen($delim))) {
1858 1858
                     break;
1859 1859
                 }
1860 1860
 
@@ -1869,7 +1869,7 @@  discard block
 block discarded – undo
1869 1869
 
1870 1870
                     if (
1871 1871
                         strlen($word) > 1 &&
1872
-                        in_array($last_char, [ "'", '"']) &&
1872
+                        in_array($last_char, ["'", '"']) &&
1873 1873
                         substr($word, -2, 1) !== '\\'
1874 1874
                     ) {
1875 1875
                         // if there is a non escaped opening quote in the keyword, this seems unlikely a mistake
@@ -1915,7 +1915,7 @@  discard block
 block discarded – undo
1915 1915
             }
1916 1916
         }
1917 1917
 
1918
-        if (! $items) {
1918
+        if (!$items) {
1919 1919
             $this->seek($s);
1920 1920
 
1921 1921
             return false;
@@ -1982,7 +1982,7 @@  discard block
 block discarded – undo
1982 1982
             $this->seek($s);
1983 1983
         }
1984 1984
 
1985
-        if (! $listOnly && $this->value($lhs)) {
1985
+        if (!$listOnly && $this->value($lhs)) {
1986 1986
             if ($lookForExp) {
1987 1987
                 $out = $this->expHelper($lhs, 0);
1988 1988
             } else {
@@ -2029,11 +2029,11 @@  discard block
 block discarded – undo
2029 2029
 
2030 2030
         if (
2031 2031
             $this->valueList($out) &&
2032
-            $this->matchChar($closingParen) && ! ($closingParen === ')' &&
2032
+            $this->matchChar($closingParen) && !($closingParen === ')' &&
2033 2033
             \in_array($out[0], [Type::T_EXPRESSION, Type::T_UNARY])) &&
2034 2034
             \in_array(Type::T_LIST, $allowedTypes)
2035 2035
         ) {
2036
-            if ($out[0] !== Type::T_LIST || ! empty($out['enclosing'])) {
2036
+            if ($out[0] !== Type::T_LIST || !empty($out['enclosing'])) {
2037 2037
                 $out = [Type::T_LIST, '', [$out]];
2038 2038
             }
2039 2039
 
@@ -2086,11 +2086,11 @@  discard block
 block discarded – undo
2086 2086
             $op = $m[1];
2087 2087
 
2088 2088
             // don't turn negative numbers into expressions
2089
-            if ($op === '-' && $whiteBefore && ! $whiteAfter && ! $varAfter) {
2089
+            if ($op === '-' && $whiteBefore && !$whiteAfter && !$varAfter) {
2090 2090
                 break;
2091 2091
             }
2092 2092
 
2093
-            if (! $this->value($rhs) && ! $this->expression($rhs, true, false)) {
2093
+            if (!$this->value($rhs) && !$this->expression($rhs, true, false)) {
2094 2094
                 break;
2095 2095
             }
2096 2096
 
@@ -2119,7 +2119,7 @@  discard block
 block discarded – undo
2119 2119
      */
2120 2120
     protected function value(&$out)
2121 2121
     {
2122
-        if (! isset($this->buffer[$this->count])) {
2122
+        if (!isset($this->buffer[$this->count])) {
2123 2123
             return false;
2124 2124
         }
2125 2125
 
@@ -2226,7 +2226,7 @@  discard block
 block discarded – undo
2226 2226
 
2227 2227
             if (
2228 2228
                 $this->keyword($inner) &&
2229
-                ! $this->func($inner, $out)
2229
+                !$this->func($inner, $out)
2230 2230
             ) {
2231 2231
                 $out = [Type::T_UNARY, '-', $inner, $this->inParens];
2232 2232
 
@@ -2404,7 +2404,7 @@  discard block
 block discarded – undo
2404 2404
                 return true;
2405 2405
             }
2406 2406
 
2407
-            if ($name !== 'expression' && ! preg_match('/^(-[a-z]+-)?calc$/', $name)) {
2407
+            if ($name !== 'expression' && !preg_match('/^(-[a-z]+-)?calc$/', $name)) {
2408 2408
                 $ss = $this->count;
2409 2409
 
2410 2410
                 if (
@@ -2425,7 +2425,7 @@  discard block
 block discarded – undo
2425 2425
             ) {
2426 2426
                 $args = [];
2427 2427
 
2428
-                if (! empty($str)) {
2428
+                if (!empty($str)) {
2429 2429
                     $args[] = [null, [Type::T_STRING, '', [$str]]];
2430 2430
                 }
2431 2431
 
@@ -2467,14 +2467,14 @@  discard block
 block discarded – undo
2467 2467
 
2468 2468
             $args[] = $arg;
2469 2469
 
2470
-            if (! $this->matchChar(',')) {
2470
+            if (!$this->matchChar(',')) {
2471 2471
                 break;
2472 2472
             }
2473 2473
 
2474 2474
             $args[] = [Type::T_STRING, '', [', ']];
2475 2475
         }
2476 2476
 
2477
-        if (! $this->matchChar(')') || ! $args) {
2477
+        if (!$this->matchChar(')') || !$args) {
2478 2478
             $this->seek($s);
2479 2479
 
2480 2480
             return false;
@@ -2518,7 +2518,7 @@  discard block
 block discarded – undo
2518 2518
             if ($this->literal('...', 3)) {
2519 2519
                 $sss = $this->count;
2520 2520
 
2521
-                if (! $this->matchChar(')')) {
2521
+                if (!$this->matchChar(')')) {
2522 2522
                     $this->throwParseError('... has to be after the final argument');
2523 2523
                 }
2524 2524
 
@@ -2531,12 +2531,12 @@  discard block
 block discarded – undo
2531 2531
 
2532 2532
             $args[] = $arg;
2533 2533
 
2534
-            if (! $this->matchChar(',')) {
2534
+            if (!$this->matchChar(',')) {
2535 2535
                 break;
2536 2536
             }
2537 2537
         }
2538 2538
 
2539
-        if (! $this->matchChar(')')) {
2539
+        if (!$this->matchChar(')')) {
2540 2540
             $this->seek($s);
2541 2541
 
2542 2542
             return false;
@@ -2558,7 +2558,7 @@  discard block
 block discarded – undo
2558 2558
     {
2559 2559
         $s = $this->count;
2560 2560
 
2561
-        if (! $this->matchChar('(')) {
2561
+        if (!$this->matchChar('(')) {
2562 2562
             return false;
2563 2563
         }
2564 2564
 
@@ -2573,12 +2573,12 @@  discard block
 block discarded – undo
2573 2573
             $keys[] = $key;
2574 2574
             $values[] = $value;
2575 2575
 
2576
-            if (! $this->matchChar(',')) {
2576
+            if (!$this->matchChar(',')) {
2577 2577
                 break;
2578 2578
             }
2579 2579
         }
2580 2580
 
2581
-        if (! $keys || ! $this->matchChar(')')) {
2581
+        if (!$keys || !$this->matchChar(')')) {
2582 2582
             $this->seek($s);
2583 2583
 
2584 2584
             return false;
@@ -2601,7 +2601,7 @@  discard block
 block discarded – undo
2601 2601
         $s = $this->count;
2602 2602
 
2603 2603
         if ($this->match('(#([0-9a-f]+)\b)', $m)) {
2604
-            if (\in_array(\strlen($m[2]), [3,4,6,8])) {
2604
+            if (\in_array(\strlen($m[2]), [3, 4, 6, 8])) {
2605 2605
                 $out = [Type::T_KEYWORD, $m[0]];
2606 2606
 
2607 2607
                 return true;
@@ -2627,7 +2627,7 @@  discard block
 block discarded – undo
2627 2627
         $s = $this->count;
2628 2628
 
2629 2629
         if ($this->match('([0-9]*(\.)?[0-9]+)([%a-zA-Z]+)?', $m, false)) {
2630
-            if (\strlen($this->buffer) === $this->count || ! ctype_digit($this->buffer[$this->count])) {
2630
+            if (\strlen($this->buffer) === $this->count || !ctype_digit($this->buffer[$this->count])) {
2631 2631
                 $this->whitespace();
2632 2632
 
2633 2633
                 $unit = new Node\Number($m[1], empty($m[3]) ? '' : $m[3]);
@@ -2789,7 +2789,7 @@  discard block
 block discarded – undo
2789 2789
 
2790 2790
         $this->eatWhiteDefault = $oldWhite;
2791 2791
 
2792
-        if (! $parts) {
2792
+        if (!$parts) {
2793 2793
             return false;
2794 2794
         }
2795 2795
 
@@ -2818,7 +2818,7 @@  discard block
 block discarded – undo
2818 2818
         $oldWhite = $this->eatWhiteDefault;
2819 2819
         $this->eatWhiteDefault = false;
2820 2820
 
2821
-        if ($nestingOpen && ! $nestingClose) {
2821
+        if ($nestingOpen && !$nestingClose) {
2822 2822
             $nestingClose = $end;
2823 2823
         }
2824 2824
 
@@ -2844,7 +2844,7 @@  discard block
 block discarded – undo
2844 2844
 
2845 2845
             $this->count -= \strlen($tok);
2846 2846
 
2847
-            if ($tok === $end && ! $nestingLevel) {
2847
+            if ($tok === $end && !$nestingLevel) {
2848 2848
                 break;
2849 2849
             }
2850 2850
 
@@ -2868,7 +2868,7 @@  discard block
 block discarded – undo
2868 2868
 
2869 2869
         $this->eatWhiteDefault = $oldWhite;
2870 2870
 
2871
-        if (! $content || $tok !== $end) {
2871
+        if (!$content || $tok !== $end) {
2872 2872
             return false;
2873 2873
         }
2874 2874
 
@@ -2960,7 +2960,7 @@  discard block
 block discarded – undo
2960 2960
                 continue;
2961 2961
             }
2962 2962
 
2963
-            if (! $parts && $this->match('[:.#]', $m, false)) {
2963
+            if (!$parts && $this->match('[:.#]', $m, false)) {
2964 2964
                 // css hacks
2965 2965
                 $parts[] = $m[0];
2966 2966
                 continue;
@@ -2971,13 +2971,13 @@  discard block
 block discarded – undo
2971 2971
 
2972 2972
         $this->eatWhiteDefault = $oldWhite;
2973 2973
 
2974
-        if (! $parts) {
2974
+        if (!$parts) {
2975 2975
             return false;
2976 2976
         }
2977 2977
 
2978 2978
         // match comment hack
2979 2979
         if (preg_match(static::$whitePattern, $this->buffer, $m, null, $this->count)) {
2980
-            if (! empty($m[0])) {
2980
+            if (!empty($m[0])) {
2981 2981
                 $parts[] = $m[0];
2982 2982
                 $this->count += \strlen($m[0]);
2983 2983
             }
@@ -3001,7 +3001,7 @@  discard block
 block discarded – undo
3001 3001
     {
3002 3002
         $s = $this->count;
3003 3003
 
3004
-        if (! $this->literal('--', 2, false)) {
3004
+        if (!$this->literal('--', 2, false)) {
3005 3005
             return false;
3006 3006
         }
3007 3007
 
@@ -3065,7 +3065,7 @@  discard block
 block discarded – undo
3065 3065
         while ($this->selector($sel, $subSelector)) {
3066 3066
             $selectors[] = $sel;
3067 3067
 
3068
-            if (! $this->matchChar(',', true)) {
3068
+            if (!$this->matchChar(',', true)) {
3069 3069
                 break;
3070 3070
             }
3071 3071
 
@@ -3074,7 +3074,7 @@  discard block
 block discarded – undo
3074 3074
             }
3075 3075
         }
3076 3076
 
3077
-        if (! $selectors) {
3077
+        if (!$selectors) {
3078 3078
             $this->seek($s);
3079 3079
 
3080 3080
             return false;
@@ -3126,7 +3126,7 @@  discard block
 block discarded – undo
3126 3126
             break;
3127 3127
         }
3128 3128
 
3129
-        if (! $selector) {
3129
+        if (!$selector) {
3130 3130
             return false;
3131 3131
         }
3132 3132
 
@@ -3159,7 +3159,7 @@  discard block
 block discarded – undo
3159 3159
         }
3160 3160
 
3161 3161
         for (;;) {
3162
-            if (! isset($this->buffer[$this->count])) {
3162
+            if (!isset($this->buffer[$this->count])) {
3163 3163
                 break;
3164 3164
             }
3165 3165
 
@@ -3286,7 +3286,7 @@  discard block
 block discarded – undo
3286 3286
                     ) {
3287 3287
                         $parts[] = '(';
3288 3288
 
3289
-                        if (! empty($str)) {
3289
+                        if (!empty($str)) {
3290 3290
                             $parts[] = $str;
3291 3291
                         }
3292 3292
 
@@ -3321,7 +3321,7 @@  discard block
 block discarded – undo
3321 3321
             ) {
3322 3322
                 $parts[] = '[';
3323 3323
 
3324
-                if (! empty($str)) {
3324
+                if (!empty($str)) {
3325 3325
                     $parts[] = $str;
3326 3326
                 }
3327 3327
 
@@ -3347,7 +3347,7 @@  discard block
 block discarded – undo
3347 3347
 
3348 3348
         $this->eatWhiteDefault = $oldWhite;
3349 3349
 
3350
-        if (! $parts) {
3350
+        if (!$parts) {
3351 3351
             return false;
3352 3352
         }
3353 3353
 
Please login to merge, or discard this patch.
SCSS/vendor/scssphp/scssphp/src/Formatter/Expanded.php 2 patches
Indentation   +38 added lines, -38 removed lines patch added patch discarded remove patch
@@ -22,48 +22,48 @@
 block discarded – undo
22 22
  */
23 23
 class Expanded extends Formatter
24 24
 {
25
-    /**
26
-     * {@inheritdoc}
27
-     */
28
-    public function __construct()
29
-    {
30
-        $this->indentLevel = 0;
31
-        $this->indentChar = '  ';
32
-        $this->break = "\n";
33
-        $this->open = ' {';
34
-        $this->close = '}';
35
-        $this->tagSeparator = ', ';
36
-        $this->assignSeparator = ': ';
37
-        $this->keepSemicolons = true;
38
-    }
25
+	/**
26
+	 * {@inheritdoc}
27
+	 */
28
+	public function __construct()
29
+	{
30
+		$this->indentLevel = 0;
31
+		$this->indentChar = '  ';
32
+		$this->break = "\n";
33
+		$this->open = ' {';
34
+		$this->close = '}';
35
+		$this->tagSeparator = ', ';
36
+		$this->assignSeparator = ': ';
37
+		$this->keepSemicolons = true;
38
+	}
39 39
 
40
-    /**
41
-     * {@inheritdoc}
42
-     */
43
-    protected function indentStr()
44
-    {
45
-        return str_repeat($this->indentChar, $this->indentLevel);
46
-    }
40
+	/**
41
+	 * {@inheritdoc}
42
+	 */
43
+	protected function indentStr()
44
+	{
45
+		return str_repeat($this->indentChar, $this->indentLevel);
46
+	}
47 47
 
48
-    /**
49
-     * {@inheritdoc}
50
-     */
51
-    protected function blockLines(OutputBlock $block)
52
-    {
53
-        $inner = $this->indentStr();
48
+	/**
49
+	 * {@inheritdoc}
50
+	 */
51
+	protected function blockLines(OutputBlock $block)
52
+	{
53
+		$inner = $this->indentStr();
54 54
 
55
-        $glue = $this->break . $inner;
55
+		$glue = $this->break . $inner;
56 56
 
57
-        foreach ($block->lines as $index => $line) {
58
-            if (substr($line, 0, 2) === '/*') {
59
-                $block->lines[$index] = preg_replace('/\r\n?|\n|\f/', $this->break, $line);
60
-            }
61
-        }
57
+		foreach ($block->lines as $index => $line) {
58
+			if (substr($line, 0, 2) === '/*') {
59
+				$block->lines[$index] = preg_replace('/\r\n?|\n|\f/', $this->break, $line);
60
+			}
61
+		}
62 62
 
63
-        $this->write($inner . implode($glue, $block->lines));
63
+		$this->write($inner . implode($glue, $block->lines));
64 64
 
65
-        if (empty($block->selectors) || ! empty($block->children)) {
66
-            $this->write($this->break);
67
-        }
68
-    }
65
+		if (empty($block->selectors) || ! empty($block->children)) {
66
+			$this->write($this->break);
67
+		}
68
+	}
69 69
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -62,7 +62,7 @@
 block discarded – undo
62 62
 
63 63
         $this->write($inner . implode($glue, $block->lines));
64 64
 
65
-        if (empty($block->selectors) || ! empty($block->children)) {
65
+        if (empty($block->selectors) || !empty($block->children)) {
66 66
             $this->write($this->break);
67 67
         }
68 68
     }
Please login to merge, or discard this patch.
SCSS/vendor/scssphp/scssphp/src/Formatter/OutputBlock.php 1 patch
Indentation   +36 added lines, -36 removed lines patch added patch discarded remove patch
@@ -19,48 +19,48 @@
 block discarded – undo
19 19
  */
20 20
 class OutputBlock
21 21
 {
22
-    /**
23
-     * @var string
24
-     */
25
-    public $type;
22
+	/**
23
+	 * @var string
24
+	 */
25
+	public $type;
26 26
 
27
-    /**
28
-     * @var integer
29
-     */
30
-    public $depth;
27
+	/**
28
+	 * @var integer
29
+	 */
30
+	public $depth;
31 31
 
32
-    /**
33
-     * @var array
34
-     */
35
-    public $selectors;
32
+	/**
33
+	 * @var array
34
+	 */
35
+	public $selectors;
36 36
 
37
-    /**
38
-     * @var array
39
-     */
40
-    public $lines;
37
+	/**
38
+	 * @var array
39
+	 */
40
+	public $lines;
41 41
 
42
-    /**
43
-     * @var array
44
-     */
45
-    public $children;
42
+	/**
43
+	 * @var array
44
+	 */
45
+	public $children;
46 46
 
47
-    /**
48
-     * @var \ScssPhp\ScssPhp\Formatter\OutputBlock
49
-     */
50
-    public $parent;
47
+	/**
48
+	 * @var \ScssPhp\ScssPhp\Formatter\OutputBlock
49
+	 */
50
+	public $parent;
51 51
 
52
-    /**
53
-     * @var string
54
-     */
55
-    public $sourceName;
52
+	/**
53
+	 * @var string
54
+	 */
55
+	public $sourceName;
56 56
 
57
-    /**
58
-     * @var integer
59
-     */
60
-    public $sourceLine;
57
+	/**
58
+	 * @var integer
59
+	 */
60
+	public $sourceLine;
61 61
 
62
-    /**
63
-     * @var integer
64
-     */
65
-    public $sourceColumn;
62
+	/**
63
+	 * @var integer
64
+	 */
65
+	public $sourceColumn;
66 66
 }
Please login to merge, or discard this patch.
SCSS/vendor/scssphp/scssphp/src/Formatter/Nested.php 2 patches
Indentation   +205 added lines, -205 removed lines patch added patch discarded remove patch
@@ -23,209 +23,209 @@
 block discarded – undo
23 23
  */
24 24
 class Nested extends Formatter
25 25
 {
26
-    /**
27
-     * @var integer
28
-     */
29
-    private $depth;
30
-
31
-    /**
32
-     * {@inheritdoc}
33
-     */
34
-    public function __construct()
35
-    {
36
-        $this->indentLevel = 0;
37
-        $this->indentChar = '  ';
38
-        $this->break = "\n";
39
-        $this->open = ' {';
40
-        $this->close = ' }';
41
-        $this->tagSeparator = ', ';
42
-        $this->assignSeparator = ': ';
43
-        $this->keepSemicolons = true;
44
-    }
45
-
46
-    /**
47
-     * {@inheritdoc}
48
-     */
49
-    protected function indentStr()
50
-    {
51
-        $n = $this->depth - 1;
52
-
53
-        return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
54
-    }
55
-
56
-    /**
57
-     * {@inheritdoc}
58
-     */
59
-    protected function blockLines(OutputBlock $block)
60
-    {
61
-        $inner = $this->indentStr();
62
-        $glue  = $this->break . $inner;
63
-
64
-        foreach ($block->lines as $index => $line) {
65
-            if (substr($line, 0, 2) === '/*') {
66
-                $block->lines[$index] = preg_replace('/\r\n?|\n|\f/', $this->break, $line);
67
-            }
68
-        }
69
-
70
-        $this->write($inner . implode($glue, $block->lines));
71
-    }
72
-
73
-    /**
74
-     * {@inheritdoc}
75
-     */
76
-    protected function block(OutputBlock $block)
77
-    {
78
-        static $depths;
79
-        static $downLevel;
80
-        static $closeBlock;
81
-        static $previousEmpty;
82
-        static $previousHasSelector;
83
-
84
-        if ($block->type === 'root') {
85
-            $depths = [ 0 ];
86
-            $downLevel = '';
87
-            $closeBlock = '';
88
-            $this->depth = 0;
89
-            $previousEmpty = false;
90
-            $previousHasSelector = false;
91
-        }
92
-
93
-        $isMediaOrDirective = \in_array($block->type, [Type::T_DIRECTIVE, Type::T_MEDIA]);
94
-        $isSupport = ($block->type === Type::T_DIRECTIVE
95
-            && $block->selectors && strpos(implode('', $block->selectors), '@supports') !== false);
96
-
97
-        while ($block->depth < end($depths) || ($block->depth == 1 && end($depths) == 1)) {
98
-            array_pop($depths);
99
-            $this->depth--;
100
-
101
-            if (
102
-                ! $this->depth && ($block->depth <= 1 || (! $this->indentLevel && $block->type === Type::T_COMMENT)) &&
103
-                (($block->selectors && ! $isMediaOrDirective) || $previousHasSelector)
104
-            ) {
105
-                $downLevel = $this->break;
106
-            }
107
-
108
-            if (empty($block->lines) && empty($block->children)) {
109
-                $previousEmpty = true;
110
-            }
111
-        }
112
-
113
-        if (empty($block->lines) && empty($block->children)) {
114
-            return;
115
-        }
116
-
117
-        $this->currentBlock = $block;
118
-
119
-        if (! empty($block->lines) || (! empty($block->children) && ($this->depth < 1 || $isSupport))) {
120
-            if ($block->depth > end($depths)) {
121
-                if (! $previousEmpty || $this->depth < 1) {
122
-                    $this->depth++;
123
-
124
-                    $depths[] = $block->depth;
125
-                } else {
126
-                    // keep the current depth unchanged but take the block depth as a new reference for following blocks
127
-                    array_pop($depths);
128
-
129
-                    $depths[] = $block->depth;
130
-                }
131
-            }
132
-        }
133
-
134
-        $previousEmpty = ($block->type === Type::T_COMMENT);
135
-        $previousHasSelector = false;
136
-
137
-        if (! empty($block->selectors)) {
138
-            if ($closeBlock) {
139
-                $this->write($closeBlock);
140
-                $closeBlock = '';
141
-            }
142
-
143
-            if ($downLevel) {
144
-                $this->write($downLevel);
145
-                $downLevel = '';
146
-            }
147
-
148
-            $this->blockSelectors($block);
149
-
150
-            $this->indentLevel++;
151
-        }
152
-
153
-        if (! empty($block->lines)) {
154
-            if ($closeBlock) {
155
-                $this->write($closeBlock);
156
-                $closeBlock = '';
157
-            }
158
-
159
-            if ($downLevel) {
160
-                $this->write($downLevel);
161
-                $downLevel = '';
162
-            }
163
-
164
-            $this->blockLines($block);
165
-
166
-            $closeBlock = $this->break;
167
-        }
168
-
169
-        if (! empty($block->children)) {
170
-            if ($this->depth > 0 && ($isMediaOrDirective || ! $this->hasFlatChild($block))) {
171
-                array_pop($depths);
172
-
173
-                $this->depth--;
174
-                $this->blockChildren($block);
175
-                $this->depth++;
176
-
177
-                $depths[] = $block->depth;
178
-            } else {
179
-                $this->blockChildren($block);
180
-            }
181
-        }
182
-
183
-        // reclear to not be spoiled by children if T_DIRECTIVE
184
-        if ($block->type === Type::T_DIRECTIVE) {
185
-            $previousHasSelector = false;
186
-        }
187
-
188
-        if (! empty($block->selectors)) {
189
-            $this->indentLevel--;
190
-
191
-            if (! $this->keepSemicolons) {
192
-                $this->strippedSemicolon = '';
193
-            }
194
-
195
-            $this->write($this->close);
196
-
197
-            $closeBlock = $this->break;
198
-
199
-            if ($this->depth > 1 && ! empty($block->children)) {
200
-                array_pop($depths);
201
-                $this->depth--;
202
-            }
203
-
204
-            if (! $isMediaOrDirective) {
205
-                $previousHasSelector = true;
206
-            }
207
-        }
208
-
209
-        if ($block->type === 'root') {
210
-            $this->write($this->break);
211
-        }
212
-    }
213
-
214
-    /**
215
-     * Block has flat child
216
-     *
217
-     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
218
-     *
219
-     * @return boolean
220
-     */
221
-    private function hasFlatChild($block)
222
-    {
223
-        foreach ($block->children as $child) {
224
-            if (empty($child->selectors)) {
225
-                return true;
226
-            }
227
-        }
228
-
229
-        return false;
230
-    }
26
+	/**
27
+	 * @var integer
28
+	 */
29
+	private $depth;
30
+
31
+	/**
32
+	 * {@inheritdoc}
33
+	 */
34
+	public function __construct()
35
+	{
36
+		$this->indentLevel = 0;
37
+		$this->indentChar = '  ';
38
+		$this->break = "\n";
39
+		$this->open = ' {';
40
+		$this->close = ' }';
41
+		$this->tagSeparator = ', ';
42
+		$this->assignSeparator = ': ';
43
+		$this->keepSemicolons = true;
44
+	}
45
+
46
+	/**
47
+	 * {@inheritdoc}
48
+	 */
49
+	protected function indentStr()
50
+	{
51
+		$n = $this->depth - 1;
52
+
53
+		return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
54
+	}
55
+
56
+	/**
57
+	 * {@inheritdoc}
58
+	 */
59
+	protected function blockLines(OutputBlock $block)
60
+	{
61
+		$inner = $this->indentStr();
62
+		$glue  = $this->break . $inner;
63
+
64
+		foreach ($block->lines as $index => $line) {
65
+			if (substr($line, 0, 2) === '/*') {
66
+				$block->lines[$index] = preg_replace('/\r\n?|\n|\f/', $this->break, $line);
67
+			}
68
+		}
69
+
70
+		$this->write($inner . implode($glue, $block->lines));
71
+	}
72
+
73
+	/**
74
+	 * {@inheritdoc}
75
+	 */
76
+	protected function block(OutputBlock $block)
77
+	{
78
+		static $depths;
79
+		static $downLevel;
80
+		static $closeBlock;
81
+		static $previousEmpty;
82
+		static $previousHasSelector;
83
+
84
+		if ($block->type === 'root') {
85
+			$depths = [ 0 ];
86
+			$downLevel = '';
87
+			$closeBlock = '';
88
+			$this->depth = 0;
89
+			$previousEmpty = false;
90
+			$previousHasSelector = false;
91
+		}
92
+
93
+		$isMediaOrDirective = \in_array($block->type, [Type::T_DIRECTIVE, Type::T_MEDIA]);
94
+		$isSupport = ($block->type === Type::T_DIRECTIVE
95
+			&& $block->selectors && strpos(implode('', $block->selectors), '@supports') !== false);
96
+
97
+		while ($block->depth < end($depths) || ($block->depth == 1 && end($depths) == 1)) {
98
+			array_pop($depths);
99
+			$this->depth--;
100
+
101
+			if (
102
+				! $this->depth && ($block->depth <= 1 || (! $this->indentLevel && $block->type === Type::T_COMMENT)) &&
103
+				(($block->selectors && ! $isMediaOrDirective) || $previousHasSelector)
104
+			) {
105
+				$downLevel = $this->break;
106
+			}
107
+
108
+			if (empty($block->lines) && empty($block->children)) {
109
+				$previousEmpty = true;
110
+			}
111
+		}
112
+
113
+		if (empty($block->lines) && empty($block->children)) {
114
+			return;
115
+		}
116
+
117
+		$this->currentBlock = $block;
118
+
119
+		if (! empty($block->lines) || (! empty($block->children) && ($this->depth < 1 || $isSupport))) {
120
+			if ($block->depth > end($depths)) {
121
+				if (! $previousEmpty || $this->depth < 1) {
122
+					$this->depth++;
123
+
124
+					$depths[] = $block->depth;
125
+				} else {
126
+					// keep the current depth unchanged but take the block depth as a new reference for following blocks
127
+					array_pop($depths);
128
+
129
+					$depths[] = $block->depth;
130
+				}
131
+			}
132
+		}
133
+
134
+		$previousEmpty = ($block->type === Type::T_COMMENT);
135
+		$previousHasSelector = false;
136
+
137
+		if (! empty($block->selectors)) {
138
+			if ($closeBlock) {
139
+				$this->write($closeBlock);
140
+				$closeBlock = '';
141
+			}
142
+
143
+			if ($downLevel) {
144
+				$this->write($downLevel);
145
+				$downLevel = '';
146
+			}
147
+
148
+			$this->blockSelectors($block);
149
+
150
+			$this->indentLevel++;
151
+		}
152
+
153
+		if (! empty($block->lines)) {
154
+			if ($closeBlock) {
155
+				$this->write($closeBlock);
156
+				$closeBlock = '';
157
+			}
158
+
159
+			if ($downLevel) {
160
+				$this->write($downLevel);
161
+				$downLevel = '';
162
+			}
163
+
164
+			$this->blockLines($block);
165
+
166
+			$closeBlock = $this->break;
167
+		}
168
+
169
+		if (! empty($block->children)) {
170
+			if ($this->depth > 0 && ($isMediaOrDirective || ! $this->hasFlatChild($block))) {
171
+				array_pop($depths);
172
+
173
+				$this->depth--;
174
+				$this->blockChildren($block);
175
+				$this->depth++;
176
+
177
+				$depths[] = $block->depth;
178
+			} else {
179
+				$this->blockChildren($block);
180
+			}
181
+		}
182
+
183
+		// reclear to not be spoiled by children if T_DIRECTIVE
184
+		if ($block->type === Type::T_DIRECTIVE) {
185
+			$previousHasSelector = false;
186
+		}
187
+
188
+		if (! empty($block->selectors)) {
189
+			$this->indentLevel--;
190
+
191
+			if (! $this->keepSemicolons) {
192
+				$this->strippedSemicolon = '';
193
+			}
194
+
195
+			$this->write($this->close);
196
+
197
+			$closeBlock = $this->break;
198
+
199
+			if ($this->depth > 1 && ! empty($block->children)) {
200
+				array_pop($depths);
201
+				$this->depth--;
202
+			}
203
+
204
+			if (! $isMediaOrDirective) {
205
+				$previousHasSelector = true;
206
+			}
207
+		}
208
+
209
+		if ($block->type === 'root') {
210
+			$this->write($this->break);
211
+		}
212
+	}
213
+
214
+	/**
215
+	 * Block has flat child
216
+	 *
217
+	 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
218
+	 *
219
+	 * @return boolean
220
+	 */
221
+	private function hasFlatChild($block)
222
+	{
223
+		foreach ($block->children as $child) {
224
+			if (empty($child->selectors)) {
225
+				return true;
226
+			}
227
+		}
228
+
229
+		return false;
230
+	}
231 231
 }
Please login to merge, or discard this patch.
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -82,7 +82,7 @@  discard block
 block discarded – undo
82 82
         static $previousHasSelector;
83 83
 
84 84
         if ($block->type === 'root') {
85
-            $depths = [ 0 ];
85
+            $depths = [0];
86 86
             $downLevel = '';
87 87
             $closeBlock = '';
88 88
             $this->depth = 0;
@@ -99,8 +99,8 @@  discard block
 block discarded – undo
99 99
             $this->depth--;
100 100
 
101 101
             if (
102
-                ! $this->depth && ($block->depth <= 1 || (! $this->indentLevel && $block->type === Type::T_COMMENT)) &&
103
-                (($block->selectors && ! $isMediaOrDirective) || $previousHasSelector)
102
+                !$this->depth && ($block->depth <= 1 || (!$this->indentLevel && $block->type === Type::T_COMMENT)) &&
103
+                (($block->selectors && !$isMediaOrDirective) || $previousHasSelector)
104 104
             ) {
105 105
                 $downLevel = $this->break;
106 106
             }
@@ -116,9 +116,9 @@  discard block
 block discarded – undo
116 116
 
117 117
         $this->currentBlock = $block;
118 118
 
119
-        if (! empty($block->lines) || (! empty($block->children) && ($this->depth < 1 || $isSupport))) {
119
+        if (!empty($block->lines) || (!empty($block->children) && ($this->depth < 1 || $isSupport))) {
120 120
             if ($block->depth > end($depths)) {
121
-                if (! $previousEmpty || $this->depth < 1) {
121
+                if (!$previousEmpty || $this->depth < 1) {
122 122
                     $this->depth++;
123 123
 
124 124
                     $depths[] = $block->depth;
@@ -134,7 +134,7 @@  discard block
 block discarded – undo
134 134
         $previousEmpty = ($block->type === Type::T_COMMENT);
135 135
         $previousHasSelector = false;
136 136
 
137
-        if (! empty($block->selectors)) {
137
+        if (!empty($block->selectors)) {
138 138
             if ($closeBlock) {
139 139
                 $this->write($closeBlock);
140 140
                 $closeBlock = '';
@@ -150,7 +150,7 @@  discard block
 block discarded – undo
150 150
             $this->indentLevel++;
151 151
         }
152 152
 
153
-        if (! empty($block->lines)) {
153
+        if (!empty($block->lines)) {
154 154
             if ($closeBlock) {
155 155
                 $this->write($closeBlock);
156 156
                 $closeBlock = '';
@@ -166,8 +166,8 @@  discard block
 block discarded – undo
166 166
             $closeBlock = $this->break;
167 167
         }
168 168
 
169
-        if (! empty($block->children)) {
170
-            if ($this->depth > 0 && ($isMediaOrDirective || ! $this->hasFlatChild($block))) {
169
+        if (!empty($block->children)) {
170
+            if ($this->depth > 0 && ($isMediaOrDirective || !$this->hasFlatChild($block))) {
171 171
                 array_pop($depths);
172 172
 
173 173
                 $this->depth--;
@@ -185,10 +185,10 @@  discard block
 block discarded – undo
185 185
             $previousHasSelector = false;
186 186
         }
187 187
 
188
-        if (! empty($block->selectors)) {
188
+        if (!empty($block->selectors)) {
189 189
             $this->indentLevel--;
190 190
 
191
-            if (! $this->keepSemicolons) {
191
+            if (!$this->keepSemicolons) {
192 192
                 $this->strippedSemicolon = '';
193 193
             }
194 194
 
@@ -196,12 +196,12 @@  discard block
 block discarded – undo
196 196
 
197 197
             $closeBlock = $this->break;
198 198
 
199
-            if ($this->depth > 1 && ! empty($block->children)) {
199
+            if ($this->depth > 1 && !empty($block->children)) {
200 200
                 array_pop($depths);
201 201
                 $this->depth--;
202 202
             }
203 203
 
204
-            if (! $isMediaOrDirective) {
204
+            if (!$isMediaOrDirective) {
205 205
                 $previousHasSelector = true;
206 206
             }
207 207
         }
Please login to merge, or discard this patch.
SCSS/vendor/scssphp/scssphp/src/Formatter/Crunched.php 2 patches
Indentation   +48 added lines, -48 removed lines patch added patch discarded remove patch
@@ -22,59 +22,59 @@
 block discarded – undo
22 22
  */
23 23
 class Crunched extends Formatter
24 24
 {
25
-    /**
26
-     * {@inheritdoc}
27
-     */
28
-    public function __construct()
29
-    {
30
-        $this->indentLevel = 0;
31
-        $this->indentChar = '  ';
32
-        $this->break = '';
33
-        $this->open = '{';
34
-        $this->close = '}';
35
-        $this->tagSeparator = ',';
36
-        $this->assignSeparator = ':';
37
-        $this->keepSemicolons = false;
38
-    }
25
+	/**
26
+	 * {@inheritdoc}
27
+	 */
28
+	public function __construct()
29
+	{
30
+		$this->indentLevel = 0;
31
+		$this->indentChar = '  ';
32
+		$this->break = '';
33
+		$this->open = '{';
34
+		$this->close = '}';
35
+		$this->tagSeparator = ',';
36
+		$this->assignSeparator = ':';
37
+		$this->keepSemicolons = false;
38
+	}
39 39
 
40
-    /**
41
-     * {@inheritdoc}
42
-     */
43
-    public function blockLines(OutputBlock $block)
44
-    {
45
-        $inner = $this->indentStr();
40
+	/**
41
+	 * {@inheritdoc}
42
+	 */
43
+	public function blockLines(OutputBlock $block)
44
+	{
45
+		$inner = $this->indentStr();
46 46
 
47
-        $glue = $this->break . $inner;
47
+		$glue = $this->break . $inner;
48 48
 
49
-        foreach ($block->lines as $index => $line) {
50
-            if (substr($line, 0, 2) === '/*') {
51
-                unset($block->lines[$index]);
52
-            }
53
-        }
49
+		foreach ($block->lines as $index => $line) {
50
+			if (substr($line, 0, 2) === '/*') {
51
+				unset($block->lines[$index]);
52
+			}
53
+		}
54 54
 
55
-        $this->write($inner . implode($glue, $block->lines));
55
+		$this->write($inner . implode($glue, $block->lines));
56 56
 
57
-        if (! empty($block->children)) {
58
-            $this->write($this->break);
59
-        }
60
-    }
57
+		if (! empty($block->children)) {
58
+			$this->write($this->break);
59
+		}
60
+	}
61 61
 
62
-    /**
63
-     * Output block selectors
64
-     *
65
-     * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
66
-     */
67
-    protected function blockSelectors(OutputBlock $block)
68
-    {
69
-        $inner = $this->indentStr();
62
+	/**
63
+	 * Output block selectors
64
+	 *
65
+	 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
66
+	 */
67
+	protected function blockSelectors(OutputBlock $block)
68
+	{
69
+		$inner = $this->indentStr();
70 70
 
71
-        $this->write(
72
-            $inner
73
-            . implode(
74
-                $this->tagSeparator,
75
-                str_replace([' > ', ' + ', ' ~ '], ['>', '+', '~'], $block->selectors)
76
-            )
77
-            . $this->open . $this->break
78
-        );
79
-    }
71
+		$this->write(
72
+			$inner
73
+			. implode(
74
+				$this->tagSeparator,
75
+				str_replace([' > ', ' + ', ' ~ '], ['>', '+', '~'], $block->selectors)
76
+			)
77
+			. $this->open . $this->break
78
+		);
79
+	}
80 80
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -54,7 +54,7 @@
 block discarded – undo
54 54
 
55 55
         $this->write($inner . implode($glue, $block->lines));
56 56
 
57
-        if (! empty($block->children)) {
57
+        if (!empty($block->children)) {
58 58
             $this->write($this->break);
59 59
         }
60 60
     }
Please login to merge, or discard this patch.