Passed
Push — master ( 0df495...36baab )
by Virginia
04:40
created
vendor/leafo/scssphp/src/Parser.php 2 patches
Indentation   +2420 added lines, -2420 removed lines patch added patch discarded remove patch
@@ -24,2471 +24,2471 @@
 block discarded – undo
24 24
  */
25 25
 class Parser
26 26
 {
27
-    const SOURCE_INDEX  = -1;
28
-    const SOURCE_LINE   = -2;
29
-    const SOURCE_COLUMN = -3;
30
-
31
-    /**
32
-     * @var array
33
-     */
34
-    protected static $precedence = [
35
-        '='   => 0,
36
-        'or'  => 1,
37
-        'and' => 2,
38
-        '=='  => 3,
39
-        '!='  => 3,
40
-        '<=>' => 3,
41
-        '<='  => 4,
42
-        '>='  => 4,
43
-        '<'   => 4,
44
-        '>'   => 4,
45
-        '+'   => 5,
46
-        '-'   => 5,
47
-        '*'   => 6,
48
-        '/'   => 6,
49
-        '%'   => 6,
50
-    ];
51
-
52
-    protected static $commentPattern;
53
-    protected static $operatorPattern;
54
-    protected static $whitePattern;
55
-
56
-    private $sourceName;
57
-    private $sourceIndex;
58
-    private $sourcePositions;
59
-    private $charset;
60
-    private $count;
61
-    private $env;
62
-    private $inParens;
63
-    private $eatWhiteDefault;
64
-    private $buffer;
65
-    private $utf8;
66
-    private $encoding;
67
-    private $patternModifiers;
68
-
69
-    /**
70
-     * Constructor
71
-     *
72
-     * @api
73
-     *
74
-     * @param string  $sourceName
75
-     * @param integer $sourceIndex
76
-     * @param string  $encoding
77
-     */
78
-    public function __construct($sourceName, $sourceIndex = 0, $encoding = 'utf-8')
79
-    {
80
-        $this->sourceName       = $sourceName ?: '(stdin)';
81
-        $this->sourceIndex      = $sourceIndex;
82
-        $this->charset          = null;
83
-        $this->utf8             = ! $encoding || strtolower($encoding) === 'utf-8';
84
-        $this->patternModifiers = $this->utf8 ? 'Aisu' : 'Ais';
85
-
86
-        if (empty(static::$operatorPattern)) {
87
-            static::$operatorPattern = '([*\/%+-]|[!=]\=|\>\=?|\<\=\>|\<\=?|and|or)';
88
-
89
-            $commentSingle      = '\/\/';
90
-            $commentMultiLeft   = '\/\*';
91
-            $commentMultiRight  = '\*\/';
92
-
93
-            static::$commentPattern = $commentMultiLeft . '.*?' . $commentMultiRight;
94
-            static::$whitePattern = $this->utf8
95
-                ? '/' . $commentSingle . '[^\n]*\s*|(' . static::$commentPattern . ')\s*|\s+/AisuS'
96
-                : '/' . $commentSingle . '[^\n]*\s*|(' . static::$commentPattern . ')\s*|\s+/AisS';
97
-        }
98
-    }
99
-
100
-    /**
101
-     * Get source file name
102
-     *
103
-     * @api
104
-     *
105
-     * @return string
106
-     */
107
-    public function getSourceName()
108
-    {
109
-        return $this->sourceName;
110
-    }
111
-
112
-    /**
113
-     * Throw parser error
114
-     *
115
-     * @api
116
-     *
117
-     * @param string $msg
118
-     *
119
-     * @throws \Leafo\ScssPhp\Exception\ParserException
120
-     */
121
-    public function throwParseError($msg = 'parse error')
122
-    {
123
-        list($line, /* $column */) = $this->getSourcePosition($this->count);
124
-
125
-        $loc = empty($this->sourceName) ? "line: $line" : "$this->sourceName on line $line";
126
-
127
-        if ($this->peek("(.*?)(\n|$)", $m, $this->count)) {
128
-            throw new ParserException("$msg: failed at `$m[1]` $loc");
129
-        }
130
-
131
-        throw new ParserException("$msg: $loc");
132
-    }
133
-
134
-    /**
135
-     * Parser buffer
136
-     *
137
-     * @api
138
-     *
139
-     * @param string $buffer
140
-     *
141
-     * @return \Leafo\ScssPhp\Block
142
-     */
143
-    public function parse($buffer)
144
-    {
145
-        // strip BOM (byte order marker)
146
-        if (substr($buffer, 0, 3) === "\xef\xbb\xbf") {
147
-            $buffer = substr($buffer, 3);
148
-        }
149
-
150
-        $this->buffer          = rtrim($buffer, "\x00..\x1f");
151
-        $this->count           = 0;
152
-        $this->env             = null;
153
-        $this->inParens        = false;
154
-        $this->eatWhiteDefault = true;
155
-
156
-        $this->saveEncoding();
157
-        $this->extractLineNumbers($buffer);
158
-
159
-        $this->pushBlock(null); // root block
160
-        $this->whitespace();
161
-        $this->pushBlock(null);
162
-        $this->popBlock();
163
-
164
-        while ($this->parseChunk()) {
165
-            ;
166
-        }
167
-
168
-        if ($this->count !== strlen($this->buffer)) {
169
-            $this->throwParseError();
170
-        }
171
-
172
-        if (! empty($this->env->parent)) {
173
-            $this->throwParseError('unclosed block');
174
-        }
175
-
176
-        if ($this->charset) {
177
-            array_unshift($this->env->children, $this->charset);
178
-        }
179
-
180
-        $this->env->isRoot    = true;
181
-
182
-        $this->restoreEncoding();
183
-
184
-        return $this->env;
185
-    }
186
-
187
-    /**
188
-     * Parse a value or value list
189
-     *
190
-     * @api
191
-     *
192
-     * @param string $buffer
193
-     * @param string $out
194
-     *
195
-     * @return boolean
196
-     */
197
-    public function parseValue($buffer, &$out)
198
-    {
199
-        $this->count           = 0;
200
-        $this->env             = null;
201
-        $this->inParens        = false;
202
-        $this->eatWhiteDefault = true;
203
-        $this->buffer          = (string) $buffer;
204
-
205
-        $this->saveEncoding();
206
-
207
-        $list = $this->valueList($out);
208
-
209
-        $this->restoreEncoding();
210
-
211
-        return $list;
212
-    }
213
-
214
-    /**
215
-     * Parse a selector or selector list
216
-     *
217
-     * @api
218
-     *
219
-     * @param string $buffer
220
-     * @param string $out
221
-     *
222
-     * @return boolean
223
-     */
224
-    public function parseSelector($buffer, &$out)
225
-    {
226
-        $this->count           = 0;
227
-        $this->env             = null;
228
-        $this->inParens        = false;
229
-        $this->eatWhiteDefault = true;
230
-        $this->buffer          = (string) $buffer;
231
-
232
-        $this->saveEncoding();
233
-
234
-        $selector = $this->selectors($out);
235
-
236
-        $this->restoreEncoding();
237
-
238
-        return $selector;
239
-    }
240
-
241
-    /**
242
-     * Parse a single chunk off the head of the buffer and append it to the
243
-     * current parse environment.
244
-     *
245
-     * Returns false when the buffer is empty, or when there is an error.
246
-     *
247
-     * This function is called repeatedly until the entire document is
248
-     * parsed.
249
-     *
250
-     * This parser is most similar to a recursive descent parser. Single
251
-     * functions represent discrete grammatical rules for the language, and
252
-     * they are able to capture the text that represents those rules.
253
-     *
254
-     * Consider the function Compiler::keyword(). (All parse functions are
255
-     * structured the same.)
256
-     *
257
-     * The function takes a single reference argument. When calling the
258
-     * function it will attempt to match a keyword on the head of the buffer.
259
-     * If it is successful, it will place the keyword in the referenced
260
-     * argument, advance the position in the buffer, and return true. If it
261
-     * fails then it won't advance the buffer and it will return false.
262
-     *
263
-     * All of these parse functions are powered by Compiler::match(), which behaves
264
-     * the same way, but takes a literal regular expression. Sometimes it is
265
-     * more convenient to use match instead of creating a new function.
266
-     *
267
-     * Because of the format of the functions, to parse an entire string of
268
-     * grammatical rules, you can chain them together using &&.
269
-     *
270
-     * But, if some of the rules in the chain succeed before one fails, then
271
-     * the buffer position will be left at an invalid state. In order to
272
-     * avoid this, Compiler::seek() is used to remember and set buffer positions.
273
-     *
274
-     * Before parsing a chain, use $s = $this->seek() to remember the current
275
-     * position into $s. Then if a chain fails, use $this->seek($s) to
276
-     * go back where we started.
277
-     *
278
-     * @return boolean
279
-     */
280
-    protected function parseChunk()
281
-    {
282
-        $s = $this->seek();
283
-
284
-        // the directives
285
-        if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] === '@') {
286
-            if ($this->literal('@at-root') &&
287
-                ($this->selectors($selector) || true) &&
288
-                ($this->map($with) || true) &&
289
-                $this->literal('{')
290
-            ) {
291
-                $atRoot = $this->pushSpecialBlock(Type::T_AT_ROOT, $s);
292
-                $atRoot->selector = $selector;
293
-                $atRoot->with = $with;
294
-
295
-                return true;
296
-            }
297
-
298
-            $this->seek($s);
299
-
300
-            if ($this->literal('@media') && $this->mediaQueryList($mediaQueryList) && $this->literal('{')) {
301
-                $media = $this->pushSpecialBlock(Type::T_MEDIA, $s);
302
-                $media->queryList = $mediaQueryList[2];
303
-
304
-                return true;
305
-            }
306
-
307
-            $this->seek($s);
308
-
309
-            if ($this->literal('@mixin') &&
310
-                $this->keyword($mixinName) &&
311
-                ($this->argumentDef($args) || true) &&
312
-                $this->literal('{')
313
-            ) {
314
-                $mixin = $this->pushSpecialBlock(Type::T_MIXIN, $s);
315
-                $mixin->name = $mixinName;
316
-                $mixin->args = $args;
317
-
318
-                return true;
319
-            }
320
-
321
-            $this->seek($s);
322
-
323
-            if ($this->literal('@include') &&
324
-                $this->keyword($mixinName) &&
325
-                ($this->literal('(') &&
326
-                    ($this->argValues($argValues) || true) &&
327
-                    $this->literal(')') || true) &&
328
-                ($this->end() ||
329
-                    $this->literal('{') && $hasBlock = true)
330
-            ) {
331
-                $child = [Type::T_INCLUDE, $mixinName, isset($argValues) ? $argValues : null, null];
332
-
333
-                if (! empty($hasBlock)) {
334
-                    $include = $this->pushSpecialBlock(Type::T_INCLUDE, $s);
335
-                    $include->child = $child;
336
-                } else {
337
-                    $this->append($child, $s);
338
-                }
339
-
340
-                return true;
341
-            }
342
-
343
-            $this->seek($s);
344
-
345
-            if ($this->literal('@scssphp-import-once') &&
346
-                $this->valueList($importPath) &&
347
-                $this->end()
348
-            ) {
349
-                $this->append([Type::T_SCSSPHP_IMPORT_ONCE, $importPath], $s);
350
-
351
-                return true;
352
-            }
353
-
354
-            $this->seek($s);
355
-
356
-            if ($this->literal('@import') &&
357
-                $this->valueList($importPath) &&
358
-                $this->end()
359
-            ) {
360
-                $this->append([Type::T_IMPORT, $importPath], $s);
361
-
362
-                return true;
363
-            }
364
-
365
-            $this->seek($s);
366
-
367
-            if ($this->literal('@import') &&
368
-                $this->url($importPath) &&
369
-                $this->end()
370
-            ) {
371
-                $this->append([Type::T_IMPORT, $importPath], $s);
372
-
373
-                return true;
374
-            }
375
-
376
-            $this->seek($s);
377
-
378
-            if ($this->literal('@extend') &&
379
-                $this->selectors($selectors) &&
380
-                $this->end()
381
-            ) {
382
-                // check for '!flag'
383
-                $optional = $this->stripOptionalFlag($selectors);
384
-                $this->append([Type::T_EXTEND, $selectors, $optional], $s);
27
+	const SOURCE_INDEX  = -1;
28
+	const SOURCE_LINE   = -2;
29
+	const SOURCE_COLUMN = -3;
30
+
31
+	/**
32
+	 * @var array
33
+	 */
34
+	protected static $precedence = [
35
+		'='   => 0,
36
+		'or'  => 1,
37
+		'and' => 2,
38
+		'=='  => 3,
39
+		'!='  => 3,
40
+		'<=>' => 3,
41
+		'<='  => 4,
42
+		'>='  => 4,
43
+		'<'   => 4,
44
+		'>'   => 4,
45
+		'+'   => 5,
46
+		'-'   => 5,
47
+		'*'   => 6,
48
+		'/'   => 6,
49
+		'%'   => 6,
50
+	];
51
+
52
+	protected static $commentPattern;
53
+	protected static $operatorPattern;
54
+	protected static $whitePattern;
55
+
56
+	private $sourceName;
57
+	private $sourceIndex;
58
+	private $sourcePositions;
59
+	private $charset;
60
+	private $count;
61
+	private $env;
62
+	private $inParens;
63
+	private $eatWhiteDefault;
64
+	private $buffer;
65
+	private $utf8;
66
+	private $encoding;
67
+	private $patternModifiers;
68
+
69
+	/**
70
+	 * Constructor
71
+	 *
72
+	 * @api
73
+	 *
74
+	 * @param string  $sourceName
75
+	 * @param integer $sourceIndex
76
+	 * @param string  $encoding
77
+	 */
78
+	public function __construct($sourceName, $sourceIndex = 0, $encoding = 'utf-8')
79
+	{
80
+		$this->sourceName       = $sourceName ?: '(stdin)';
81
+		$this->sourceIndex      = $sourceIndex;
82
+		$this->charset          = null;
83
+		$this->utf8             = ! $encoding || strtolower($encoding) === 'utf-8';
84
+		$this->patternModifiers = $this->utf8 ? 'Aisu' : 'Ais';
85
+
86
+		if (empty(static::$operatorPattern)) {
87
+			static::$operatorPattern = '([*\/%+-]|[!=]\=|\>\=?|\<\=\>|\<\=?|and|or)';
88
+
89
+			$commentSingle      = '\/\/';
90
+			$commentMultiLeft   = '\/\*';
91
+			$commentMultiRight  = '\*\/';
92
+
93
+			static::$commentPattern = $commentMultiLeft . '.*?' . $commentMultiRight;
94
+			static::$whitePattern = $this->utf8
95
+				? '/' . $commentSingle . '[^\n]*\s*|(' . static::$commentPattern . ')\s*|\s+/AisuS'
96
+				: '/' . $commentSingle . '[^\n]*\s*|(' . static::$commentPattern . ')\s*|\s+/AisS';
97
+		}
98
+	}
99
+
100
+	/**
101
+	 * Get source file name
102
+	 *
103
+	 * @api
104
+	 *
105
+	 * @return string
106
+	 */
107
+	public function getSourceName()
108
+	{
109
+		return $this->sourceName;
110
+	}
111
+
112
+	/**
113
+	 * Throw parser error
114
+	 *
115
+	 * @api
116
+	 *
117
+	 * @param string $msg
118
+	 *
119
+	 * @throws \Leafo\ScssPhp\Exception\ParserException
120
+	 */
121
+	public function throwParseError($msg = 'parse error')
122
+	{
123
+		list($line, /* $column */) = $this->getSourcePosition($this->count);
124
+
125
+		$loc = empty($this->sourceName) ? "line: $line" : "$this->sourceName on line $line";
126
+
127
+		if ($this->peek("(.*?)(\n|$)", $m, $this->count)) {
128
+			throw new ParserException("$msg: failed at `$m[1]` $loc");
129
+		}
130
+
131
+		throw new ParserException("$msg: $loc");
132
+	}
133
+
134
+	/**
135
+	 * Parser buffer
136
+	 *
137
+	 * @api
138
+	 *
139
+	 * @param string $buffer
140
+	 *
141
+	 * @return \Leafo\ScssPhp\Block
142
+	 */
143
+	public function parse($buffer)
144
+	{
145
+		// strip BOM (byte order marker)
146
+		if (substr($buffer, 0, 3) === "\xef\xbb\xbf") {
147
+			$buffer = substr($buffer, 3);
148
+		}
149
+
150
+		$this->buffer          = rtrim($buffer, "\x00..\x1f");
151
+		$this->count           = 0;
152
+		$this->env             = null;
153
+		$this->inParens        = false;
154
+		$this->eatWhiteDefault = true;
155
+
156
+		$this->saveEncoding();
157
+		$this->extractLineNumbers($buffer);
158
+
159
+		$this->pushBlock(null); // root block
160
+		$this->whitespace();
161
+		$this->pushBlock(null);
162
+		$this->popBlock();
163
+
164
+		while ($this->parseChunk()) {
165
+			;
166
+		}
167
+
168
+		if ($this->count !== strlen($this->buffer)) {
169
+			$this->throwParseError();
170
+		}
171
+
172
+		if (! empty($this->env->parent)) {
173
+			$this->throwParseError('unclosed block');
174
+		}
175
+
176
+		if ($this->charset) {
177
+			array_unshift($this->env->children, $this->charset);
178
+		}
179
+
180
+		$this->env->isRoot    = true;
181
+
182
+		$this->restoreEncoding();
183
+
184
+		return $this->env;
185
+	}
186
+
187
+	/**
188
+	 * Parse a value or value list
189
+	 *
190
+	 * @api
191
+	 *
192
+	 * @param string $buffer
193
+	 * @param string $out
194
+	 *
195
+	 * @return boolean
196
+	 */
197
+	public function parseValue($buffer, &$out)
198
+	{
199
+		$this->count           = 0;
200
+		$this->env             = null;
201
+		$this->inParens        = false;
202
+		$this->eatWhiteDefault = true;
203
+		$this->buffer          = (string) $buffer;
204
+
205
+		$this->saveEncoding();
206
+
207
+		$list = $this->valueList($out);
208
+
209
+		$this->restoreEncoding();
210
+
211
+		return $list;
212
+	}
213
+
214
+	/**
215
+	 * Parse a selector or selector list
216
+	 *
217
+	 * @api
218
+	 *
219
+	 * @param string $buffer
220
+	 * @param string $out
221
+	 *
222
+	 * @return boolean
223
+	 */
224
+	public function parseSelector($buffer, &$out)
225
+	{
226
+		$this->count           = 0;
227
+		$this->env             = null;
228
+		$this->inParens        = false;
229
+		$this->eatWhiteDefault = true;
230
+		$this->buffer          = (string) $buffer;
231
+
232
+		$this->saveEncoding();
233
+
234
+		$selector = $this->selectors($out);
235
+
236
+		$this->restoreEncoding();
237
+
238
+		return $selector;
239
+	}
240
+
241
+	/**
242
+	 * Parse a single chunk off the head of the buffer and append it to the
243
+	 * current parse environment.
244
+	 *
245
+	 * Returns false when the buffer is empty, or when there is an error.
246
+	 *
247
+	 * This function is called repeatedly until the entire document is
248
+	 * parsed.
249
+	 *
250
+	 * This parser is most similar to a recursive descent parser. Single
251
+	 * functions represent discrete grammatical rules for the language, and
252
+	 * they are able to capture the text that represents those rules.
253
+	 *
254
+	 * Consider the function Compiler::keyword(). (All parse functions are
255
+	 * structured the same.)
256
+	 *
257
+	 * The function takes a single reference argument. When calling the
258
+	 * function it will attempt to match a keyword on the head of the buffer.
259
+	 * If it is successful, it will place the keyword in the referenced
260
+	 * argument, advance the position in the buffer, and return true. If it
261
+	 * fails then it won't advance the buffer and it will return false.
262
+	 *
263
+	 * All of these parse functions are powered by Compiler::match(), which behaves
264
+	 * the same way, but takes a literal regular expression. Sometimes it is
265
+	 * more convenient to use match instead of creating a new function.
266
+	 *
267
+	 * Because of the format of the functions, to parse an entire string of
268
+	 * grammatical rules, you can chain them together using &&.
269
+	 *
270
+	 * But, if some of the rules in the chain succeed before one fails, then
271
+	 * the buffer position will be left at an invalid state. In order to
272
+	 * avoid this, Compiler::seek() is used to remember and set buffer positions.
273
+	 *
274
+	 * Before parsing a chain, use $s = $this->seek() to remember the current
275
+	 * position into $s. Then if a chain fails, use $this->seek($s) to
276
+	 * go back where we started.
277
+	 *
278
+	 * @return boolean
279
+	 */
280
+	protected function parseChunk()
281
+	{
282
+		$s = $this->seek();
283
+
284
+		// the directives
285
+		if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] === '@') {
286
+			if ($this->literal('@at-root') &&
287
+				($this->selectors($selector) || true) &&
288
+				($this->map($with) || true) &&
289
+				$this->literal('{')
290
+			) {
291
+				$atRoot = $this->pushSpecialBlock(Type::T_AT_ROOT, $s);
292
+				$atRoot->selector = $selector;
293
+				$atRoot->with = $with;
294
+
295
+				return true;
296
+			}
297
+
298
+			$this->seek($s);
299
+
300
+			if ($this->literal('@media') && $this->mediaQueryList($mediaQueryList) && $this->literal('{')) {
301
+				$media = $this->pushSpecialBlock(Type::T_MEDIA, $s);
302
+				$media->queryList = $mediaQueryList[2];
303
+
304
+				return true;
305
+			}
306
+
307
+			$this->seek($s);
308
+
309
+			if ($this->literal('@mixin') &&
310
+				$this->keyword($mixinName) &&
311
+				($this->argumentDef($args) || true) &&
312
+				$this->literal('{')
313
+			) {
314
+				$mixin = $this->pushSpecialBlock(Type::T_MIXIN, $s);
315
+				$mixin->name = $mixinName;
316
+				$mixin->args = $args;
317
+
318
+				return true;
319
+			}
320
+
321
+			$this->seek($s);
322
+
323
+			if ($this->literal('@include') &&
324
+				$this->keyword($mixinName) &&
325
+				($this->literal('(') &&
326
+					($this->argValues($argValues) || true) &&
327
+					$this->literal(')') || true) &&
328
+				($this->end() ||
329
+					$this->literal('{') && $hasBlock = true)
330
+			) {
331
+				$child = [Type::T_INCLUDE, $mixinName, isset($argValues) ? $argValues : null, null];
332
+
333
+				if (! empty($hasBlock)) {
334
+					$include = $this->pushSpecialBlock(Type::T_INCLUDE, $s);
335
+					$include->child = $child;
336
+				} else {
337
+					$this->append($child, $s);
338
+				}
339
+
340
+				return true;
341
+			}
342
+
343
+			$this->seek($s);
344
+
345
+			if ($this->literal('@scssphp-import-once') &&
346
+				$this->valueList($importPath) &&
347
+				$this->end()
348
+			) {
349
+				$this->append([Type::T_SCSSPHP_IMPORT_ONCE, $importPath], $s);
350
+
351
+				return true;
352
+			}
353
+
354
+			$this->seek($s);
355
+
356
+			if ($this->literal('@import') &&
357
+				$this->valueList($importPath) &&
358
+				$this->end()
359
+			) {
360
+				$this->append([Type::T_IMPORT, $importPath], $s);
361
+
362
+				return true;
363
+			}
364
+
365
+			$this->seek($s);
366
+
367
+			if ($this->literal('@import') &&
368
+				$this->url($importPath) &&
369
+				$this->end()
370
+			) {
371
+				$this->append([Type::T_IMPORT, $importPath], $s);
372
+
373
+				return true;
374
+			}
375
+
376
+			$this->seek($s);
377
+
378
+			if ($this->literal('@extend') &&
379
+				$this->selectors($selectors) &&
380
+				$this->end()
381
+			) {
382
+				// check for '!flag'
383
+				$optional = $this->stripOptionalFlag($selectors);
384
+				$this->append([Type::T_EXTEND, $selectors, $optional], $s);
385 385
 
386
-                return true;
387
-            }
386
+				return true;
387
+			}
388 388
 
389
-            $this->seek($s);
389
+			$this->seek($s);
390 390
 
391
-            if ($this->literal('@function') &&
392
-                $this->keyword($fnName) &&
393
-                $this->argumentDef($args) &&
394
-                $this->literal('{')
395
-            ) {
396
-                $func = $this->pushSpecialBlock(Type::T_FUNCTION, $s);
397
-                $func->name = $fnName;
398
-                $func->args = $args;
391
+			if ($this->literal('@function') &&
392
+				$this->keyword($fnName) &&
393
+				$this->argumentDef($args) &&
394
+				$this->literal('{')
395
+			) {
396
+				$func = $this->pushSpecialBlock(Type::T_FUNCTION, $s);
397
+				$func->name = $fnName;
398
+				$func->args = $args;
399 399
 
400
-                return true;
401
-            }
400
+				return true;
401
+			}
402 402
 
403
-            $this->seek($s);
403
+			$this->seek($s);
404 404
 
405
-            if ($this->literal('@break') && $this->end()) {
406
-                $this->append([Type::T_BREAK], $s);
405
+			if ($this->literal('@break') && $this->end()) {
406
+				$this->append([Type::T_BREAK], $s);
407 407
 
408
-                return true;
409
-            }
408
+				return true;
409
+			}
410 410
 
411
-            $this->seek($s);
411
+			$this->seek($s);
412 412
 
413
-            if ($this->literal('@continue') && $this->end()) {
414
-                $this->append([Type::T_CONTINUE], $s);
413
+			if ($this->literal('@continue') && $this->end()) {
414
+				$this->append([Type::T_CONTINUE], $s);
415 415
 
416
-                return true;
417
-            }
416
+				return true;
417
+			}
418 418
 
419
-            $this->seek($s);
419
+			$this->seek($s);
420 420
 
421 421
 
422
-            if ($this->literal('@return') && ($this->valueList($retVal) || true) && $this->end()) {
423
-                $this->append([Type::T_RETURN, isset($retVal) ? $retVal : [Type::T_NULL]], $s);
422
+			if ($this->literal('@return') && ($this->valueList($retVal) || true) && $this->end()) {
423
+				$this->append([Type::T_RETURN, isset($retVal) ? $retVal : [Type::T_NULL]], $s);
424 424
 
425
-                return true;
426
-            }
425
+				return true;
426
+			}
427 427
 
428
-            $this->seek($s);
428
+			$this->seek($s);
429 429
 
430
-            if ($this->literal('@each') &&
431
-                $this->genericList($varNames, 'variable', ',', false) &&
432
-                $this->literal('in') &&
433
-                $this->valueList($list) &&
434
-                $this->literal('{')
435
-            ) {
436
-                $each = $this->pushSpecialBlock(Type::T_EACH, $s);
430
+			if ($this->literal('@each') &&
431
+				$this->genericList($varNames, 'variable', ',', false) &&
432
+				$this->literal('in') &&
433
+				$this->valueList($list) &&
434
+				$this->literal('{')
435
+			) {
436
+				$each = $this->pushSpecialBlock(Type::T_EACH, $s);
437 437
 
438
-                foreach ($varNames[2] as $varName) {
439
-                    $each->vars[] = $varName[1];
440
-                }
438
+				foreach ($varNames[2] as $varName) {
439
+					$each->vars[] = $varName[1];
440
+				}
441 441
 
442
-                $each->list = $list;
442
+				$each->list = $list;
443 443
 
444
-                return true;
445
-            }
444
+				return true;
445
+			}
446 446
 
447
-            $this->seek($s);
447
+			$this->seek($s);
448 448
 
449
-            if ($this->literal('@while') &&
450
-                $this->expression($cond) &&
451
-                $this->literal('{')
452
-            ) {
453
-                $while = $this->pushSpecialBlock(Type::T_WHILE, $s);
454
-                $while->cond = $cond;
449
+			if ($this->literal('@while') &&
450
+				$this->expression($cond) &&
451
+				$this->literal('{')
452
+			) {
453
+				$while = $this->pushSpecialBlock(Type::T_WHILE, $s);
454
+				$while->cond = $cond;
455 455
 
456
-                return true;
457
-            }
456
+				return true;
457
+			}
458 458
 
459
-            $this->seek($s);
459
+			$this->seek($s);
460 460
 
461
-            if ($this->literal('@for') &&
462
-                $this->variable($varName) &&
463
-                $this->literal('from') &&
464
-                $this->expression($start) &&
465
-                ($this->literal('through') ||
466
-                    ($forUntil = true && $this->literal('to'))) &&
467
-                $this->expression($end) &&
468
-                $this->literal('{')
469
-            ) {
470
-                $for = $this->pushSpecialBlock(Type::T_FOR, $s);
471
-                $for->var = $varName[1];
472
-                $for->start = $start;
473
-                $for->end = $end;
474
-                $for->until = isset($forUntil);
461
+			if ($this->literal('@for') &&
462
+				$this->variable($varName) &&
463
+				$this->literal('from') &&
464
+				$this->expression($start) &&
465
+				($this->literal('through') ||
466
+					($forUntil = true && $this->literal('to'))) &&
467
+				$this->expression($end) &&
468
+				$this->literal('{')
469
+			) {
470
+				$for = $this->pushSpecialBlock(Type::T_FOR, $s);
471
+				$for->var = $varName[1];
472
+				$for->start = $start;
473
+				$for->end = $end;
474
+				$for->until = isset($forUntil);
475 475
 
476
-                return true;
477
-            }
476
+				return true;
477
+			}
478 478
 
479
-            $this->seek($s);
479
+			$this->seek($s);
480 480
 
481
-            if ($this->literal('@if') && $this->valueList($cond) && $this->literal('{')) {
482
-                $if = $this->pushSpecialBlock(Type::T_IF, $s);
483
-                $if->cond = $cond;
484
-                $if->cases = [];
481
+			if ($this->literal('@if') && $this->valueList($cond) && $this->literal('{')) {
482
+				$if = $this->pushSpecialBlock(Type::T_IF, $s);
483
+				$if->cond = $cond;
484
+				$if->cases = [];
485 485
 
486
-                return true;
487
-            }
486
+				return true;
487
+			}
488 488
 
489
-            $this->seek($s);
489
+			$this->seek($s);
490 490
 
491
-            if ($this->literal('@debug') &&
492
-                $this->valueList($value) &&
493
-                $this->end()
494
-            ) {
495
-                $this->append([Type::T_DEBUG, $value], $s);
491
+			if ($this->literal('@debug') &&
492
+				$this->valueList($value) &&
493
+				$this->end()
494
+			) {
495
+				$this->append([Type::T_DEBUG, $value], $s);
496 496
 
497
-                return true;
498
-            }
497
+				return true;
498
+			}
499 499
 
500
-            $this->seek($s);
500
+			$this->seek($s);
501 501
 
502
-            if ($this->literal('@warn') &&
503
-                $this->valueList($value) &&
504
-                $this->end()
505
-            ) {
506
-                $this->append([Type::T_WARN, $value], $s);
502
+			if ($this->literal('@warn') &&
503
+				$this->valueList($value) &&
504
+				$this->end()
505
+			) {
506
+				$this->append([Type::T_WARN, $value], $s);
507 507
 
508
-                return true;
509
-            }
508
+				return true;
509
+			}
510 510
 
511
-            $this->seek($s);
511
+			$this->seek($s);
512 512
 
513
-            if ($this->literal('@error') &&
514
-                $this->valueList($value) &&
515
-                $this->end()
516
-            ) {
517
-                $this->append([Type::T_ERROR, $value], $s);
513
+			if ($this->literal('@error') &&
514
+				$this->valueList($value) &&
515
+				$this->end()
516
+			) {
517
+				$this->append([Type::T_ERROR, $value], $s);
518 518
 
519
-                return true;
520
-            }
519
+				return true;
520
+			}
521 521
 
522
-            $this->seek($s);
523
-
524
-            if ($this->literal('@content') && $this->end()) {
525
-                $this->append([Type::T_MIXIN_CONTENT], $s);
526
-
527
-                return true;
528
-            }
529
-
530
-            $this->seek($s);
531
-
532
-            $last = $this->last();
533
-
534
-            if (isset($last) && $last[0] === Type::T_IF) {
535
-                list(, $if) = $last;
536
-
537
-                if ($this->literal('@else')) {
538
-                    if ($this->literal('{')) {
539
-                        $else = $this->pushSpecialBlock(Type::T_ELSE, $s);
540
-                    } elseif ($this->literal('if') && $this->valueList($cond) && $this->literal('{')) {
541
-                        $else = $this->pushSpecialBlock(Type::T_ELSEIF, $s);
542
-                        $else->cond = $cond;
543
-                    }
544
-
545
-                    if (isset($else)) {
546
-                        $else->dontAppend = true;
547
-                        $if->cases[] = $else;
548
-
549
-                        return true;
550
-                    }
551
-                }
552
-
553
-                $this->seek($s);
554
-            }
522
+			$this->seek($s);
523
+
524
+			if ($this->literal('@content') && $this->end()) {
525
+				$this->append([Type::T_MIXIN_CONTENT], $s);
526
+
527
+				return true;
528
+			}
529
+
530
+			$this->seek($s);
531
+
532
+			$last = $this->last();
533
+
534
+			if (isset($last) && $last[0] === Type::T_IF) {
535
+				list(, $if) = $last;
536
+
537
+				if ($this->literal('@else')) {
538
+					if ($this->literal('{')) {
539
+						$else = $this->pushSpecialBlock(Type::T_ELSE, $s);
540
+					} elseif ($this->literal('if') && $this->valueList($cond) && $this->literal('{')) {
541
+						$else = $this->pushSpecialBlock(Type::T_ELSEIF, $s);
542
+						$else->cond = $cond;
543
+					}
544
+
545
+					if (isset($else)) {
546
+						$else->dontAppend = true;
547
+						$if->cases[] = $else;
548
+
549
+						return true;
550
+					}
551
+				}
552
+
553
+				$this->seek($s);
554
+			}
555 555
 
556
-            // only retain the first @charset directive encountered
557
-            if ($this->literal('@charset') &&
558
-                $this->valueList($charset) &&
559
-                $this->end()
560
-            ) {
561
-                if (! isset($this->charset)) {
562
-                    $statement = [Type::T_CHARSET, $charset];
563
-
564
-                    list($line, $column) = $this->getSourcePosition($s);
565
-
566
-                    $statement[static::SOURCE_LINE]   = $line;
567
-                    $statement[static::SOURCE_COLUMN] = $column;
568
-                    $statement[static::SOURCE_INDEX]  = $this->sourceIndex;
569
-
570
-                    $this->charset = $statement;
571
-                }
556
+			// only retain the first @charset directive encountered
557
+			if ($this->literal('@charset') &&
558
+				$this->valueList($charset) &&
559
+				$this->end()
560
+			) {
561
+				if (! isset($this->charset)) {
562
+					$statement = [Type::T_CHARSET, $charset];
563
+
564
+					list($line, $column) = $this->getSourcePosition($s);
565
+
566
+					$statement[static::SOURCE_LINE]   = $line;
567
+					$statement[static::SOURCE_COLUMN] = $column;
568
+					$statement[static::SOURCE_INDEX]  = $this->sourceIndex;
569
+
570
+					$this->charset = $statement;
571
+				}
572 572
 
573
-                return true;
574
-            }
573
+				return true;
574
+			}
575 575
 
576
-            $this->seek($s);
576
+			$this->seek($s);
577 577
 
578
-            // doesn't match built in directive, do generic one
579
-            if ($this->literal('@', false) &&
580
-                $this->keyword($dirName) &&
581
-                ($this->variable($dirValue) || $this->openString('{', $dirValue) || true) &&
582
-                $this->literal('{')
583
-            ) {
584
-                if ($dirName === 'media') {
585
-                    $directive = $this->pushSpecialBlock(Type::T_MEDIA, $s);
586
-                } else {
587
-                    $directive = $this->pushSpecialBlock(Type::T_DIRECTIVE, $s);
588
-                    $directive->name = $dirName;
589
-                }
590
-
591
-                if (isset($dirValue)) {
592
-                    $directive->value = $dirValue;
593
-                }
594
-
595
-                return true;
596
-            }
597
-
598
-            $this->seek($s);
599
-
600
-            return false;
601
-        }
602
-
603
-        // property shortcut
604
-        // captures most properties before having to parse a selector
605
-        if ($this->keyword($name, false) &&
606
-            $this->literal(': ') &&
607
-            $this->valueList($value) &&
608
-            $this->end()
609
-        ) {
610
-            $name = [Type::T_STRING, '', [$name]];
611
-            $this->append([Type::T_ASSIGN, $name, $value], $s);
612
-
613
-            return true;
614
-        }
615
-
616
-        $this->seek($s);
617
-
618
-        // variable assigns
619
-        if ($this->variable($name) &&
620
-            $this->literal(':') &&
621
-            $this->valueList($value) &&
622
-            $this->end()
623
-        ) {
624
-            // check for '!flag'
625
-            $assignmentFlags = $this->stripAssignmentFlags($value);
626
-            $this->append([Type::T_ASSIGN, $name, $value, $assignmentFlags], $s);
627
-
628
-            return true;
629
-        }
630
-
631
-        $this->seek($s);
632
-
633
-        // misc
634
-        if ($this->literal('-->')) {
635
-            return true;
636
-        }
637
-
638
-        // opening css block
639
-        if ($this->selectors($selectors) && $this->literal('{')) {
640
-            $this->pushBlock($selectors, $s);
641
-
642
-            return true;
643
-        }
644
-
645
-        $this->seek($s);
646
-
647
-        // property assign, or nested assign
648
-        if ($this->propertyName($name) && $this->literal(':')) {
649
-            $foundSomething = false;
650
-
651
-            if ($this->valueList($value)) {
652
-                $this->append([Type::T_ASSIGN, $name, $value], $s);
653
-                $foundSomething = true;
654
-            }
655
-
656
-            if ($this->literal('{')) {
657
-                $propBlock = $this->pushSpecialBlock(Type::T_NESTED_PROPERTY, $s);
658
-                $propBlock->prefix = $name;
659
-                $foundSomething = true;
660
-            } elseif ($foundSomething) {
661
-                $foundSomething = $this->end();
662
-            }
663
-
664
-            if ($foundSomething) {
665
-                return true;
666
-            }
667
-        }
668
-
669
-        $this->seek($s);
670
-
671
-        // closing a block
672
-        if ($this->literal('}')) {
673
-            $block = $this->popBlock();
674
-
675
-            if (isset($block->type) && $block->type === Type::T_INCLUDE) {
676
-                $include = $block->child;
677
-                unset($block->child);
678
-                $include[3] = $block;
679
-                $this->append($include, $s);
680
-            } elseif (empty($block->dontAppend)) {
681
-                $type = isset($block->type) ? $block->type : Type::T_BLOCK;
682
-                $this->append([$type, $block], $s);
683
-            }
684
-
685
-            return true;
686
-        }
687
-
688
-        // extra stuff
689
-        if ($this->literal(';') ||
690
-            $this->literal('<!--')
691
-        ) {
692
-            return true;
693
-        }
694
-
695
-        return false;
696
-    }
697
-
698
-    /**
699
-     * Push block onto parse tree
700
-     *
701
-     * @param array   $selectors
702
-     * @param integer $pos
703
-     *
704
-     * @return \Leafo\ScssPhp\Block
705
-     */
706
-    protected function pushBlock($selectors, $pos = 0)
707
-    {
708
-        list($line, $column) = $this->getSourcePosition($pos);
709
-
710
-        $b = new Block;
711
-        $b->sourceName   = $this->sourceName;
712
-        $b->sourceLine   = $line;
713
-        $b->sourceColumn = $column;
714
-        $b->sourceIndex  = $this->sourceIndex;
715
-        $b->selectors    = $selectors;
716
-        $b->comments     = [];
717
-        $b->parent       = $this->env;
718
-
719
-        if (! $this->env) {
720
-            $b->children = [];
721
-        } elseif (empty($this->env->children)) {
722
-            $this->env->children = $this->env->comments;
723
-            $b->children = [];
724
-            $this->env->comments = [];
725
-        } else {
726
-            $b->children = $this->env->comments;
727
-            $this->env->comments = [];
728
-        }
729
-
730
-        $this->env = $b;
731
-
732
-        return $b;
733
-    }
734
-
735
-    /**
736
-     * Push special (named) block onto parse tree
737
-     *
738
-     * @param string  $type
739
-     * @param integer $pos
740
-     *
741
-     * @return \Leafo\ScssPhp\Block
742
-     */
743
-    protected function pushSpecialBlock($type, $pos)
744
-    {
745
-        $block = $this->pushBlock(null, $pos);
746
-        $block->type = $type;
747
-
748
-        return $block;
749
-    }
750
-
751
-    /**
752
-     * Pop scope and return last block
753
-     *
754
-     * @return \Leafo\ScssPhp\Block
755
-     *
756
-     * @throws \Exception
757
-     */
758
-    protected function popBlock()
759
-    {
760
-        $block = $this->env;
761
-
762
-        if (empty($block->parent)) {
763
-            $this->throwParseError('unexpected }');
764
-        }
765
-
766
-        $this->env = $block->parent;
767
-        unset($block->parent);
768
-
769
-        $comments = $block->comments;
770
-        if (count($comments)) {
771
-            $this->env->comments = $comments;
772
-            unset($block->comments);
773
-        }
774
-
775
-        return $block;
776
-    }
777
-
778
-    /**
779
-     * Peek input stream
780
-     *
781
-     * @param string  $regex
782
-     * @param array   $out
783
-     * @param integer $from
784
-     *
785
-     * @return integer
786
-     */
787
-    protected function peek($regex, &$out, $from = null)
788
-    {
789
-        if (! isset($from)) {
790
-            $from = $this->count;
791
-        }
792
-
793
-        $r = '/' . $regex . '/' . $this->patternModifiers;
794
-        $result = preg_match($r, $this->buffer, $out, null, $from);
795
-
796
-        return $result;
797
-    }
798
-
799
-    /**
800
-     * Seek to position in input stream (or return current position in input stream)
801
-     *
802
-     * @param integer $where
803
-     *
804
-     * @return integer
805
-     */
806
-    protected function seek($where = null)
807
-    {
808
-        if ($where === null) {
809
-            return $this->count;
810
-        }
811
-
812
-        $this->count = $where;
813
-
814
-        return true;
815
-    }
816
-
817
-    /**
818
-     * Match string looking for either ending delim, escape, or string interpolation
819
-     *
820
-     * {@internal This is a workaround for preg_match's 250K string match limit. }}
821
-     *
822
-     * @param array  $m     Matches (passed by reference)
823
-     * @param string $delim Delimeter
824
-     *
825
-     * @return boolean True if match; false otherwise
826
-     */
827
-    protected function matchString(&$m, $delim)
828
-    {
829
-        $token = null;
830
-
831
-        $end = strlen($this->buffer);
832
-
833
-        // look for either ending delim, escape, or string interpolation
834
-        foreach (['#{', '\\', $delim] as $lookahead) {
835
-            $pos = strpos($this->buffer, $lookahead, $this->count);
836
-
837
-            if ($pos !== false && $pos < $end) {
838
-                $end = $pos;
839
-                $token = $lookahead;
840
-            }
841
-        }
842
-
843
-        if (! isset($token)) {
844
-            return false;
845
-        }
846
-
847
-        $match = substr($this->buffer, $this->count, $end - $this->count);
848
-        $m = [
849
-            $match . $token,
850
-            $match,
851
-            $token
852
-        ];
853
-        $this->count = $end + strlen($token);
854
-
855
-        return true;
856
-    }
857
-
858
-    /**
859
-     * Try to match something on head of buffer
860
-     *
861
-     * @param string  $regex
862
-     * @param array   $out
863
-     * @param boolean $eatWhitespace
864
-     *
865
-     * @return boolean
866
-     */
867
-    protected function match($regex, &$out, $eatWhitespace = null)
868
-    {
869
-        if (! isset($eatWhitespace)) {
870
-            $eatWhitespace = $this->eatWhiteDefault;
871
-        }
872
-
873
-        $r = '/' . $regex . '/' . $this->patternModifiers;
874
-
875
-        if (preg_match($r, $this->buffer, $out, null, $this->count)) {
876
-            $this->count += strlen($out[0]);
877
-
878
-            if ($eatWhitespace) {
879
-                $this->whitespace();
880
-            }
881
-
882
-            return true;
883
-        }
884
-
885
-        return false;
886
-    }
887
-
888
-    /**
889
-     * Match literal string
890
-     *
891
-     * @param string  $what
892
-     * @param boolean $eatWhitespace
893
-     *
894
-     * @return boolean
895
-     */
896
-    protected function literal($what, $eatWhitespace = null)
897
-    {
898
-        if (! isset($eatWhitespace)) {
899
-            $eatWhitespace = $this->eatWhiteDefault;
900
-        }
901
-
902
-        $len = strlen($what);
903
-
904
-        if (strcasecmp(substr($this->buffer, $this->count, $len), $what) === 0) {
905
-            $this->count += $len;
906
-
907
-            if ($eatWhitespace) {
908
-                $this->whitespace();
909
-            }
910
-
911
-            return true;
912
-        }
913
-
914
-        return false;
915
-    }
916
-
917
-    /**
918
-     * Match some whitespace
919
-     *
920
-     * @return boolean
921
-     */
922
-    protected function whitespace()
923
-    {
924
-        $gotWhite = false;
925
-
926
-        while (preg_match(static::$whitePattern, $this->buffer, $m, null, $this->count)) {
927
-            if (isset($m[1]) && empty($this->commentsSeen[$this->count])) {
928
-                $this->appendComment([Type::T_COMMENT, $m[1]]);
929
-
930
-                $this->commentsSeen[$this->count] = true;
931
-            }
932
-
933
-            $this->count += strlen($m[0]);
934
-            $gotWhite = true;
935
-        }
936
-
937
-        return $gotWhite;
938
-    }
939
-
940
-    /**
941
-     * Append comment to current block
942
-     *
943
-     * @param array $comment
944
-     */
945
-    protected function appendComment($comment)
946
-    {
947
-        $comment[1] = substr(preg_replace(['/^\s+/m', '/^(.)/m'], ['', ' \1'], $comment[1]), 1);
948
-
949
-        $this->env->comments[] = $comment;
950
-    }
951
-
952
-    /**
953
-     * Append statement to current block
954
-     *
955
-     * @param array   $statement
956
-     * @param integer $pos
957
-     */
958
-    protected function append($statement, $pos = null)
959
-    {
960
-        if ($pos !== null) {
961
-            list($line, $column) = $this->getSourcePosition($pos);
962
-
963
-            $statement[static::SOURCE_LINE]   = $line;
964
-            $statement[static::SOURCE_COLUMN] = $column;
965
-            $statement[static::SOURCE_INDEX]  = $this->sourceIndex;
966
-        }
967
-
968
-        $this->env->children[] = $statement;
969
-
970
-        $comments = $this->env->comments;
971
-
972
-        if (count($comments)) {
973
-            $this->env->children = array_merge($this->env->children, $comments);
974
-            $this->env->comments = [];
975
-        }
976
-    }
977
-
978
-    /**
979
-     * Returns last child was appended
980
-     *
981
-     * @return array|null
982
-     */
983
-    protected function last()
984
-    {
985
-        $i = count($this->env->children) - 1;
986
-
987
-        if (isset($this->env->children[$i])) {
988
-            return $this->env->children[$i];
989
-        }
990
-    }
991
-
992
-    /**
993
-     * Parse media query list
994
-     *
995
-     * @param array $out
996
-     *
997
-     * @return boolean
998
-     */
999
-    protected function mediaQueryList(&$out)
1000
-    {
1001
-        return $this->genericList($out, 'mediaQuery', ',', false);
1002
-    }
1003
-
1004
-    /**
1005
-     * Parse media query
1006
-     *
1007
-     * @param array $out
1008
-     *
1009
-     * @return boolean
1010
-     */
1011
-    protected function mediaQuery(&$out)
1012
-    {
1013
-        $expressions = null;
1014
-        $parts = [];
1015
-
1016
-        if (($this->literal('only') && ($only = true) || $this->literal('not') && ($not = true) || true) &&
1017
-            $this->mixedKeyword($mediaType)
1018
-        ) {
1019
-            $prop = [Type::T_MEDIA_TYPE];
1020
-
1021
-            if (isset($only)) {
1022
-                $prop[] = [Type::T_KEYWORD, 'only'];
1023
-            }
1024
-
1025
-            if (isset($not)) {
1026
-                $prop[] = [Type::T_KEYWORD, 'not'];
1027
-            }
1028
-
1029
-            $media = [Type::T_LIST, '', []];
1030
-
1031
-            foreach ((array) $mediaType as $type) {
1032
-                if (is_array($type)) {
1033
-                    $media[2][] = $type;
1034
-                } else {
1035
-                    $media[2][] = [Type::T_KEYWORD, $type];
1036
-                }
1037
-            }
1038
-
1039
-            $prop[]  = $media;
1040
-            $parts[] = $prop;
1041
-        }
1042
-
1043
-        if (empty($parts) || $this->literal('and')) {
1044
-            $this->genericList($expressions, 'mediaExpression', 'and', false);
1045
-
1046
-            if (is_array($expressions)) {
1047
-                $parts = array_merge($parts, $expressions[2]);
1048
-            }
1049
-        }
1050
-
1051
-        $out = $parts;
1052
-
1053
-        return true;
1054
-    }
1055
-
1056
-    /**
1057
-     * Parse media expression
1058
-     *
1059
-     * @param array $out
1060
-     *
1061
-     * @return boolean
1062
-     */
1063
-    protected function mediaExpression(&$out)
1064
-    {
1065
-        $s = $this->seek();
1066
-        $value = null;
1067
-
1068
-        if ($this->literal('(') &&
1069
-            $this->expression($feature) &&
1070
-            ($this->literal(':') && $this->expression($value) || true) &&
1071
-            $this->literal(')')
1072
-        ) {
1073
-            $out = [Type::T_MEDIA_EXPRESSION, $feature];
1074
-
1075
-            if ($value) {
1076
-                $out[] = $value;
1077
-            }
1078
-
1079
-            return true;
1080
-        }
1081
-
1082
-        $this->seek($s);
1083
-
1084
-        return false;
1085
-    }
1086
-
1087
-    /**
1088
-     * Parse argument values
1089
-     *
1090
-     * @param array $out
1091
-     *
1092
-     * @return boolean
1093
-     */
1094
-    protected function argValues(&$out)
1095
-    {
1096
-        if ($this->genericList($list, 'argValue', ',', false)) {
1097
-            $out = $list[2];
1098
-
1099
-            return true;
1100
-        }
1101
-
1102
-        return false;
1103
-    }
1104
-
1105
-    /**
1106
-     * Parse argument value
1107
-     *
1108
-     * @param array $out
1109
-     *
1110
-     * @return boolean
1111
-     */
1112
-    protected function argValue(&$out)
1113
-    {
1114
-        $s = $this->seek();
1115
-
1116
-        $keyword = null;
1117
-
1118
-        if (! $this->variable($keyword) || ! $this->literal(':')) {
1119
-            $this->seek($s);
1120
-            $keyword = null;
1121
-        }
1122
-
1123
-        if ($this->genericList($value, 'expression')) {
1124
-            $out = [$keyword, $value, false];
1125
-            $s = $this->seek();
1126
-
1127
-            if ($this->literal('...')) {
1128
-                $out[2] = true;
1129
-            } else {
1130
-                $this->seek($s);
1131
-            }
1132
-
1133
-            return true;
1134
-        }
1135
-
1136
-        return false;
1137
-    }
1138
-
1139
-    /**
1140
-     * Parse comma separated value list
1141
-     *
1142
-     * @param string $out
1143
-     *
1144
-     * @return boolean
1145
-     */
1146
-    protected function valueList(&$out)
1147
-    {
1148
-        return $this->genericList($out, 'spaceList', ',');
1149
-    }
1150
-
1151
-    /**
1152
-     * Parse space separated value list
1153
-     *
1154
-     * @param array $out
1155
-     *
1156
-     * @return boolean
1157
-     */
1158
-    protected function spaceList(&$out)
1159
-    {
1160
-        return $this->genericList($out, 'expression');
1161
-    }
1162
-
1163
-    /**
1164
-     * Parse generic list
1165
-     *
1166
-     * @param array    $out
1167
-     * @param callable $parseItem
1168
-     * @param string   $delim
1169
-     * @param boolean  $flatten
1170
-     *
1171
-     * @return boolean
1172
-     */
1173
-    protected function genericList(&$out, $parseItem, $delim = '', $flatten = true)
1174
-    {
1175
-        $s = $this->seek();
1176
-        $items = [];
1177
-
1178
-        while ($this->$parseItem($value)) {
1179
-            $items[] = $value;
1180
-
1181
-            if ($delim) {
1182
-                if (! $this->literal($delim)) {
1183
-                    break;
1184
-                }
1185
-            }
1186
-        }
1187
-
1188
-        if (count($items) === 0) {
1189
-            $this->seek($s);
1190
-
1191
-            return false;
1192
-        }
1193
-
1194
-        if ($flatten && count($items) === 1) {
1195
-            $out = $items[0];
1196
-        } else {
1197
-            $out = [Type::T_LIST, $delim, $items];
1198
-        }
1199
-
1200
-        return true;
1201
-    }
1202
-
1203
-    /**
1204
-     * Parse expression
1205
-     *
1206
-     * @param array $out
1207
-     *
1208
-     * @return boolean
1209
-     */
1210
-    protected function expression(&$out)
1211
-    {
1212
-        $s = $this->seek();
1213
-
1214
-        if ($this->literal('(')) {
1215
-            if ($this->literal(')')) {
1216
-                $out = [Type::T_LIST, '', []];
1217
-
1218
-                return true;
1219
-            }
1220
-
1221
-            if ($this->valueList($out) && $this->literal(')') && $out[0] === Type::T_LIST) {
1222
-                return true;
1223
-            }
1224
-
1225
-            $this->seek($s);
1226
-
1227
-            if ($this->map($out)) {
1228
-                return true;
1229
-            }
1230
-
1231
-            $this->seek($s);
1232
-        }
1233
-
1234
-        if ($this->value($lhs)) {
1235
-            $out = $this->expHelper($lhs, 0);
1236
-
1237
-            return true;
1238
-        }
1239
-
1240
-        return false;
1241
-    }
1242
-
1243
-    /**
1244
-     * Parse left-hand side of subexpression
1245
-     *
1246
-     * @param array   $lhs
1247
-     * @param integer $minP
1248
-     *
1249
-     * @return array
1250
-     */
1251
-    protected function expHelper($lhs, $minP)
1252
-    {
1253
-        $operators = static::$operatorPattern;
1254
-
1255
-        $ss = $this->seek();
1256
-        $whiteBefore = isset($this->buffer[$this->count - 1]) &&
1257
-            ctype_space($this->buffer[$this->count - 1]);
1258
-
1259
-        while ($this->match($operators, $m, false) && static::$precedence[$m[1]] >= $minP) {
1260
-            $whiteAfter = isset($this->buffer[$this->count]) &&
1261
-                ctype_space($this->buffer[$this->count]);
1262
-            $varAfter = isset($this->buffer[$this->count]) &&
1263
-                $this->buffer[$this->count] === '$';
1264
-
1265
-            $this->whitespace();
1266
-
1267
-            $op = $m[1];
1268
-
1269
-            // don't turn negative numbers into expressions
1270
-            if ($op === '-' && $whiteBefore && ! $whiteAfter && ! $varAfter) {
1271
-                break;
1272
-            }
1273
-
1274
-            if (! $this->value($rhs)) {
1275
-                break;
1276
-            }
1277
-
1278
-            // peek and see if rhs belongs to next operator
1279
-            if ($this->peek($operators, $next) && static::$precedence[$next[1]] > static::$precedence[$op]) {
1280
-                $rhs = $this->expHelper($rhs, static::$precedence[$next[1]]);
1281
-            }
1282
-
1283
-            $lhs = [Type::T_EXPRESSION, $op, $lhs, $rhs, $this->inParens, $whiteBefore, $whiteAfter];
1284
-            $ss = $this->seek();
1285
-            $whiteBefore = isset($this->buffer[$this->count - 1]) &&
1286
-                ctype_space($this->buffer[$this->count - 1]);
1287
-        }
1288
-
1289
-        $this->seek($ss);
1290
-
1291
-        return $lhs;
1292
-    }
1293
-
1294
-    /**
1295
-     * Parse value
1296
-     *
1297
-     * @param array $out
1298
-     *
1299
-     * @return boolean
1300
-     */
1301
-    protected function value(&$out)
1302
-    {
1303
-        $s = $this->seek();
1304
-
1305
-        if ($this->literal('not', false) && $this->whitespace() && $this->value($inner)) {
1306
-            $out = [Type::T_UNARY, 'not', $inner, $this->inParens];
1307
-
1308
-            return true;
1309
-        }
1310
-
1311
-        $this->seek($s);
1312
-
1313
-        if ($this->literal('not', false) && $this->parenValue($inner)) {
1314
-            $out = [Type::T_UNARY, 'not', $inner, $this->inParens];
1315
-
1316
-            return true;
1317
-        }
1318
-
1319
-        $this->seek($s);
1320
-
1321
-        if ($this->literal('+') && $this->value($inner)) {
1322
-            $out = [Type::T_UNARY, '+', $inner, $this->inParens];
1323
-
1324
-            return true;
1325
-        }
1326
-
1327
-        $this->seek($s);
1328
-
1329
-        // negation
1330
-        if ($this->literal('-', false) &&
1331
-            ($this->variable($inner) ||
1332
-            $this->unit($inner) ||
1333
-            $this->parenValue($inner))
1334
-        ) {
1335
-            $out = [Type::T_UNARY, '-', $inner, $this->inParens];
1336
-
1337
-            return true;
1338
-        }
1339
-
1340
-        $this->seek($s);
1341
-
1342
-        if ($this->parenValue($out) ||
1343
-            $this->interpolation($out) ||
1344
-            $this->variable($out) ||
1345
-            $this->color($out) ||
1346
-            $this->unit($out) ||
1347
-            $this->string($out) ||
1348
-            $this->func($out) ||
1349
-            $this->progid($out)
1350
-        ) {
1351
-            return true;
1352
-        }
1353
-
1354
-        if ($this->keyword($keyword)) {
1355
-            if ($keyword === 'null') {
1356
-                $out = [Type::T_NULL];
1357
-            } else {
1358
-                $out = [Type::T_KEYWORD, $keyword];
1359
-            }
1360
-
1361
-            return true;
1362
-        }
1363
-
1364
-        return false;
1365
-    }
1366
-
1367
-    /**
1368
-     * Parse parenthesized value
1369
-     *
1370
-     * @param array $out
1371
-     *
1372
-     * @return boolean
1373
-     */
1374
-    protected function parenValue(&$out)
1375
-    {
1376
-        $s = $this->seek();
1377
-
1378
-        $inParens = $this->inParens;
1379
-
1380
-        if ($this->literal('(')) {
1381
-            if ($this->literal(')')) {
1382
-                $out = [Type::T_LIST, '', []];
1383
-
1384
-                return true;
1385
-            }
1386
-
1387
-            $this->inParens = true;
1388
-
1389
-            if ($this->expression($exp) && $this->literal(')')) {
1390
-                $out = $exp;
1391
-                $this->inParens = $inParens;
1392
-
1393
-                return true;
1394
-            }
1395
-        }
1396
-
1397
-        $this->inParens = $inParens;
1398
-        $this->seek($s);
1399
-
1400
-        return false;
1401
-    }
1402
-
1403
-    /**
1404
-     * Parse "progid:"
1405
-     *
1406
-     * @param array $out
1407
-     *
1408
-     * @return boolean
1409
-     */
1410
-    protected function progid(&$out)
1411
-    {
1412
-        $s = $this->seek();
1413
-
1414
-        if ($this->literal('progid:', false) &&
1415
-            $this->openString('(', $fn) &&
1416
-            $this->literal('(')
1417
-        ) {
1418
-            $this->openString(')', $args, '(');
1419
-
1420
-            if ($this->literal(')')) {
1421
-                $out = [Type::T_STRING, '', [
1422
-                    'progid:', $fn, '(', $args, ')'
1423
-                ]];
1424
-
1425
-                return true;
1426
-            }
1427
-        }
1428
-
1429
-        $this->seek($s);
1430
-
1431
-        return false;
1432
-    }
1433
-
1434
-    /**
1435
-     * Parse function call
1436
-     *
1437
-     * @param array $out
1438
-     *
1439
-     * @return boolean
1440
-     */
1441
-    protected function func(&$func)
1442
-    {
1443
-        $s = $this->seek();
1444
-
1445
-        if ($this->keyword($name, false) &&
1446
-            $this->literal('(')
1447
-        ) {
1448
-            if ($name === 'alpha' && $this->argumentList($args)) {
1449
-                $func = [Type::T_FUNCTION, $name, [Type::T_STRING, '', $args]];
1450
-
1451
-                return true;
1452
-            }
1453
-
1454
-            if ($name !== 'expression' && ! preg_match('/^(-[a-z]+-)?calc$/', $name)) {
1455
-                $ss = $this->seek();
1456
-
1457
-                if ($this->argValues($args) && $this->literal(')')) {
1458
-                    $func = [Type::T_FUNCTION_CALL, $name, $args];
1459
-
1460
-                    return true;
1461
-                }
1462
-
1463
-                $this->seek($ss);
1464
-            }
1465
-
1466
-            if (($this->openString(')', $str, '(') || true) &&
1467
-                $this->literal(')')
1468
-            ) {
1469
-                $args = [];
1470
-
1471
-                if (! empty($str)) {
1472
-                    $args[] = [null, [Type::T_STRING, '', [$str]]];
1473
-                }
1474
-
1475
-                $func = [Type::T_FUNCTION_CALL, $name, $args];
1476
-
1477
-                return true;
1478
-            }
1479
-        }
1480
-
1481
-        $this->seek($s);
578
+			// doesn't match built in directive, do generic one
579
+			if ($this->literal('@', false) &&
580
+				$this->keyword($dirName) &&
581
+				($this->variable($dirValue) || $this->openString('{', $dirValue) || true) &&
582
+				$this->literal('{')
583
+			) {
584
+				if ($dirName === 'media') {
585
+					$directive = $this->pushSpecialBlock(Type::T_MEDIA, $s);
586
+				} else {
587
+					$directive = $this->pushSpecialBlock(Type::T_DIRECTIVE, $s);
588
+					$directive->name = $dirName;
589
+				}
590
+
591
+				if (isset($dirValue)) {
592
+					$directive->value = $dirValue;
593
+				}
594
+
595
+				return true;
596
+			}
597
+
598
+			$this->seek($s);
599
+
600
+			return false;
601
+		}
602
+
603
+		// property shortcut
604
+		// captures most properties before having to parse a selector
605
+		if ($this->keyword($name, false) &&
606
+			$this->literal(': ') &&
607
+			$this->valueList($value) &&
608
+			$this->end()
609
+		) {
610
+			$name = [Type::T_STRING, '', [$name]];
611
+			$this->append([Type::T_ASSIGN, $name, $value], $s);
612
+
613
+			return true;
614
+		}
615
+
616
+		$this->seek($s);
617
+
618
+		// variable assigns
619
+		if ($this->variable($name) &&
620
+			$this->literal(':') &&
621
+			$this->valueList($value) &&
622
+			$this->end()
623
+		) {
624
+			// check for '!flag'
625
+			$assignmentFlags = $this->stripAssignmentFlags($value);
626
+			$this->append([Type::T_ASSIGN, $name, $value, $assignmentFlags], $s);
627
+
628
+			return true;
629
+		}
630
+
631
+		$this->seek($s);
632
+
633
+		// misc
634
+		if ($this->literal('-->')) {
635
+			return true;
636
+		}
637
+
638
+		// opening css block
639
+		if ($this->selectors($selectors) && $this->literal('{')) {
640
+			$this->pushBlock($selectors, $s);
641
+
642
+			return true;
643
+		}
644
+
645
+		$this->seek($s);
646
+
647
+		// property assign, or nested assign
648
+		if ($this->propertyName($name) && $this->literal(':')) {
649
+			$foundSomething = false;
650
+
651
+			if ($this->valueList($value)) {
652
+				$this->append([Type::T_ASSIGN, $name, $value], $s);
653
+				$foundSomething = true;
654
+			}
655
+
656
+			if ($this->literal('{')) {
657
+				$propBlock = $this->pushSpecialBlock(Type::T_NESTED_PROPERTY, $s);
658
+				$propBlock->prefix = $name;
659
+				$foundSomething = true;
660
+			} elseif ($foundSomething) {
661
+				$foundSomething = $this->end();
662
+			}
663
+
664
+			if ($foundSomething) {
665
+				return true;
666
+			}
667
+		}
668
+
669
+		$this->seek($s);
670
+
671
+		// closing a block
672
+		if ($this->literal('}')) {
673
+			$block = $this->popBlock();
674
+
675
+			if (isset($block->type) && $block->type === Type::T_INCLUDE) {
676
+				$include = $block->child;
677
+				unset($block->child);
678
+				$include[3] = $block;
679
+				$this->append($include, $s);
680
+			} elseif (empty($block->dontAppend)) {
681
+				$type = isset($block->type) ? $block->type : Type::T_BLOCK;
682
+				$this->append([$type, $block], $s);
683
+			}
684
+
685
+			return true;
686
+		}
687
+
688
+		// extra stuff
689
+		if ($this->literal(';') ||
690
+			$this->literal('<!--')
691
+		) {
692
+			return true;
693
+		}
694
+
695
+		return false;
696
+	}
697
+
698
+	/**
699
+	 * Push block onto parse tree
700
+	 *
701
+	 * @param array   $selectors
702
+	 * @param integer $pos
703
+	 *
704
+	 * @return \Leafo\ScssPhp\Block
705
+	 */
706
+	protected function pushBlock($selectors, $pos = 0)
707
+	{
708
+		list($line, $column) = $this->getSourcePosition($pos);
709
+
710
+		$b = new Block;
711
+		$b->sourceName   = $this->sourceName;
712
+		$b->sourceLine   = $line;
713
+		$b->sourceColumn = $column;
714
+		$b->sourceIndex  = $this->sourceIndex;
715
+		$b->selectors    = $selectors;
716
+		$b->comments     = [];
717
+		$b->parent       = $this->env;
718
+
719
+		if (! $this->env) {
720
+			$b->children = [];
721
+		} elseif (empty($this->env->children)) {
722
+			$this->env->children = $this->env->comments;
723
+			$b->children = [];
724
+			$this->env->comments = [];
725
+		} else {
726
+			$b->children = $this->env->comments;
727
+			$this->env->comments = [];
728
+		}
729
+
730
+		$this->env = $b;
731
+
732
+		return $b;
733
+	}
734
+
735
+	/**
736
+	 * Push special (named) block onto parse tree
737
+	 *
738
+	 * @param string  $type
739
+	 * @param integer $pos
740
+	 *
741
+	 * @return \Leafo\ScssPhp\Block
742
+	 */
743
+	protected function pushSpecialBlock($type, $pos)
744
+	{
745
+		$block = $this->pushBlock(null, $pos);
746
+		$block->type = $type;
747
+
748
+		return $block;
749
+	}
750
+
751
+	/**
752
+	 * Pop scope and return last block
753
+	 *
754
+	 * @return \Leafo\ScssPhp\Block
755
+	 *
756
+	 * @throws \Exception
757
+	 */
758
+	protected function popBlock()
759
+	{
760
+		$block = $this->env;
761
+
762
+		if (empty($block->parent)) {
763
+			$this->throwParseError('unexpected }');
764
+		}
765
+
766
+		$this->env = $block->parent;
767
+		unset($block->parent);
768
+
769
+		$comments = $block->comments;
770
+		if (count($comments)) {
771
+			$this->env->comments = $comments;
772
+			unset($block->comments);
773
+		}
774
+
775
+		return $block;
776
+	}
777
+
778
+	/**
779
+	 * Peek input stream
780
+	 *
781
+	 * @param string  $regex
782
+	 * @param array   $out
783
+	 * @param integer $from
784
+	 *
785
+	 * @return integer
786
+	 */
787
+	protected function peek($regex, &$out, $from = null)
788
+	{
789
+		if (! isset($from)) {
790
+			$from = $this->count;
791
+		}
792
+
793
+		$r = '/' . $regex . '/' . $this->patternModifiers;
794
+		$result = preg_match($r, $this->buffer, $out, null, $from);
795
+
796
+		return $result;
797
+	}
798
+
799
+	/**
800
+	 * Seek to position in input stream (or return current position in input stream)
801
+	 *
802
+	 * @param integer $where
803
+	 *
804
+	 * @return integer
805
+	 */
806
+	protected function seek($where = null)
807
+	{
808
+		if ($where === null) {
809
+			return $this->count;
810
+		}
811
+
812
+		$this->count = $where;
813
+
814
+		return true;
815
+	}
816
+
817
+	/**
818
+	 * Match string looking for either ending delim, escape, or string interpolation
819
+	 *
820
+	 * {@internal This is a workaround for preg_match's 250K string match limit. }}
821
+	 *
822
+	 * @param array  $m     Matches (passed by reference)
823
+	 * @param string $delim Delimeter
824
+	 *
825
+	 * @return boolean True if match; false otherwise
826
+	 */
827
+	protected function matchString(&$m, $delim)
828
+	{
829
+		$token = null;
830
+
831
+		$end = strlen($this->buffer);
832
+
833
+		// look for either ending delim, escape, or string interpolation
834
+		foreach (['#{', '\\', $delim] as $lookahead) {
835
+			$pos = strpos($this->buffer, $lookahead, $this->count);
836
+
837
+			if ($pos !== false && $pos < $end) {
838
+				$end = $pos;
839
+				$token = $lookahead;
840
+			}
841
+		}
842
+
843
+		if (! isset($token)) {
844
+			return false;
845
+		}
846
+
847
+		$match = substr($this->buffer, $this->count, $end - $this->count);
848
+		$m = [
849
+			$match . $token,
850
+			$match,
851
+			$token
852
+		];
853
+		$this->count = $end + strlen($token);
854
+
855
+		return true;
856
+	}
857
+
858
+	/**
859
+	 * Try to match something on head of buffer
860
+	 *
861
+	 * @param string  $regex
862
+	 * @param array   $out
863
+	 * @param boolean $eatWhitespace
864
+	 *
865
+	 * @return boolean
866
+	 */
867
+	protected function match($regex, &$out, $eatWhitespace = null)
868
+	{
869
+		if (! isset($eatWhitespace)) {
870
+			$eatWhitespace = $this->eatWhiteDefault;
871
+		}
872
+
873
+		$r = '/' . $regex . '/' . $this->patternModifiers;
874
+
875
+		if (preg_match($r, $this->buffer, $out, null, $this->count)) {
876
+			$this->count += strlen($out[0]);
877
+
878
+			if ($eatWhitespace) {
879
+				$this->whitespace();
880
+			}
881
+
882
+			return true;
883
+		}
884
+
885
+		return false;
886
+	}
887
+
888
+	/**
889
+	 * Match literal string
890
+	 *
891
+	 * @param string  $what
892
+	 * @param boolean $eatWhitespace
893
+	 *
894
+	 * @return boolean
895
+	 */
896
+	protected function literal($what, $eatWhitespace = null)
897
+	{
898
+		if (! isset($eatWhitespace)) {
899
+			$eatWhitespace = $this->eatWhiteDefault;
900
+		}
901
+
902
+		$len = strlen($what);
903
+
904
+		if (strcasecmp(substr($this->buffer, $this->count, $len), $what) === 0) {
905
+			$this->count += $len;
906
+
907
+			if ($eatWhitespace) {
908
+				$this->whitespace();
909
+			}
910
+
911
+			return true;
912
+		}
913
+
914
+		return false;
915
+	}
916
+
917
+	/**
918
+	 * Match some whitespace
919
+	 *
920
+	 * @return boolean
921
+	 */
922
+	protected function whitespace()
923
+	{
924
+		$gotWhite = false;
925
+
926
+		while (preg_match(static::$whitePattern, $this->buffer, $m, null, $this->count)) {
927
+			if (isset($m[1]) && empty($this->commentsSeen[$this->count])) {
928
+				$this->appendComment([Type::T_COMMENT, $m[1]]);
929
+
930
+				$this->commentsSeen[$this->count] = true;
931
+			}
932
+
933
+			$this->count += strlen($m[0]);
934
+			$gotWhite = true;
935
+		}
936
+
937
+		return $gotWhite;
938
+	}
939
+
940
+	/**
941
+	 * Append comment to current block
942
+	 *
943
+	 * @param array $comment
944
+	 */
945
+	protected function appendComment($comment)
946
+	{
947
+		$comment[1] = substr(preg_replace(['/^\s+/m', '/^(.)/m'], ['', ' \1'], $comment[1]), 1);
948
+
949
+		$this->env->comments[] = $comment;
950
+	}
951
+
952
+	/**
953
+	 * Append statement to current block
954
+	 *
955
+	 * @param array   $statement
956
+	 * @param integer $pos
957
+	 */
958
+	protected function append($statement, $pos = null)
959
+	{
960
+		if ($pos !== null) {
961
+			list($line, $column) = $this->getSourcePosition($pos);
962
+
963
+			$statement[static::SOURCE_LINE]   = $line;
964
+			$statement[static::SOURCE_COLUMN] = $column;
965
+			$statement[static::SOURCE_INDEX]  = $this->sourceIndex;
966
+		}
967
+
968
+		$this->env->children[] = $statement;
969
+
970
+		$comments = $this->env->comments;
971
+
972
+		if (count($comments)) {
973
+			$this->env->children = array_merge($this->env->children, $comments);
974
+			$this->env->comments = [];
975
+		}
976
+	}
977
+
978
+	/**
979
+	 * Returns last child was appended
980
+	 *
981
+	 * @return array|null
982
+	 */
983
+	protected function last()
984
+	{
985
+		$i = count($this->env->children) - 1;
986
+
987
+		if (isset($this->env->children[$i])) {
988
+			return $this->env->children[$i];
989
+		}
990
+	}
991
+
992
+	/**
993
+	 * Parse media query list
994
+	 *
995
+	 * @param array $out
996
+	 *
997
+	 * @return boolean
998
+	 */
999
+	protected function mediaQueryList(&$out)
1000
+	{
1001
+		return $this->genericList($out, 'mediaQuery', ',', false);
1002
+	}
1003
+
1004
+	/**
1005
+	 * Parse media query
1006
+	 *
1007
+	 * @param array $out
1008
+	 *
1009
+	 * @return boolean
1010
+	 */
1011
+	protected function mediaQuery(&$out)
1012
+	{
1013
+		$expressions = null;
1014
+		$parts = [];
1015
+
1016
+		if (($this->literal('only') && ($only = true) || $this->literal('not') && ($not = true) || true) &&
1017
+			$this->mixedKeyword($mediaType)
1018
+		) {
1019
+			$prop = [Type::T_MEDIA_TYPE];
1020
+
1021
+			if (isset($only)) {
1022
+				$prop[] = [Type::T_KEYWORD, 'only'];
1023
+			}
1024
+
1025
+			if (isset($not)) {
1026
+				$prop[] = [Type::T_KEYWORD, 'not'];
1027
+			}
1028
+
1029
+			$media = [Type::T_LIST, '', []];
1030
+
1031
+			foreach ((array) $mediaType as $type) {
1032
+				if (is_array($type)) {
1033
+					$media[2][] = $type;
1034
+				} else {
1035
+					$media[2][] = [Type::T_KEYWORD, $type];
1036
+				}
1037
+			}
1038
+
1039
+			$prop[]  = $media;
1040
+			$parts[] = $prop;
1041
+		}
1042
+
1043
+		if (empty($parts) || $this->literal('and')) {
1044
+			$this->genericList($expressions, 'mediaExpression', 'and', false);
1045
+
1046
+			if (is_array($expressions)) {
1047
+				$parts = array_merge($parts, $expressions[2]);
1048
+			}
1049
+		}
1050
+
1051
+		$out = $parts;
1052
+
1053
+		return true;
1054
+	}
1055
+
1056
+	/**
1057
+	 * Parse media expression
1058
+	 *
1059
+	 * @param array $out
1060
+	 *
1061
+	 * @return boolean
1062
+	 */
1063
+	protected function mediaExpression(&$out)
1064
+	{
1065
+		$s = $this->seek();
1066
+		$value = null;
1067
+
1068
+		if ($this->literal('(') &&
1069
+			$this->expression($feature) &&
1070
+			($this->literal(':') && $this->expression($value) || true) &&
1071
+			$this->literal(')')
1072
+		) {
1073
+			$out = [Type::T_MEDIA_EXPRESSION, $feature];
1074
+
1075
+			if ($value) {
1076
+				$out[] = $value;
1077
+			}
1078
+
1079
+			return true;
1080
+		}
1081
+
1082
+		$this->seek($s);
1083
+
1084
+		return false;
1085
+	}
1086
+
1087
+	/**
1088
+	 * Parse argument values
1089
+	 *
1090
+	 * @param array $out
1091
+	 *
1092
+	 * @return boolean
1093
+	 */
1094
+	protected function argValues(&$out)
1095
+	{
1096
+		if ($this->genericList($list, 'argValue', ',', false)) {
1097
+			$out = $list[2];
1098
+
1099
+			return true;
1100
+		}
1101
+
1102
+		return false;
1103
+	}
1104
+
1105
+	/**
1106
+	 * Parse argument value
1107
+	 *
1108
+	 * @param array $out
1109
+	 *
1110
+	 * @return boolean
1111
+	 */
1112
+	protected function argValue(&$out)
1113
+	{
1114
+		$s = $this->seek();
1115
+
1116
+		$keyword = null;
1117
+
1118
+		if (! $this->variable($keyword) || ! $this->literal(':')) {
1119
+			$this->seek($s);
1120
+			$keyword = null;
1121
+		}
1122
+
1123
+		if ($this->genericList($value, 'expression')) {
1124
+			$out = [$keyword, $value, false];
1125
+			$s = $this->seek();
1126
+
1127
+			if ($this->literal('...')) {
1128
+				$out[2] = true;
1129
+			} else {
1130
+				$this->seek($s);
1131
+			}
1132
+
1133
+			return true;
1134
+		}
1135
+
1136
+		return false;
1137
+	}
1138
+
1139
+	/**
1140
+	 * Parse comma separated value list
1141
+	 *
1142
+	 * @param string $out
1143
+	 *
1144
+	 * @return boolean
1145
+	 */
1146
+	protected function valueList(&$out)
1147
+	{
1148
+		return $this->genericList($out, 'spaceList', ',');
1149
+	}
1150
+
1151
+	/**
1152
+	 * Parse space separated value list
1153
+	 *
1154
+	 * @param array $out
1155
+	 *
1156
+	 * @return boolean
1157
+	 */
1158
+	protected function spaceList(&$out)
1159
+	{
1160
+		return $this->genericList($out, 'expression');
1161
+	}
1162
+
1163
+	/**
1164
+	 * Parse generic list
1165
+	 *
1166
+	 * @param array    $out
1167
+	 * @param callable $parseItem
1168
+	 * @param string   $delim
1169
+	 * @param boolean  $flatten
1170
+	 *
1171
+	 * @return boolean
1172
+	 */
1173
+	protected function genericList(&$out, $parseItem, $delim = '', $flatten = true)
1174
+	{
1175
+		$s = $this->seek();
1176
+		$items = [];
1177
+
1178
+		while ($this->$parseItem($value)) {
1179
+			$items[] = $value;
1180
+
1181
+			if ($delim) {
1182
+				if (! $this->literal($delim)) {
1183
+					break;
1184
+				}
1185
+			}
1186
+		}
1187
+
1188
+		if (count($items) === 0) {
1189
+			$this->seek($s);
1190
+
1191
+			return false;
1192
+		}
1193
+
1194
+		if ($flatten && count($items) === 1) {
1195
+			$out = $items[0];
1196
+		} else {
1197
+			$out = [Type::T_LIST, $delim, $items];
1198
+		}
1199
+
1200
+		return true;
1201
+	}
1202
+
1203
+	/**
1204
+	 * Parse expression
1205
+	 *
1206
+	 * @param array $out
1207
+	 *
1208
+	 * @return boolean
1209
+	 */
1210
+	protected function expression(&$out)
1211
+	{
1212
+		$s = $this->seek();
1213
+
1214
+		if ($this->literal('(')) {
1215
+			if ($this->literal(')')) {
1216
+				$out = [Type::T_LIST, '', []];
1217
+
1218
+				return true;
1219
+			}
1220
+
1221
+			if ($this->valueList($out) && $this->literal(')') && $out[0] === Type::T_LIST) {
1222
+				return true;
1223
+			}
1224
+
1225
+			$this->seek($s);
1226
+
1227
+			if ($this->map($out)) {
1228
+				return true;
1229
+			}
1230
+
1231
+			$this->seek($s);
1232
+		}
1233
+
1234
+		if ($this->value($lhs)) {
1235
+			$out = $this->expHelper($lhs, 0);
1236
+
1237
+			return true;
1238
+		}
1239
+
1240
+		return false;
1241
+	}
1242
+
1243
+	/**
1244
+	 * Parse left-hand side of subexpression
1245
+	 *
1246
+	 * @param array   $lhs
1247
+	 * @param integer $minP
1248
+	 *
1249
+	 * @return array
1250
+	 */
1251
+	protected function expHelper($lhs, $minP)
1252
+	{
1253
+		$operators = static::$operatorPattern;
1254
+
1255
+		$ss = $this->seek();
1256
+		$whiteBefore = isset($this->buffer[$this->count - 1]) &&
1257
+			ctype_space($this->buffer[$this->count - 1]);
1258
+
1259
+		while ($this->match($operators, $m, false) && static::$precedence[$m[1]] >= $minP) {
1260
+			$whiteAfter = isset($this->buffer[$this->count]) &&
1261
+				ctype_space($this->buffer[$this->count]);
1262
+			$varAfter = isset($this->buffer[$this->count]) &&
1263
+				$this->buffer[$this->count] === '$';
1264
+
1265
+			$this->whitespace();
1266
+
1267
+			$op = $m[1];
1268
+
1269
+			// don't turn negative numbers into expressions
1270
+			if ($op === '-' && $whiteBefore && ! $whiteAfter && ! $varAfter) {
1271
+				break;
1272
+			}
1273
+
1274
+			if (! $this->value($rhs)) {
1275
+				break;
1276
+			}
1277
+
1278
+			// peek and see if rhs belongs to next operator
1279
+			if ($this->peek($operators, $next) && static::$precedence[$next[1]] > static::$precedence[$op]) {
1280
+				$rhs = $this->expHelper($rhs, static::$precedence[$next[1]]);
1281
+			}
1282
+
1283
+			$lhs = [Type::T_EXPRESSION, $op, $lhs, $rhs, $this->inParens, $whiteBefore, $whiteAfter];
1284
+			$ss = $this->seek();
1285
+			$whiteBefore = isset($this->buffer[$this->count - 1]) &&
1286
+				ctype_space($this->buffer[$this->count - 1]);
1287
+		}
1288
+
1289
+		$this->seek($ss);
1290
+
1291
+		return $lhs;
1292
+	}
1293
+
1294
+	/**
1295
+	 * Parse value
1296
+	 *
1297
+	 * @param array $out
1298
+	 *
1299
+	 * @return boolean
1300
+	 */
1301
+	protected function value(&$out)
1302
+	{
1303
+		$s = $this->seek();
1304
+
1305
+		if ($this->literal('not', false) && $this->whitespace() && $this->value($inner)) {
1306
+			$out = [Type::T_UNARY, 'not', $inner, $this->inParens];
1307
+
1308
+			return true;
1309
+		}
1310
+
1311
+		$this->seek($s);
1312
+
1313
+		if ($this->literal('not', false) && $this->parenValue($inner)) {
1314
+			$out = [Type::T_UNARY, 'not', $inner, $this->inParens];
1315
+
1316
+			return true;
1317
+		}
1318
+
1319
+		$this->seek($s);
1320
+
1321
+		if ($this->literal('+') && $this->value($inner)) {
1322
+			$out = [Type::T_UNARY, '+', $inner, $this->inParens];
1323
+
1324
+			return true;
1325
+		}
1326
+
1327
+		$this->seek($s);
1328
+
1329
+		// negation
1330
+		if ($this->literal('-', false) &&
1331
+			($this->variable($inner) ||
1332
+			$this->unit($inner) ||
1333
+			$this->parenValue($inner))
1334
+		) {
1335
+			$out = [Type::T_UNARY, '-', $inner, $this->inParens];
1336
+
1337
+			return true;
1338
+		}
1339
+
1340
+		$this->seek($s);
1341
+
1342
+		if ($this->parenValue($out) ||
1343
+			$this->interpolation($out) ||
1344
+			$this->variable($out) ||
1345
+			$this->color($out) ||
1346
+			$this->unit($out) ||
1347
+			$this->string($out) ||
1348
+			$this->func($out) ||
1349
+			$this->progid($out)
1350
+		) {
1351
+			return true;
1352
+		}
1353
+
1354
+		if ($this->keyword($keyword)) {
1355
+			if ($keyword === 'null') {
1356
+				$out = [Type::T_NULL];
1357
+			} else {
1358
+				$out = [Type::T_KEYWORD, $keyword];
1359
+			}
1360
+
1361
+			return true;
1362
+		}
1363
+
1364
+		return false;
1365
+	}
1366
+
1367
+	/**
1368
+	 * Parse parenthesized value
1369
+	 *
1370
+	 * @param array $out
1371
+	 *
1372
+	 * @return boolean
1373
+	 */
1374
+	protected function parenValue(&$out)
1375
+	{
1376
+		$s = $this->seek();
1377
+
1378
+		$inParens = $this->inParens;
1379
+
1380
+		if ($this->literal('(')) {
1381
+			if ($this->literal(')')) {
1382
+				$out = [Type::T_LIST, '', []];
1383
+
1384
+				return true;
1385
+			}
1386
+
1387
+			$this->inParens = true;
1388
+
1389
+			if ($this->expression($exp) && $this->literal(')')) {
1390
+				$out = $exp;
1391
+				$this->inParens = $inParens;
1392
+
1393
+				return true;
1394
+			}
1395
+		}
1396
+
1397
+		$this->inParens = $inParens;
1398
+		$this->seek($s);
1399
+
1400
+		return false;
1401
+	}
1402
+
1403
+	/**
1404
+	 * Parse "progid:"
1405
+	 *
1406
+	 * @param array $out
1407
+	 *
1408
+	 * @return boolean
1409
+	 */
1410
+	protected function progid(&$out)
1411
+	{
1412
+		$s = $this->seek();
1413
+
1414
+		if ($this->literal('progid:', false) &&
1415
+			$this->openString('(', $fn) &&
1416
+			$this->literal('(')
1417
+		) {
1418
+			$this->openString(')', $args, '(');
1419
+
1420
+			if ($this->literal(')')) {
1421
+				$out = [Type::T_STRING, '', [
1422
+					'progid:', $fn, '(', $args, ')'
1423
+				]];
1424
+
1425
+				return true;
1426
+			}
1427
+		}
1428
+
1429
+		$this->seek($s);
1430
+
1431
+		return false;
1432
+	}
1433
+
1434
+	/**
1435
+	 * Parse function call
1436
+	 *
1437
+	 * @param array $out
1438
+	 *
1439
+	 * @return boolean
1440
+	 */
1441
+	protected function func(&$func)
1442
+	{
1443
+		$s = $this->seek();
1444
+
1445
+		if ($this->keyword($name, false) &&
1446
+			$this->literal('(')
1447
+		) {
1448
+			if ($name === 'alpha' && $this->argumentList($args)) {
1449
+				$func = [Type::T_FUNCTION, $name, [Type::T_STRING, '', $args]];
1450
+
1451
+				return true;
1452
+			}
1453
+
1454
+			if ($name !== 'expression' && ! preg_match('/^(-[a-z]+-)?calc$/', $name)) {
1455
+				$ss = $this->seek();
1456
+
1457
+				if ($this->argValues($args) && $this->literal(')')) {
1458
+					$func = [Type::T_FUNCTION_CALL, $name, $args];
1459
+
1460
+					return true;
1461
+				}
1462
+
1463
+				$this->seek($ss);
1464
+			}
1465
+
1466
+			if (($this->openString(')', $str, '(') || true) &&
1467
+				$this->literal(')')
1468
+			) {
1469
+				$args = [];
1470
+
1471
+				if (! empty($str)) {
1472
+					$args[] = [null, [Type::T_STRING, '', [$str]]];
1473
+				}
1474
+
1475
+				$func = [Type::T_FUNCTION_CALL, $name, $args];
1476
+
1477
+				return true;
1478
+			}
1479
+		}
1480
+
1481
+		$this->seek($s);
1482 1482
 
1483
-        return false;
1484
-    }
1483
+		return false;
1484
+	}
1485 1485
 
1486
-    /**
1487
-     * Parse function call argument list
1488
-     *
1489
-     * @param array $out
1490
-     *
1491
-     * @return boolean
1492
-     */
1493
-    protected function argumentList(&$out)
1494
-    {
1495
-        $s = $this->seek();
1496
-        $this->literal('(');
1497
-
1498
-        $args = [];
1499
-
1500
-        while ($this->keyword($var)) {
1501
-            if ($this->literal('=') && $this->expression($exp)) {
1502
-                $args[] = [Type::T_STRING, '', [$var . '=']];
1503
-                $arg = $exp;
1504
-            } else {
1505
-                break;
1506
-            }
1507
-
1508
-            $args[] = $arg;
1509
-
1510
-            if (! $this->literal(',')) {
1511
-                break;
1512
-            }
1513
-
1514
-            $args[] = [Type::T_STRING, '', [', ']];
1515
-        }
1516
-
1517
-        if (! $this->literal(')') || ! count($args)) {
1518
-            $this->seek($s);
1519
-
1520
-            return false;
1521
-        }
1486
+	/**
1487
+	 * Parse function call argument list
1488
+	 *
1489
+	 * @param array $out
1490
+	 *
1491
+	 * @return boolean
1492
+	 */
1493
+	protected function argumentList(&$out)
1494
+	{
1495
+		$s = $this->seek();
1496
+		$this->literal('(');
1497
+
1498
+		$args = [];
1499
+
1500
+		while ($this->keyword($var)) {
1501
+			if ($this->literal('=') && $this->expression($exp)) {
1502
+				$args[] = [Type::T_STRING, '', [$var . '=']];
1503
+				$arg = $exp;
1504
+			} else {
1505
+				break;
1506
+			}
1507
+
1508
+			$args[] = $arg;
1509
+
1510
+			if (! $this->literal(',')) {
1511
+				break;
1512
+			}
1513
+
1514
+			$args[] = [Type::T_STRING, '', [', ']];
1515
+		}
1516
+
1517
+		if (! $this->literal(')') || ! count($args)) {
1518
+			$this->seek($s);
1519
+
1520
+			return false;
1521
+		}
1522 1522
 
1523
-        $out = $args;
1523
+		$out = $args;
1524 1524
 
1525
-        return true;
1526
-    }
1525
+		return true;
1526
+	}
1527 1527
 
1528
-    /**
1529
-     * Parse mixin/function definition  argument list
1530
-     *
1531
-     * @param array $out
1532
-     *
1533
-     * @return boolean
1534
-     */
1535
-    protected function argumentDef(&$out)
1536
-    {
1537
-        $s = $this->seek();
1538
-        $this->literal('(');
1539
-
1540
-        $args = [];
1541
-
1542
-        while ($this->variable($var)) {
1543
-            $arg = [$var[1], null, false];
1544
-
1545
-            $ss = $this->seek();
1546
-
1547
-            if ($this->literal(':') && $this->genericList($defaultVal, 'expression')) {
1548
-                $arg[1] = $defaultVal;
1549
-            } else {
1550
-                $this->seek($ss);
1551
-            }
1552
-
1553
-            $ss = $this->seek();
1554
-
1555
-            if ($this->literal('...')) {
1556
-                $sss = $this->seek();
1557
-
1558
-                if (! $this->literal(')')) {
1559
-                    $this->throwParseError('... has to be after the final argument');
1560
-                }
1561
-
1562
-                $arg[2] = true;
1563
-                $this->seek($sss);
1564
-            } else {
1565
-                $this->seek($ss);
1566
-            }
1567
-
1568
-            $args[] = $arg;
1569
-
1570
-            if (! $this->literal(',')) {
1571
-                break;
1572
-            }
1573
-        }
1574
-
1575
-        if (! $this->literal(')')) {
1576
-            $this->seek($s);
1577
-
1578
-            return false;
1579
-        }
1580
-
1581
-        $out = $args;
1582
-
1583
-        return true;
1584
-    }
1585
-
1586
-    /**
1587
-     * Parse map
1588
-     *
1589
-     * @param array $out
1590
-     *
1591
-     * @return boolean
1592
-     */
1593
-    protected function map(&$out)
1594
-    {
1595
-        $s = $this->seek();
1596
-
1597
-        if (! $this->literal('(')) {
1598
-            return false;
1599
-        }
1600
-
1601
-        $keys = [];
1602
-        $values = [];
1603
-
1604
-        while ($this->genericList($key, 'expression') && $this->literal(':') &&
1605
-            $this->genericList($value, 'expression')
1606
-        ) {
1607
-            $keys[] = $key;
1608
-            $values[] = $value;
1609
-
1610
-            if (! $this->literal(',')) {
1611
-                break;
1612
-            }
1613
-        }
1614
-
1615
-        if (! count($keys) || ! $this->literal(')')) {
1616
-            $this->seek($s);
1617
-
1618
-            return false;
1619
-        }
1620
-
1621
-        $out = [Type::T_MAP, $keys, $values];
1622
-
1623
-        return true;
1624
-    }
1625
-
1626
-    /**
1627
-     * Parse color
1628
-     *
1629
-     * @param array $out
1630
-     *
1631
-     * @return boolean
1632
-     */
1633
-    protected function color(&$out)
1634
-    {
1635
-        $color = [Type::T_COLOR];
1636
-
1637
-        if ($this->match('(#([0-9a-f]{6})|#([0-9a-f]{3}))', $m)) {
1638
-            if (isset($m[3])) {
1639
-                $num = hexdec($m[3]);
1640
-
1641
-                foreach ([3, 2, 1] as $i) {
1642
-                    $t = $num & 0xf;
1643
-                    $color[$i] = $t << 4 | $t;
1644
-                    $num >>= 4;
1645
-                }
1646
-            } else {
1647
-                $num = hexdec($m[2]);
1648
-
1649
-                foreach ([3, 2, 1] as $i) {
1650
-                    $color[$i] = $num & 0xff;
1651
-                    $num >>= 8;
1652
-                }
1653
-            }
1654
-
1655
-            $out = $color;
1656
-
1657
-            return true;
1658
-        }
1659
-
1660
-        return false;
1661
-    }
1662
-
1663
-    /**
1664
-     * Parse number with unit
1665
-     *
1666
-     * @param array $out
1667
-     *
1668
-     * @return boolean
1669
-     */
1670
-    protected function unit(&$unit)
1671
-    {
1672
-        if ($this->match('([0-9]*(\.)?[0-9]+)([%a-zA-Z]+)?', $m)) {
1673
-            $unit = new Node\Number($m[1], empty($m[3]) ? '' : $m[3]);
1674
-
1675
-            return true;
1676
-        }
1677
-
1678
-        return false;
1679
-    }
1680
-
1681
-    /**
1682
-     * Parse string
1683
-     *
1684
-     * @param array $out
1685
-     *
1686
-     * @return boolean
1687
-     */
1688
-    protected function string(&$out)
1689
-    {
1690
-        $s = $this->seek();
1691
-
1692
-        if ($this->literal('"', false)) {
1693
-            $delim = '"';
1694
-        } elseif ($this->literal("'", false)) {
1695
-            $delim = "'";
1696
-        } else {
1697
-            return false;
1698
-        }
1699
-
1700
-        $content = [];
1701
-        $oldWhite = $this->eatWhiteDefault;
1702
-        $this->eatWhiteDefault = false;
1703
-        $hasInterpolation = false;
1704
-
1705
-        while ($this->matchString($m, $delim)) {
1706
-            if ($m[1] !== '') {
1707
-                $content[] = $m[1];
1708
-            }
1709
-
1710
-            if ($m[2] === '#{') {
1711
-                $this->count -= strlen($m[2]);
1712
-
1713
-                if ($this->interpolation($inter, false)) {
1714
-                    $content[] = $inter;
1715
-                    $hasInterpolation = true;
1716
-                } else {
1717
-                    $this->count += strlen($m[2]);
1718
-                    $content[] = '#{'; // ignore it
1719
-                }
1720
-            } elseif ($m[2] === '\\') {
1721
-                if ($this->literal('"', false)) {
1722
-                    $content[] = $m[2] . '"';
1723
-                } elseif ($this->literal("'", false)) {
1724
-                    $content[] = $m[2] . "'";
1725
-                } else {
1726
-                    $content[] = $m[2];
1727
-                }
1728
-            } else {
1729
-                $this->count -= strlen($delim);
1730
-                break; // delim
1731
-            }
1732
-        }
1733
-
1734
-        $this->eatWhiteDefault = $oldWhite;
1735
-
1736
-        if ($this->literal($delim)) {
1737
-            if ($hasInterpolation) {
1738
-                $delim = '"';
1739
-
1740
-                foreach ($content as &$string) {
1741
-                    if ($string === "\\'") {
1742
-                        $string = "'";
1743
-                    } elseif ($string === '\\"') {
1744
-                        $string = '"';
1745
-                    }
1746
-                }
1747
-            }
1748
-
1749
-            $out = [Type::T_STRING, $delim, $content];
1750
-
1751
-            return true;
1752
-        }
1753
-
1754
-        $this->seek($s);
1755
-
1756
-        return false;
1757
-    }
1758
-
1759
-    /**
1760
-     * Parse keyword or interpolation
1761
-     *
1762
-     * @param array $out
1763
-     *
1764
-     * @return boolean
1765
-     */
1766
-    protected function mixedKeyword(&$out)
1767
-    {
1768
-        $parts = [];
1769
-
1770
-        $oldWhite = $this->eatWhiteDefault;
1771
-        $this->eatWhiteDefault = false;
1772
-
1773
-        for (;;) {
1774
-            if ($this->keyword($key)) {
1775
-                $parts[] = $key;
1776
-                continue;
1777
-            }
1778
-
1779
-            if ($this->interpolation($inter)) {
1780
-                $parts[] = $inter;
1781
-                continue;
1782
-            }
1783
-
1784
-            break;
1785
-        }
1786
-
1787
-        $this->eatWhiteDefault = $oldWhite;
1788
-
1789
-        if (count($parts) === 0) {
1790
-            return false;
1791
-        }
1792
-
1793
-        if ($this->eatWhiteDefault) {
1794
-            $this->whitespace();
1795
-        }
1796
-
1797
-        $out = $parts;
1798
-
1799
-        return true;
1800
-    }
1801
-
1802
-    /**
1803
-     * Parse an unbounded string stopped by $end
1804
-     *
1805
-     * @param string $end
1806
-     * @param array  $out
1807
-     * @param string $nestingOpen
1808
-     *
1809
-     * @return boolean
1810
-     */
1811
-    protected function openString($end, &$out, $nestingOpen = null)
1812
-    {
1813
-        $oldWhite = $this->eatWhiteDefault;
1814
-        $this->eatWhiteDefault = false;
1815
-
1816
-        $patt = '(.*?)([\'"]|#\{|' . $this->pregQuote($end) . '|' . static::$commentPattern . ')';
1817
-
1818
-        $nestingLevel = 0;
1819
-
1820
-        $content = [];
1821
-
1822
-        while ($this->match($patt, $m, false)) {
1823
-            if (isset($m[1]) && $m[1] !== '') {
1824
-                $content[] = $m[1];
1825
-
1826
-                if ($nestingOpen) {
1827
-                    $nestingLevel += substr_count($m[1], $nestingOpen);
1828
-                }
1829
-            }
1830
-
1831
-            $tok = $m[2];
1832
-
1833
-            $this->count-= strlen($tok);
1834
-
1835
-            if ($tok === $end && ! $nestingLevel--) {
1836
-                break;
1837
-            }
1838
-
1839
-            if (($tok === "'" || $tok === '"') && $this->string($str)) {
1840
-                $content[] = $str;
1841
-                continue;
1842
-            }
1843
-
1844
-            if ($tok === '#{' && $this->interpolation($inter)) {
1845
-                $content[] = $inter;
1846
-                continue;
1847
-            }
1848
-
1849
-            $content[] = $tok;
1850
-            $this->count+= strlen($tok);
1851
-        }
1852
-
1853
-        $this->eatWhiteDefault = $oldWhite;
1854
-
1855
-        if (count($content) === 0) {
1856
-            return false;
1857
-        }
1858
-
1859
-        // trim the end
1860
-        if (is_string(end($content))) {
1861
-            $content[count($content) - 1] = rtrim(end($content));
1862
-        }
1863
-
1864
-        $out = [Type::T_STRING, '', $content];
1865
-
1866
-        return true;
1867
-    }
1868
-
1869
-    /**
1870
-     * Parser interpolation
1871
-     *
1872
-     * @param array   $out
1873
-     * @param boolean $lookWhite save information about whitespace before and after
1874
-     *
1875
-     * @return boolean
1876
-     */
1877
-    protected function interpolation(&$out, $lookWhite = true)
1878
-    {
1879
-        $oldWhite = $this->eatWhiteDefault;
1880
-        $this->eatWhiteDefault = true;
1881
-
1882
-        $s = $this->seek();
1883
-
1884
-        if ($this->literal('#{') && $this->valueList($value) && $this->literal('}', false)) {
1885
-            if ($lookWhite) {
1886
-                $left = preg_match('/\s/', $this->buffer[$s - 1]) ? ' ' : '';
1887
-                $right = preg_match('/\s/', $this->buffer[$this->count]) ? ' ': '';
1888
-            } else {
1889
-                $left = $right = false;
1890
-            }
1891
-
1892
-            $out = [Type::T_INTERPOLATE, $value, $left, $right];
1893
-            $this->eatWhiteDefault = $oldWhite;
1894
-
1895
-            if ($this->eatWhiteDefault) {
1896
-                $this->whitespace();
1897
-            }
1898
-
1899
-            return true;
1900
-        }
1901
-
1902
-        $this->seek($s);
1903
-        $this->eatWhiteDefault = $oldWhite;
1904
-
1905
-        return false;
1906
-    }
1907
-
1908
-    /**
1909
-     * Parse property name (as an array of parts or a string)
1910
-     *
1911
-     * @param array $out
1912
-     *
1913
-     * @return boolean
1914
-     */
1915
-    protected function propertyName(&$out)
1916
-    {
1917
-        $parts = [];
1918
-
1919
-        $oldWhite = $this->eatWhiteDefault;
1920
-        $this->eatWhiteDefault = false;
1921
-
1922
-        for (;;) {
1923
-            if ($this->interpolation($inter)) {
1924
-                $parts[] = $inter;
1925
-                continue;
1926
-            }
1927
-
1928
-            if ($this->keyword($text)) {
1929
-                $parts[] = $text;
1930
-                continue;
1931
-            }
1932
-
1933
-            if (count($parts) === 0 && $this->match('[:.#]', $m, false)) {
1934
-                // css hacks
1935
-                $parts[] = $m[0];
1936
-                continue;
1937
-            }
1938
-
1939
-            break;
1940
-        }
1941
-
1942
-        $this->eatWhiteDefault = $oldWhite;
1943
-
1944
-        if (count($parts) === 0) {
1945
-            return false;
1946
-        }
1947
-
1948
-        // match comment hack
1949
-        if (preg_match(
1950
-            static::$whitePattern,
1951
-            $this->buffer,
1952
-            $m,
1953
-            null,
1954
-            $this->count
1955
-        )) {
1956
-            if (! empty($m[0])) {
1957
-                $parts[] = $m[0];
1958
-                $this->count += strlen($m[0]);
1959
-            }
1960
-        }
1961
-
1962
-        $this->whitespace(); // get any extra whitespace
1963
-
1964
-        $out = [Type::T_STRING, '', $parts];
1965
-
1966
-        return true;
1967
-    }
1968
-
1969
-    /**
1970
-     * Parse comma separated selector list
1971
-     *
1972
-     * @param array $out
1973
-     *
1974
-     * @return boolean
1975
-     */
1976
-    protected function selectors(&$out)
1977
-    {
1978
-        $s = $this->seek();
1979
-        $selectors = [];
1980
-
1981
-        while ($this->selector($sel)) {
1982
-            $selectors[] = $sel;
1983
-
1984
-            if (! $this->literal(',')) {
1985
-                break;
1986
-            }
1987
-
1988
-            while ($this->literal(',')) {
1989
-                ; // ignore extra
1990
-            }
1991
-        }
1992
-
1993
-        if (count($selectors) === 0) {
1994
-            $this->seek($s);
1995
-
1996
-            return false;
1997
-        }
1998
-
1999
-        $out = $selectors;
2000
-
2001
-        return true;
2002
-    }
2003
-
2004
-    /**
2005
-     * Parse whitespace separated selector list
2006
-     *
2007
-     * @param array $out
2008
-     *
2009
-     * @return boolean
2010
-     */
2011
-    protected function selector(&$out)
2012
-    {
2013
-        $selector = [];
2014
-
2015
-        for (;;) {
2016
-            if ($this->match('[>+~]+', $m)) {
2017
-                $selector[] = [$m[0]];
2018
-                continue;
2019
-            }
2020
-
2021
-            if ($this->selectorSingle($part)) {
2022
-                $selector[] = $part;
2023
-                $this->match('\s+', $m);
2024
-                continue;
2025
-            }
2026
-
2027
-            if ($this->match('\/[^\/]+\/', $m)) {
2028
-                $selector[] = [$m[0]];
2029
-                continue;
2030
-            }
2031
-
2032
-            break;
2033
-        }
2034
-
2035
-        if (count($selector) === 0) {
2036
-            return false;
2037
-        }
2038
-
2039
-        $out = $selector;
2040
-        return true;
2041
-    }
2042
-
2043
-    /**
2044
-     * Parse the parts that make up a selector
2045
-     *
2046
-     * {@internal
2047
-     *     div[yes=no]#something.hello.world:nth-child(-2n+1)%placeholder
2048
-     * }}
2049
-     *
2050
-     * @param array $out
2051
-     *
2052
-     * @return boolean
2053
-     */
2054
-    protected function selectorSingle(&$out)
2055
-    {
2056
-        $oldWhite = $this->eatWhiteDefault;
2057
-        $this->eatWhiteDefault = false;
2058
-
2059
-        $parts = [];
2060
-
2061
-        if ($this->literal('*', false)) {
2062
-            $parts[] = '*';
2063
-        }
2064
-
2065
-        for (;;) {
2066
-            // see if we can stop early
2067
-            if ($this->match('\s*[{,]', $m)) {
2068
-                $this->count--;
2069
-                break;
2070
-            }
2071
-
2072
-            $s = $this->seek();
2073
-
2074
-            // self
2075
-            if ($this->literal('&', false)) {
2076
-                $parts[] = Compiler::$selfSelector;
2077
-                continue;
2078
-            }
2079
-
2080
-            if ($this->literal('.', false)) {
2081
-                $parts[] = '.';
2082
-                continue;
2083
-            }
2084
-
2085
-            if ($this->literal('|', false)) {
2086
-                $parts[] = '|';
2087
-                continue;
2088
-            }
2089
-
2090
-            if ($this->match('\\\\\S', $m)) {
2091
-                $parts[] = $m[0];
2092
-                continue;
2093
-            }
2094
-
2095
-            // for keyframes
2096
-            if ($this->unit($unit)) {
2097
-                $parts[] = $unit;
2098
-                continue;
2099
-            }
2100
-
2101
-            if ($this->keyword($name)) {
2102
-                $parts[] = $name;
2103
-                continue;
2104
-            }
2105
-
2106
-            if ($this->interpolation($inter)) {
2107
-                $parts[] = $inter;
2108
-                continue;
2109
-            }
2110
-
2111
-            if ($this->literal('%', false) && $this->placeholder($placeholder)) {
2112
-                $parts[] = '%';
2113
-                $parts[] = $placeholder;
2114
-                continue;
2115
-            }
2116
-
2117
-            if ($this->literal('#', false)) {
2118
-                $parts[] = '#';
2119
-                continue;
2120
-            }
2121
-
2122
-            // a pseudo selector
2123
-            if ($this->match('::?', $m) && $this->mixedKeyword($nameParts)) {
2124
-                $parts[] = $m[0];
2125
-
2126
-                foreach ($nameParts as $sub) {
2127
-                    $parts[] = $sub;
2128
-                }
2129
-
2130
-                $ss = $this->seek();
2131
-
2132
-                if ($this->literal('(') &&
2133
-                    ($this->openString(')', $str, '(') || true) &&
2134
-                    $this->literal(')')
2135
-                ) {
2136
-                    $parts[] = '(';
2137
-
2138
-                    if (! empty($str)) {
2139
-                        $parts[] = $str;
2140
-                    }
2141
-
2142
-                    $parts[] = ')';
2143
-                } else {
2144
-                    $this->seek($ss);
2145
-                }
2146
-
2147
-                continue;
2148
-            }
2149
-
2150
-            $this->seek($s);
2151
-
2152
-            // attribute selector
2153
-            if ($this->literal('[') &&
2154
-               ($this->openString(']', $str, '[') || true) &&
2155
-               $this->literal(']')
2156
-            ) {
2157
-                $parts[] = '[';
2158
-
2159
-                if (! empty($str)) {
2160
-                    $parts[] = $str;
2161
-                }
2162
-
2163
-                $parts[] = ']';
2164
-
2165
-                continue;
2166
-            }
2167
-
2168
-            $this->seek($s);
2169
-
2170
-            break;
2171
-        }
2172
-
2173
-        $this->eatWhiteDefault = $oldWhite;
2174
-
2175
-        if (count($parts) === 0) {
2176
-            return false;
2177
-        }
2178
-
2179
-        $out = $parts;
2180
-
2181
-        return true;
2182
-    }
2183
-
2184
-    /**
2185
-     * Parse a variable
2186
-     *
2187
-     * @param array $out
2188
-     *
2189
-     * @return boolean
2190
-     */
2191
-    protected function variable(&$out)
2192
-    {
2193
-        $s = $this->seek();
2194
-
2195
-        if ($this->literal('$', false) && $this->keyword($name)) {
2196
-            $out = [Type::T_VARIABLE, $name];
2197
-
2198
-            return true;
2199
-        }
2200
-
2201
-        $this->seek($s);
2202
-
2203
-        return false;
2204
-    }
2205
-
2206
-    /**
2207
-     * Parse a keyword
2208
-     *
2209
-     * @param string  $word
2210
-     * @param boolean $eatWhitespace
2211
-     *
2212
-     * @return boolean
2213
-     */
2214
-    protected function keyword(&$word, $eatWhitespace = null)
2215
-    {
2216
-        if ($this->match(
2217
-            $this->utf8
2218
-                ? '(([\pL\w_\-\*!"\']|[\\\\].)([\pL\w\-_"\']|[\\\\].)*)'
2219
-                : '(([\w_\-\*!"\']|[\\\\].)([\w\-_"\']|[\\\\].)*)',
2220
-            $m,
2221
-            $eatWhitespace
2222
-        )) {
2223
-            $word = $m[1];
2224
-
2225
-            return true;
2226
-        }
2227
-
2228
-        return false;
2229
-    }
2230
-
2231
-    /**
2232
-     * Parse a placeholder
2233
-     *
2234
-     * @param string $placeholder
2235
-     *
2236
-     * @return boolean
2237
-     */
2238
-    protected function placeholder(&$placeholder)
2239
-    {
2240
-        if ($this->match(
2241
-            $this->utf8
2242
-                ? '([\pL\w\-_]+|#[{][$][\pL\w\-_]+[}])'
2243
-                : '([\w\-_]+|#[{][$][\w\-_]+[}])',
2244
-            $m
2245
-        )) {
2246
-            $placeholder = $m[1];
2247
-
2248
-            return true;
2249
-        }
2250
-
2251
-        return false;
2252
-    }
2253
-
2254
-    /**
2255
-     * Parse a url
2256
-     *
2257
-     * @param array $out
2258
-     *
2259
-     * @return boolean
2260
-     */
2261
-    protected function url(&$out)
2262
-    {
2263
-        if ($this->match('(url\(\s*(["\']?)([^)]+)\2\s*\))', $m)) {
2264
-            $out = [Type::T_STRING, '', ['url(' . $m[2] . $m[3] . $m[2] . ')']];
2265
-
2266
-            return true;
2267
-        }
2268
-
2269
-        return false;
2270
-    }
2271
-
2272
-    /**
2273
-     * Consume an end of statement delimiter
2274
-     *
2275
-     * @return boolean
2276
-     */
2277
-    protected function end()
2278
-    {
2279
-        if ($this->literal(';')) {
2280
-            return true;
2281
-        }
2282
-
2283
-        if ($this->count === strlen($this->buffer) || $this->buffer[$this->count] === '}') {
2284
-            // if there is end of file or a closing block next then we don't need a ;
2285
-            return true;
2286
-        }
2287
-
2288
-        return false;
2289
-    }
2290
-
2291
-    /**
2292
-     * Strip assignment flag from the list
2293
-     *
2294
-     * @param array $value
2295
-     *
2296
-     * @return array
2297
-     */
2298
-    protected function stripAssignmentFlags(&$value)
2299
-    {
2300
-        $flags = [];
2301
-
2302
-        for ($token = &$value; $token[0] === Type::T_LIST && ($s = count($token[2])); $token = &$lastNode) {
2303
-            $lastNode = &$token[2][$s - 1];
2304
-
2305
-            while ($lastNode[0] === Type::T_KEYWORD && in_array($lastNode[1], ['!default', '!global'])) {
2306
-                array_pop($token[2]);
2307
-
2308
-                $node = end($token[2]);
2309
-
2310
-                $token = $this->flattenList($token);
2311
-
2312
-                $flags[] = $lastNode[1];
2313
-
2314
-                $lastNode = $node;
2315
-            }
2316
-        }
2317
-
2318
-        return $flags;
2319
-    }
2320
-
2321
-    /**
2322
-     * Strip optional flag from selector list
2323
-     *
2324
-     * @param array $selectors
2325
-     *
2326
-     * @return string
2327
-     */
2328
-    protected function stripOptionalFlag(&$selectors)
2329
-    {
2330
-        $optional = false;
2331
-
2332
-        $selector = end($selectors);
2333
-        $part = end($selector);
2334
-
2335
-        if ($part === ['!optional']) {
2336
-            array_pop($selectors[count($selectors) - 1]);
2337
-
2338
-            $optional = true;
2339
-        }
2340
-
2341
-        return $optional;
2342
-    }
2343
-
2344
-    /**
2345
-     * Turn list of length 1 into value type
2346
-     *
2347
-     * @param array $value
2348
-     *
2349
-     * @return array
2350
-     */
2351
-    protected function flattenList($value)
2352
-    {
2353
-        if ($value[0] === Type::T_LIST && count($value[2]) === 1) {
2354
-            return $this->flattenList($value[2][0]);
2355
-        }
2356
-
2357
-        return $value;
2358
-    }
2359
-
2360
-    /**
2361
-     * @deprecated
2362
-     *
2363
-     * {@internal
2364
-     *     advance counter to next occurrence of $what
2365
-     *     $until - don't include $what in advance
2366
-     *     $allowNewline, if string, will be used as valid char set
2367
-     * }}
2368
-     */
2369
-    protected function to($what, &$out, $until = false, $allowNewline = false)
2370
-    {
2371
-        if (is_string($allowNewline)) {
2372
-            $validChars = $allowNewline;
2373
-        } else {
2374
-            $validChars = $allowNewline ? '.' : "[^\n]";
2375
-        }
2376
-
2377
-        if (! $this->match('(' . $validChars . '*?)' . $this->pregQuote($what), $m, ! $until)) {
2378
-            return false;
2379
-        }
2380
-
2381
-        if ($until) {
2382
-            $this->count -= strlen($what); // give back $what
2383
-        }
2384
-
2385
-        $out = $m[1];
2386
-
2387
-        return true;
2388
-    }
2389
-
2390
-    /**
2391
-     * @deprecated
2392
-     */
2393
-    protected function show()
2394
-    {
2395
-        if ($this->peek("(.*?)(\n|$)", $m, $this->count)) {
2396
-            return $m[1];
2397
-        }
2398
-
2399
-        return '';
2400
-    }
2401
-
2402
-    /**
2403
-     * Quote regular expression
2404
-     *
2405
-     * @param string $what
2406
-     *
2407
-     * @return string
2408
-     */
2409
-    private function pregQuote($what)
2410
-    {
2411
-        return preg_quote($what, '/');
2412
-    }
2413
-
2414
-    /**
2415
-     * Extract line numbers from buffer
2416
-     *
2417
-     * @param string $buffer
2418
-     */
2419
-    private function extractLineNumbers($buffer)
2420
-    {
2421
-        $this->sourcePositions = [0 => 0];
2422
-        $prev = 0;
2423
-
2424
-        while (($pos = strpos($buffer, "\n", $prev)) !== false) {
2425
-            $this->sourcePositions[] = $pos;
2426
-            $prev = $pos + 1;
2427
-        }
2428
-
2429
-        $this->sourcePositions[] = strlen($buffer);
2430
-
2431
-        if (substr($buffer, -1) !== "\n") {
2432
-            $this->sourcePositions[] = strlen($buffer) + 1;
2433
-        }
2434
-    }
2435
-
2436
-    /**
2437
-     * Get source line number and column (given character position in the buffer)
2438
-     *
2439
-     * @param integer $pos
2440
-     *
2441
-     * @return integer
2442
-     */
2443
-    private function getSourcePosition($pos)
2444
-    {
2445
-        $low = 0;
2446
-        $high = count($this->sourcePositions);
2447
-
2448
-        while ($low < $high) {
2449
-            $mid = (int) (($high + $low) / 2);
2450
-
2451
-            if ($pos < $this->sourcePositions[$mid]) {
2452
-                $high = $mid - 1;
2453
-                continue;
2454
-            }
2455
-
2456
-            if ($pos >= $this->sourcePositions[$mid + 1]) {
2457
-                $low = $mid + 1;
2458
-                continue;
2459
-            }
2460
-
2461
-            return [$mid + 1, $pos - $this->sourcePositions[$mid]];
2462
-        }
2463
-
2464
-        return [$low + 1, $pos - $this->sourcePositions[$low]];
2465
-    }
2466
-
2467
-    /**
2468
-     * Save internal encoding
2469
-     */
2470
-    private function saveEncoding()
2471
-    {
2472
-        if (version_compare(PHP_VERSION, '7.2.0') >= 0) {
2473
-            return;
2474
-        }
2475
-
2476
-        $iniDirective = 'mbstring' . '.func_overload'; // deprecated in PHP 7.2
2477
-
2478
-        if (ini_get($iniDirective) & 2) {
2479
-            $this->encoding = mb_internal_encoding();
2480
-
2481
-            mb_internal_encoding('iso-8859-1');
2482
-        }
2483
-    }
2484
-
2485
-    /**
2486
-     * Restore internal encoding
2487
-     */
2488
-    private function restoreEncoding()
2489
-    {
2490
-        if ($this->encoding) {
2491
-            mb_internal_encoding($this->encoding);
2492
-        }
2493
-    }
1528
+	/**
1529
+	 * Parse mixin/function definition  argument list
1530
+	 *
1531
+	 * @param array $out
1532
+	 *
1533
+	 * @return boolean
1534
+	 */
1535
+	protected function argumentDef(&$out)
1536
+	{
1537
+		$s = $this->seek();
1538
+		$this->literal('(');
1539
+
1540
+		$args = [];
1541
+
1542
+		while ($this->variable($var)) {
1543
+			$arg = [$var[1], null, false];
1544
+
1545
+			$ss = $this->seek();
1546
+
1547
+			if ($this->literal(':') && $this->genericList($defaultVal, 'expression')) {
1548
+				$arg[1] = $defaultVal;
1549
+			} else {
1550
+				$this->seek($ss);
1551
+			}
1552
+
1553
+			$ss = $this->seek();
1554
+
1555
+			if ($this->literal('...')) {
1556
+				$sss = $this->seek();
1557
+
1558
+				if (! $this->literal(')')) {
1559
+					$this->throwParseError('... has to be after the final argument');
1560
+				}
1561
+
1562
+				$arg[2] = true;
1563
+				$this->seek($sss);
1564
+			} else {
1565
+				$this->seek($ss);
1566
+			}
1567
+
1568
+			$args[] = $arg;
1569
+
1570
+			if (! $this->literal(',')) {
1571
+				break;
1572
+			}
1573
+		}
1574
+
1575
+		if (! $this->literal(')')) {
1576
+			$this->seek($s);
1577
+
1578
+			return false;
1579
+		}
1580
+
1581
+		$out = $args;
1582
+
1583
+		return true;
1584
+	}
1585
+
1586
+	/**
1587
+	 * Parse map
1588
+	 *
1589
+	 * @param array $out
1590
+	 *
1591
+	 * @return boolean
1592
+	 */
1593
+	protected function map(&$out)
1594
+	{
1595
+		$s = $this->seek();
1596
+
1597
+		if (! $this->literal('(')) {
1598
+			return false;
1599
+		}
1600
+
1601
+		$keys = [];
1602
+		$values = [];
1603
+
1604
+		while ($this->genericList($key, 'expression') && $this->literal(':') &&
1605
+			$this->genericList($value, 'expression')
1606
+		) {
1607
+			$keys[] = $key;
1608
+			$values[] = $value;
1609
+
1610
+			if (! $this->literal(',')) {
1611
+				break;
1612
+			}
1613
+		}
1614
+
1615
+		if (! count($keys) || ! $this->literal(')')) {
1616
+			$this->seek($s);
1617
+
1618
+			return false;
1619
+		}
1620
+
1621
+		$out = [Type::T_MAP, $keys, $values];
1622
+
1623
+		return true;
1624
+	}
1625
+
1626
+	/**
1627
+	 * Parse color
1628
+	 *
1629
+	 * @param array $out
1630
+	 *
1631
+	 * @return boolean
1632
+	 */
1633
+	protected function color(&$out)
1634
+	{
1635
+		$color = [Type::T_COLOR];
1636
+
1637
+		if ($this->match('(#([0-9a-f]{6})|#([0-9a-f]{3}))', $m)) {
1638
+			if (isset($m[3])) {
1639
+				$num = hexdec($m[3]);
1640
+
1641
+				foreach ([3, 2, 1] as $i) {
1642
+					$t = $num & 0xf;
1643
+					$color[$i] = $t << 4 | $t;
1644
+					$num >>= 4;
1645
+				}
1646
+			} else {
1647
+				$num = hexdec($m[2]);
1648
+
1649
+				foreach ([3, 2, 1] as $i) {
1650
+					$color[$i] = $num & 0xff;
1651
+					$num >>= 8;
1652
+				}
1653
+			}
1654
+
1655
+			$out = $color;
1656
+
1657
+			return true;
1658
+		}
1659
+
1660
+		return false;
1661
+	}
1662
+
1663
+	/**
1664
+	 * Parse number with unit
1665
+	 *
1666
+	 * @param array $out
1667
+	 *
1668
+	 * @return boolean
1669
+	 */
1670
+	protected function unit(&$unit)
1671
+	{
1672
+		if ($this->match('([0-9]*(\.)?[0-9]+)([%a-zA-Z]+)?', $m)) {
1673
+			$unit = new Node\Number($m[1], empty($m[3]) ? '' : $m[3]);
1674
+
1675
+			return true;
1676
+		}
1677
+
1678
+		return false;
1679
+	}
1680
+
1681
+	/**
1682
+	 * Parse string
1683
+	 *
1684
+	 * @param array $out
1685
+	 *
1686
+	 * @return boolean
1687
+	 */
1688
+	protected function string(&$out)
1689
+	{
1690
+		$s = $this->seek();
1691
+
1692
+		if ($this->literal('"', false)) {
1693
+			$delim = '"';
1694
+		} elseif ($this->literal("'", false)) {
1695
+			$delim = "'";
1696
+		} else {
1697
+			return false;
1698
+		}
1699
+
1700
+		$content = [];
1701
+		$oldWhite = $this->eatWhiteDefault;
1702
+		$this->eatWhiteDefault = false;
1703
+		$hasInterpolation = false;
1704
+
1705
+		while ($this->matchString($m, $delim)) {
1706
+			if ($m[1] !== '') {
1707
+				$content[] = $m[1];
1708
+			}
1709
+
1710
+			if ($m[2] === '#{') {
1711
+				$this->count -= strlen($m[2]);
1712
+
1713
+				if ($this->interpolation($inter, false)) {
1714
+					$content[] = $inter;
1715
+					$hasInterpolation = true;
1716
+				} else {
1717
+					$this->count += strlen($m[2]);
1718
+					$content[] = '#{'; // ignore it
1719
+				}
1720
+			} elseif ($m[2] === '\\') {
1721
+				if ($this->literal('"', false)) {
1722
+					$content[] = $m[2] . '"';
1723
+				} elseif ($this->literal("'", false)) {
1724
+					$content[] = $m[2] . "'";
1725
+				} else {
1726
+					$content[] = $m[2];
1727
+				}
1728
+			} else {
1729
+				$this->count -= strlen($delim);
1730
+				break; // delim
1731
+			}
1732
+		}
1733
+
1734
+		$this->eatWhiteDefault = $oldWhite;
1735
+
1736
+		if ($this->literal($delim)) {
1737
+			if ($hasInterpolation) {
1738
+				$delim = '"';
1739
+
1740
+				foreach ($content as &$string) {
1741
+					if ($string === "\\'") {
1742
+						$string = "'";
1743
+					} elseif ($string === '\\"') {
1744
+						$string = '"';
1745
+					}
1746
+				}
1747
+			}
1748
+
1749
+			$out = [Type::T_STRING, $delim, $content];
1750
+
1751
+			return true;
1752
+		}
1753
+
1754
+		$this->seek($s);
1755
+
1756
+		return false;
1757
+	}
1758
+
1759
+	/**
1760
+	 * Parse keyword or interpolation
1761
+	 *
1762
+	 * @param array $out
1763
+	 *
1764
+	 * @return boolean
1765
+	 */
1766
+	protected function mixedKeyword(&$out)
1767
+	{
1768
+		$parts = [];
1769
+
1770
+		$oldWhite = $this->eatWhiteDefault;
1771
+		$this->eatWhiteDefault = false;
1772
+
1773
+		for (;;) {
1774
+			if ($this->keyword($key)) {
1775
+				$parts[] = $key;
1776
+				continue;
1777
+			}
1778
+
1779
+			if ($this->interpolation($inter)) {
1780
+				$parts[] = $inter;
1781
+				continue;
1782
+			}
1783
+
1784
+			break;
1785
+		}
1786
+
1787
+		$this->eatWhiteDefault = $oldWhite;
1788
+
1789
+		if (count($parts) === 0) {
1790
+			return false;
1791
+		}
1792
+
1793
+		if ($this->eatWhiteDefault) {
1794
+			$this->whitespace();
1795
+		}
1796
+
1797
+		$out = $parts;
1798
+
1799
+		return true;
1800
+	}
1801
+
1802
+	/**
1803
+	 * Parse an unbounded string stopped by $end
1804
+	 *
1805
+	 * @param string $end
1806
+	 * @param array  $out
1807
+	 * @param string $nestingOpen
1808
+	 *
1809
+	 * @return boolean
1810
+	 */
1811
+	protected function openString($end, &$out, $nestingOpen = null)
1812
+	{
1813
+		$oldWhite = $this->eatWhiteDefault;
1814
+		$this->eatWhiteDefault = false;
1815
+
1816
+		$patt = '(.*?)([\'"]|#\{|' . $this->pregQuote($end) . '|' . static::$commentPattern . ')';
1817
+
1818
+		$nestingLevel = 0;
1819
+
1820
+		$content = [];
1821
+
1822
+		while ($this->match($patt, $m, false)) {
1823
+			if (isset($m[1]) && $m[1] !== '') {
1824
+				$content[] = $m[1];
1825
+
1826
+				if ($nestingOpen) {
1827
+					$nestingLevel += substr_count($m[1], $nestingOpen);
1828
+				}
1829
+			}
1830
+
1831
+			$tok = $m[2];
1832
+
1833
+			$this->count-= strlen($tok);
1834
+
1835
+			if ($tok === $end && ! $nestingLevel--) {
1836
+				break;
1837
+			}
1838
+
1839
+			if (($tok === "'" || $tok === '"') && $this->string($str)) {
1840
+				$content[] = $str;
1841
+				continue;
1842
+			}
1843
+
1844
+			if ($tok === '#{' && $this->interpolation($inter)) {
1845
+				$content[] = $inter;
1846
+				continue;
1847
+			}
1848
+
1849
+			$content[] = $tok;
1850
+			$this->count+= strlen($tok);
1851
+		}
1852
+
1853
+		$this->eatWhiteDefault = $oldWhite;
1854
+
1855
+		if (count($content) === 0) {
1856
+			return false;
1857
+		}
1858
+
1859
+		// trim the end
1860
+		if (is_string(end($content))) {
1861
+			$content[count($content) - 1] = rtrim(end($content));
1862
+		}
1863
+
1864
+		$out = [Type::T_STRING, '', $content];
1865
+
1866
+		return true;
1867
+	}
1868
+
1869
+	/**
1870
+	 * Parser interpolation
1871
+	 *
1872
+	 * @param array   $out
1873
+	 * @param boolean $lookWhite save information about whitespace before and after
1874
+	 *
1875
+	 * @return boolean
1876
+	 */
1877
+	protected function interpolation(&$out, $lookWhite = true)
1878
+	{
1879
+		$oldWhite = $this->eatWhiteDefault;
1880
+		$this->eatWhiteDefault = true;
1881
+
1882
+		$s = $this->seek();
1883
+
1884
+		if ($this->literal('#{') && $this->valueList($value) && $this->literal('}', false)) {
1885
+			if ($lookWhite) {
1886
+				$left = preg_match('/\s/', $this->buffer[$s - 1]) ? ' ' : '';
1887
+				$right = preg_match('/\s/', $this->buffer[$this->count]) ? ' ': '';
1888
+			} else {
1889
+				$left = $right = false;
1890
+			}
1891
+
1892
+			$out = [Type::T_INTERPOLATE, $value, $left, $right];
1893
+			$this->eatWhiteDefault = $oldWhite;
1894
+
1895
+			if ($this->eatWhiteDefault) {
1896
+				$this->whitespace();
1897
+			}
1898
+
1899
+			return true;
1900
+		}
1901
+
1902
+		$this->seek($s);
1903
+		$this->eatWhiteDefault = $oldWhite;
1904
+
1905
+		return false;
1906
+	}
1907
+
1908
+	/**
1909
+	 * Parse property name (as an array of parts or a string)
1910
+	 *
1911
+	 * @param array $out
1912
+	 *
1913
+	 * @return boolean
1914
+	 */
1915
+	protected function propertyName(&$out)
1916
+	{
1917
+		$parts = [];
1918
+
1919
+		$oldWhite = $this->eatWhiteDefault;
1920
+		$this->eatWhiteDefault = false;
1921
+
1922
+		for (;;) {
1923
+			if ($this->interpolation($inter)) {
1924
+				$parts[] = $inter;
1925
+				continue;
1926
+			}
1927
+
1928
+			if ($this->keyword($text)) {
1929
+				$parts[] = $text;
1930
+				continue;
1931
+			}
1932
+
1933
+			if (count($parts) === 0 && $this->match('[:.#]', $m, false)) {
1934
+				// css hacks
1935
+				$parts[] = $m[0];
1936
+				continue;
1937
+			}
1938
+
1939
+			break;
1940
+		}
1941
+
1942
+		$this->eatWhiteDefault = $oldWhite;
1943
+
1944
+		if (count($parts) === 0) {
1945
+			return false;
1946
+		}
1947
+
1948
+		// match comment hack
1949
+		if (preg_match(
1950
+			static::$whitePattern,
1951
+			$this->buffer,
1952
+			$m,
1953
+			null,
1954
+			$this->count
1955
+		)) {
1956
+			if (! empty($m[0])) {
1957
+				$parts[] = $m[0];
1958
+				$this->count += strlen($m[0]);
1959
+			}
1960
+		}
1961
+
1962
+		$this->whitespace(); // get any extra whitespace
1963
+
1964
+		$out = [Type::T_STRING, '', $parts];
1965
+
1966
+		return true;
1967
+	}
1968
+
1969
+	/**
1970
+	 * Parse comma separated selector list
1971
+	 *
1972
+	 * @param array $out
1973
+	 *
1974
+	 * @return boolean
1975
+	 */
1976
+	protected function selectors(&$out)
1977
+	{
1978
+		$s = $this->seek();
1979
+		$selectors = [];
1980
+
1981
+		while ($this->selector($sel)) {
1982
+			$selectors[] = $sel;
1983
+
1984
+			if (! $this->literal(',')) {
1985
+				break;
1986
+			}
1987
+
1988
+			while ($this->literal(',')) {
1989
+				; // ignore extra
1990
+			}
1991
+		}
1992
+
1993
+		if (count($selectors) === 0) {
1994
+			$this->seek($s);
1995
+
1996
+			return false;
1997
+		}
1998
+
1999
+		$out = $selectors;
2000
+
2001
+		return true;
2002
+	}
2003
+
2004
+	/**
2005
+	 * Parse whitespace separated selector list
2006
+	 *
2007
+	 * @param array $out
2008
+	 *
2009
+	 * @return boolean
2010
+	 */
2011
+	protected function selector(&$out)
2012
+	{
2013
+		$selector = [];
2014
+
2015
+		for (;;) {
2016
+			if ($this->match('[>+~]+', $m)) {
2017
+				$selector[] = [$m[0]];
2018
+				continue;
2019
+			}
2020
+
2021
+			if ($this->selectorSingle($part)) {
2022
+				$selector[] = $part;
2023
+				$this->match('\s+', $m);
2024
+				continue;
2025
+			}
2026
+
2027
+			if ($this->match('\/[^\/]+\/', $m)) {
2028
+				$selector[] = [$m[0]];
2029
+				continue;
2030
+			}
2031
+
2032
+			break;
2033
+		}
2034
+
2035
+		if (count($selector) === 0) {
2036
+			return false;
2037
+		}
2038
+
2039
+		$out = $selector;
2040
+		return true;
2041
+	}
2042
+
2043
+	/**
2044
+	 * Parse the parts that make up a selector
2045
+	 *
2046
+	 * {@internal
2047
+	 *     div[yes=no]#something.hello.world:nth-child(-2n+1)%placeholder
2048
+	 * }}
2049
+	 *
2050
+	 * @param array $out
2051
+	 *
2052
+	 * @return boolean
2053
+	 */
2054
+	protected function selectorSingle(&$out)
2055
+	{
2056
+		$oldWhite = $this->eatWhiteDefault;
2057
+		$this->eatWhiteDefault = false;
2058
+
2059
+		$parts = [];
2060
+
2061
+		if ($this->literal('*', false)) {
2062
+			$parts[] = '*';
2063
+		}
2064
+
2065
+		for (;;) {
2066
+			// see if we can stop early
2067
+			if ($this->match('\s*[{,]', $m)) {
2068
+				$this->count--;
2069
+				break;
2070
+			}
2071
+
2072
+			$s = $this->seek();
2073
+
2074
+			// self
2075
+			if ($this->literal('&', false)) {
2076
+				$parts[] = Compiler::$selfSelector;
2077
+				continue;
2078
+			}
2079
+
2080
+			if ($this->literal('.', false)) {
2081
+				$parts[] = '.';
2082
+				continue;
2083
+			}
2084
+
2085
+			if ($this->literal('|', false)) {
2086
+				$parts[] = '|';
2087
+				continue;
2088
+			}
2089
+
2090
+			if ($this->match('\\\\\S', $m)) {
2091
+				$parts[] = $m[0];
2092
+				continue;
2093
+			}
2094
+
2095
+			// for keyframes
2096
+			if ($this->unit($unit)) {
2097
+				$parts[] = $unit;
2098
+				continue;
2099
+			}
2100
+
2101
+			if ($this->keyword($name)) {
2102
+				$parts[] = $name;
2103
+				continue;
2104
+			}
2105
+
2106
+			if ($this->interpolation($inter)) {
2107
+				$parts[] = $inter;
2108
+				continue;
2109
+			}
2110
+
2111
+			if ($this->literal('%', false) && $this->placeholder($placeholder)) {
2112
+				$parts[] = '%';
2113
+				$parts[] = $placeholder;
2114
+				continue;
2115
+			}
2116
+
2117
+			if ($this->literal('#', false)) {
2118
+				$parts[] = '#';
2119
+				continue;
2120
+			}
2121
+
2122
+			// a pseudo selector
2123
+			if ($this->match('::?', $m) && $this->mixedKeyword($nameParts)) {
2124
+				$parts[] = $m[0];
2125
+
2126
+				foreach ($nameParts as $sub) {
2127
+					$parts[] = $sub;
2128
+				}
2129
+
2130
+				$ss = $this->seek();
2131
+
2132
+				if ($this->literal('(') &&
2133
+					($this->openString(')', $str, '(') || true) &&
2134
+					$this->literal(')')
2135
+				) {
2136
+					$parts[] = '(';
2137
+
2138
+					if (! empty($str)) {
2139
+						$parts[] = $str;
2140
+					}
2141
+
2142
+					$parts[] = ')';
2143
+				} else {
2144
+					$this->seek($ss);
2145
+				}
2146
+
2147
+				continue;
2148
+			}
2149
+
2150
+			$this->seek($s);
2151
+
2152
+			// attribute selector
2153
+			if ($this->literal('[') &&
2154
+			   ($this->openString(']', $str, '[') || true) &&
2155
+			   $this->literal(']')
2156
+			) {
2157
+				$parts[] = '[';
2158
+
2159
+				if (! empty($str)) {
2160
+					$parts[] = $str;
2161
+				}
2162
+
2163
+				$parts[] = ']';
2164
+
2165
+				continue;
2166
+			}
2167
+
2168
+			$this->seek($s);
2169
+
2170
+			break;
2171
+		}
2172
+
2173
+		$this->eatWhiteDefault = $oldWhite;
2174
+
2175
+		if (count($parts) === 0) {
2176
+			return false;
2177
+		}
2178
+
2179
+		$out = $parts;
2180
+
2181
+		return true;
2182
+	}
2183
+
2184
+	/**
2185
+	 * Parse a variable
2186
+	 *
2187
+	 * @param array $out
2188
+	 *
2189
+	 * @return boolean
2190
+	 */
2191
+	protected function variable(&$out)
2192
+	{
2193
+		$s = $this->seek();
2194
+
2195
+		if ($this->literal('$', false) && $this->keyword($name)) {
2196
+			$out = [Type::T_VARIABLE, $name];
2197
+
2198
+			return true;
2199
+		}
2200
+
2201
+		$this->seek($s);
2202
+
2203
+		return false;
2204
+	}
2205
+
2206
+	/**
2207
+	 * Parse a keyword
2208
+	 *
2209
+	 * @param string  $word
2210
+	 * @param boolean $eatWhitespace
2211
+	 *
2212
+	 * @return boolean
2213
+	 */
2214
+	protected function keyword(&$word, $eatWhitespace = null)
2215
+	{
2216
+		if ($this->match(
2217
+			$this->utf8
2218
+				? '(([\pL\w_\-\*!"\']|[\\\\].)([\pL\w\-_"\']|[\\\\].)*)'
2219
+				: '(([\w_\-\*!"\']|[\\\\].)([\w\-_"\']|[\\\\].)*)',
2220
+			$m,
2221
+			$eatWhitespace
2222
+		)) {
2223
+			$word = $m[1];
2224
+
2225
+			return true;
2226
+		}
2227
+
2228
+		return false;
2229
+	}
2230
+
2231
+	/**
2232
+	 * Parse a placeholder
2233
+	 *
2234
+	 * @param string $placeholder
2235
+	 *
2236
+	 * @return boolean
2237
+	 */
2238
+	protected function placeholder(&$placeholder)
2239
+	{
2240
+		if ($this->match(
2241
+			$this->utf8
2242
+				? '([\pL\w\-_]+|#[{][$][\pL\w\-_]+[}])'
2243
+				: '([\w\-_]+|#[{][$][\w\-_]+[}])',
2244
+			$m
2245
+		)) {
2246
+			$placeholder = $m[1];
2247
+
2248
+			return true;
2249
+		}
2250
+
2251
+		return false;
2252
+	}
2253
+
2254
+	/**
2255
+	 * Parse a url
2256
+	 *
2257
+	 * @param array $out
2258
+	 *
2259
+	 * @return boolean
2260
+	 */
2261
+	protected function url(&$out)
2262
+	{
2263
+		if ($this->match('(url\(\s*(["\']?)([^)]+)\2\s*\))', $m)) {
2264
+			$out = [Type::T_STRING, '', ['url(' . $m[2] . $m[3] . $m[2] . ')']];
2265
+
2266
+			return true;
2267
+		}
2268
+
2269
+		return false;
2270
+	}
2271
+
2272
+	/**
2273
+	 * Consume an end of statement delimiter
2274
+	 *
2275
+	 * @return boolean
2276
+	 */
2277
+	protected function end()
2278
+	{
2279
+		if ($this->literal(';')) {
2280
+			return true;
2281
+		}
2282
+
2283
+		if ($this->count === strlen($this->buffer) || $this->buffer[$this->count] === '}') {
2284
+			// if there is end of file or a closing block next then we don't need a ;
2285
+			return true;
2286
+		}
2287
+
2288
+		return false;
2289
+	}
2290
+
2291
+	/**
2292
+	 * Strip assignment flag from the list
2293
+	 *
2294
+	 * @param array $value
2295
+	 *
2296
+	 * @return array
2297
+	 */
2298
+	protected function stripAssignmentFlags(&$value)
2299
+	{
2300
+		$flags = [];
2301
+
2302
+		for ($token = &$value; $token[0] === Type::T_LIST && ($s = count($token[2])); $token = &$lastNode) {
2303
+			$lastNode = &$token[2][$s - 1];
2304
+
2305
+			while ($lastNode[0] === Type::T_KEYWORD && in_array($lastNode[1], ['!default', '!global'])) {
2306
+				array_pop($token[2]);
2307
+
2308
+				$node = end($token[2]);
2309
+
2310
+				$token = $this->flattenList($token);
2311
+
2312
+				$flags[] = $lastNode[1];
2313
+
2314
+				$lastNode = $node;
2315
+			}
2316
+		}
2317
+
2318
+		return $flags;
2319
+	}
2320
+
2321
+	/**
2322
+	 * Strip optional flag from selector list
2323
+	 *
2324
+	 * @param array $selectors
2325
+	 *
2326
+	 * @return string
2327
+	 */
2328
+	protected function stripOptionalFlag(&$selectors)
2329
+	{
2330
+		$optional = false;
2331
+
2332
+		$selector = end($selectors);
2333
+		$part = end($selector);
2334
+
2335
+		if ($part === ['!optional']) {
2336
+			array_pop($selectors[count($selectors) - 1]);
2337
+
2338
+			$optional = true;
2339
+		}
2340
+
2341
+		return $optional;
2342
+	}
2343
+
2344
+	/**
2345
+	 * Turn list of length 1 into value type
2346
+	 *
2347
+	 * @param array $value
2348
+	 *
2349
+	 * @return array
2350
+	 */
2351
+	protected function flattenList($value)
2352
+	{
2353
+		if ($value[0] === Type::T_LIST && count($value[2]) === 1) {
2354
+			return $this->flattenList($value[2][0]);
2355
+		}
2356
+
2357
+		return $value;
2358
+	}
2359
+
2360
+	/**
2361
+	 * @deprecated
2362
+	 *
2363
+	 * {@internal
2364
+	 *     advance counter to next occurrence of $what
2365
+	 *     $until - don't include $what in advance
2366
+	 *     $allowNewline, if string, will be used as valid char set
2367
+	 * }}
2368
+	 */
2369
+	protected function to($what, &$out, $until = false, $allowNewline = false)
2370
+	{
2371
+		if (is_string($allowNewline)) {
2372
+			$validChars = $allowNewline;
2373
+		} else {
2374
+			$validChars = $allowNewline ? '.' : "[^\n]";
2375
+		}
2376
+
2377
+		if (! $this->match('(' . $validChars . '*?)' . $this->pregQuote($what), $m, ! $until)) {
2378
+			return false;
2379
+		}
2380
+
2381
+		if ($until) {
2382
+			$this->count -= strlen($what); // give back $what
2383
+		}
2384
+
2385
+		$out = $m[1];
2386
+
2387
+		return true;
2388
+	}
2389
+
2390
+	/**
2391
+	 * @deprecated
2392
+	 */
2393
+	protected function show()
2394
+	{
2395
+		if ($this->peek("(.*?)(\n|$)", $m, $this->count)) {
2396
+			return $m[1];
2397
+		}
2398
+
2399
+		return '';
2400
+	}
2401
+
2402
+	/**
2403
+	 * Quote regular expression
2404
+	 *
2405
+	 * @param string $what
2406
+	 *
2407
+	 * @return string
2408
+	 */
2409
+	private function pregQuote($what)
2410
+	{
2411
+		return preg_quote($what, '/');
2412
+	}
2413
+
2414
+	/**
2415
+	 * Extract line numbers from buffer
2416
+	 *
2417
+	 * @param string $buffer
2418
+	 */
2419
+	private function extractLineNumbers($buffer)
2420
+	{
2421
+		$this->sourcePositions = [0 => 0];
2422
+		$prev = 0;
2423
+
2424
+		while (($pos = strpos($buffer, "\n", $prev)) !== false) {
2425
+			$this->sourcePositions[] = $pos;
2426
+			$prev = $pos + 1;
2427
+		}
2428
+
2429
+		$this->sourcePositions[] = strlen($buffer);
2430
+
2431
+		if (substr($buffer, -1) !== "\n") {
2432
+			$this->sourcePositions[] = strlen($buffer) + 1;
2433
+		}
2434
+	}
2435
+
2436
+	/**
2437
+	 * Get source line number and column (given character position in the buffer)
2438
+	 *
2439
+	 * @param integer $pos
2440
+	 *
2441
+	 * @return integer
2442
+	 */
2443
+	private function getSourcePosition($pos)
2444
+	{
2445
+		$low = 0;
2446
+		$high = count($this->sourcePositions);
2447
+
2448
+		while ($low < $high) {
2449
+			$mid = (int) (($high + $low) / 2);
2450
+
2451
+			if ($pos < $this->sourcePositions[$mid]) {
2452
+				$high = $mid - 1;
2453
+				continue;
2454
+			}
2455
+
2456
+			if ($pos >= $this->sourcePositions[$mid + 1]) {
2457
+				$low = $mid + 1;
2458
+				continue;
2459
+			}
2460
+
2461
+			return [$mid + 1, $pos - $this->sourcePositions[$mid]];
2462
+		}
2463
+
2464
+		return [$low + 1, $pos - $this->sourcePositions[$low]];
2465
+	}
2466
+
2467
+	/**
2468
+	 * Save internal encoding
2469
+	 */
2470
+	private function saveEncoding()
2471
+	{
2472
+		if (version_compare(PHP_VERSION, '7.2.0') >= 0) {
2473
+			return;
2474
+		}
2475
+
2476
+		$iniDirective = 'mbstring' . '.func_overload'; // deprecated in PHP 7.2
2477
+
2478
+		if (ini_get($iniDirective) & 2) {
2479
+			$this->encoding = mb_internal_encoding();
2480
+
2481
+			mb_internal_encoding('iso-8859-1');
2482
+		}
2483
+	}
2484
+
2485
+	/**
2486
+	 * Restore internal encoding
2487
+	 */
2488
+	private function restoreEncoding()
2489
+	{
2490
+		if ($this->encoding) {
2491
+			mb_internal_encoding($this->encoding);
2492
+		}
2493
+	}
2494 2494
 }
Please login to merge, or discard this patch.
Spacing   +29 added lines, -29 removed lines patch added patch discarded remove patch
@@ -169,7 +169,7 @@  discard block
 block discarded – undo
169 169
             $this->throwParseError();
170 170
         }
171 171
 
172
-        if (! empty($this->env->parent)) {
172
+        if ( ! empty($this->env->parent)) {
173 173
             $this->throwParseError('unclosed block');
174 174
         }
175 175
 
@@ -177,7 +177,7 @@  discard block
 block discarded – undo
177 177
             array_unshift($this->env->children, $this->charset);
178 178
         }
179 179
 
180
-        $this->env->isRoot    = true;
180
+        $this->env->isRoot = true;
181 181
 
182 182
         $this->restoreEncoding();
183 183
 
@@ -330,7 +330,7 @@  discard block
 block discarded – undo
330 330
             ) {
331 331
                 $child = [Type::T_INCLUDE, $mixinName, isset($argValues) ? $argValues : null, null];
332 332
 
333
-                if (! empty($hasBlock)) {
333
+                if ( ! empty($hasBlock)) {
334 334
                     $include = $this->pushSpecialBlock(Type::T_INCLUDE, $s);
335 335
                     $include->child = $child;
336 336
                 } else {
@@ -558,7 +558,7 @@  discard block
 block discarded – undo
558 558
                 $this->valueList($charset) &&
559 559
                 $this->end()
560 560
             ) {
561
-                if (! isset($this->charset)) {
561
+                if ( ! isset($this->charset)) {
562 562
                     $statement = [Type::T_CHARSET, $charset];
563 563
 
564 564
                     list($line, $column) = $this->getSourcePosition($s);
@@ -716,7 +716,7 @@  discard block
 block discarded – undo
716 716
         $b->comments     = [];
717 717
         $b->parent       = $this->env;
718 718
 
719
-        if (! $this->env) {
719
+        if ( ! $this->env) {
720 720
             $b->children = [];
721 721
         } elseif (empty($this->env->children)) {
722 722
             $this->env->children = $this->env->comments;
@@ -786,7 +786,7 @@  discard block
 block discarded – undo
786 786
      */
787 787
     protected function peek($regex, &$out, $from = null)
788 788
     {
789
-        if (! isset($from)) {
789
+        if ( ! isset($from)) {
790 790
             $from = $this->count;
791 791
         }
792 792
 
@@ -840,7 +840,7 @@  discard block
 block discarded – undo
840 840
             }
841 841
         }
842 842
 
843
-        if (! isset($token)) {
843
+        if ( ! isset($token)) {
844 844
             return false;
845 845
         }
846 846
 
@@ -866,7 +866,7 @@  discard block
 block discarded – undo
866 866
      */
867 867
     protected function match($regex, &$out, $eatWhitespace = null)
868 868
     {
869
-        if (! isset($eatWhitespace)) {
869
+        if ( ! isset($eatWhitespace)) {
870 870
             $eatWhitespace = $this->eatWhiteDefault;
871 871
         }
872 872
 
@@ -895,7 +895,7 @@  discard block
 block discarded – undo
895 895
      */
896 896
     protected function literal($what, $eatWhitespace = null)
897 897
     {
898
-        if (! isset($eatWhitespace)) {
898
+        if ( ! isset($eatWhitespace)) {
899 899
             $eatWhitespace = $this->eatWhiteDefault;
900 900
         }
901 901
 
@@ -1115,7 +1115,7 @@  discard block
 block discarded – undo
1115 1115
 
1116 1116
         $keyword = null;
1117 1117
 
1118
-        if (! $this->variable($keyword) || ! $this->literal(':')) {
1118
+        if ( ! $this->variable($keyword) || ! $this->literal(':')) {
1119 1119
             $this->seek($s);
1120 1120
             $keyword = null;
1121 1121
         }
@@ -1179,7 +1179,7 @@  discard block
 block discarded – undo
1179 1179
             $items[] = $value;
1180 1180
 
1181 1181
             if ($delim) {
1182
-                if (! $this->literal($delim)) {
1182
+                if ( ! $this->literal($delim)) {
1183 1183
                     break;
1184 1184
                 }
1185 1185
             }
@@ -1271,7 +1271,7 @@  discard block
 block discarded – undo
1271 1271
                 break;
1272 1272
             }
1273 1273
 
1274
-            if (! $this->value($rhs)) {
1274
+            if ( ! $this->value($rhs)) {
1275 1275
                 break;
1276 1276
             }
1277 1277
 
@@ -1468,7 +1468,7 @@  discard block
 block discarded – undo
1468 1468
             ) {
1469 1469
                 $args = [];
1470 1470
 
1471
-                if (! empty($str)) {
1471
+                if ( ! empty($str)) {
1472 1472
                     $args[] = [null, [Type::T_STRING, '', [$str]]];
1473 1473
                 }
1474 1474
 
@@ -1507,14 +1507,14 @@  discard block
 block discarded – undo
1507 1507
 
1508 1508
             $args[] = $arg;
1509 1509
 
1510
-            if (! $this->literal(',')) {
1510
+            if ( ! $this->literal(',')) {
1511 1511
                 break;
1512 1512
             }
1513 1513
 
1514 1514
             $args[] = [Type::T_STRING, '', [', ']];
1515 1515
         }
1516 1516
 
1517
-        if (! $this->literal(')') || ! count($args)) {
1517
+        if ( ! $this->literal(')') || ! count($args)) {
1518 1518
             $this->seek($s);
1519 1519
 
1520 1520
             return false;
@@ -1555,7 +1555,7 @@  discard block
 block discarded – undo
1555 1555
             if ($this->literal('...')) {
1556 1556
                 $sss = $this->seek();
1557 1557
 
1558
-                if (! $this->literal(')')) {
1558
+                if ( ! $this->literal(')')) {
1559 1559
                     $this->throwParseError('... has to be after the final argument');
1560 1560
                 }
1561 1561
 
@@ -1567,12 +1567,12 @@  discard block
 block discarded – undo
1567 1567
 
1568 1568
             $args[] = $arg;
1569 1569
 
1570
-            if (! $this->literal(',')) {
1570
+            if ( ! $this->literal(',')) {
1571 1571
                 break;
1572 1572
             }
1573 1573
         }
1574 1574
 
1575
-        if (! $this->literal(')')) {
1575
+        if ( ! $this->literal(')')) {
1576 1576
             $this->seek($s);
1577 1577
 
1578 1578
             return false;
@@ -1594,7 +1594,7 @@  discard block
 block discarded – undo
1594 1594
     {
1595 1595
         $s = $this->seek();
1596 1596
 
1597
-        if (! $this->literal('(')) {
1597
+        if ( ! $this->literal('(')) {
1598 1598
             return false;
1599 1599
         }
1600 1600
 
@@ -1607,12 +1607,12 @@  discard block
 block discarded – undo
1607 1607
             $keys[] = $key;
1608 1608
             $values[] = $value;
1609 1609
 
1610
-            if (! $this->literal(',')) {
1610
+            if ( ! $this->literal(',')) {
1611 1611
                 break;
1612 1612
             }
1613 1613
         }
1614 1614
 
1615
-        if (! count($keys) || ! $this->literal(')')) {
1615
+        if ( ! count($keys) || ! $this->literal(')')) {
1616 1616
             $this->seek($s);
1617 1617
 
1618 1618
             return false;
@@ -1830,7 +1830,7 @@  discard block
 block discarded – undo
1830 1830
 
1831 1831
             $tok = $m[2];
1832 1832
 
1833
-            $this->count-= strlen($tok);
1833
+            $this->count -= strlen($tok);
1834 1834
 
1835 1835
             if ($tok === $end && ! $nestingLevel--) {
1836 1836
                 break;
@@ -1847,7 +1847,7 @@  discard block
 block discarded – undo
1847 1847
             }
1848 1848
 
1849 1849
             $content[] = $tok;
1850
-            $this->count+= strlen($tok);
1850
+            $this->count += strlen($tok);
1851 1851
         }
1852 1852
 
1853 1853
         $this->eatWhiteDefault = $oldWhite;
@@ -1884,7 +1884,7 @@  discard block
 block discarded – undo
1884 1884
         if ($this->literal('#{') && $this->valueList($value) && $this->literal('}', false)) {
1885 1885
             if ($lookWhite) {
1886 1886
                 $left = preg_match('/\s/', $this->buffer[$s - 1]) ? ' ' : '';
1887
-                $right = preg_match('/\s/', $this->buffer[$this->count]) ? ' ': '';
1887
+                $right = preg_match('/\s/', $this->buffer[$this->count]) ? ' ' : '';
1888 1888
             } else {
1889 1889
                 $left = $right = false;
1890 1890
             }
@@ -1953,7 +1953,7 @@  discard block
 block discarded – undo
1953 1953
             null,
1954 1954
             $this->count
1955 1955
         )) {
1956
-            if (! empty($m[0])) {
1956
+            if ( ! empty($m[0])) {
1957 1957
                 $parts[] = $m[0];
1958 1958
                 $this->count += strlen($m[0]);
1959 1959
             }
@@ -1981,7 +1981,7 @@  discard block
 block discarded – undo
1981 1981
         while ($this->selector($sel)) {
1982 1982
             $selectors[] = $sel;
1983 1983
 
1984
-            if (! $this->literal(',')) {
1984
+            if ( ! $this->literal(',')) {
1985 1985
                 break;
1986 1986
             }
1987 1987
 
@@ -2135,7 +2135,7 @@  discard block
 block discarded – undo
2135 2135
                 ) {
2136 2136
                     $parts[] = '(';
2137 2137
 
2138
-                    if (! empty($str)) {
2138
+                    if ( ! empty($str)) {
2139 2139
                         $parts[] = $str;
2140 2140
                     }
2141 2141
 
@@ -2156,7 +2156,7 @@  discard block
 block discarded – undo
2156 2156
             ) {
2157 2157
                 $parts[] = '[';
2158 2158
 
2159
-                if (! empty($str)) {
2159
+                if ( ! empty($str)) {
2160 2160
                     $parts[] = $str;
2161 2161
                 }
2162 2162
 
@@ -2374,7 +2374,7 @@  discard block
 block discarded – undo
2374 2374
             $validChars = $allowNewline ? '.' : "[^\n]";
2375 2375
         }
2376 2376
 
2377
-        if (! $this->match('(' . $validChars . '*?)' . $this->pregQuote($what), $m, ! $until)) {
2377
+        if ( ! $this->match('(' . $validChars . '*?)' . $this->pregQuote($what), $m, ! $until)) {
2378 2378
             return false;
2379 2379
         }
2380 2380
 
Please login to merge, or discard this patch.
vendor/leafo/scssphp/src/Compiler.php 2 patches
Indentation   +5191 added lines, -5191 removed lines patch added patch discarded remove patch
@@ -57,5280 +57,5280 @@
 block discarded – undo
57 57
  */
58 58
 class Compiler
59 59
 {
60
-    const LINE_COMMENTS = 1;
61
-    const DEBUG_INFO    = 2;
62
-
63
-    const WITH_RULE     = 1;
64
-    const WITH_MEDIA    = 2;
65
-    const WITH_SUPPORTS = 4;
66
-    const WITH_ALL      = 7;
67
-
68
-    const SOURCE_MAP_NONE   = 0;
69
-    const SOURCE_MAP_INLINE = 1;
70
-    const SOURCE_MAP_FILE   = 2;
71
-
72
-    /**
73
-     * @var array
74
-     */
75
-    static protected $operatorNames = [
76
-        '+'   => 'add',
77
-        '-'   => 'sub',
78
-        '*'   => 'mul',
79
-        '/'   => 'div',
80
-        '%'   => 'mod',
81
-
82
-        '=='  => 'eq',
83
-        '!='  => 'neq',
84
-        '<'   => 'lt',
85
-        '>'   => 'gt',
86
-
87
-        '<='  => 'lte',
88
-        '>='  => 'gte',
89
-        '<=>' => 'cmp',
90
-    ];
91
-
92
-    /**
93
-     * @var array
94
-     */
95
-    static protected $namespaces = [
96
-        'special'  => '%',
97
-        'mixin'    => '@',
98
-        'function' => '^',
99
-    ];
100
-
101
-    static public $true = [Type::T_KEYWORD, 'true'];
102
-    static public $false = [Type::T_KEYWORD, 'false'];
103
-    static public $null = [Type::T_NULL];
104
-    static public $nullString = [Type::T_STRING, '', []];
105
-    static public $defaultValue = [Type::T_KEYWORD, ''];
106
-    static public $selfSelector = [Type::T_SELF];
107
-    static public $emptyList = [Type::T_LIST, '', []];
108
-    static public $emptyMap = [Type::T_MAP, [], []];
109
-    static public $emptyString = [Type::T_STRING, '"', []];
110
-    static public $with = [Type::T_KEYWORD, 'with'];
111
-    static public $without = [Type::T_KEYWORD, 'without'];
112
-
113
-    protected $importPaths = [''];
114
-    protected $importCache = [];
115
-    protected $importedFiles = [];
116
-    protected $userFunctions = [];
117
-    protected $registeredVars = [];
118
-    protected $registeredFeatures = [
119
-        'extend-selector-pseudoclass' => false,
120
-        'at-error'                    => true,
121
-        'units-level-3'               => false,
122
-        'global-variable-shadowing'   => false,
123
-    ];
124
-
125
-    protected $encoding = null;
126
-    protected $lineNumberStyle = null;
127
-
128
-    protected $sourceMap = self::SOURCE_MAP_NONE;
129
-    protected $sourceMapOptions = [];
130
-
131
-    /**
132
-     * @var string|\Leafo\ScssPhp\Formatter
133
-     */
134
-    protected $formatter = 'Leafo\ScssPhp\Formatter\Nested';
135
-
136
-    protected $rootEnv;
137
-    protected $rootBlock;
138
-
139
-    /**
140
-     * @var \Leafo\ScssPhp\Compiler\Environment
141
-     */
142
-    protected $env;
143
-    protected $scope;
144
-    protected $storeEnv;
145
-    protected $charsetSeen;
146
-    protected $sourceNames;
147
-
148
-    private $indentLevel;
149
-    private $commentsSeen;
150
-    private $extends;
151
-    private $extendsMap;
152
-    private $parsedFiles;
153
-    private $parser;
154
-    private $sourceIndex;
155
-    private $sourceLine;
156
-    private $sourceColumn;
157
-    private $stderr;
158
-    private $shouldEvaluate;
159
-    private $ignoreErrors;
160
-
161
-    /**
162
-     * Constructor
163
-     */
164
-    public function __construct()
165
-    {
166
-        $this->parsedFiles = [];
167
-        $this->sourceNames = [];
168
-    }
169
-
170
-    /**
171
-     * Compile scss
172
-     *
173
-     * @api
174
-     *
175
-     * @param string $code
176
-     * @param string $path
177
-     *
178
-     * @return string
179
-     */
180
-    public function compile($code, $path = null)
181
-    {
182
-        $this->indentLevel    = -1;
183
-        $this->commentsSeen   = [];
184
-        $this->extends        = [];
185
-        $this->extendsMap     = [];
186
-        $this->sourceIndex    = null;
187
-        $this->sourceLine     = null;
188
-        $this->sourceColumn   = null;
189
-        $this->env            = null;
190
-        $this->scope          = null;
191
-        $this->storeEnv       = null;
192
-        $this->charsetSeen    = null;
193
-        $this->shouldEvaluate = null;
194
-        $this->stderr         = fopen('php://stderr', 'w');
195
-
196
-        $this->parser = $this->parserFactory($path);
197
-        $tree = $this->parser->parse($code);
198
-        $this->parser = null;
199
-
200
-        $this->formatter = new $this->formatter();
201
-        $this->rootBlock = null;
202
-        $this->rootEnv   = $this->pushEnv($tree);
203
-
204
-        $this->injectVariables($this->registeredVars);
205
-        $this->compileRoot($tree);
206
-        $this->popEnv();
207
-
208
-        $sourceMapGenerator = null;
209
-
210
-        if ($this->sourceMap) {
211
-            if (is_object($this->sourceMap) && $this->sourceMap instanceof SourceMapGenerator) {
212
-                $sourceMapGenerator = $this->sourceMap;
213
-                $this->sourceMap = self::SOURCE_MAP_FILE;
214
-            } elseif ($this->sourceMap !== self::SOURCE_MAP_NONE) {
215
-                $sourceMapGenerator = new SourceMapGenerator($this->sourceMapOptions);
216
-            }
217
-        }
218
-
219
-        $out = $this->formatter->format($this->scope, $sourceMapGenerator);
220
-
221
-        if (! empty($out) && $this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) {
222
-            $sourceMap    = $sourceMapGenerator->generateJson();
223
-            $sourceMapUrl = null;
224
-
225
-            switch ($this->sourceMap) {
226
-                case self::SOURCE_MAP_INLINE:
227
-                    $sourceMapUrl = sprintf('data:application/json,%s', Util::encodeURIComponent($sourceMap));
228
-                    break;
229
-
230
-                case self::SOURCE_MAP_FILE:
231
-                    $sourceMapUrl = $sourceMapGenerator->saveMap($sourceMap);
232
-                    break;
233
-            }
234
-
235
-            $out .= sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl);
236
-        }
237
-
238
-        return $out;
239
-    }
240
-
241
-    /**
242
-     * Instantiate parser
243
-     *
244
-     * @param string $path
245
-     *
246
-     * @return \Leafo\ScssPhp\Parser
247
-     */
248
-    protected function parserFactory($path)
249
-    {
250
-        $parser = new Parser($path, count($this->sourceNames), $this->encoding);
251
-
252
-        $this->sourceNames[] = $path;
253
-        $this->addParsedFile($path);
254
-
255
-        return $parser;
256
-    }
257
-
258
-    /**
259
-     * Is self extend?
260
-     *
261
-     * @param array $target
262
-     * @param array $origin
263
-     *
264
-     * @return boolean
265
-     */
266
-    protected function isSelfExtend($target, $origin)
267
-    {
268
-        foreach ($origin as $sel) {
269
-            if (in_array($target, $sel)) {
270
-                return true;
271
-            }
272
-        }
273
-
274
-        return false;
275
-    }
276
-
277
-    /**
278
-     * Push extends
279
-     *
280
-     * @param array     $target
281
-     * @param array     $origin
282
-     * @param \stdClass $block
283
-     */
284
-    protected function pushExtends($target, $origin, $block)
285
-    {
286
-        if ($this->isSelfExtend($target, $origin)) {
287
-            return;
288
-        }
289
-
290
-        $i = count($this->extends);
291
-        $this->extends[] = [$target, $origin, $block];
292
-
293
-        foreach ($target as $part) {
294
-            if (isset($this->extendsMap[$part])) {
295
-                $this->extendsMap[$part][] = $i;
296
-            } else {
297
-                $this->extendsMap[$part] = [$i];
298
-            }
299
-        }
300
-    }
301
-
302
-    /**
303
-     * Make output block
304
-     *
305
-     * @param string $type
306
-     * @param array  $selectors
307
-     *
308
-     * @return \Leafo\ScssPhp\Formatter\OutputBlock
309
-     */
310
-    protected function makeOutputBlock($type, $selectors = null)
311
-    {
312
-        $out = new OutputBlock;
313
-        $out->type         = $type;
314
-        $out->lines        = [];
315
-        $out->children     = [];
316
-        $out->parent       = $this->scope;
317
-        $out->selectors    = $selectors;
318
-        $out->depth        = $this->env->depth;
319
-        $out->sourceName   = $this->env->block->sourceName;
320
-        $out->sourceLine   = $this->env->block->sourceLine;
321
-        $out->sourceColumn = $this->env->block->sourceColumn;
322
-
323
-        return $out;
324
-    }
325
-
326
-    /**
327
-     * Compile root
328
-     *
329
-     * @param \Leafo\ScssPhp\Block $rootBlock
330
-     */
331
-    protected function compileRoot(Block $rootBlock)
332
-    {
333
-        $this->rootBlock = $this->scope = $this->makeOutputBlock(Type::T_ROOT);
334
-
335
-        $this->compileChildrenNoReturn($rootBlock->children, $this->scope);
336
-        $this->flattenSelectors($this->scope);
337
-        $this->missingSelectors();
338
-    }
339
-
340
-    /**
341
-     * Report missing selectors
342
-     */
343
-    protected function missingSelectors()
344
-    {
345
-        foreach ($this->extends as $extend) {
346
-            if (isset($extend[3])) {
347
-                continue;
348
-            }
349
-
350
-            list($target, $origin, $block) = $extend;
351
-
352
-            // ignore if !optional
353
-            if ($block[2]) {
354
-                continue;
355
-            }
356
-
357
-            $target = implode(' ', $target);
358
-            $origin = $this->collapseSelectors($origin);
359
-
360
-            $this->sourceLine = $block[Parser::SOURCE_LINE];
361
-            $this->throwError("\"$origin\" failed to @extend \"$target\". The selector \"$target\" was not found.");
362
-        }
363
-    }
364
-
365
-    /**
366
-     * Flatten selectors
367
-     *
368
-     * @param \Leafo\ScssPhp\Formatter\OutputBlock $block
369
-     * @param string                               $parentKey
370
-     */
371
-    protected function flattenSelectors(OutputBlock $block, $parentKey = null)
372
-    {
373
-        if ($block->selectors) {
374
-            $selectors = [];
375
-
376
-            foreach ($block->selectors as $s) {
377
-                $selectors[] = $s;
378
-
379
-                if (! is_array($s)) {
380
-                    continue;
381
-                }
382
-
383
-                // check extends
384
-                if (! empty($this->extendsMap)) {
385
-                    $this->matchExtends($s, $selectors);
386
-
387
-                    // remove duplicates
388
-                    array_walk($selectors, function (&$value) {
389
-                        $value = serialize($value);
390
-                    });
391
-
392
-                    $selectors = array_unique($selectors);
393
-
394
-                    array_walk($selectors, function (&$value) {
395
-                        $value = unserialize($value);
396
-                    });
397
-                }
398
-            }
399
-
400
-            $block->selectors = [];
401
-            $placeholderSelector = false;
402
-
403
-            foreach ($selectors as $selector) {
404
-                if ($this->hasSelectorPlaceholder($selector)) {
405
-                    $placeholderSelector = true;
406
-                    continue;
407
-                }
408
-
409
-                $block->selectors[] = $this->compileSelector($selector);
410
-            }
411
-
412
-            if ($placeholderSelector && 0 === count($block->selectors) && null !== $parentKey) {
413
-                unset($block->parent->children[$parentKey]);
414
-
415
-                return;
416
-            }
417
-        }
418
-
419
-        foreach ($block->children as $key => $child) {
420
-            $this->flattenSelectors($child, $key);
421
-        }
422
-    }
423
-
424
-    /**
425
-     * Match extends
426
-     *
427
-     * @param array   $selector
428
-     * @param array   $out
429
-     * @param integer $from
430
-     * @param boolean $initial
431
-     */
432
-    protected function matchExtends($selector, &$out, $from = 0, $initial = true)
433
-    {
434
-        foreach ($selector as $i => $part) {
435
-            if ($i < $from) {
436
-                continue;
437
-            }
438
-
439
-            if ($this->matchExtendsSingle($part, $origin)) {
440
-                $after = array_slice($selector, $i + 1);
441
-                $before = array_slice($selector, 0, $i);
442
-
443
-                list($before, $nonBreakableBefore) = $this->extractRelationshipFromFragment($before);
444
-
445
-                foreach ($origin as $new) {
446
-                    $k = 0;
447
-
448
-                    // remove shared parts
449
-                    if ($initial) {
450
-                        while ($k < $i && isset($new[$k]) && $selector[$k] === $new[$k]) {
451
-                            $k++;
452
-                        }
453
-                    }
454
-
455
-                    $replacement = [];
456
-                    $tempReplacement = $k > 0 ? array_slice($new, $k) : $new;
457
-
458
-                    for ($l = count($tempReplacement) - 1; $l >= 0; $l--) {
459
-                        $slice = $tempReplacement[$l];
460
-                        array_unshift($replacement, $slice);
461
-
462
-                        if (! $this->isImmediateRelationshipCombinator(end($slice))) {
463
-                            break;
464
-                        }
465
-                    }
466
-
467
-                    $afterBefore = $l != 0 ? array_slice($tempReplacement, 0, $l) : [];
468
-
469
-                    // Merge shared direct relationships.
470
-                    $mergedBefore = $this->mergeDirectRelationships($afterBefore, $nonBreakableBefore);
471
-
472
-                    $result = array_merge(
473
-                        $before,
474
-                        $mergedBefore,
475
-                        $replacement,
476
-                        $after
477
-                    );
478
-
479
-                    if ($result === $selector) {
480
-                        continue;
481
-                    }
482
-
483
-                    $out[] = $result;
484
-
485
-                    // recursively check for more matches
486
-                    $this->matchExtends($result, $out, count($before) + count($mergedBefore), false);
487
-
488
-                    // selector sequence merging
489
-                    if (! empty($before) && count($new) > 1) {
490
-                        $sharedParts = $k > 0 ? array_slice($before, 0, $k) : [];
491
-                        $postSharedParts = $k > 0 ? array_slice($before, $k) : $before;
492
-
493
-                        list($injectBetweenSharedParts, $nonBreakable2) = $this->extractRelationshipFromFragment($afterBefore);
494
-
495
-                        $result2 = array_merge(
496
-                            $sharedParts,
497
-                            $injectBetweenSharedParts,
498
-                            $postSharedParts,
499
-                            $nonBreakable2,
500
-                            $nonBreakableBefore,
501
-                            $replacement,
502
-                            $after
503
-                        );
504
-
505
-                        $out[] = $result2;
506
-                    }
507
-                }
508
-            }
509
-        }
510
-    }
511
-
512
-    /**
513
-     * Match extends single
514
-     *
515
-     * @param array $rawSingle
516
-     * @param array $outOrigin
517
-     *
518
-     * @return boolean
519
-     */
520
-    protected function matchExtendsSingle($rawSingle, &$outOrigin)
521
-    {
522
-        $counts = [];
523
-        $single = [];
524
-
525
-        foreach ($rawSingle as $part) {
526
-            // matches Number
527
-            if (! is_string($part)) {
528
-                return false;
529
-            }
530
-
531
-            if (! preg_match('/^[\[.:#%]/', $part) && count($single)) {
532
-                $single[count($single) - 1] .= $part;
533
-            } else {
534
-                $single[] = $part;
535
-            }
536
-        }
537
-
538
-        $extendingDecoratedTag = false;
539
-
540
-        if (count($single) > 1) {
541
-            $matches = null;
542
-            $extendingDecoratedTag = preg_match('/^[a-z0-9]+$/i', $single[0], $matches) ? $matches[0] : false;
543
-        }
544
-
545
-        foreach ($single as $part) {
546
-            if (isset($this->extendsMap[$part])) {
547
-                foreach ($this->extendsMap[$part] as $idx) {
548
-                    $counts[$idx] = isset($counts[$idx]) ? $counts[$idx] + 1 : 1;
549
-                }
550
-            }
551
-        }
552
-
553
-        $outOrigin = [];
554
-        $found = false;
555
-
556
-        foreach ($counts as $idx => $count) {
557
-            list($target, $origin, /* $block */) = $this->extends[$idx];
558
-
559
-            // check count
560
-            if ($count !== count($target)) {
561
-                continue;
562
-            }
563
-
564
-            $this->extends[$idx][3] = true;
565
-
566
-            $rem = array_diff($single, $target);
567
-
568
-            foreach ($origin as $j => $new) {
569
-                // prevent infinite loop when target extends itself
570
-                if ($this->isSelfExtend($single, $origin)) {
571
-                    return false;
572
-                }
573
-
574
-                $replacement = end($new);
575
-
576
-                // Extending a decorated tag with another tag is not possible.
577
-                if ($extendingDecoratedTag && $replacement[0] != $extendingDecoratedTag &&
578
-                    preg_match('/^[a-z0-9]+$/i', $replacement[0])
579
-                ) {
580
-                    unset($origin[$j]);
581
-                    continue;
582
-                }
583
-
584
-                $combined = $this->combineSelectorSingle($replacement, $rem);
585
-
586
-                if (count(array_diff($combined, $origin[$j][count($origin[$j]) - 1]))) {
587
-                    $origin[$j][count($origin[$j]) - 1] = $combined;
588
-                }
589
-            }
590
-
591
-            $outOrigin = array_merge($outOrigin, $origin);
592
-
593
-            $found = true;
594
-        }
595
-
596
-        return $found;
597
-    }
598
-
599
-
600
-    /**
601
-     * Extract a relationship from the fragment.
602
-     *
603
-     * When extracting the last portion of a selector we will be left with a
604
-     * fragment which may end with a direction relationship combinator. This
605
-     * method will extract the relationship fragment and return it along side
606
-     * the rest.
607
-     *
608
-     * @param array $fragment The selector fragment maybe ending with a direction relationship combinator.
609
-     * @return array The selector without the relationship fragment if any, the relationship fragment.
610
-     */
611
-    protected function extractRelationshipFromFragment(array $fragment)
612
-    {
613
-        $parents = [];
614
-        $children = [];
615
-        $j = $i = count($fragment);
616
-
617
-        for (;;) {
618
-            $children = $j != $i ? array_slice($fragment, $j, $i - $j) : [];
619
-            $parents = array_slice($fragment, 0, $j);
620
-            $slice = end($parents);
621
-
622
-            if (empty($slice) || ! $this->isImmediateRelationshipCombinator($slice[0])) {
623
-                break;
624
-            }
625
-
626
-            $j -= 2;
627
-        }
628
-
629
-        return [$parents, $children];
630
-    }
631
-
632
-    /**
633
-     * Combine selector single
634
-     *
635
-     * @param array $base
636
-     * @param array $other
637
-     *
638
-     * @return array
639
-     */
640
-    protected function combineSelectorSingle($base, $other)
641
-    {
642
-        $tag = [];
643
-        $out = [];
644
-        $wasTag = true;
645
-
646
-        foreach ([$base, $other] as $single) {
647
-            foreach ($single as $part) {
648
-                if (preg_match('/^[\[.:#]/', $part)) {
649
-                    $out[] = $part;
650
-                    $wasTag = false;
651
-                } elseif (preg_match('/^[^_-]/', $part)) {
652
-                    $tag[] = $part;
653
-                    $wasTag = true;
654
-                } elseif ($wasTag) {
655
-                    $tag[count($tag) - 1] .= $part;
656
-                } else {
657
-                    $out[count($out) - 1] .= $part;
658
-                }
659
-            }
660
-        }
661
-
662
-        if (count($tag)) {
663
-            array_unshift($out, $tag[0]);
664
-        }
665
-
666
-        return $out;
667
-    }
668
-
669
-    /**
670
-     * Compile media
671
-     *
672
-     * @param \Leafo\ScssPhp\Block $media
673
-     */
674
-    protected function compileMedia(Block $media)
675
-    {
676
-        $this->pushEnv($media);
677
-
678
-        $mediaQuery = $this->compileMediaQuery($this->multiplyMedia($this->env));
679
-
680
-        if (! empty($mediaQuery)) {
681
-            $this->scope = $this->makeOutputBlock(Type::T_MEDIA, [$mediaQuery]);
682
-
683
-            $parentScope = $this->mediaParent($this->scope);
684
-            $parentScope->children[] = $this->scope;
685
-
686
-            // top level properties in a media cause it to be wrapped
687
-            $needsWrap = false;
688
-
689
-            foreach ($media->children as $child) {
690
-                $type = $child[0];
691
-
692
-                if ($type !== Type::T_BLOCK &&
693
-                    $type !== Type::T_MEDIA &&
694
-                    $type !== Type::T_DIRECTIVE &&
695
-                    $type !== Type::T_IMPORT
696
-                ) {
697
-                    $needsWrap = true;
698
-                    break;
699
-                }
700
-            }
701
-
702
-            if ($needsWrap) {
703
-                $wrapped = new Block;
704
-                $wrapped->sourceName   = $media->sourceName;
705
-                $wrapped->sourceIndex  = $media->sourceIndex;
706
-                $wrapped->sourceLine   = $media->sourceLine;
707
-                $wrapped->sourceColumn = $media->sourceColumn;
708
-                $wrapped->selectors    = [];
709
-                $wrapped->comments     = [];
710
-                $wrapped->parent       = $media;
711
-                $wrapped->children     = $media->children;
712
-
713
-                $media->children = [[Type::T_BLOCK, $wrapped]];
714
-            }
715
-
716
-            $this->compileChildrenNoReturn($media->children, $this->scope);
717
-
718
-            $this->scope = $this->scope->parent;
719
-        }
720
-
721
-        $this->popEnv();
722
-    }
723
-
724
-    /**
725
-     * Media parent
726
-     *
727
-     * @param \Leafo\ScssPhp\Formatter\OutputBlock $scope
728
-     *
729
-     * @return \Leafo\ScssPhp\Formatter\OutputBlock
730
-     */
731
-    protected function mediaParent(OutputBlock $scope)
732
-    {
733
-        while (! empty($scope->parent)) {
734
-            if (! empty($scope->type) && $scope->type !== Type::T_MEDIA) {
735
-                break;
736
-            }
737
-
738
-            $scope = $scope->parent;
739
-        }
740
-
741
-        return $scope;
742
-    }
743
-
744
-    /**
745
-     * Compile directive
746
-     *
747
-     * @param \Leafo\ScssPhp\Block $block
748
-     */
749
-    protected function compileDirective(Block $block)
750
-    {
751
-        $s = '@' . $block->name;
752
-
753
-        if (! empty($block->value)) {
754
-            $s .= ' ' . $this->compileValue($block->value);
755
-        }
756
-
757
-        if ($block->name === 'keyframes' || substr($block->name, -10) === '-keyframes') {
758
-            $this->compileKeyframeBlock($block, [$s]);
759
-        } else {
760
-            $this->compileNestedBlock($block, [$s]);
761
-        }
762
-    }
763
-
764
-    /**
765
-     * Compile at-root
766
-     *
767
-     * @param \Leafo\ScssPhp\Block $block
768
-     */
769
-    protected function compileAtRoot(Block $block)
770
-    {
771
-        $env     = $this->pushEnv($block);
772
-        $envs    = $this->compactEnv($env);
773
-        $without = isset($block->with) ? $this->compileWith($block->with) : static::WITH_RULE;
774
-
775
-        // wrap inline selector
776
-        if ($block->selector) {
777
-            $wrapped = new Block;
778
-            $wrapped->sourceName   = $block->sourceName;
779
-            $wrapped->sourceIndex  = $block->sourceIndex;
780
-            $wrapped->sourceLine   = $block->sourceLine;
781
-            $wrapped->sourceColumn = $block->sourceColumn;
782
-            $wrapped->selectors    = $block->selector;
783
-            $wrapped->comments     = [];
784
-            $wrapped->parent       = $block;
785
-            $wrapped->children     = $block->children;
786
-
787
-            $block->children = [[Type::T_BLOCK, $wrapped]];
788
-        }
789
-
790
-        $this->env = $this->filterWithout($envs, $without);
791
-        $newBlock  = $this->spliceTree($envs, $block, $without);
792
-
793
-        $saveScope   = $this->scope;
794
-        $this->scope = $this->rootBlock;
795
-
796
-        $this->compileChild($newBlock, $this->scope);
797
-
798
-        $this->scope = $saveScope;
799
-        $this->env   = $this->extractEnv($envs);
800
-
801
-        $this->popEnv();
802
-    }
803
-
804
-    /**
805
-     * Splice parse tree
806
-     *
807
-     * @param array                $envs
808
-     * @param \Leafo\ScssPhp\Block $block
809
-     * @param integer              $without
810
-     *
811
-     * @return array
812
-     */
813
-    private function spliceTree($envs, Block $block, $without)
814
-    {
815
-        $newBlock = null;
816
-
817
-        foreach ($envs as $e) {
818
-            if (! isset($e->block)) {
819
-                continue;
820
-            }
821
-
822
-            if ($e->block === $block) {
823
-                continue;
824
-            }
825
-
826
-            if (isset($e->block->type) && $e->block->type === Type::T_AT_ROOT) {
827
-                continue;
828
-            }
829
-
830
-            if ($e->block && $this->isWithout($without, $e->block)) {
831
-                continue;
832
-            }
833
-
834
-            $b = new Block;
835
-            $b->sourceName   = $e->block->sourceName;
836
-            $b->sourceIndex  = $e->block->sourceIndex;
837
-            $b->sourceLine   = $e->block->sourceLine;
838
-            $b->sourceColumn = $e->block->sourceColumn;
839
-            $b->selectors    = [];
840
-            $b->comments     = $e->block->comments;
841
-            $b->parent       = null;
842
-
843
-            if ($newBlock) {
844
-                $type = isset($newBlock->type) ? $newBlock->type : Type::T_BLOCK;
845
-
846
-                $b->children = [[$type, $newBlock]];
847
-
848
-                $newBlock->parent = $b;
849
-            } elseif (count($block->children)) {
850
-                foreach ($block->children as $child) {
851
-                    if ($child[0] === Type::T_BLOCK) {
852
-                        $child[1]->parent = $b;
853
-                    }
854
-                }
855
-
856
-                $b->children = $block->children;
857
-            }
858
-
859
-            if (isset($e->block->type)) {
860
-                $b->type = $e->block->type;
861
-            }
862
-
863
-            if (isset($e->block->name)) {
864
-                $b->name = $e->block->name;
865
-            }
866
-
867
-            if (isset($e->block->queryList)) {
868
-                $b->queryList = $e->block->queryList;
869
-            }
870
-
871
-            if (isset($e->block->value)) {
872
-                $b->value = $e->block->value;
873
-            }
874
-
875
-            $newBlock = $b;
876
-        }
877
-
878
-        $type = isset($newBlock->type) ? $newBlock->type : Type::T_BLOCK;
879
-
880
-        return [$type, $newBlock];
881
-    }
882
-
883
-    /**
884
-     * Compile @at-root's with: inclusion / without: exclusion into filter flags
885
-     *
886
-     * @param array $with
887
-     *
888
-     * @return integer
889
-     */
890
-    private function compileWith($with)
891
-    {
892
-        static $mapping = [
893
-            'rule'     => self::WITH_RULE,
894
-            'media'    => self::WITH_MEDIA,
895
-            'supports' => self::WITH_SUPPORTS,
896
-            'all'      => self::WITH_ALL,
897
-        ];
898
-
899
-        // exclude selectors by default
900
-        $without = static::WITH_RULE;
901
-
902
-        if ($this->libMapHasKey([$with, static::$with])) {
903
-            $without = static::WITH_ALL;
904
-
905
-            $list = $this->coerceList($this->libMapGet([$with, static::$with]));
906
-
907
-            foreach ($list[2] as $item) {
908
-                $keyword = $this->compileStringContent($this->coerceString($item));
909
-
910
-                if (array_key_exists($keyword, $mapping)) {
911
-                    $without &= ~($mapping[$keyword]);
912
-                }
913
-            }
914
-        }
915
-
916
-        if ($this->libMapHasKey([$with, static::$without])) {
917
-            $without = 0;
918
-
919
-            $list = $this->coerceList($this->libMapGet([$with, static::$without]));
920
-
921
-            foreach ($list[2] as $item) {
922
-                $keyword = $this->compileStringContent($this->coerceString($item));
923
-
924
-                if (array_key_exists($keyword, $mapping)) {
925
-                    $without |= $mapping[$keyword];
926
-                }
927
-            }
928
-        }
929
-
930
-        return $without;
931
-    }
932
-
933
-    /**
934
-     * Filter env stack
935
-     *
936
-     * @param array   $envs
937
-     * @param integer $without
938
-     *
939
-     * @return \Leafo\ScssPhp\Compiler\Environment
940
-     */
941
-    private function filterWithout($envs, $without)
942
-    {
943
-        $filtered = [];
944
-
945
-        foreach ($envs as $e) {
946
-            if ($e->block && $this->isWithout($without, $e->block)) {
947
-                continue;
948
-            }
949
-
950
-            $filtered[] = $e;
951
-        }
952
-
953
-        return $this->extractEnv($filtered);
954
-    }
955
-
956
-    /**
957
-     * Filter WITH rules
958
-     *
959
-     * @param integer              $without
960
-     * @param \Leafo\ScssPhp\Block $block
961
-     *
962
-     * @return boolean
963
-     */
964
-    private function isWithout($without, Block $block)
965
-    {
966
-        if ((($without & static::WITH_RULE) && isset($block->selectors)) ||
967
-            (($without & static::WITH_MEDIA) &&
968
-                isset($block->type) && $block->type === Type::T_MEDIA) ||
969
-            (($without & static::WITH_SUPPORTS) &&
970
-                isset($block->type) && $block->type === Type::T_DIRECTIVE &&
971
-                isset($block->name) && $block->name === 'supports')
972
-        ) {
973
-            return true;
974
-        }
975
-
976
-        return false;
977
-    }
978
-
979
-    /**
980
-     * Compile keyframe block
981
-     *
982
-     * @param \Leafo\ScssPhp\Block $block
983
-     * @param array                $selectors
984
-     */
985
-    protected function compileKeyframeBlock(Block $block, $selectors)
986
-    {
987
-        $env = $this->pushEnv($block);
988
-
989
-        $envs = $this->compactEnv($env);
990
-
991
-        $this->env = $this->extractEnv(array_filter($envs, function (Environment $e) {
992
-            return ! isset($e->block->selectors);
993
-        }));
994
-
995
-        $this->scope = $this->makeOutputBlock($block->type, $selectors);
996
-        $this->scope->depth = 1;
997
-        $this->scope->parent->children[] = $this->scope;
998
-
999
-        $this->compileChildrenNoReturn($block->children, $this->scope);
1000
-
1001
-        $this->scope = $this->scope->parent;
1002
-        $this->env   = $this->extractEnv($envs);
1003
-
1004
-        $this->popEnv();
1005
-    }
1006
-
1007
-    /**
1008
-     * Compile nested block
1009
-     *
1010
-     * @param \Leafo\ScssPhp\Block $block
1011
-     * @param array                $selectors
1012
-     */
1013
-    protected function compileNestedBlock(Block $block, $selectors)
1014
-    {
1015
-        $this->pushEnv($block);
1016
-
1017
-        $this->scope = $this->makeOutputBlock($block->type, $selectors);
1018
-        $this->scope->parent->children[] = $this->scope;
1019
-
1020
-        $this->compileChildrenNoReturn($block->children, $this->scope);
1021
-
1022
-        $this->scope = $this->scope->parent;
1023
-
1024
-        $this->popEnv();
1025
-    }
1026
-
1027
-    /**
1028
-     * Recursively compiles a block.
1029
-     *
1030
-     * A block is analogous to a CSS block in most cases. A single SCSS document
1031
-     * is encapsulated in a block when parsed, but it does not have parent tags
1032
-     * so all of its children appear on the root level when compiled.
1033
-     *
1034
-     * Blocks are made up of selectors and children.
1035
-     *
1036
-     * The children of a block are just all the blocks that are defined within.
1037
-     *
1038
-     * Compiling the block involves pushing a fresh environment on the stack,
1039
-     * and iterating through the props, compiling each one.
1040
-     *
1041
-     * @see Compiler::compileChild()
1042
-     *
1043
-     * @param \Leafo\ScssPhp\Block $block
1044
-     */
1045
-    protected function compileBlock(Block $block)
1046
-    {
1047
-        $env = $this->pushEnv($block);
1048
-        $env->selectors = $this->evalSelectors($block->selectors);
1049
-
1050
-        $out = $this->makeOutputBlock(null);
1051
-
1052
-        if (isset($this->lineNumberStyle) && count($env->selectors) && count($block->children)) {
1053
-            $annotation = $this->makeOutputBlock(Type::T_COMMENT);
1054
-            $annotation->depth = 0;
1055
-
1056
-            $file = $this->sourceNames[$block->sourceIndex];
1057
-            $line = $block->sourceLine;
1058
-
1059
-            switch ($this->lineNumberStyle) {
1060
-                case static::LINE_COMMENTS:
1061
-                    $annotation->lines[] = '/* line ' . $line
1062
-                                         . ($file ? ', ' . $file : '')
1063
-                                         . ' */';
1064
-                    break;
1065
-
1066
-                case static::DEBUG_INFO:
1067
-                    $annotation->lines[] = '@media -sass-debug-info{'
1068
-                                         . ($file ? 'filename{font-family:"' . $file . '"}' : '')
1069
-                                         . 'line{font-family:' . $line . '}}';
1070
-                    break;
1071
-            }
1072
-
1073
-            $this->scope->children[] = $annotation;
1074
-        }
1075
-
1076
-        $this->scope->children[] = $out;
1077
-
1078
-        if (count($block->children)) {
1079
-            $out->selectors = $this->multiplySelectors($env);
1080
-
1081
-            $this->compileChildrenNoReturn($block->children, $out);
1082
-        }
1083
-
1084
-        $this->formatter->stripSemicolon($out->lines);
1085
-
1086
-        $this->popEnv();
1087
-    }
1088
-
1089
-    /**
1090
-     * Compile root level comment
1091
-     *
1092
-     * @param array $block
1093
-     */
1094
-    protected function compileComment($block)
1095
-    {
1096
-        $out = $this->makeOutputBlock(Type::T_COMMENT);
1097
-        $out->lines[] = $block[1];
1098
-        $this->scope->children[] = $out;
1099
-    }
1100
-
1101
-    /**
1102
-     * Evaluate selectors
1103
-     *
1104
-     * @param array $selectors
1105
-     *
1106
-     * @return array
1107
-     */
1108
-    protected function evalSelectors($selectors)
1109
-    {
1110
-        $this->shouldEvaluate = false;
1111
-
1112
-        $selectors = array_map([$this, 'evalSelector'], $selectors);
1113
-
1114
-        // after evaluating interpolates, we might need a second pass
1115
-        if ($this->shouldEvaluate) {
1116
-            $buffer = $this->collapseSelectors($selectors);
1117
-            $parser = $this->parserFactory(__METHOD__);
1118
-
1119
-            if ($parser->parseSelector($buffer, $newSelectors)) {
1120
-                $selectors = array_map([$this, 'evalSelector'], $newSelectors);
1121
-            }
1122
-        }
1123
-
1124
-        return $selectors;
1125
-    }
1126
-
1127
-    /**
1128
-     * Evaluate selector
1129
-     *
1130
-     * @param array $selector
1131
-     *
1132
-     * @return array
1133
-     */
1134
-    protected function evalSelector($selector)
1135
-    {
1136
-        return array_map([$this, 'evalSelectorPart'], $selector);
1137
-    }
1138
-
1139
-    /**
1140
-     * Evaluate selector part; replaces all the interpolates, stripping quotes
1141
-     *
1142
-     * @param array $part
1143
-     *
1144
-     * @return array
1145
-     */
1146
-    protected function evalSelectorPart($part)
1147
-    {
1148
-        foreach ($part as &$p) {
1149
-            if (is_array($p) && ($p[0] === Type::T_INTERPOLATE || $p[0] === Type::T_STRING)) {
1150
-                $p = $this->compileValue($p);
1151
-
1152
-                // force re-evaluation
1153
-                if (strpos($p, '&') !== false || strpos($p, ',') !== false) {
1154
-                    $this->shouldEvaluate = true;
1155
-                }
1156
-            } elseif (is_string($p) && strlen($p) >= 2 &&
1157
-                ($first = $p[0]) && ($first === '"' || $first === "'") &&
1158
-                substr($p, -1) === $first
1159
-            ) {
1160
-                $p = substr($p, 1, -1);
1161
-            }
1162
-        }
1163
-
1164
-        return $this->flattenSelectorSingle($part);
1165
-    }
1166
-
1167
-    /**
1168
-     * Collapse selectors
1169
-     *
1170
-     * @param array $selectors
1171
-     *
1172
-     * @return string
1173
-     */
1174
-    protected function collapseSelectors($selectors)
1175
-    {
1176
-        $parts = [];
1177
-
1178
-        foreach ($selectors as $selector) {
1179
-            $output = '';
1180
-
1181
-            array_walk_recursive(
1182
-                $selector,
1183
-                function ($value, $key) use (&$output) {
1184
-                    $output .= $value;
1185
-                }
1186
-            );
1187
-
1188
-            $parts[] = $output;
1189
-        }
1190
-
1191
-        return implode(', ', $parts);
1192
-    }
1193
-
1194
-    /**
1195
-     * Flatten selector single; joins together .classes and #ids
1196
-     *
1197
-     * @param array $single
1198
-     *
1199
-     * @return array
1200
-     */
1201
-    protected function flattenSelectorSingle($single)
1202
-    {
1203
-        $joined = [];
1204
-
1205
-        foreach ($single as $part) {
1206
-            if (empty($joined) ||
1207
-                ! is_string($part) ||
1208
-                preg_match('/[\[.:#%]/', $part)
1209
-            ) {
1210
-                $joined[] = $part;
1211
-                continue;
1212
-            }
1213
-
1214
-            if (is_array(end($joined))) {
1215
-                $joined[] = $part;
1216
-            } else {
1217
-                $joined[count($joined) - 1] .= $part;
1218
-            }
1219
-        }
1220
-
1221
-        return $joined;
1222
-    }
1223
-
1224
-    /**
1225
-     * Compile selector to string; self(&) should have been replaced by now
1226
-     *
1227
-     * @param string|array $selector
1228
-     *
1229
-     * @return string
1230
-     */
1231
-    protected function compileSelector($selector)
1232
-    {
1233
-        if (! is_array($selector)) {
1234
-            return $selector; // media and the like
1235
-        }
1236
-
1237
-        return implode(
1238
-            ' ',
1239
-            array_map(
1240
-                [$this, 'compileSelectorPart'],
1241
-                $selector
1242
-            )
1243
-        );
1244
-    }
1245
-
1246
-    /**
1247
-     * Compile selector part
1248
-     *
1249
-     * @param array $piece
1250
-     *
1251
-     * @return string
1252
-     */
1253
-    protected function compileSelectorPart($piece)
1254
-    {
1255
-        foreach ($piece as &$p) {
1256
-            if (! is_array($p)) {
1257
-                continue;
1258
-            }
1259
-
1260
-            switch ($p[0]) {
1261
-                case Type::T_SELF:
1262
-                    $p = '&';
1263
-                    break;
1264
-
1265
-                default:
1266
-                    $p = $this->compileValue($p);
1267
-                    break;
1268
-            }
1269
-        }
1270
-
1271
-        return implode($piece);
1272
-    }
1273
-
1274
-    /**
1275
-     * Has selector placeholder?
1276
-     *
1277
-     * @param array $selector
1278
-     *
1279
-     * @return boolean
1280
-     */
1281
-    protected function hasSelectorPlaceholder($selector)
1282
-    {
1283
-        if (! is_array($selector)) {
1284
-            return false;
1285
-        }
1286
-
1287
-        foreach ($selector as $parts) {
1288
-            foreach ($parts as $part) {
1289
-                if (strlen($part) && '%' === $part[0]) {
1290
-                    return true;
1291
-                }
1292
-            }
1293
-        }
1294
-
1295
-        return false;
1296
-    }
1297
-
1298
-    /**
1299
-     * Compile children and return result
1300
-     *
1301
-     * @param array                                $stms
1302
-     * @param \Leafo\ScssPhp\Formatter\OutputBlock $out
1303
-     *
1304
-     * @return array
1305
-     */
1306
-    protected function compileChildren($stms, OutputBlock $out)
1307
-    {
1308
-        foreach ($stms as $stm) {
1309
-            $ret = $this->compileChild($stm, $out);
1310
-
1311
-            if (isset($ret)) {
1312
-                return $ret;
1313
-            }
1314
-        }
1315
-    }
1316
-
1317
-    /**
1318
-     * Compile children and throw exception if unexpected @return
1319
-     *
1320
-     * @param array                                $stms
1321
-     * @param \Leafo\ScssPhp\Formatter\OutputBlock $out
1322
-     *
1323
-     * @throws \Exception
1324
-     */
1325
-    protected function compileChildrenNoReturn($stms, OutputBlock $out)
1326
-    {
1327
-        foreach ($stms as $stm) {
1328
-            $ret = $this->compileChild($stm, $out);
1329
-
1330
-            if (isset($ret)) {
1331
-                $this->throwError('@return may only be used within a function');
1332
-
1333
-                return;
1334
-            }
1335
-        }
1336
-    }
1337
-
1338
-    /**
1339
-     * Compile media query
1340
-     *
1341
-     * @param array $queryList
1342
-     *
1343
-     * @return string
1344
-     */
1345
-    protected function compileMediaQuery($queryList)
1346
-    {
1347
-        $out = '@media';
1348
-        $first = true;
1349
-
1350
-        foreach ($queryList as $query) {
1351
-            $type = null;
1352
-            $parts = [];
1353
-
1354
-            foreach ($query as $q) {
1355
-                switch ($q[0]) {
1356
-                    case Type::T_MEDIA_TYPE:
1357
-                        if ($type) {
1358
-                            $type = $this->mergeMediaTypes(
1359
-                                $type,
1360
-                                array_map([$this, 'compileValue'], array_slice($q, 1))
1361
-                            );
1362
-
1363
-                            if (empty($type)) { // merge failed
1364
-                                return null;
1365
-                            }
1366
-                        } else {
1367
-                            $type = array_map([$this, 'compileValue'], array_slice($q, 1));
1368
-                        }
1369
-                        break;
1370
-
1371
-                    case Type::T_MEDIA_EXPRESSION:
1372
-                        if (isset($q[2])) {
1373
-                            $parts[] = '('
1374
-                                . $this->compileValue($q[1])
1375
-                                . $this->formatter->assignSeparator
1376
-                                . $this->compileValue($q[2])
1377
-                                . ')';
1378
-                        } else {
1379
-                            $parts[] = '('
1380
-                                . $this->compileValue($q[1])
1381
-                                . ')';
1382
-                        }
1383
-                        break;
1384
-
1385
-                    case Type::T_MEDIA_VALUE:
1386
-                        $parts[] = $this->compileValue($q[1]);
1387
-                        break;
1388
-                }
1389
-            }
1390
-
1391
-            if ($type) {
1392
-                array_unshift($parts, implode(' ', array_filter($type)));
1393
-            }
1394
-
1395
-            if (! empty($parts)) {
1396
-                if ($first) {
1397
-                    $first = false;
1398
-                    $out .= ' ';
1399
-                } else {
1400
-                    $out .= $this->formatter->tagSeparator;
1401
-                }
1402
-
1403
-                $out .= implode(' and ', $parts);
1404
-            }
1405
-        }
1406
-
1407
-        return $out;
1408
-    }
1409
-
1410
-    protected function mergeDirectRelationships($selectors1, $selectors2)
1411
-    {
1412
-        if (empty($selectors1) || empty($selectors2)) {
1413
-            return array_merge($selectors1, $selectors2);
1414
-        }
1415
-
1416
-        $part1 = end($selectors1);
1417
-        $part2 = end($selectors2);
1418
-
1419
-        if (! $this->isImmediateRelationshipCombinator($part1[0]) || $part1 !== $part2) {
1420
-            return array_merge($selectors1, $selectors2);
1421
-        }
1422
-
1423
-        $merged = [];
1424
-
1425
-        do {
1426
-            $part1 = array_pop($selectors1);
1427
-            $part2 = array_pop($selectors2);
1428
-
1429
-            if ($this->isImmediateRelationshipCombinator($part1[0]) && $part1 !== $part2) {
1430
-                $merged = array_merge($selectors1, [$part1], $selectors2, [$part2], $merged);
1431
-                break;
1432
-            }
1433
-
1434
-            array_unshift($merged, $part1);
1435
-            array_unshift($merged, [array_pop($selectors1)[0] . array_pop($selectors2)[0]]);
1436
-        } while (! empty($selectors1) && ! empty($selectors2));
1437
-
1438
-        return $merged;
1439
-    }
1440
-
1441
-    /**
1442
-     * Merge media types
1443
-     *
1444
-     * @param array $type1
1445
-     * @param array $type2
1446
-     *
1447
-     * @return array|null
1448
-     */
1449
-    protected function mergeMediaTypes($type1, $type2)
1450
-    {
1451
-        if (empty($type1)) {
1452
-            return $type2;
1453
-        }
1454
-
1455
-        if (empty($type2)) {
1456
-            return $type1;
1457
-        }
1458
-
1459
-        $m1 = '';
1460
-        $t1 = '';
1461
-
1462
-        if (count($type1) > 1) {
1463
-            $m1= strtolower($type1[0]);
1464
-            $t1= strtolower($type1[1]);
1465
-        } else {
1466
-            $t1 = strtolower($type1[0]);
1467
-        }
1468
-
1469
-        $m2 = '';
1470
-        $t2 = '';
1471
-
1472
-        if (count($type2) > 1) {
1473
-            $m2 = strtolower($type2[0]);
1474
-            $t2 = strtolower($type2[1]);
1475
-        } else {
1476
-            $t2 = strtolower($type2[0]);
1477
-        }
1478
-
1479
-        if (($m1 === Type::T_NOT) ^ ($m2 === Type::T_NOT)) {
1480
-            if ($t1 === $t2) {
1481
-                return null;
1482
-            }
1483
-
1484
-            return [
1485
-                $m1 === Type::T_NOT ? $m2 : $m1,
1486
-                $m1 === Type::T_NOT ? $t2 : $t1,
1487
-            ];
1488
-        }
1489
-
1490
-        if ($m1 === Type::T_NOT && $m2 === Type::T_NOT) {
1491
-            // CSS has no way of representing "neither screen nor print"
1492
-            if ($t1 !== $t2) {
1493
-                return null;
1494
-            }
1495
-
1496
-            return [Type::T_NOT, $t1];
1497
-        }
1498
-
1499
-        if ($t1 !== $t2) {
1500
-            return null;
1501
-        }
1502
-
1503
-        // t1 == t2, neither m1 nor m2 are "not"
1504
-        return [empty($m1)? $m2 : $m1, $t1];
1505
-    }
1506
-
1507
-    /**
1508
-     * Compile import; returns true if the value was something that could be imported
1509
-     *
1510
-     * @param array   $rawPath
1511
-     * @param array   $out
1512
-     * @param boolean $once
1513
-     *
1514
-     * @return boolean
1515
-     */
1516
-    protected function compileImport($rawPath, $out, $once = false)
1517
-    {
1518
-        if ($rawPath[0] === Type::T_STRING) {
1519
-            $path = $this->compileStringContent($rawPath);
1520
-
1521
-            if ($path = $this->findImport($path)) {
1522
-                if (! $once || ! in_array($path, $this->importedFiles)) {
1523
-                    $this->importFile($path, $out);
1524
-                    $this->importedFiles[] = $path;
1525
-                }
1526
-
1527
-                return true;
1528
-            }
1529
-
1530
-            return false;
1531
-        }
1532
-
1533
-        if ($rawPath[0] === Type::T_LIST) {
1534
-            // handle a list of strings
1535
-            if (count($rawPath[2]) === 0) {
1536
-                return false;
1537
-            }
1538
-
1539
-            foreach ($rawPath[2] as $path) {
1540
-                if ($path[0] !== Type::T_STRING) {
1541
-                    return false;
1542
-                }
1543
-            }
1544
-
1545
-            foreach ($rawPath[2] as $path) {
1546
-                $this->compileImport($path, $out);
1547
-            }
1548
-
1549
-            return true;
1550
-        }
1551
-
1552
-        return false;
1553
-    }
1554
-
1555
-    /**
1556
-     * Compile child; returns a value to halt execution
1557
-     *
1558
-     * @param array                                $child
1559
-     * @param \Leafo\ScssPhp\Formatter\OutputBlock $out
1560
-     *
1561
-     * @return array
1562
-     */
1563
-    protected function compileChild($child, OutputBlock $out)
1564
-    {
1565
-        $this->sourceIndex  = isset($child[Parser::SOURCE_INDEX]) ? $child[Parser::SOURCE_INDEX] : null;
1566
-        $this->sourceLine   = isset($child[Parser::SOURCE_LINE]) ? $child[Parser::SOURCE_LINE] : -1;
1567
-        $this->sourceColumn = isset($child[Parser::SOURCE_COLUMN]) ? $child[Parser::SOURCE_COLUMN] : -1;
1568
-
1569
-        switch ($child[0]) {
1570
-            case Type::T_SCSSPHP_IMPORT_ONCE:
1571
-                list(, $rawPath) = $child;
1572
-
1573
-                $rawPath = $this->reduce($rawPath);
1574
-
1575
-                if (! $this->compileImport($rawPath, $out, true)) {
1576
-                    $out->lines[] = '@import ' . $this->compileValue($rawPath) . ';';
1577
-                }
1578
-                break;
1579
-
1580
-            case Type::T_IMPORT:
1581
-                list(, $rawPath) = $child;
1582
-
1583
-                $rawPath = $this->reduce($rawPath);
1584
-
1585
-                if (! $this->compileImport($rawPath, $out)) {
1586
-                    $out->lines[] = '@import ' . $this->compileValue($rawPath) . ';';
1587
-                }
1588
-                break;
1589
-
1590
-            case Type::T_DIRECTIVE:
1591
-                $this->compileDirective($child[1]);
1592
-                break;
1593
-
1594
-            case Type::T_AT_ROOT:
1595
-                $this->compileAtRoot($child[1]);
1596
-                break;
1597
-
1598
-            case Type::T_MEDIA:
1599
-                $this->compileMedia($child[1]);
1600
-                break;
1601
-
1602
-            case Type::T_BLOCK:
1603
-                $this->compileBlock($child[1]);
1604
-                break;
1605
-
1606
-            case Type::T_CHARSET:
1607
-                if (! $this->charsetSeen) {
1608
-                    $this->charsetSeen = true;
1609
-
1610
-                    $out->lines[] = '@charset ' . $this->compileValue($child[1]) . ';';
1611
-                }
1612
-                break;
1613
-
1614
-            case Type::T_ASSIGN:
1615
-                list(, $name, $value) = $child;
1616
-
1617
-                if ($name[0] === Type::T_VARIABLE) {
1618
-                    $flags = isset($child[3]) ? $child[3] : [];
1619
-                    $isDefault = in_array('!default', $flags);
1620
-                    $isGlobal = in_array('!global', $flags);
1621
-
1622
-                    if ($isGlobal) {
1623
-                        $this->set($name[1], $this->reduce($value), false, $this->rootEnv);
1624
-                        break;
1625
-                    }
1626
-
1627
-                    $shouldSet = $isDefault &&
1628
-                        (($result = $this->get($name[1], false)) === null
1629
-                        || $result === static::$null);
1630
-
1631
-                    if (! $isDefault || $shouldSet) {
1632
-                        $this->set($name[1], $this->reduce($value));
1633
-                    }
1634
-                    break;
1635
-                }
1636
-
1637
-                $compiledName = $this->compileValue($name);
1638
-
1639
-                // handle shorthand syntax: size / line-height
1640
-                if ($compiledName === 'font') {
1641
-                    if ($value[0] === Type::T_EXPRESSION && $value[1] === '/') {
1642
-                        $value = $this->expToString($value);
1643
-                    } elseif ($value[0] === Type::T_LIST) {
1644
-                        foreach ($value[2] as &$item) {
1645
-                            if ($item[0] === Type::T_EXPRESSION && $item[1] === '/') {
1646
-                                $item = $this->expToString($item);
1647
-                            }
1648
-                        }
1649
-                    }
1650
-                }
1651
-
1652
-                // if the value reduces to null from something else then
1653
-                // the property should be discarded
1654
-                if ($value[0] !== Type::T_NULL) {
1655
-                    $value = $this->reduce($value);
1656
-
1657
-                    if ($value[0] === Type::T_NULL || $value === static::$nullString) {
1658
-                        break;
1659
-                    }
1660
-                }
1661
-
1662
-                $compiledValue = $this->compileValue($value);
1663
-
1664
-                $out->lines[] = $this->formatter->property(
1665
-                    $compiledName,
1666
-                    $compiledValue
1667
-                );
1668
-                break;
1669
-
1670
-            case Type::T_COMMENT:
1671
-                if ($out->type === Type::T_ROOT) {
1672
-                    $this->compileComment($child);
1673
-                    break;
1674
-                }
1675
-
1676
-                $out->lines[] = $child[1];
1677
-                break;
1678
-
1679
-            case Type::T_MIXIN:
1680
-            case Type::T_FUNCTION:
1681
-                list(, $block) = $child;
1682
-
1683
-                $this->set(static::$namespaces[$block->type] . $block->name, $block);
1684
-                break;
1685
-
1686
-            case Type::T_EXTEND:
1687
-                list(, $selectors) = $child;
1688
-
1689
-                foreach ($selectors as $sel) {
1690
-                    $results = $this->evalSelectors([$sel]);
1691
-
1692
-                    foreach ($results as $result) {
1693
-                        // only use the first one
1694
-                        $result = current($result);
1695
-
1696
-                        $this->pushExtends($result, $out->selectors, $child);
1697
-                    }
1698
-                }
1699
-                break;
1700
-
1701
-            case Type::T_IF:
1702
-                list(, $if) = $child;
1703
-
1704
-                if ($this->isTruthy($this->reduce($if->cond, true))) {
1705
-                    return $this->compileChildren($if->children, $out);
1706
-                }
1707
-
1708
-                foreach ($if->cases as $case) {
1709
-                    if ($case->type === Type::T_ELSE ||
1710
-                        $case->type === Type::T_ELSEIF && $this->isTruthy($this->reduce($case->cond))
1711
-                    ) {
1712
-                        return $this->compileChildren($case->children, $out);
1713
-                    }
1714
-                }
1715
-                break;
1716
-
1717
-            case Type::T_EACH:
1718
-                list(, $each) = $child;
1719
-
1720
-                $list = $this->coerceList($this->reduce($each->list));
1721
-
1722
-                $this->pushEnv();
1723
-
1724
-                foreach ($list[2] as $item) {
1725
-                    if (count($each->vars) === 1) {
1726
-                        $this->set($each->vars[0], $item, true);
1727
-                    } else {
1728
-                        list(,, $values) = $this->coerceList($item);
1729
-
1730
-                        foreach ($each->vars as $i => $var) {
1731
-                            $this->set($var, isset($values[$i]) ? $values[$i] : static::$null, true);
1732
-                        }
1733
-                    }
1734
-
1735
-                    $ret = $this->compileChildren($each->children, $out);
1736
-
1737
-                    if ($ret) {
1738
-                        if ($ret[0] !== Type::T_CONTROL) {
1739
-                            $this->popEnv();
1740
-
1741
-                            return $ret;
1742
-                        }
1743
-
1744
-                        if ($ret[1]) {
1745
-                            break;
1746
-                        }
1747
-                    }
1748
-                }
60
+	const LINE_COMMENTS = 1;
61
+	const DEBUG_INFO    = 2;
62
+
63
+	const WITH_RULE     = 1;
64
+	const WITH_MEDIA    = 2;
65
+	const WITH_SUPPORTS = 4;
66
+	const WITH_ALL      = 7;
67
+
68
+	const SOURCE_MAP_NONE   = 0;
69
+	const SOURCE_MAP_INLINE = 1;
70
+	const SOURCE_MAP_FILE   = 2;
71
+
72
+	/**
73
+	 * @var array
74
+	 */
75
+	static protected $operatorNames = [
76
+		'+'   => 'add',
77
+		'-'   => 'sub',
78
+		'*'   => 'mul',
79
+		'/'   => 'div',
80
+		'%'   => 'mod',
81
+
82
+		'=='  => 'eq',
83
+		'!='  => 'neq',
84
+		'<'   => 'lt',
85
+		'>'   => 'gt',
86
+
87
+		'<='  => 'lte',
88
+		'>='  => 'gte',
89
+		'<=>' => 'cmp',
90
+	];
91
+
92
+	/**
93
+	 * @var array
94
+	 */
95
+	static protected $namespaces = [
96
+		'special'  => '%',
97
+		'mixin'    => '@',
98
+		'function' => '^',
99
+	];
100
+
101
+	static public $true = [Type::T_KEYWORD, 'true'];
102
+	static public $false = [Type::T_KEYWORD, 'false'];
103
+	static public $null = [Type::T_NULL];
104
+	static public $nullString = [Type::T_STRING, '', []];
105
+	static public $defaultValue = [Type::T_KEYWORD, ''];
106
+	static public $selfSelector = [Type::T_SELF];
107
+	static public $emptyList = [Type::T_LIST, '', []];
108
+	static public $emptyMap = [Type::T_MAP, [], []];
109
+	static public $emptyString = [Type::T_STRING, '"', []];
110
+	static public $with = [Type::T_KEYWORD, 'with'];
111
+	static public $without = [Type::T_KEYWORD, 'without'];
112
+
113
+	protected $importPaths = [''];
114
+	protected $importCache = [];
115
+	protected $importedFiles = [];
116
+	protected $userFunctions = [];
117
+	protected $registeredVars = [];
118
+	protected $registeredFeatures = [
119
+		'extend-selector-pseudoclass' => false,
120
+		'at-error'                    => true,
121
+		'units-level-3'               => false,
122
+		'global-variable-shadowing'   => false,
123
+	];
124
+
125
+	protected $encoding = null;
126
+	protected $lineNumberStyle = null;
127
+
128
+	protected $sourceMap = self::SOURCE_MAP_NONE;
129
+	protected $sourceMapOptions = [];
130
+
131
+	/**
132
+	 * @var string|\Leafo\ScssPhp\Formatter
133
+	 */
134
+	protected $formatter = 'Leafo\ScssPhp\Formatter\Nested';
135
+
136
+	protected $rootEnv;
137
+	protected $rootBlock;
138
+
139
+	/**
140
+	 * @var \Leafo\ScssPhp\Compiler\Environment
141
+	 */
142
+	protected $env;
143
+	protected $scope;
144
+	protected $storeEnv;
145
+	protected $charsetSeen;
146
+	protected $sourceNames;
147
+
148
+	private $indentLevel;
149
+	private $commentsSeen;
150
+	private $extends;
151
+	private $extendsMap;
152
+	private $parsedFiles;
153
+	private $parser;
154
+	private $sourceIndex;
155
+	private $sourceLine;
156
+	private $sourceColumn;
157
+	private $stderr;
158
+	private $shouldEvaluate;
159
+	private $ignoreErrors;
160
+
161
+	/**
162
+	 * Constructor
163
+	 */
164
+	public function __construct()
165
+	{
166
+		$this->parsedFiles = [];
167
+		$this->sourceNames = [];
168
+	}
169
+
170
+	/**
171
+	 * Compile scss
172
+	 *
173
+	 * @api
174
+	 *
175
+	 * @param string $code
176
+	 * @param string $path
177
+	 *
178
+	 * @return string
179
+	 */
180
+	public function compile($code, $path = null)
181
+	{
182
+		$this->indentLevel    = -1;
183
+		$this->commentsSeen   = [];
184
+		$this->extends        = [];
185
+		$this->extendsMap     = [];
186
+		$this->sourceIndex    = null;
187
+		$this->sourceLine     = null;
188
+		$this->sourceColumn   = null;
189
+		$this->env            = null;
190
+		$this->scope          = null;
191
+		$this->storeEnv       = null;
192
+		$this->charsetSeen    = null;
193
+		$this->shouldEvaluate = null;
194
+		$this->stderr         = fopen('php://stderr', 'w');
195
+
196
+		$this->parser = $this->parserFactory($path);
197
+		$tree = $this->parser->parse($code);
198
+		$this->parser = null;
199
+
200
+		$this->formatter = new $this->formatter();
201
+		$this->rootBlock = null;
202
+		$this->rootEnv   = $this->pushEnv($tree);
203
+
204
+		$this->injectVariables($this->registeredVars);
205
+		$this->compileRoot($tree);
206
+		$this->popEnv();
207
+
208
+		$sourceMapGenerator = null;
209
+
210
+		if ($this->sourceMap) {
211
+			if (is_object($this->sourceMap) && $this->sourceMap instanceof SourceMapGenerator) {
212
+				$sourceMapGenerator = $this->sourceMap;
213
+				$this->sourceMap = self::SOURCE_MAP_FILE;
214
+			} elseif ($this->sourceMap !== self::SOURCE_MAP_NONE) {
215
+				$sourceMapGenerator = new SourceMapGenerator($this->sourceMapOptions);
216
+			}
217
+		}
218
+
219
+		$out = $this->formatter->format($this->scope, $sourceMapGenerator);
220
+
221
+		if (! empty($out) && $this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) {
222
+			$sourceMap    = $sourceMapGenerator->generateJson();
223
+			$sourceMapUrl = null;
224
+
225
+			switch ($this->sourceMap) {
226
+				case self::SOURCE_MAP_INLINE:
227
+					$sourceMapUrl = sprintf('data:application/json,%s', Util::encodeURIComponent($sourceMap));
228
+					break;
229
+
230
+				case self::SOURCE_MAP_FILE:
231
+					$sourceMapUrl = $sourceMapGenerator->saveMap($sourceMap);
232
+					break;
233
+			}
234
+
235
+			$out .= sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl);
236
+		}
237
+
238
+		return $out;
239
+	}
240
+
241
+	/**
242
+	 * Instantiate parser
243
+	 *
244
+	 * @param string $path
245
+	 *
246
+	 * @return \Leafo\ScssPhp\Parser
247
+	 */
248
+	protected function parserFactory($path)
249
+	{
250
+		$parser = new Parser($path, count($this->sourceNames), $this->encoding);
251
+
252
+		$this->sourceNames[] = $path;
253
+		$this->addParsedFile($path);
254
+
255
+		return $parser;
256
+	}
257
+
258
+	/**
259
+	 * Is self extend?
260
+	 *
261
+	 * @param array $target
262
+	 * @param array $origin
263
+	 *
264
+	 * @return boolean
265
+	 */
266
+	protected function isSelfExtend($target, $origin)
267
+	{
268
+		foreach ($origin as $sel) {
269
+			if (in_array($target, $sel)) {
270
+				return true;
271
+			}
272
+		}
273
+
274
+		return false;
275
+	}
276
+
277
+	/**
278
+	 * Push extends
279
+	 *
280
+	 * @param array     $target
281
+	 * @param array     $origin
282
+	 * @param \stdClass $block
283
+	 */
284
+	protected function pushExtends($target, $origin, $block)
285
+	{
286
+		if ($this->isSelfExtend($target, $origin)) {
287
+			return;
288
+		}
289
+
290
+		$i = count($this->extends);
291
+		$this->extends[] = [$target, $origin, $block];
292
+
293
+		foreach ($target as $part) {
294
+			if (isset($this->extendsMap[$part])) {
295
+				$this->extendsMap[$part][] = $i;
296
+			} else {
297
+				$this->extendsMap[$part] = [$i];
298
+			}
299
+		}
300
+	}
301
+
302
+	/**
303
+	 * Make output block
304
+	 *
305
+	 * @param string $type
306
+	 * @param array  $selectors
307
+	 *
308
+	 * @return \Leafo\ScssPhp\Formatter\OutputBlock
309
+	 */
310
+	protected function makeOutputBlock($type, $selectors = null)
311
+	{
312
+		$out = new OutputBlock;
313
+		$out->type         = $type;
314
+		$out->lines        = [];
315
+		$out->children     = [];
316
+		$out->parent       = $this->scope;
317
+		$out->selectors    = $selectors;
318
+		$out->depth        = $this->env->depth;
319
+		$out->sourceName   = $this->env->block->sourceName;
320
+		$out->sourceLine   = $this->env->block->sourceLine;
321
+		$out->sourceColumn = $this->env->block->sourceColumn;
322
+
323
+		return $out;
324
+	}
325
+
326
+	/**
327
+	 * Compile root
328
+	 *
329
+	 * @param \Leafo\ScssPhp\Block $rootBlock
330
+	 */
331
+	protected function compileRoot(Block $rootBlock)
332
+	{
333
+		$this->rootBlock = $this->scope = $this->makeOutputBlock(Type::T_ROOT);
334
+
335
+		$this->compileChildrenNoReturn($rootBlock->children, $this->scope);
336
+		$this->flattenSelectors($this->scope);
337
+		$this->missingSelectors();
338
+	}
339
+
340
+	/**
341
+	 * Report missing selectors
342
+	 */
343
+	protected function missingSelectors()
344
+	{
345
+		foreach ($this->extends as $extend) {
346
+			if (isset($extend[3])) {
347
+				continue;
348
+			}
349
+
350
+			list($target, $origin, $block) = $extend;
351
+
352
+			// ignore if !optional
353
+			if ($block[2]) {
354
+				continue;
355
+			}
356
+
357
+			$target = implode(' ', $target);
358
+			$origin = $this->collapseSelectors($origin);
359
+
360
+			$this->sourceLine = $block[Parser::SOURCE_LINE];
361
+			$this->throwError("\"$origin\" failed to @extend \"$target\". The selector \"$target\" was not found.");
362
+		}
363
+	}
364
+
365
+	/**
366
+	 * Flatten selectors
367
+	 *
368
+	 * @param \Leafo\ScssPhp\Formatter\OutputBlock $block
369
+	 * @param string                               $parentKey
370
+	 */
371
+	protected function flattenSelectors(OutputBlock $block, $parentKey = null)
372
+	{
373
+		if ($block->selectors) {
374
+			$selectors = [];
375
+
376
+			foreach ($block->selectors as $s) {
377
+				$selectors[] = $s;
378
+
379
+				if (! is_array($s)) {
380
+					continue;
381
+				}
382
+
383
+				// check extends
384
+				if (! empty($this->extendsMap)) {
385
+					$this->matchExtends($s, $selectors);
386
+
387
+					// remove duplicates
388
+					array_walk($selectors, function (&$value) {
389
+						$value = serialize($value);
390
+					});
391
+
392
+					$selectors = array_unique($selectors);
393
+
394
+					array_walk($selectors, function (&$value) {
395
+						$value = unserialize($value);
396
+					});
397
+				}
398
+			}
399
+
400
+			$block->selectors = [];
401
+			$placeholderSelector = false;
402
+
403
+			foreach ($selectors as $selector) {
404
+				if ($this->hasSelectorPlaceholder($selector)) {
405
+					$placeholderSelector = true;
406
+					continue;
407
+				}
408
+
409
+				$block->selectors[] = $this->compileSelector($selector);
410
+			}
411
+
412
+			if ($placeholderSelector && 0 === count($block->selectors) && null !== $parentKey) {
413
+				unset($block->parent->children[$parentKey]);
414
+
415
+				return;
416
+			}
417
+		}
418
+
419
+		foreach ($block->children as $key => $child) {
420
+			$this->flattenSelectors($child, $key);
421
+		}
422
+	}
423
+
424
+	/**
425
+	 * Match extends
426
+	 *
427
+	 * @param array   $selector
428
+	 * @param array   $out
429
+	 * @param integer $from
430
+	 * @param boolean $initial
431
+	 */
432
+	protected function matchExtends($selector, &$out, $from = 0, $initial = true)
433
+	{
434
+		foreach ($selector as $i => $part) {
435
+			if ($i < $from) {
436
+				continue;
437
+			}
438
+
439
+			if ($this->matchExtendsSingle($part, $origin)) {
440
+				$after = array_slice($selector, $i + 1);
441
+				$before = array_slice($selector, 0, $i);
442
+
443
+				list($before, $nonBreakableBefore) = $this->extractRelationshipFromFragment($before);
444
+
445
+				foreach ($origin as $new) {
446
+					$k = 0;
447
+
448
+					// remove shared parts
449
+					if ($initial) {
450
+						while ($k < $i && isset($new[$k]) && $selector[$k] === $new[$k]) {
451
+							$k++;
452
+						}
453
+					}
454
+
455
+					$replacement = [];
456
+					$tempReplacement = $k > 0 ? array_slice($new, $k) : $new;
457
+
458
+					for ($l = count($tempReplacement) - 1; $l >= 0; $l--) {
459
+						$slice = $tempReplacement[$l];
460
+						array_unshift($replacement, $slice);
461
+
462
+						if (! $this->isImmediateRelationshipCombinator(end($slice))) {
463
+							break;
464
+						}
465
+					}
466
+
467
+					$afterBefore = $l != 0 ? array_slice($tempReplacement, 0, $l) : [];
468
+
469
+					// Merge shared direct relationships.
470
+					$mergedBefore = $this->mergeDirectRelationships($afterBefore, $nonBreakableBefore);
471
+
472
+					$result = array_merge(
473
+						$before,
474
+						$mergedBefore,
475
+						$replacement,
476
+						$after
477
+					);
478
+
479
+					if ($result === $selector) {
480
+						continue;
481
+					}
482
+
483
+					$out[] = $result;
484
+
485
+					// recursively check for more matches
486
+					$this->matchExtends($result, $out, count($before) + count($mergedBefore), false);
487
+
488
+					// selector sequence merging
489
+					if (! empty($before) && count($new) > 1) {
490
+						$sharedParts = $k > 0 ? array_slice($before, 0, $k) : [];
491
+						$postSharedParts = $k > 0 ? array_slice($before, $k) : $before;
492
+
493
+						list($injectBetweenSharedParts, $nonBreakable2) = $this->extractRelationshipFromFragment($afterBefore);
494
+
495
+						$result2 = array_merge(
496
+							$sharedParts,
497
+							$injectBetweenSharedParts,
498
+							$postSharedParts,
499
+							$nonBreakable2,
500
+							$nonBreakableBefore,
501
+							$replacement,
502
+							$after
503
+						);
504
+
505
+						$out[] = $result2;
506
+					}
507
+				}
508
+			}
509
+		}
510
+	}
511
+
512
+	/**
513
+	 * Match extends single
514
+	 *
515
+	 * @param array $rawSingle
516
+	 * @param array $outOrigin
517
+	 *
518
+	 * @return boolean
519
+	 */
520
+	protected function matchExtendsSingle($rawSingle, &$outOrigin)
521
+	{
522
+		$counts = [];
523
+		$single = [];
524
+
525
+		foreach ($rawSingle as $part) {
526
+			// matches Number
527
+			if (! is_string($part)) {
528
+				return false;
529
+			}
530
+
531
+			if (! preg_match('/^[\[.:#%]/', $part) && count($single)) {
532
+				$single[count($single) - 1] .= $part;
533
+			} else {
534
+				$single[] = $part;
535
+			}
536
+		}
537
+
538
+		$extendingDecoratedTag = false;
539
+
540
+		if (count($single) > 1) {
541
+			$matches = null;
542
+			$extendingDecoratedTag = preg_match('/^[a-z0-9]+$/i', $single[0], $matches) ? $matches[0] : false;
543
+		}
544
+
545
+		foreach ($single as $part) {
546
+			if (isset($this->extendsMap[$part])) {
547
+				foreach ($this->extendsMap[$part] as $idx) {
548
+					$counts[$idx] = isset($counts[$idx]) ? $counts[$idx] + 1 : 1;
549
+				}
550
+			}
551
+		}
552
+
553
+		$outOrigin = [];
554
+		$found = false;
555
+
556
+		foreach ($counts as $idx => $count) {
557
+			list($target, $origin, /* $block */) = $this->extends[$idx];
558
+
559
+			// check count
560
+			if ($count !== count($target)) {
561
+				continue;
562
+			}
563
+
564
+			$this->extends[$idx][3] = true;
565
+
566
+			$rem = array_diff($single, $target);
567
+
568
+			foreach ($origin as $j => $new) {
569
+				// prevent infinite loop when target extends itself
570
+				if ($this->isSelfExtend($single, $origin)) {
571
+					return false;
572
+				}
573
+
574
+				$replacement = end($new);
575
+
576
+				// Extending a decorated tag with another tag is not possible.
577
+				if ($extendingDecoratedTag && $replacement[0] != $extendingDecoratedTag &&
578
+					preg_match('/^[a-z0-9]+$/i', $replacement[0])
579
+				) {
580
+					unset($origin[$j]);
581
+					continue;
582
+				}
583
+
584
+				$combined = $this->combineSelectorSingle($replacement, $rem);
585
+
586
+				if (count(array_diff($combined, $origin[$j][count($origin[$j]) - 1]))) {
587
+					$origin[$j][count($origin[$j]) - 1] = $combined;
588
+				}
589
+			}
590
+
591
+			$outOrigin = array_merge($outOrigin, $origin);
592
+
593
+			$found = true;
594
+		}
595
+
596
+		return $found;
597
+	}
598
+
599
+
600
+	/**
601
+	 * Extract a relationship from the fragment.
602
+	 *
603
+	 * When extracting the last portion of a selector we will be left with a
604
+	 * fragment which may end with a direction relationship combinator. This
605
+	 * method will extract the relationship fragment and return it along side
606
+	 * the rest.
607
+	 *
608
+	 * @param array $fragment The selector fragment maybe ending with a direction relationship combinator.
609
+	 * @return array The selector without the relationship fragment if any, the relationship fragment.
610
+	 */
611
+	protected function extractRelationshipFromFragment(array $fragment)
612
+	{
613
+		$parents = [];
614
+		$children = [];
615
+		$j = $i = count($fragment);
616
+
617
+		for (;;) {
618
+			$children = $j != $i ? array_slice($fragment, $j, $i - $j) : [];
619
+			$parents = array_slice($fragment, 0, $j);
620
+			$slice = end($parents);
621
+
622
+			if (empty($slice) || ! $this->isImmediateRelationshipCombinator($slice[0])) {
623
+				break;
624
+			}
625
+
626
+			$j -= 2;
627
+		}
628
+
629
+		return [$parents, $children];
630
+	}
631
+
632
+	/**
633
+	 * Combine selector single
634
+	 *
635
+	 * @param array $base
636
+	 * @param array $other
637
+	 *
638
+	 * @return array
639
+	 */
640
+	protected function combineSelectorSingle($base, $other)
641
+	{
642
+		$tag = [];
643
+		$out = [];
644
+		$wasTag = true;
645
+
646
+		foreach ([$base, $other] as $single) {
647
+			foreach ($single as $part) {
648
+				if (preg_match('/^[\[.:#]/', $part)) {
649
+					$out[] = $part;
650
+					$wasTag = false;
651
+				} elseif (preg_match('/^[^_-]/', $part)) {
652
+					$tag[] = $part;
653
+					$wasTag = true;
654
+				} elseif ($wasTag) {
655
+					$tag[count($tag) - 1] .= $part;
656
+				} else {
657
+					$out[count($out) - 1] .= $part;
658
+				}
659
+			}
660
+		}
661
+
662
+		if (count($tag)) {
663
+			array_unshift($out, $tag[0]);
664
+		}
665
+
666
+		return $out;
667
+	}
668
+
669
+	/**
670
+	 * Compile media
671
+	 *
672
+	 * @param \Leafo\ScssPhp\Block $media
673
+	 */
674
+	protected function compileMedia(Block $media)
675
+	{
676
+		$this->pushEnv($media);
677
+
678
+		$mediaQuery = $this->compileMediaQuery($this->multiplyMedia($this->env));
679
+
680
+		if (! empty($mediaQuery)) {
681
+			$this->scope = $this->makeOutputBlock(Type::T_MEDIA, [$mediaQuery]);
682
+
683
+			$parentScope = $this->mediaParent($this->scope);
684
+			$parentScope->children[] = $this->scope;
685
+
686
+			// top level properties in a media cause it to be wrapped
687
+			$needsWrap = false;
688
+
689
+			foreach ($media->children as $child) {
690
+				$type = $child[0];
691
+
692
+				if ($type !== Type::T_BLOCK &&
693
+					$type !== Type::T_MEDIA &&
694
+					$type !== Type::T_DIRECTIVE &&
695
+					$type !== Type::T_IMPORT
696
+				) {
697
+					$needsWrap = true;
698
+					break;
699
+				}
700
+			}
701
+
702
+			if ($needsWrap) {
703
+				$wrapped = new Block;
704
+				$wrapped->sourceName   = $media->sourceName;
705
+				$wrapped->sourceIndex  = $media->sourceIndex;
706
+				$wrapped->sourceLine   = $media->sourceLine;
707
+				$wrapped->sourceColumn = $media->sourceColumn;
708
+				$wrapped->selectors    = [];
709
+				$wrapped->comments     = [];
710
+				$wrapped->parent       = $media;
711
+				$wrapped->children     = $media->children;
712
+
713
+				$media->children = [[Type::T_BLOCK, $wrapped]];
714
+			}
715
+
716
+			$this->compileChildrenNoReturn($media->children, $this->scope);
717
+
718
+			$this->scope = $this->scope->parent;
719
+		}
720
+
721
+		$this->popEnv();
722
+	}
723
+
724
+	/**
725
+	 * Media parent
726
+	 *
727
+	 * @param \Leafo\ScssPhp\Formatter\OutputBlock $scope
728
+	 *
729
+	 * @return \Leafo\ScssPhp\Formatter\OutputBlock
730
+	 */
731
+	protected function mediaParent(OutputBlock $scope)
732
+	{
733
+		while (! empty($scope->parent)) {
734
+			if (! empty($scope->type) && $scope->type !== Type::T_MEDIA) {
735
+				break;
736
+			}
737
+
738
+			$scope = $scope->parent;
739
+		}
740
+
741
+		return $scope;
742
+	}
743
+
744
+	/**
745
+	 * Compile directive
746
+	 *
747
+	 * @param \Leafo\ScssPhp\Block $block
748
+	 */
749
+	protected function compileDirective(Block $block)
750
+	{
751
+		$s = '@' . $block->name;
752
+
753
+		if (! empty($block->value)) {
754
+			$s .= ' ' . $this->compileValue($block->value);
755
+		}
756
+
757
+		if ($block->name === 'keyframes' || substr($block->name, -10) === '-keyframes') {
758
+			$this->compileKeyframeBlock($block, [$s]);
759
+		} else {
760
+			$this->compileNestedBlock($block, [$s]);
761
+		}
762
+	}
763
+
764
+	/**
765
+	 * Compile at-root
766
+	 *
767
+	 * @param \Leafo\ScssPhp\Block $block
768
+	 */
769
+	protected function compileAtRoot(Block $block)
770
+	{
771
+		$env     = $this->pushEnv($block);
772
+		$envs    = $this->compactEnv($env);
773
+		$without = isset($block->with) ? $this->compileWith($block->with) : static::WITH_RULE;
774
+
775
+		// wrap inline selector
776
+		if ($block->selector) {
777
+			$wrapped = new Block;
778
+			$wrapped->sourceName   = $block->sourceName;
779
+			$wrapped->sourceIndex  = $block->sourceIndex;
780
+			$wrapped->sourceLine   = $block->sourceLine;
781
+			$wrapped->sourceColumn = $block->sourceColumn;
782
+			$wrapped->selectors    = $block->selector;
783
+			$wrapped->comments     = [];
784
+			$wrapped->parent       = $block;
785
+			$wrapped->children     = $block->children;
786
+
787
+			$block->children = [[Type::T_BLOCK, $wrapped]];
788
+		}
789
+
790
+		$this->env = $this->filterWithout($envs, $without);
791
+		$newBlock  = $this->spliceTree($envs, $block, $without);
792
+
793
+		$saveScope   = $this->scope;
794
+		$this->scope = $this->rootBlock;
795
+
796
+		$this->compileChild($newBlock, $this->scope);
797
+
798
+		$this->scope = $saveScope;
799
+		$this->env   = $this->extractEnv($envs);
800
+
801
+		$this->popEnv();
802
+	}
803
+
804
+	/**
805
+	 * Splice parse tree
806
+	 *
807
+	 * @param array                $envs
808
+	 * @param \Leafo\ScssPhp\Block $block
809
+	 * @param integer              $without
810
+	 *
811
+	 * @return array
812
+	 */
813
+	private function spliceTree($envs, Block $block, $without)
814
+	{
815
+		$newBlock = null;
816
+
817
+		foreach ($envs as $e) {
818
+			if (! isset($e->block)) {
819
+				continue;
820
+			}
821
+
822
+			if ($e->block === $block) {
823
+				continue;
824
+			}
825
+
826
+			if (isset($e->block->type) && $e->block->type === Type::T_AT_ROOT) {
827
+				continue;
828
+			}
829
+
830
+			if ($e->block && $this->isWithout($without, $e->block)) {
831
+				continue;
832
+			}
833
+
834
+			$b = new Block;
835
+			$b->sourceName   = $e->block->sourceName;
836
+			$b->sourceIndex  = $e->block->sourceIndex;
837
+			$b->sourceLine   = $e->block->sourceLine;
838
+			$b->sourceColumn = $e->block->sourceColumn;
839
+			$b->selectors    = [];
840
+			$b->comments     = $e->block->comments;
841
+			$b->parent       = null;
842
+
843
+			if ($newBlock) {
844
+				$type = isset($newBlock->type) ? $newBlock->type : Type::T_BLOCK;
845
+
846
+				$b->children = [[$type, $newBlock]];
847
+
848
+				$newBlock->parent = $b;
849
+			} elseif (count($block->children)) {
850
+				foreach ($block->children as $child) {
851
+					if ($child[0] === Type::T_BLOCK) {
852
+						$child[1]->parent = $b;
853
+					}
854
+				}
855
+
856
+				$b->children = $block->children;
857
+			}
858
+
859
+			if (isset($e->block->type)) {
860
+				$b->type = $e->block->type;
861
+			}
862
+
863
+			if (isset($e->block->name)) {
864
+				$b->name = $e->block->name;
865
+			}
866
+
867
+			if (isset($e->block->queryList)) {
868
+				$b->queryList = $e->block->queryList;
869
+			}
870
+
871
+			if (isset($e->block->value)) {
872
+				$b->value = $e->block->value;
873
+			}
874
+
875
+			$newBlock = $b;
876
+		}
877
+
878
+		$type = isset($newBlock->type) ? $newBlock->type : Type::T_BLOCK;
879
+
880
+		return [$type, $newBlock];
881
+	}
882
+
883
+	/**
884
+	 * Compile @at-root's with: inclusion / without: exclusion into filter flags
885
+	 *
886
+	 * @param array $with
887
+	 *
888
+	 * @return integer
889
+	 */
890
+	private function compileWith($with)
891
+	{
892
+		static $mapping = [
893
+			'rule'     => self::WITH_RULE,
894
+			'media'    => self::WITH_MEDIA,
895
+			'supports' => self::WITH_SUPPORTS,
896
+			'all'      => self::WITH_ALL,
897
+		];
898
+
899
+		// exclude selectors by default
900
+		$without = static::WITH_RULE;
901
+
902
+		if ($this->libMapHasKey([$with, static::$with])) {
903
+			$without = static::WITH_ALL;
904
+
905
+			$list = $this->coerceList($this->libMapGet([$with, static::$with]));
906
+
907
+			foreach ($list[2] as $item) {
908
+				$keyword = $this->compileStringContent($this->coerceString($item));
909
+
910
+				if (array_key_exists($keyword, $mapping)) {
911
+					$without &= ~($mapping[$keyword]);
912
+				}
913
+			}
914
+		}
915
+
916
+		if ($this->libMapHasKey([$with, static::$without])) {
917
+			$without = 0;
918
+
919
+			$list = $this->coerceList($this->libMapGet([$with, static::$without]));
920
+
921
+			foreach ($list[2] as $item) {
922
+				$keyword = $this->compileStringContent($this->coerceString($item));
923
+
924
+				if (array_key_exists($keyword, $mapping)) {
925
+					$without |= $mapping[$keyword];
926
+				}
927
+			}
928
+		}
929
+
930
+		return $without;
931
+	}
932
+
933
+	/**
934
+	 * Filter env stack
935
+	 *
936
+	 * @param array   $envs
937
+	 * @param integer $without
938
+	 *
939
+	 * @return \Leafo\ScssPhp\Compiler\Environment
940
+	 */
941
+	private function filterWithout($envs, $without)
942
+	{
943
+		$filtered = [];
944
+
945
+		foreach ($envs as $e) {
946
+			if ($e->block && $this->isWithout($without, $e->block)) {
947
+				continue;
948
+			}
949
+
950
+			$filtered[] = $e;
951
+		}
952
+
953
+		return $this->extractEnv($filtered);
954
+	}
955
+
956
+	/**
957
+	 * Filter WITH rules
958
+	 *
959
+	 * @param integer              $without
960
+	 * @param \Leafo\ScssPhp\Block $block
961
+	 *
962
+	 * @return boolean
963
+	 */
964
+	private function isWithout($without, Block $block)
965
+	{
966
+		if ((($without & static::WITH_RULE) && isset($block->selectors)) ||
967
+			(($without & static::WITH_MEDIA) &&
968
+				isset($block->type) && $block->type === Type::T_MEDIA) ||
969
+			(($without & static::WITH_SUPPORTS) &&
970
+				isset($block->type) && $block->type === Type::T_DIRECTIVE &&
971
+				isset($block->name) && $block->name === 'supports')
972
+		) {
973
+			return true;
974
+		}
975
+
976
+		return false;
977
+	}
978
+
979
+	/**
980
+	 * Compile keyframe block
981
+	 *
982
+	 * @param \Leafo\ScssPhp\Block $block
983
+	 * @param array                $selectors
984
+	 */
985
+	protected function compileKeyframeBlock(Block $block, $selectors)
986
+	{
987
+		$env = $this->pushEnv($block);
988
+
989
+		$envs = $this->compactEnv($env);
990
+
991
+		$this->env = $this->extractEnv(array_filter($envs, function (Environment $e) {
992
+			return ! isset($e->block->selectors);
993
+		}));
994
+
995
+		$this->scope = $this->makeOutputBlock($block->type, $selectors);
996
+		$this->scope->depth = 1;
997
+		$this->scope->parent->children[] = $this->scope;
998
+
999
+		$this->compileChildrenNoReturn($block->children, $this->scope);
1000
+
1001
+		$this->scope = $this->scope->parent;
1002
+		$this->env   = $this->extractEnv($envs);
1003
+
1004
+		$this->popEnv();
1005
+	}
1006
+
1007
+	/**
1008
+	 * Compile nested block
1009
+	 *
1010
+	 * @param \Leafo\ScssPhp\Block $block
1011
+	 * @param array                $selectors
1012
+	 */
1013
+	protected function compileNestedBlock(Block $block, $selectors)
1014
+	{
1015
+		$this->pushEnv($block);
1016
+
1017
+		$this->scope = $this->makeOutputBlock($block->type, $selectors);
1018
+		$this->scope->parent->children[] = $this->scope;
1019
+
1020
+		$this->compileChildrenNoReturn($block->children, $this->scope);
1021
+
1022
+		$this->scope = $this->scope->parent;
1023
+
1024
+		$this->popEnv();
1025
+	}
1026
+
1027
+	/**
1028
+	 * Recursively compiles a block.
1029
+	 *
1030
+	 * A block is analogous to a CSS block in most cases. A single SCSS document
1031
+	 * is encapsulated in a block when parsed, but it does not have parent tags
1032
+	 * so all of its children appear on the root level when compiled.
1033
+	 *
1034
+	 * Blocks are made up of selectors and children.
1035
+	 *
1036
+	 * The children of a block are just all the blocks that are defined within.
1037
+	 *
1038
+	 * Compiling the block involves pushing a fresh environment on the stack,
1039
+	 * and iterating through the props, compiling each one.
1040
+	 *
1041
+	 * @see Compiler::compileChild()
1042
+	 *
1043
+	 * @param \Leafo\ScssPhp\Block $block
1044
+	 */
1045
+	protected function compileBlock(Block $block)
1046
+	{
1047
+		$env = $this->pushEnv($block);
1048
+		$env->selectors = $this->evalSelectors($block->selectors);
1049
+
1050
+		$out = $this->makeOutputBlock(null);
1051
+
1052
+		if (isset($this->lineNumberStyle) && count($env->selectors) && count($block->children)) {
1053
+			$annotation = $this->makeOutputBlock(Type::T_COMMENT);
1054
+			$annotation->depth = 0;
1055
+
1056
+			$file = $this->sourceNames[$block->sourceIndex];
1057
+			$line = $block->sourceLine;
1058
+
1059
+			switch ($this->lineNumberStyle) {
1060
+				case static::LINE_COMMENTS:
1061
+					$annotation->lines[] = '/* line ' . $line
1062
+										 . ($file ? ', ' . $file : '')
1063
+										 . ' */';
1064
+					break;
1065
+
1066
+				case static::DEBUG_INFO:
1067
+					$annotation->lines[] = '@media -sass-debug-info{'
1068
+										 . ($file ? 'filename{font-family:"' . $file . '"}' : '')
1069
+										 . 'line{font-family:' . $line . '}}';
1070
+					break;
1071
+			}
1072
+
1073
+			$this->scope->children[] = $annotation;
1074
+		}
1075
+
1076
+		$this->scope->children[] = $out;
1077
+
1078
+		if (count($block->children)) {
1079
+			$out->selectors = $this->multiplySelectors($env);
1080
+
1081
+			$this->compileChildrenNoReturn($block->children, $out);
1082
+		}
1083
+
1084
+		$this->formatter->stripSemicolon($out->lines);
1085
+
1086
+		$this->popEnv();
1087
+	}
1088
+
1089
+	/**
1090
+	 * Compile root level comment
1091
+	 *
1092
+	 * @param array $block
1093
+	 */
1094
+	protected function compileComment($block)
1095
+	{
1096
+		$out = $this->makeOutputBlock(Type::T_COMMENT);
1097
+		$out->lines[] = $block[1];
1098
+		$this->scope->children[] = $out;
1099
+	}
1100
+
1101
+	/**
1102
+	 * Evaluate selectors
1103
+	 *
1104
+	 * @param array $selectors
1105
+	 *
1106
+	 * @return array
1107
+	 */
1108
+	protected function evalSelectors($selectors)
1109
+	{
1110
+		$this->shouldEvaluate = false;
1111
+
1112
+		$selectors = array_map([$this, 'evalSelector'], $selectors);
1113
+
1114
+		// after evaluating interpolates, we might need a second pass
1115
+		if ($this->shouldEvaluate) {
1116
+			$buffer = $this->collapseSelectors($selectors);
1117
+			$parser = $this->parserFactory(__METHOD__);
1118
+
1119
+			if ($parser->parseSelector($buffer, $newSelectors)) {
1120
+				$selectors = array_map([$this, 'evalSelector'], $newSelectors);
1121
+			}
1122
+		}
1123
+
1124
+		return $selectors;
1125
+	}
1126
+
1127
+	/**
1128
+	 * Evaluate selector
1129
+	 *
1130
+	 * @param array $selector
1131
+	 *
1132
+	 * @return array
1133
+	 */
1134
+	protected function evalSelector($selector)
1135
+	{
1136
+		return array_map([$this, 'evalSelectorPart'], $selector);
1137
+	}
1138
+
1139
+	/**
1140
+	 * Evaluate selector part; replaces all the interpolates, stripping quotes
1141
+	 *
1142
+	 * @param array $part
1143
+	 *
1144
+	 * @return array
1145
+	 */
1146
+	protected function evalSelectorPart($part)
1147
+	{
1148
+		foreach ($part as &$p) {
1149
+			if (is_array($p) && ($p[0] === Type::T_INTERPOLATE || $p[0] === Type::T_STRING)) {
1150
+				$p = $this->compileValue($p);
1151
+
1152
+				// force re-evaluation
1153
+				if (strpos($p, '&') !== false || strpos($p, ',') !== false) {
1154
+					$this->shouldEvaluate = true;
1155
+				}
1156
+			} elseif (is_string($p) && strlen($p) >= 2 &&
1157
+				($first = $p[0]) && ($first === '"' || $first === "'") &&
1158
+				substr($p, -1) === $first
1159
+			) {
1160
+				$p = substr($p, 1, -1);
1161
+			}
1162
+		}
1163
+
1164
+		return $this->flattenSelectorSingle($part);
1165
+	}
1166
+
1167
+	/**
1168
+	 * Collapse selectors
1169
+	 *
1170
+	 * @param array $selectors
1171
+	 *
1172
+	 * @return string
1173
+	 */
1174
+	protected function collapseSelectors($selectors)
1175
+	{
1176
+		$parts = [];
1177
+
1178
+		foreach ($selectors as $selector) {
1179
+			$output = '';
1180
+
1181
+			array_walk_recursive(
1182
+				$selector,
1183
+				function ($value, $key) use (&$output) {
1184
+					$output .= $value;
1185
+				}
1186
+			);
1187
+
1188
+			$parts[] = $output;
1189
+		}
1190
+
1191
+		return implode(', ', $parts);
1192
+	}
1193
+
1194
+	/**
1195
+	 * Flatten selector single; joins together .classes and #ids
1196
+	 *
1197
+	 * @param array $single
1198
+	 *
1199
+	 * @return array
1200
+	 */
1201
+	protected function flattenSelectorSingle($single)
1202
+	{
1203
+		$joined = [];
1204
+
1205
+		foreach ($single as $part) {
1206
+			if (empty($joined) ||
1207
+				! is_string($part) ||
1208
+				preg_match('/[\[.:#%]/', $part)
1209
+			) {
1210
+				$joined[] = $part;
1211
+				continue;
1212
+			}
1213
+
1214
+			if (is_array(end($joined))) {
1215
+				$joined[] = $part;
1216
+			} else {
1217
+				$joined[count($joined) - 1] .= $part;
1218
+			}
1219
+		}
1220
+
1221
+		return $joined;
1222
+	}
1223
+
1224
+	/**
1225
+	 * Compile selector to string; self(&) should have been replaced by now
1226
+	 *
1227
+	 * @param string|array $selector
1228
+	 *
1229
+	 * @return string
1230
+	 */
1231
+	protected function compileSelector($selector)
1232
+	{
1233
+		if (! is_array($selector)) {
1234
+			return $selector; // media and the like
1235
+		}
1236
+
1237
+		return implode(
1238
+			' ',
1239
+			array_map(
1240
+				[$this, 'compileSelectorPart'],
1241
+				$selector
1242
+			)
1243
+		);
1244
+	}
1245
+
1246
+	/**
1247
+	 * Compile selector part
1248
+	 *
1249
+	 * @param array $piece
1250
+	 *
1251
+	 * @return string
1252
+	 */
1253
+	protected function compileSelectorPart($piece)
1254
+	{
1255
+		foreach ($piece as &$p) {
1256
+			if (! is_array($p)) {
1257
+				continue;
1258
+			}
1259
+
1260
+			switch ($p[0]) {
1261
+				case Type::T_SELF:
1262
+					$p = '&';
1263
+					break;
1264
+
1265
+				default:
1266
+					$p = $this->compileValue($p);
1267
+					break;
1268
+			}
1269
+		}
1270
+
1271
+		return implode($piece);
1272
+	}
1273
+
1274
+	/**
1275
+	 * Has selector placeholder?
1276
+	 *
1277
+	 * @param array $selector
1278
+	 *
1279
+	 * @return boolean
1280
+	 */
1281
+	protected function hasSelectorPlaceholder($selector)
1282
+	{
1283
+		if (! is_array($selector)) {
1284
+			return false;
1285
+		}
1286
+
1287
+		foreach ($selector as $parts) {
1288
+			foreach ($parts as $part) {
1289
+				if (strlen($part) && '%' === $part[0]) {
1290
+					return true;
1291
+				}
1292
+			}
1293
+		}
1294
+
1295
+		return false;
1296
+	}
1297
+
1298
+	/**
1299
+	 * Compile children and return result
1300
+	 *
1301
+	 * @param array                                $stms
1302
+	 * @param \Leafo\ScssPhp\Formatter\OutputBlock $out
1303
+	 *
1304
+	 * @return array
1305
+	 */
1306
+	protected function compileChildren($stms, OutputBlock $out)
1307
+	{
1308
+		foreach ($stms as $stm) {
1309
+			$ret = $this->compileChild($stm, $out);
1310
+
1311
+			if (isset($ret)) {
1312
+				return $ret;
1313
+			}
1314
+		}
1315
+	}
1316
+
1317
+	/**
1318
+	 * Compile children and throw exception if unexpected @return
1319
+	 *
1320
+	 * @param array                                $stms
1321
+	 * @param \Leafo\ScssPhp\Formatter\OutputBlock $out
1322
+	 *
1323
+	 * @throws \Exception
1324
+	 */
1325
+	protected function compileChildrenNoReturn($stms, OutputBlock $out)
1326
+	{
1327
+		foreach ($stms as $stm) {
1328
+			$ret = $this->compileChild($stm, $out);
1329
+
1330
+			if (isset($ret)) {
1331
+				$this->throwError('@return may only be used within a function');
1332
+
1333
+				return;
1334
+			}
1335
+		}
1336
+	}
1337
+
1338
+	/**
1339
+	 * Compile media query
1340
+	 *
1341
+	 * @param array $queryList
1342
+	 *
1343
+	 * @return string
1344
+	 */
1345
+	protected function compileMediaQuery($queryList)
1346
+	{
1347
+		$out = '@media';
1348
+		$first = true;
1349
+
1350
+		foreach ($queryList as $query) {
1351
+			$type = null;
1352
+			$parts = [];
1353
+
1354
+			foreach ($query as $q) {
1355
+				switch ($q[0]) {
1356
+					case Type::T_MEDIA_TYPE:
1357
+						if ($type) {
1358
+							$type = $this->mergeMediaTypes(
1359
+								$type,
1360
+								array_map([$this, 'compileValue'], array_slice($q, 1))
1361
+							);
1362
+
1363
+							if (empty($type)) { // merge failed
1364
+								return null;
1365
+							}
1366
+						} else {
1367
+							$type = array_map([$this, 'compileValue'], array_slice($q, 1));
1368
+						}
1369
+						break;
1370
+
1371
+					case Type::T_MEDIA_EXPRESSION:
1372
+						if (isset($q[2])) {
1373
+							$parts[] = '('
1374
+								. $this->compileValue($q[1])
1375
+								. $this->formatter->assignSeparator
1376
+								. $this->compileValue($q[2])
1377
+								. ')';
1378
+						} else {
1379
+							$parts[] = '('
1380
+								. $this->compileValue($q[1])
1381
+								. ')';
1382
+						}
1383
+						break;
1384
+
1385
+					case Type::T_MEDIA_VALUE:
1386
+						$parts[] = $this->compileValue($q[1]);
1387
+						break;
1388
+				}
1389
+			}
1390
+
1391
+			if ($type) {
1392
+				array_unshift($parts, implode(' ', array_filter($type)));
1393
+			}
1394
+
1395
+			if (! empty($parts)) {
1396
+				if ($first) {
1397
+					$first = false;
1398
+					$out .= ' ';
1399
+				} else {
1400
+					$out .= $this->formatter->tagSeparator;
1401
+				}
1402
+
1403
+				$out .= implode(' and ', $parts);
1404
+			}
1405
+		}
1406
+
1407
+		return $out;
1408
+	}
1409
+
1410
+	protected function mergeDirectRelationships($selectors1, $selectors2)
1411
+	{
1412
+		if (empty($selectors1) || empty($selectors2)) {
1413
+			return array_merge($selectors1, $selectors2);
1414
+		}
1415
+
1416
+		$part1 = end($selectors1);
1417
+		$part2 = end($selectors2);
1418
+
1419
+		if (! $this->isImmediateRelationshipCombinator($part1[0]) || $part1 !== $part2) {
1420
+			return array_merge($selectors1, $selectors2);
1421
+		}
1422
+
1423
+		$merged = [];
1424
+
1425
+		do {
1426
+			$part1 = array_pop($selectors1);
1427
+			$part2 = array_pop($selectors2);
1428
+
1429
+			if ($this->isImmediateRelationshipCombinator($part1[0]) && $part1 !== $part2) {
1430
+				$merged = array_merge($selectors1, [$part1], $selectors2, [$part2], $merged);
1431
+				break;
1432
+			}
1433
+
1434
+			array_unshift($merged, $part1);
1435
+			array_unshift($merged, [array_pop($selectors1)[0] . array_pop($selectors2)[0]]);
1436
+		} while (! empty($selectors1) && ! empty($selectors2));
1437
+
1438
+		return $merged;
1439
+	}
1440
+
1441
+	/**
1442
+	 * Merge media types
1443
+	 *
1444
+	 * @param array $type1
1445
+	 * @param array $type2
1446
+	 *
1447
+	 * @return array|null
1448
+	 */
1449
+	protected function mergeMediaTypes($type1, $type2)
1450
+	{
1451
+		if (empty($type1)) {
1452
+			return $type2;
1453
+		}
1454
+
1455
+		if (empty($type2)) {
1456
+			return $type1;
1457
+		}
1458
+
1459
+		$m1 = '';
1460
+		$t1 = '';
1461
+
1462
+		if (count($type1) > 1) {
1463
+			$m1= strtolower($type1[0]);
1464
+			$t1= strtolower($type1[1]);
1465
+		} else {
1466
+			$t1 = strtolower($type1[0]);
1467
+		}
1468
+
1469
+		$m2 = '';
1470
+		$t2 = '';
1471
+
1472
+		if (count($type2) > 1) {
1473
+			$m2 = strtolower($type2[0]);
1474
+			$t2 = strtolower($type2[1]);
1475
+		} else {
1476
+			$t2 = strtolower($type2[0]);
1477
+		}
1478
+
1479
+		if (($m1 === Type::T_NOT) ^ ($m2 === Type::T_NOT)) {
1480
+			if ($t1 === $t2) {
1481
+				return null;
1482
+			}
1483
+
1484
+			return [
1485
+				$m1 === Type::T_NOT ? $m2 : $m1,
1486
+				$m1 === Type::T_NOT ? $t2 : $t1,
1487
+			];
1488
+		}
1489
+
1490
+		if ($m1 === Type::T_NOT && $m2 === Type::T_NOT) {
1491
+			// CSS has no way of representing "neither screen nor print"
1492
+			if ($t1 !== $t2) {
1493
+				return null;
1494
+			}
1495
+
1496
+			return [Type::T_NOT, $t1];
1497
+		}
1498
+
1499
+		if ($t1 !== $t2) {
1500
+			return null;
1501
+		}
1502
+
1503
+		// t1 == t2, neither m1 nor m2 are "not"
1504
+		return [empty($m1)? $m2 : $m1, $t1];
1505
+	}
1506
+
1507
+	/**
1508
+	 * Compile import; returns true if the value was something that could be imported
1509
+	 *
1510
+	 * @param array   $rawPath
1511
+	 * @param array   $out
1512
+	 * @param boolean $once
1513
+	 *
1514
+	 * @return boolean
1515
+	 */
1516
+	protected function compileImport($rawPath, $out, $once = false)
1517
+	{
1518
+		if ($rawPath[0] === Type::T_STRING) {
1519
+			$path = $this->compileStringContent($rawPath);
1520
+
1521
+			if ($path = $this->findImport($path)) {
1522
+				if (! $once || ! in_array($path, $this->importedFiles)) {
1523
+					$this->importFile($path, $out);
1524
+					$this->importedFiles[] = $path;
1525
+				}
1526
+
1527
+				return true;
1528
+			}
1529
+
1530
+			return false;
1531
+		}
1532
+
1533
+		if ($rawPath[0] === Type::T_LIST) {
1534
+			// handle a list of strings
1535
+			if (count($rawPath[2]) === 0) {
1536
+				return false;
1537
+			}
1538
+
1539
+			foreach ($rawPath[2] as $path) {
1540
+				if ($path[0] !== Type::T_STRING) {
1541
+					return false;
1542
+				}
1543
+			}
1544
+
1545
+			foreach ($rawPath[2] as $path) {
1546
+				$this->compileImport($path, $out);
1547
+			}
1548
+
1549
+			return true;
1550
+		}
1551
+
1552
+		return false;
1553
+	}
1554
+
1555
+	/**
1556
+	 * Compile child; returns a value to halt execution
1557
+	 *
1558
+	 * @param array                                $child
1559
+	 * @param \Leafo\ScssPhp\Formatter\OutputBlock $out
1560
+	 *
1561
+	 * @return array
1562
+	 */
1563
+	protected function compileChild($child, OutputBlock $out)
1564
+	{
1565
+		$this->sourceIndex  = isset($child[Parser::SOURCE_INDEX]) ? $child[Parser::SOURCE_INDEX] : null;
1566
+		$this->sourceLine   = isset($child[Parser::SOURCE_LINE]) ? $child[Parser::SOURCE_LINE] : -1;
1567
+		$this->sourceColumn = isset($child[Parser::SOURCE_COLUMN]) ? $child[Parser::SOURCE_COLUMN] : -1;
1568
+
1569
+		switch ($child[0]) {
1570
+			case Type::T_SCSSPHP_IMPORT_ONCE:
1571
+				list(, $rawPath) = $child;
1572
+
1573
+				$rawPath = $this->reduce($rawPath);
1574
+
1575
+				if (! $this->compileImport($rawPath, $out, true)) {
1576
+					$out->lines[] = '@import ' . $this->compileValue($rawPath) . ';';
1577
+				}
1578
+				break;
1579
+
1580
+			case Type::T_IMPORT:
1581
+				list(, $rawPath) = $child;
1582
+
1583
+				$rawPath = $this->reduce($rawPath);
1584
+
1585
+				if (! $this->compileImport($rawPath, $out)) {
1586
+					$out->lines[] = '@import ' . $this->compileValue($rawPath) . ';';
1587
+				}
1588
+				break;
1589
+
1590
+			case Type::T_DIRECTIVE:
1591
+				$this->compileDirective($child[1]);
1592
+				break;
1593
+
1594
+			case Type::T_AT_ROOT:
1595
+				$this->compileAtRoot($child[1]);
1596
+				break;
1597
+
1598
+			case Type::T_MEDIA:
1599
+				$this->compileMedia($child[1]);
1600
+				break;
1601
+
1602
+			case Type::T_BLOCK:
1603
+				$this->compileBlock($child[1]);
1604
+				break;
1605
+
1606
+			case Type::T_CHARSET:
1607
+				if (! $this->charsetSeen) {
1608
+					$this->charsetSeen = true;
1609
+
1610
+					$out->lines[] = '@charset ' . $this->compileValue($child[1]) . ';';
1611
+				}
1612
+				break;
1613
+
1614
+			case Type::T_ASSIGN:
1615
+				list(, $name, $value) = $child;
1616
+
1617
+				if ($name[0] === Type::T_VARIABLE) {
1618
+					$flags = isset($child[3]) ? $child[3] : [];
1619
+					$isDefault = in_array('!default', $flags);
1620
+					$isGlobal = in_array('!global', $flags);
1621
+
1622
+					if ($isGlobal) {
1623
+						$this->set($name[1], $this->reduce($value), false, $this->rootEnv);
1624
+						break;
1625
+					}
1626
+
1627
+					$shouldSet = $isDefault &&
1628
+						(($result = $this->get($name[1], false)) === null
1629
+						|| $result === static::$null);
1630
+
1631
+					if (! $isDefault || $shouldSet) {
1632
+						$this->set($name[1], $this->reduce($value));
1633
+					}
1634
+					break;
1635
+				}
1636
+
1637
+				$compiledName = $this->compileValue($name);
1638
+
1639
+				// handle shorthand syntax: size / line-height
1640
+				if ($compiledName === 'font') {
1641
+					if ($value[0] === Type::T_EXPRESSION && $value[1] === '/') {
1642
+						$value = $this->expToString($value);
1643
+					} elseif ($value[0] === Type::T_LIST) {
1644
+						foreach ($value[2] as &$item) {
1645
+							if ($item[0] === Type::T_EXPRESSION && $item[1] === '/') {
1646
+								$item = $this->expToString($item);
1647
+							}
1648
+						}
1649
+					}
1650
+				}
1651
+
1652
+				// if the value reduces to null from something else then
1653
+				// the property should be discarded
1654
+				if ($value[0] !== Type::T_NULL) {
1655
+					$value = $this->reduce($value);
1656
+
1657
+					if ($value[0] === Type::T_NULL || $value === static::$nullString) {
1658
+						break;
1659
+					}
1660
+				}
1661
+
1662
+				$compiledValue = $this->compileValue($value);
1663
+
1664
+				$out->lines[] = $this->formatter->property(
1665
+					$compiledName,
1666
+					$compiledValue
1667
+				);
1668
+				break;
1669
+
1670
+			case Type::T_COMMENT:
1671
+				if ($out->type === Type::T_ROOT) {
1672
+					$this->compileComment($child);
1673
+					break;
1674
+				}
1675
+
1676
+				$out->lines[] = $child[1];
1677
+				break;
1678
+
1679
+			case Type::T_MIXIN:
1680
+			case Type::T_FUNCTION:
1681
+				list(, $block) = $child;
1682
+
1683
+				$this->set(static::$namespaces[$block->type] . $block->name, $block);
1684
+				break;
1685
+
1686
+			case Type::T_EXTEND:
1687
+				list(, $selectors) = $child;
1688
+
1689
+				foreach ($selectors as $sel) {
1690
+					$results = $this->evalSelectors([$sel]);
1691
+
1692
+					foreach ($results as $result) {
1693
+						// only use the first one
1694
+						$result = current($result);
1695
+
1696
+						$this->pushExtends($result, $out->selectors, $child);
1697
+					}
1698
+				}
1699
+				break;
1700
+
1701
+			case Type::T_IF:
1702
+				list(, $if) = $child;
1703
+
1704
+				if ($this->isTruthy($this->reduce($if->cond, true))) {
1705
+					return $this->compileChildren($if->children, $out);
1706
+				}
1707
+
1708
+				foreach ($if->cases as $case) {
1709
+					if ($case->type === Type::T_ELSE ||
1710
+						$case->type === Type::T_ELSEIF && $this->isTruthy($this->reduce($case->cond))
1711
+					) {
1712
+						return $this->compileChildren($case->children, $out);
1713
+					}
1714
+				}
1715
+				break;
1716
+
1717
+			case Type::T_EACH:
1718
+				list(, $each) = $child;
1719
+
1720
+				$list = $this->coerceList($this->reduce($each->list));
1721
+
1722
+				$this->pushEnv();
1723
+
1724
+				foreach ($list[2] as $item) {
1725
+					if (count($each->vars) === 1) {
1726
+						$this->set($each->vars[0], $item, true);
1727
+					} else {
1728
+						list(,, $values) = $this->coerceList($item);
1729
+
1730
+						foreach ($each->vars as $i => $var) {
1731
+							$this->set($var, isset($values[$i]) ? $values[$i] : static::$null, true);
1732
+						}
1733
+					}
1734
+
1735
+					$ret = $this->compileChildren($each->children, $out);
1736
+
1737
+					if ($ret) {
1738
+						if ($ret[0] !== Type::T_CONTROL) {
1739
+							$this->popEnv();
1740
+
1741
+							return $ret;
1742
+						}
1743
+
1744
+						if ($ret[1]) {
1745
+							break;
1746
+						}
1747
+					}
1748
+				}
1749 1749
 
1750
-                $this->popEnv();
1751
-                break;
1750
+				$this->popEnv();
1751
+				break;
1752 1752
 
1753
-            case Type::T_WHILE:
1754
-                list(, $while) = $child;
1753
+			case Type::T_WHILE:
1754
+				list(, $while) = $child;
1755 1755
 
1756
-                while ($this->isTruthy($this->reduce($while->cond, true))) {
1757
-                    $ret = $this->compileChildren($while->children, $out);
1756
+				while ($this->isTruthy($this->reduce($while->cond, true))) {
1757
+					$ret = $this->compileChildren($while->children, $out);
1758 1758
 
1759
-                    if ($ret) {
1760
-                        if ($ret[0] !== Type::T_CONTROL) {
1761
-                            return $ret;
1762
-                        }
1759
+					if ($ret) {
1760
+						if ($ret[0] !== Type::T_CONTROL) {
1761
+							return $ret;
1762
+						}
1763 1763
 
1764
-                        if ($ret[1]) {
1765
-                            break;
1766
-                        }
1767
-                    }
1768
-                }
1769
-                break;
1764
+						if ($ret[1]) {
1765
+							break;
1766
+						}
1767
+					}
1768
+				}
1769
+				break;
1770 1770
 
1771
-            case Type::T_FOR:
1772
-                list(, $for) = $child;
1771
+			case Type::T_FOR:
1772
+				list(, $for) = $child;
1773 1773
 
1774
-                $start = $this->reduce($for->start, true);
1775
-                $end   = $this->reduce($for->end, true);
1774
+				$start = $this->reduce($for->start, true);
1775
+				$end   = $this->reduce($for->end, true);
1776 1776
 
1777
-                if (! ($start[2] == $end[2] || $end->unitless())) {
1778
-                    $this->throwError('Incompatible units: "%s" and "%s".', $start->unitStr(), $end->unitStr());
1777
+				if (! ($start[2] == $end[2] || $end->unitless())) {
1778
+					$this->throwError('Incompatible units: "%s" and "%s".', $start->unitStr(), $end->unitStr());
1779 1779
 
1780
-                    break;
1781
-                }
1780
+					break;
1781
+				}
1782 1782
 
1783
-                $unit  = $start[2];
1784
-                $start = $start[1];
1785
-                $end   = $end[1];
1783
+				$unit  = $start[2];
1784
+				$start = $start[1];
1785
+				$end   = $end[1];
1786 1786
 
1787
-                $d = $start < $end ? 1 : -1;
1787
+				$d = $start < $end ? 1 : -1;
1788 1788
 
1789
-                for (;;) {
1790
-                    if ((! $for->until && $start - $d == $end) ||
1791
-                        ($for->until && $start == $end)
1792
-                    ) {
1793
-                        break;
1794
-                    }
1789
+				for (;;) {
1790
+					if ((! $for->until && $start - $d == $end) ||
1791
+						($for->until && $start == $end)
1792
+					) {
1793
+						break;
1794
+					}
1795 1795
 
1796
-                    $this->set($for->var, new Node\Number($start, $unit));
1797
-                    $start += $d;
1796
+					$this->set($for->var, new Node\Number($start, $unit));
1797
+					$start += $d;
1798 1798
 
1799
-                    $ret = $this->compileChildren($for->children, $out);
1799
+					$ret = $this->compileChildren($for->children, $out);
1800 1800
 
1801
-                    if ($ret) {
1802
-                        if ($ret[0] !== Type::T_CONTROL) {
1803
-                            return $ret;
1804
-                        }
1801
+					if ($ret) {
1802
+						if ($ret[0] !== Type::T_CONTROL) {
1803
+							return $ret;
1804
+						}
1805 1805
 
1806
-                        if ($ret[1]) {
1807
-                            break;
1808
-                        }
1809
-                    }
1810
-                }
1811
-                break;
1806
+						if ($ret[1]) {
1807
+							break;
1808
+						}
1809
+					}
1810
+				}
1811
+				break;
1812 1812
 
1813
-            case Type::T_BREAK:
1814
-                return [Type::T_CONTROL, true];
1813
+			case Type::T_BREAK:
1814
+				return [Type::T_CONTROL, true];
1815 1815
 
1816
-            case Type::T_CONTINUE:
1817
-                return [Type::T_CONTROL, false];
1816
+			case Type::T_CONTINUE:
1817
+				return [Type::T_CONTROL, false];
1818 1818
 
1819
-            case Type::T_RETURN:
1820
-                return $this->reduce($child[1], true);
1819
+			case Type::T_RETURN:
1820
+				return $this->reduce($child[1], true);
1821 1821
 
1822
-            case Type::T_NESTED_PROPERTY:
1823
-                list(, $prop) = $child;
1822
+			case Type::T_NESTED_PROPERTY:
1823
+				list(, $prop) = $child;
1824 1824
 
1825
-                $prefixed = [];
1826
-                $prefix = $this->compileValue($prop->prefix) . '-';
1825
+				$prefixed = [];
1826
+				$prefix = $this->compileValue($prop->prefix) . '-';
1827 1827
 
1828
-                foreach ($prop->children as $child) {
1829
-                    switch ($child[0]) {
1830
-                        case Type::T_ASSIGN:
1831
-                            array_unshift($child[1][2], $prefix);
1832
-                            break;
1828
+				foreach ($prop->children as $child) {
1829
+					switch ($child[0]) {
1830
+						case Type::T_ASSIGN:
1831
+							array_unshift($child[1][2], $prefix);
1832
+							break;
1833 1833
 
1834
-                        case Type::T_NESTED_PROPERTY:
1835
-                            array_unshift($child[1]->prefix[2], $prefix);
1836
-                            break;
1837
-                    }
1834
+						case Type::T_NESTED_PROPERTY:
1835
+							array_unshift($child[1]->prefix[2], $prefix);
1836
+							break;
1837
+					}
1838 1838
 
1839
-                    $prefixed[] = $child;
1840
-                }
1839
+					$prefixed[] = $child;
1840
+				}
1841 1841
 
1842
-                $this->compileChildrenNoReturn($prefixed, $out);
1843
-                break;
1842
+				$this->compileChildrenNoReturn($prefixed, $out);
1843
+				break;
1844 1844
 
1845
-            case Type::T_INCLUDE:
1846
-                // including a mixin
1847
-                list(, $name, $argValues, $content) = $child;
1845
+			case Type::T_INCLUDE:
1846
+				// including a mixin
1847
+				list(, $name, $argValues, $content) = $child;
1848 1848
 
1849
-                $mixin = $this->get(static::$namespaces['mixin'] . $name, false);
1849
+				$mixin = $this->get(static::$namespaces['mixin'] . $name, false);
1850 1850
 
1851
-                if (! $mixin) {
1852
-                    $this->throwError("Undefined mixin $name");
1853
-                    break;
1854
-                }
1851
+				if (! $mixin) {
1852
+					$this->throwError("Undefined mixin $name");
1853
+					break;
1854
+				}
1855 1855
 
1856
-                $callingScope = $this->getStoreEnv();
1856
+				$callingScope = $this->getStoreEnv();
1857 1857
 
1858
-                // push scope, apply args
1859
-                $this->pushEnv();
1860
-                $this->env->depth--;
1858
+				// push scope, apply args
1859
+				$this->pushEnv();
1860
+				$this->env->depth--;
1861 1861
 
1862
-                $storeEnv = $this->storeEnv;
1863
-                $this->storeEnv = $this->env;
1862
+				$storeEnv = $this->storeEnv;
1863
+				$this->storeEnv = $this->env;
1864 1864
 
1865
-                if (isset($content)) {
1866
-                    $content->scope = $callingScope;
1865
+				if (isset($content)) {
1866
+					$content->scope = $callingScope;
1867 1867
 
1868
-                    $this->setRaw(static::$namespaces['special'] . 'content', $content, $this->env);
1869
-                }
1868
+					$this->setRaw(static::$namespaces['special'] . 'content', $content, $this->env);
1869
+				}
1870 1870
 
1871
-                if (isset($mixin->args)) {
1872
-                    $this->applyArguments($mixin->args, $argValues);
1873
-                }
1871
+				if (isset($mixin->args)) {
1872
+					$this->applyArguments($mixin->args, $argValues);
1873
+				}
1874 1874
 
1875
-                $this->env->marker = 'mixin';
1875
+				$this->env->marker = 'mixin';
1876 1876
 
1877
-                $this->compileChildrenNoReturn($mixin->children, $out);
1877
+				$this->compileChildrenNoReturn($mixin->children, $out);
1878 1878
 
1879
-                $this->storeEnv = $storeEnv;
1879
+				$this->storeEnv = $storeEnv;
1880 1880
 
1881
-                $this->popEnv();
1882
-                break;
1881
+				$this->popEnv();
1882
+				break;
1883 1883
 
1884
-            case Type::T_MIXIN_CONTENT:
1885
-                $content = $this->get(static::$namespaces['special'] . 'content', false, $this->getStoreEnv())
1886
-                         ?: $this->get(static::$namespaces['special'] . 'content', false, $this->env);
1884
+			case Type::T_MIXIN_CONTENT:
1885
+				$content = $this->get(static::$namespaces['special'] . 'content', false, $this->getStoreEnv())
1886
+						 ?: $this->get(static::$namespaces['special'] . 'content', false, $this->env);
1887 1887
 
1888
-                if (! $content) {
1889
-                    $content = new \stdClass();
1890
-                    $content->scope = new \stdClass();
1891
-                    $content->children = $this->storeEnv->parent->block->children;
1892
-                    break;
1893
-                }
1894
-
1895
-                $storeEnv = $this->storeEnv;
1896
-                $this->storeEnv = $content->scope;
1897
-
1898
-                $this->compileChildrenNoReturn($content->children, $out);
1899
-
1900
-                $this->storeEnv = $storeEnv;
1901
-                break;
1902
-
1903
-            case Type::T_DEBUG:
1904
-                list(, $value) = $child;
1905
-
1906
-                $line = $this->sourceLine;
1907
-                $value = $this->compileValue($this->reduce($value, true));
1908
-                fwrite($this->stderr, "Line $line DEBUG: $value\n");
1909
-                break;
1910
-
1911
-            case Type::T_WARN:
1912
-                list(, $value) = $child;
1913
-
1914
-                $line = $this->sourceLine;
1915
-                $value = $this->compileValue($this->reduce($value, true));
1916
-                fwrite($this->stderr, "Line $line WARN: $value\n");
1917
-                break;
1918
-
1919
-            case Type::T_ERROR:
1920
-                list(, $value) = $child;
1921
-
1922
-                $line = $this->sourceLine;
1923
-                $value = $this->compileValue($this->reduce($value, true));
1924
-                $this->throwError("Line $line ERROR: $value\n");
1925
-                break;
1926
-
1927
-            case Type::T_CONTROL:
1928
-                $this->throwError('@break/@continue not permitted in this scope');
1929
-                break;
1930
-
1931
-            default:
1932
-                $this->throwError("unknown child type: $child[0]");
1933
-        }
1934
-    }
1935
-
1936
-    /**
1937
-     * Reduce expression to string
1938
-     *
1939
-     * @param array $exp
1940
-     *
1941
-     * @return array
1942
-     */
1943
-    protected function expToString($exp)
1944
-    {
1945
-        list(, $op, $left, $right, /* $inParens */, $whiteLeft, $whiteRight) = $exp;
1946
-
1947
-        $content = [$this->reduce($left)];
1948
-
1949
-        if ($whiteLeft) {
1950
-            $content[] = ' ';
1951
-        }
1952
-
1953
-        $content[] = $op;
1954
-
1955
-        if ($whiteRight) {
1956
-            $content[] = ' ';
1957
-        }
1958
-
1959
-        $content[] = $this->reduce($right);
1960
-
1961
-        return [Type::T_STRING, '', $content];
1962
-    }
1963
-
1964
-    /**
1965
-     * Is truthy?
1966
-     *
1967
-     * @param array $value
1968
-     *
1969
-     * @return array
1970
-     */
1971
-    protected function isTruthy($value)
1972
-    {
1973
-        return $value !== static::$false && $value !== static::$null;
1974
-    }
1975
-
1976
-    /**
1977
-     * Is the value a direct relationship combinator?
1978
-     *
1979
-     * @param string $value
1980
-     *
1981
-     * @return boolean
1982
-     */
1983
-    protected function isImmediateRelationshipCombinator($value)
1984
-    {
1985
-        return $value === '>' || $value === '+' || $value === '~';
1986
-    }
1987
-
1988
-    /**
1989
-     * Should $value cause its operand to eval
1990
-     *
1991
-     * @param array $value
1992
-     *
1993
-     * @return boolean
1994
-     */
1995
-    protected function shouldEval($value)
1996
-    {
1997
-        switch ($value[0]) {
1998
-            case Type::T_EXPRESSION:
1999
-                if ($value[1] === '/') {
2000
-                    return $this->shouldEval($value[2], $value[3]);
2001
-                }
2002
-
2003
-                // fall-thru
2004
-            case Type::T_VARIABLE:
2005
-            case Type::T_FUNCTION_CALL:
2006
-                return true;
2007
-        }
2008
-
2009
-        return false;
2010
-    }
2011
-
2012
-    /**
2013
-     * Reduce value
2014
-     *
2015
-     * @param array   $value
2016
-     * @param boolean $inExp
2017
-     *
2018
-     * @return array|\Leafo\ScssPhp\Node\Number
2019
-     */
2020
-    protected function reduce($value, $inExp = false)
2021
-    {
2022
-        list($type) = $value;
2023
-
2024
-        switch ($type) {
2025
-            case Type::T_EXPRESSION:
2026
-                list(, $op, $left, $right, $inParens) = $value;
2027
-
2028
-                $opName = isset(static::$operatorNames[$op]) ? static::$operatorNames[$op] : $op;
2029
-                $inExp = $inExp || $this->shouldEval($left) || $this->shouldEval($right);
2030
-
2031
-                $left = $this->reduce($left, true);
2032
-
2033
-                if ($op !== 'and' && $op !== 'or') {
2034
-                    $right = $this->reduce($right, true);
2035
-                }
2036
-
2037
-                // special case: looks like css shorthand
2038
-                if ($opName == 'div' && ! $inParens && ! $inExp && isset($right[2])
2039
-                    && (($right[0] !== Type::T_NUMBER && $right[2] != '')
2040
-                    || ($right[0] === Type::T_NUMBER && ! $right->unitless()))
2041
-                ) {
2042
-                    return $this->expToString($value);
2043
-                }
2044
-
2045
-                $left = $this->coerceForExpression($left);
2046
-                $right = $this->coerceForExpression($right);
2047
-
2048
-                $ltype = $left[0];
2049
-                $rtype = $right[0];
2050
-
2051
-                $ucOpName = ucfirst($opName);
2052
-                $ucLType  = ucfirst($ltype);
2053
-                $ucRType  = ucfirst($rtype);
2054
-
2055
-                // this tries:
2056
-                // 1. op[op name][left type][right type]
2057
-                // 2. op[left type][right type] (passing the op as first arg
2058
-                // 3. op[op name]
2059
-                $fn = "op${ucOpName}${ucLType}${ucRType}";
2060
-
2061
-                if (is_callable([$this, $fn]) ||
2062
-                    (($fn = "op${ucLType}${ucRType}") &&
2063
-                        is_callable([$this, $fn]) &&
2064
-                        $passOp = true) ||
2065
-                    (($fn = "op${ucOpName}") &&
2066
-                        is_callable([$this, $fn]) &&
2067
-                        $genOp = true)
2068
-                ) {
2069
-                    $coerceUnit = false;
2070
-
2071
-                    if (! isset($genOp) &&
2072
-                        $left[0] === Type::T_NUMBER && $right[0] === Type::T_NUMBER
2073
-                    ) {
2074
-                        $coerceUnit = true;
2075
-
2076
-                        switch ($opName) {
2077
-                            case 'mul':
2078
-                                $targetUnit = $left[2];
2079
-
2080
-                                foreach ($right[2] as $unit => $exp) {
2081
-                                    $targetUnit[$unit] = (isset($targetUnit[$unit]) ? $targetUnit[$unit] : 0) + $exp;
2082
-                                }
2083
-                                break;
2084
-
2085
-                            case 'div':
2086
-                                $targetUnit = $left[2];
2087
-
2088
-                                foreach ($right[2] as $unit => $exp) {
2089
-                                    $targetUnit[$unit] = (isset($targetUnit[$unit]) ? $targetUnit[$unit] : 0) - $exp;
2090
-                                }
2091
-                                break;
2092
-
2093
-                            case 'mod':
2094
-                                $targetUnit = $left[2];
2095
-                                break;
2096
-
2097
-                            default:
2098
-                                $targetUnit = $left->unitless() ? $right[2] : $left[2];
2099
-                        }
2100
-
2101
-                        if (! $left->unitless() && ! $right->unitless()) {
2102
-                            $left = $left->normalize();
2103
-                            $right = $right->normalize();
2104
-                        }
2105
-                    }
2106
-
2107
-                    $shouldEval = $inParens || $inExp;
2108
-
2109
-                    if (isset($passOp)) {
2110
-                        $out = $this->$fn($op, $left, $right, $shouldEval);
2111
-                    } else {
2112
-                        $out = $this->$fn($left, $right, $shouldEval);
2113
-                    }
2114
-
2115
-                    if (isset($out)) {
2116
-                        if ($coerceUnit && $out[0] === Type::T_NUMBER) {
2117
-                            $out = $out->coerce($targetUnit);
2118
-                        }
2119
-
2120
-                        return $out;
2121
-                    }
2122
-                }
2123
-
2124
-                return $this->expToString($value);
2125
-
2126
-            case Type::T_UNARY:
2127
-                list(, $op, $exp, $inParens) = $value;
2128
-
2129
-                $inExp = $inExp || $this->shouldEval($exp);
2130
-                $exp = $this->reduce($exp);
2131
-
2132
-                if ($exp[0] === Type::T_NUMBER) {
2133
-                    switch ($op) {
2134
-                        case '+':
2135
-                            return new Node\Number($exp[1], $exp[2]);
2136
-
2137
-                        case '-':
2138
-                            return new Node\Number(-$exp[1], $exp[2]);
2139
-                    }
2140
-                }
2141
-
2142
-                if ($op === 'not') {
2143
-                    if ($inExp || $inParens) {
2144
-                        if ($exp === static::$false || $exp === static::$null) {
2145
-                            return static::$true;
2146
-                        }
2147
-
2148
-                        return static::$false;
2149
-                    }
2150
-
2151
-                    $op = $op . ' ';
2152
-                }
2153
-
2154
-                return [Type::T_STRING, '', [$op, $exp]];
2155
-
2156
-            case Type::T_VARIABLE:
2157
-                list(, $name) = $value;
2158
-
2159
-                return $this->reduce($this->get($name));
2160
-
2161
-            case Type::T_LIST:
2162
-                foreach ($value[2] as &$item) {
2163
-                    $item = $this->reduce($item);
2164
-                }
2165
-
2166
-                return $value;
2167
-
2168
-            case Type::T_MAP:
2169
-                foreach ($value[1] as &$item) {
2170
-                    $item = $this->reduce($item);
2171
-                }
2172
-
2173
-                foreach ($value[2] as &$item) {
2174
-                    $item = $this->reduce($item);
2175
-                }
2176
-
2177
-                return $value;
2178
-
2179
-            case Type::T_STRING:
2180
-                foreach ($value[2] as &$item) {
2181
-                    if (is_array($item) || $item instanceof \ArrayAccess) {
2182
-                        $item = $this->reduce($item);
2183
-                    }
2184
-                }
2185
-
2186
-                return $value;
2187
-
2188
-            case Type::T_INTERPOLATE:
2189
-                $value[1] = $this->reduce($value[1]);
2190
-
2191
-                return $value;
2192
-
2193
-            case Type::T_FUNCTION_CALL:
2194
-                list(, $name, $argValues) = $value;
2195
-
2196
-                return $this->fncall($name, $argValues);
2197
-
2198
-            default:
2199
-                return $value;
2200
-        }
2201
-    }
2202
-
2203
-    /**
2204
-     * Function caller
2205
-     *
2206
-     * @param string $name
2207
-     * @param array  $argValues
2208
-     *
2209
-     * @return array|null
2210
-     */
2211
-    private function fncall($name, $argValues)
2212
-    {
2213
-        // SCSS @function
2214
-        if ($this->callScssFunction($name, $argValues, $returnValue)) {
2215
-            return $returnValue;
2216
-        }
2217
-
2218
-        // native PHP functions
2219
-        if ($this->callNativeFunction($name, $argValues, $returnValue)) {
2220
-            return $returnValue;
2221
-        }
2222
-
2223
-        // for CSS functions, simply flatten the arguments into a list
2224
-        $listArgs = [];
2225
-
2226
-        foreach ((array) $argValues as $arg) {
2227
-            if (empty($arg[0])) {
2228
-                $listArgs[] = $this->reduce($arg[1]);
2229
-            }
2230
-        }
2231
-
2232
-        return [Type::T_FUNCTION, $name, [Type::T_LIST, ',', $listArgs]];
2233
-    }
2234
-
2235
-    /**
2236
-     * Normalize name
2237
-     *
2238
-     * @param string $name
2239
-     *
2240
-     * @return string
2241
-     */
2242
-    protected function normalizeName($name)
2243
-    {
2244
-        return str_replace('-', '_', $name);
2245
-    }
2246
-
2247
-    /**
2248
-     * Normalize value
2249
-     *
2250
-     * @param array $value
2251
-     *
2252
-     * @return array
2253
-     */
2254
-    public function normalizeValue($value)
2255
-    {
2256
-        $value = $this->coerceForExpression($this->reduce($value));
2257
-        list($type) = $value;
2258
-
2259
-        switch ($type) {
2260
-            case Type::T_LIST:
2261
-                $value = $this->extractInterpolation($value);
2262
-
2263
-                if ($value[0] !== Type::T_LIST) {
2264
-                    return [Type::T_KEYWORD, $this->compileValue($value)];
2265
-                }
2266
-
2267
-                foreach ($value[2] as $key => $item) {
2268
-                    $value[2][$key] = $this->normalizeValue($item);
2269
-                }
2270
-
2271
-                return $value;
2272
-
2273
-            case Type::T_STRING:
2274
-                return [$type, '"', [$this->compileStringContent($value)]];
2275
-
2276
-            case Type::T_NUMBER:
2277
-                return $value->normalize();
2278
-
2279
-            case Type::T_INTERPOLATE:
2280
-                return [Type::T_KEYWORD, $this->compileValue($value)];
2281
-
2282
-            default:
2283
-                return $value;
2284
-        }
2285
-    }
2286
-
2287
-    /**
2288
-     * Add numbers
2289
-     *
2290
-     * @param array $left
2291
-     * @param array $right
2292
-     *
2293
-     * @return \Leafo\ScssPhp\Node\Number
2294
-     */
2295
-    protected function opAddNumberNumber($left, $right)
2296
-    {
2297
-        return new Node\Number($left[1] + $right[1], $left[2]);
2298
-    }
2299
-
2300
-    /**
2301
-     * Multiply numbers
2302
-     *
2303
-     * @param array $left
2304
-     * @param array $right
2305
-     *
2306
-     * @return \Leafo\ScssPhp\Node\Number
2307
-     */
2308
-    protected function opMulNumberNumber($left, $right)
2309
-    {
2310
-        return new Node\Number($left[1] * $right[1], $left[2]);
2311
-    }
2312
-
2313
-    /**
2314
-     * Subtract numbers
2315
-     *
2316
-     * @param array $left
2317
-     * @param array $right
2318
-     *
2319
-     * @return \Leafo\ScssPhp\Node\Number
2320
-     */
2321
-    protected function opSubNumberNumber($left, $right)
2322
-    {
2323
-        return new Node\Number($left[1] - $right[1], $left[2]);
2324
-    }
2325
-
2326
-    /**
2327
-     * Divide numbers
2328
-     *
2329
-     * @param array $left
2330
-     * @param array $right
2331
-     *
2332
-     * @return array|\Leafo\ScssPhp\Node\Number
2333
-     */
2334
-    protected function opDivNumberNumber($left, $right)
2335
-    {
2336
-        if ($right[1] == 0) {
2337
-            return [Type::T_STRING, '', [$left[1] . $left[2] . '/' . $right[1] . $right[2]]];
2338
-        }
2339
-
2340
-        return new Node\Number($left[1] / $right[1], $left[2]);
2341
-    }
2342
-
2343
-    /**
2344
-     * Mod numbers
2345
-     *
2346
-     * @param array $left
2347
-     * @param array $right
2348
-     *
2349
-     * @return \Leafo\ScssPhp\Node\Number
2350
-     */
2351
-    protected function opModNumberNumber($left, $right)
2352
-    {
2353
-        return new Node\Number($left[1] % $right[1], $left[2]);
2354
-    }
2355
-
2356
-    /**
2357
-     * Add strings
2358
-     *
2359
-     * @param array $left
2360
-     * @param array $right
2361
-     *
2362
-     * @return array
2363
-     */
2364
-    protected function opAdd($left, $right)
2365
-    {
2366
-        if ($strLeft = $this->coerceString($left)) {
2367
-            if ($right[0] === Type::T_STRING) {
2368
-                $right[1] = '';
2369
-            }
2370
-
2371
-            $strLeft[2][] = $right;
2372
-
2373
-            return $strLeft;
2374
-        }
2375
-
2376
-        if ($strRight = $this->coerceString($right)) {
2377
-            if ($left[0] === Type::T_STRING) {
2378
-                $left[1] = '';
2379
-            }
2380
-
2381
-            array_unshift($strRight[2], $left);
2382
-
2383
-            return $strRight;
2384
-        }
2385
-    }
2386
-
2387
-    /**
2388
-     * Boolean and
2389
-     *
2390
-     * @param array   $left
2391
-     * @param array   $right
2392
-     * @param boolean $shouldEval
2393
-     *
2394
-     * @return array
2395
-     */
2396
-    protected function opAnd($left, $right, $shouldEval)
2397
-    {
2398
-        if (! $shouldEval) {
2399
-            return;
2400
-        }
2401
-
2402
-        if ($left !== static::$false and $left !== static::$null) {
2403
-            return $this->reduce($right, true);
2404
-        }
2405
-
2406
-        return $left;
2407
-    }
2408
-
2409
-    /**
2410
-     * Boolean or
2411
-     *
2412
-     * @param array   $left
2413
-     * @param array   $right
2414
-     * @param boolean $shouldEval
2415
-     *
2416
-     * @return array
2417
-     */
2418
-    protected function opOr($left, $right, $shouldEval)
2419
-    {
2420
-        if (! $shouldEval) {
2421
-            return;
2422
-        }
2423
-
2424
-        if ($left !== static::$false and $left !== static::$null) {
2425
-            return $left;
2426
-        }
2427
-
2428
-        return $this->reduce($right, true);
2429
-    }
2430
-
2431
-    /**
2432
-     * Compare colors
2433
-     *
2434
-     * @param string $op
2435
-     * @param array  $left
2436
-     * @param array  $right
2437
-     *
2438
-     * @return array
2439
-     */
2440
-    protected function opColorColor($op, $left, $right)
2441
-    {
2442
-        $out = [Type::T_COLOR];
2443
-
2444
-        foreach ([1, 2, 3] as $i) {
2445
-            $lval = isset($left[$i]) ? $left[$i] : 0;
2446
-            $rval = isset($right[$i]) ? $right[$i] : 0;
2447
-
2448
-            switch ($op) {
2449
-                case '+':
2450
-                    $out[] = $lval + $rval;
2451
-                    break;
2452
-
2453
-                case '-':
2454
-                    $out[] = $lval - $rval;
2455
-                    break;
2456
-
2457
-                case '*':
2458
-                    $out[] = $lval * $rval;
2459
-                    break;
2460
-
2461
-                case '%':
2462
-                    $out[] = $lval % $rval;
2463
-                    break;
2464
-
2465
-                case '/':
2466
-                    if ($rval == 0) {
2467
-                        $this->throwError("color: Can't divide by zero");
2468
-                        break 2;
2469
-                    }
2470
-
2471
-                    $out[] = (int) ($lval / $rval);
2472
-                    break;
2473
-
2474
-                case '==':
2475
-                    return $this->opEq($left, $right);
2476
-
2477
-                case '!=':
2478
-                    return $this->opNeq($left, $right);
2479
-
2480
-                default:
2481
-                    $this->throwError("color: unknown op $op");
2482
-                    break 2;
2483
-            }
2484
-        }
2485
-
2486
-        if (isset($left[4])) {
2487
-            $out[4] = $left[4];
2488
-        } elseif (isset($right[4])) {
2489
-            $out[4] = $right[4];
2490
-        }
2491
-
2492
-        return $this->fixColor($out);
2493
-    }
2494
-
2495
-    /**
2496
-     * Compare color and number
2497
-     *
2498
-     * @param string $op
2499
-     * @param array  $left
2500
-     * @param array  $right
2501
-     *
2502
-     * @return array
2503
-     */
2504
-    protected function opColorNumber($op, $left, $right)
2505
-    {
2506
-        $value = $right[1];
2507
-
2508
-        return $this->opColorColor(
2509
-            $op,
2510
-            $left,
2511
-            [Type::T_COLOR, $value, $value, $value]
2512
-        );
2513
-    }
2514
-
2515
-    /**
2516
-     * Compare number and color
2517
-     *
2518
-     * @param string $op
2519
-     * @param array  $left
2520
-     * @param array  $right
2521
-     *
2522
-     * @return array
2523
-     */
2524
-    protected function opNumberColor($op, $left, $right)
2525
-    {
2526
-        $value = $left[1];
2527
-
2528
-        return $this->opColorColor(
2529
-            $op,
2530
-            [Type::T_COLOR, $value, $value, $value],
2531
-            $right
2532
-        );
2533
-    }
2534
-
2535
-    /**
2536
-     * Compare number1 == number2
2537
-     *
2538
-     * @param array $left
2539
-     * @param array $right
2540
-     *
2541
-     * @return array
2542
-     */
2543
-    protected function opEq($left, $right)
2544
-    {
2545
-        if (($lStr = $this->coerceString($left)) && ($rStr = $this->coerceString($right))) {
2546
-            $lStr[1] = '';
2547
-            $rStr[1] = '';
2548
-
2549
-            $left = $this->compileValue($lStr);
2550
-            $right = $this->compileValue($rStr);
2551
-        }
2552
-
2553
-        return $this->toBool($left === $right);
2554
-    }
2555
-
2556
-    /**
2557
-     * Compare number1 != number2
2558
-     *
2559
-     * @param array $left
2560
-     * @param array $right
2561
-     *
2562
-     * @return array
2563
-     */
2564
-    protected function opNeq($left, $right)
2565
-    {
2566
-        if (($lStr = $this->coerceString($left)) && ($rStr = $this->coerceString($right))) {
2567
-            $lStr[1] = '';
2568
-            $rStr[1] = '';
2569
-
2570
-            $left = $this->compileValue($lStr);
2571
-            $right = $this->compileValue($rStr);
2572
-        }
2573
-
2574
-        return $this->toBool($left !== $right);
2575
-    }
2576
-
2577
-    /**
2578
-     * Compare number1 >= number2
2579
-     *
2580
-     * @param array $left
2581
-     * @param array $right
2582
-     *
2583
-     * @return array
2584
-     */
2585
-    protected function opGteNumberNumber($left, $right)
2586
-    {
2587
-        return $this->toBool($left[1] >= $right[1]);
2588
-    }
2589
-
2590
-    /**
2591
-     * Compare number1 > number2
2592
-     *
2593
-     * @param array $left
2594
-     * @param array $right
2595
-     *
2596
-     * @return array
2597
-     */
2598
-    protected function opGtNumberNumber($left, $right)
2599
-    {
2600
-        return $this->toBool($left[1] > $right[1]);
2601
-    }
2602
-
2603
-    /**
2604
-     * Compare number1 <= number2
2605
-     *
2606
-     * @param array $left
2607
-     * @param array $right
2608
-     *
2609
-     * @return array
2610
-     */
2611
-    protected function opLteNumberNumber($left, $right)
2612
-    {
2613
-        return $this->toBool($left[1] <= $right[1]);
2614
-    }
2615
-
2616
-    /**
2617
-     * Compare number1 < number2
2618
-     *
2619
-     * @param array $left
2620
-     * @param array $right
2621
-     *
2622
-     * @return array
2623
-     */
2624
-    protected function opLtNumberNumber($left, $right)
2625
-    {
2626
-        return $this->toBool($left[1] < $right[1]);
2627
-    }
2628
-
2629
-    /**
2630
-     * Three-way comparison, aka spaceship operator
2631
-     *
2632
-     * @param array $left
2633
-     * @param array $right
2634
-     *
2635
-     * @return \Leafo\ScssPhp\Node\Number
2636
-     */
2637
-    protected function opCmpNumberNumber($left, $right)
2638
-    {
2639
-        $n = $left[1] - $right[1];
2640
-
2641
-        return new Node\Number($n ? $n / abs($n) : 0, '');
2642
-    }
2643
-
2644
-    /**
2645
-     * Cast to boolean
2646
-     *
2647
-     * @api
2648
-     *
2649
-     * @param mixed $thing
2650
-     *
2651
-     * @return array
2652
-     */
2653
-    public function toBool($thing)
2654
-    {
2655
-        return $thing ? static::$true : static::$false;
2656
-    }
2657
-
2658
-    /**
2659
-     * Compiles a primitive value into a CSS property value.
2660
-     *
2661
-     * Values in scssphp are typed by being wrapped in arrays, their format is
2662
-     * typically:
2663
-     *
2664
-     *     array(type, contents [, additional_contents]*)
2665
-     *
2666
-     * The input is expected to be reduced. This function will not work on
2667
-     * things like expressions and variables.
2668
-     *
2669
-     * @api
2670
-     *
2671
-     * @param array $value
2672
-     *
2673
-     * @return string
2674
-     */
2675
-    public function compileValue($value)
2676
-    {
2677
-        $value = $this->reduce($value);
2678
-
2679
-        list($type) = $value;
2680
-
2681
-        switch ($type) {
2682
-            case Type::T_KEYWORD:
2683
-                return $value[1];
2684
-
2685
-            case Type::T_COLOR:
2686
-                // [1] - red component (either number for a %)
2687
-                // [2] - green component
2688
-                // [3] - blue component
2689
-                // [4] - optional alpha component
2690
-                list(, $r, $g, $b) = $value;
2691
-
2692
-                $r = round($r);
2693
-                $g = round($g);
2694
-                $b = round($b);
2695
-
2696
-                if (count($value) === 5 && $value[4] !== 1) { // rgba
2697
-                    return 'rgba(' . $r . ', ' . $g . ', ' . $b . ', ' . $value[4] . ')';
2698
-                }
2699
-
2700
-                $h = sprintf('#%02x%02x%02x', $r, $g, $b);
2701
-
2702
-                // Converting hex color to short notation (e.g. #003399 to #039)
2703
-                if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {
2704
-                    $h = '#' . $h[1] . $h[3] . $h[5];
2705
-                }
2706
-
2707
-                return $h;
2708
-
2709
-            case Type::T_NUMBER:
2710
-                return $value->output($this);
2711
-
2712
-            case Type::T_STRING:
2713
-                return $value[1] . $this->compileStringContent($value) . $value[1];
2714
-
2715
-            case Type::T_FUNCTION:
2716
-                $args = ! empty($value[2]) ? $this->compileValue($value[2]) : '';
2717
-
2718
-                return "$value[1]($args)";
2719
-
2720
-            case Type::T_LIST:
2721
-                $value = $this->extractInterpolation($value);
2722
-
2723
-                if ($value[0] !== Type::T_LIST) {
2724
-                    return $this->compileValue($value);
2725
-                }
2726
-
2727
-                list(, $delim, $items) = $value;
2728
-
2729
-                if ($delim !== ' ') {
2730
-                    $delim .= ' ';
2731
-                }
2732
-
2733
-                $filtered = [];
2734
-
2735
-                foreach ($items as $item) {
2736
-                    if ($item[0] === Type::T_NULL) {
2737
-                        continue;
2738
-                    }
2739
-
2740
-                    $filtered[] = $this->compileValue($item);
2741
-                }
2742
-
2743
-                return implode("$delim", $filtered);
2744
-
2745
-            case Type::T_MAP:
2746
-                $keys = $value[1];
2747
-                $values = $value[2];
2748
-                $filtered = [];
2749
-
2750
-                for ($i = 0, $s = count($keys); $i < $s; $i++) {
2751
-                    $filtered[$this->compileValue($keys[$i])] = $this->compileValue($values[$i]);
2752
-                }
2753
-
2754
-                array_walk($filtered, function (&$value, $key) {
2755
-                    $value = $key . ': ' . $value;
2756
-                });
2757
-
2758
-                return '(' . implode(', ', $filtered) . ')';
2759
-
2760
-            case Type::T_INTERPOLATED:
2761
-                // node created by extractInterpolation
2762
-                list(, $interpolate, $left, $right) = $value;
2763
-                list(,, $whiteLeft, $whiteRight) = $interpolate;
2764
-
2765
-                $left = count($left[2]) > 0 ?
2766
-                    $this->compileValue($left) . $whiteLeft : '';
2767
-
2768
-                $right = count($right[2]) > 0 ?
2769
-                    $whiteRight . $this->compileValue($right) : '';
2770
-
2771
-                return $left . $this->compileValue($interpolate) . $right;
2772
-
2773
-            case Type::T_INTERPOLATE:
2774
-                // raw parse node
2775
-                list(, $exp) = $value;
2776
-
2777
-                // strip quotes if it's a string
2778
-                $reduced = $this->reduce($exp);
2779
-
2780
-                switch ($reduced[0]) {
2781
-                    case Type::T_LIST:
2782
-                        $reduced = $this->extractInterpolation($reduced);
2783
-
2784
-                        if ($reduced[0] !== Type::T_LIST) {
2785
-                            break;
2786
-                        }
2787
-
2788
-                        list(, $delim, $items) = $reduced;
2789
-
2790
-                        if ($delim !== ' ') {
2791
-                            $delim .= ' ';
2792
-                        }
2793
-
2794
-                        $filtered = [];
2795
-
2796
-                        foreach ($items as $item) {
2797
-                            if ($item[0] === Type::T_NULL) {
2798
-                                continue;
2799
-                            }
2800
-
2801
-                            $temp = $this->compileValue([Type::T_KEYWORD, $item]);
2802
-                            if ($temp[0] === Type::T_STRING) {
2803
-                                $filtered[] = $this->compileStringContent($temp);
2804
-                            } elseif ($temp[0] === Type::T_KEYWORD) {
2805
-                                $filtered[] = $temp[1];
2806
-                            } else {
2807
-                                $filtered[] = $this->compileValue($temp);
2808
-                            }
2809
-                        }
2810
-
2811
-                        $reduced = [Type::T_KEYWORD, implode("$delim", $filtered)];
2812
-                        break;
2813
-
2814
-                    case Type::T_STRING:
2815
-                        $reduced = [Type::T_KEYWORD, $this->compileStringContent($reduced)];
2816
-                        break;
2817
-
2818
-                    case Type::T_NULL:
2819
-                        $reduced = [Type::T_KEYWORD, ''];
2820
-                }
2821
-
2822
-                return $this->compileValue($reduced);
2823
-
2824
-            case Type::T_NULL:
2825
-                return 'null';
2826
-
2827
-            default:
2828
-                $this->throwError("unknown value type: $type");
2829
-        }
2830
-    }
2831
-
2832
-    /**
2833
-     * Flatten list
2834
-     *
2835
-     * @param array $list
2836
-     *
2837
-     * @return string
2838
-     */
2839
-    protected function flattenList($list)
2840
-    {
2841
-        return $this->compileValue($list);
2842
-    }
2843
-
2844
-    /**
2845
-     * Compile string content
2846
-     *
2847
-     * @param array $string
2848
-     *
2849
-     * @return string
2850
-     */
2851
-    protected function compileStringContent($string)
2852
-    {
2853
-        $parts = [];
2854
-
2855
-        foreach ($string[2] as $part) {
2856
-            if (is_array($part) || $part instanceof \ArrayAccess) {
2857
-                $parts[] = $this->compileValue($part);
2858
-            } else {
2859
-                $parts[] = $part;
2860
-            }
2861
-        }
2862
-
2863
-        return implode($parts);
2864
-    }
2865
-
2866
-    /**
2867
-     * Extract interpolation; it doesn't need to be recursive, compileValue will handle that
2868
-     *
2869
-     * @param array $list
2870
-     *
2871
-     * @return array
2872
-     */
2873
-    protected function extractInterpolation($list)
2874
-    {
2875
-        $items = $list[2];
2876
-
2877
-        foreach ($items as $i => $item) {
2878
-            if ($item[0] === Type::T_INTERPOLATE) {
2879
-                $before = [Type::T_LIST, $list[1], array_slice($items, 0, $i)];
2880
-                $after  = [Type::T_LIST, $list[1], array_slice($items, $i + 1)];
2881
-
2882
-                return [Type::T_INTERPOLATED, $item, $before, $after];
2883
-            }
2884
-        }
2885
-
2886
-        return $list;
2887
-    }
2888
-
2889
-    /**
2890
-     * Find the final set of selectors
2891
-     *
2892
-     * @param \Leafo\ScssPhp\Compiler\Environment $env
2893
-     *
2894
-     * @return array
2895
-     */
2896
-    protected function multiplySelectors(Environment $env)
2897
-    {
2898
-        $envs            = $this->compactEnv($env);
2899
-        $selectors       = [];
2900
-        $parentSelectors = [[]];
2901
-
2902
-        while ($env = array_pop($envs)) {
2903
-            if (empty($env->selectors)) {
2904
-                continue;
2905
-            }
2906
-
2907
-            $selectors = [];
2908
-
2909
-            foreach ($env->selectors as $selector) {
2910
-                foreach ($parentSelectors as $parent) {
2911
-                    $selectors[] = $this->joinSelectors($parent, $selector);
2912
-                }
2913
-            }
2914
-
2915
-            $parentSelectors = $selectors;
2916
-        }
2917
-
2918
-        return $selectors;
2919
-    }
2920
-
2921
-    /**
2922
-     * Join selectors; looks for & to replace, or append parent before child
2923
-     *
2924
-     * @param array $parent
2925
-     * @param array $child
2926
-     *
2927
-     * @return array
2928
-     */
2929
-    protected function joinSelectors($parent, $child)
2930
-    {
2931
-        $setSelf = false;
2932
-        $out = [];
2933
-
2934
-        foreach ($child as $part) {
2935
-            $newPart = [];
2936
-
2937
-            foreach ($part as $p) {
2938
-                if ($p === static::$selfSelector) {
2939
-                    $setSelf = true;
2940
-
2941
-                    foreach ($parent as $i => $parentPart) {
2942
-                        if ($i > 0) {
2943
-                            $out[] = $newPart;
2944
-                            $newPart = [];
2945
-                        }
2946
-
2947
-                        foreach ($parentPart as $pp) {
2948
-                            $newPart[] = $pp;
2949
-                        }
2950
-                    }
2951
-                } else {
2952
-                    $newPart[] = $p;
2953
-                }
2954
-            }
2955
-
2956
-            $out[] = $newPart;
2957
-        }
2958
-
2959
-        return $setSelf ? $out : array_merge($parent, $child);
2960
-    }
2961
-
2962
-    /**
2963
-     * Multiply media
2964
-     *
2965
-     * @param \Leafo\ScssPhp\Compiler\Environment $env
2966
-     * @param array                               $childQueries
2967
-     *
2968
-     * @return array
2969
-     */
2970
-    protected function multiplyMedia(Environment $env = null, $childQueries = null)
2971
-    {
2972
-        if (! isset($env) ||
2973
-            ! empty($env->block->type) && $env->block->type !== Type::T_MEDIA
2974
-        ) {
2975
-            return $childQueries;
2976
-        }
2977
-
2978
-        // plain old block, skip
2979
-        if (empty($env->block->type)) {
2980
-            return $this->multiplyMedia($env->parent, $childQueries);
2981
-        }
2982
-
2983
-        $parentQueries = isset($env->block->queryList)
2984
-            ? $env->block->queryList
2985
-            : [[[Type::T_MEDIA_VALUE, $env->block->value]]];
2986
-
2987
-        if ($childQueries === null) {
2988
-            $childQueries = $parentQueries;
2989
-        } else {
2990
-            $originalQueries = $childQueries;
2991
-            $childQueries = [];
2992
-
2993
-            foreach ($parentQueries as $parentQuery) {
2994
-                foreach ($originalQueries as $childQuery) {
2995
-                    $childQueries []= array_merge($parentQuery, $childQuery);
2996
-                }
2997
-            }
2998
-        }
2999
-
3000
-        return $this->multiplyMedia($env->parent, $childQueries);
3001
-    }
3002
-
3003
-    /**
3004
-     * Convert env linked list to stack
3005
-     *
3006
-     * @param \Leafo\ScssPhp\Compiler\Environment $env
3007
-     *
3008
-     * @return array
3009
-     */
3010
-    private function compactEnv(Environment $env)
3011
-    {
3012
-        for ($envs = []; $env; $env = $env->parent) {
3013
-            $envs[] = $env;
3014
-        }
3015
-
3016
-        return $envs;
3017
-    }
3018
-
3019
-    /**
3020
-     * Convert env stack to singly linked list
3021
-     *
3022
-     * @param array $envs
3023
-     *
3024
-     * @return \Leafo\ScssPhp\Compiler\Environment
3025
-     */
3026
-    private function extractEnv($envs)
3027
-    {
3028
-        for ($env = null; $e = array_pop($envs);) {
3029
-            $e->parent = $env;
3030
-            $env = $e;
3031
-        }
3032
-
3033
-        return $env;
3034
-    }
3035
-
3036
-    /**
3037
-     * Push environment
3038
-     *
3039
-     * @param \Leafo\ScssPhp\Block $block
3040
-     *
3041
-     * @return \Leafo\ScssPhp\Compiler\Environment
3042
-     */
3043
-    protected function pushEnv(Block $block = null)
3044
-    {
3045
-        $env = new Environment;
3046
-        $env->parent = $this->env;
3047
-        $env->store  = [];
3048
-        $env->block  = $block;
3049
-        $env->depth  = isset($this->env->depth) ? $this->env->depth + 1 : 0;
3050
-
3051
-        $this->env = $env;
3052
-
3053
-        return $env;
3054
-    }
3055
-
3056
-    /**
3057
-     * Pop environment
3058
-     */
3059
-    protected function popEnv()
3060
-    {
3061
-        $this->env = $this->env->parent;
3062
-    }
3063
-
3064
-    /**
3065
-     * Get store environment
3066
-     *
3067
-     * @return \Leafo\ScssPhp\Compiler\Environment
3068
-     */
3069
-    protected function getStoreEnv()
3070
-    {
3071
-        return isset($this->storeEnv) ? $this->storeEnv : $this->env;
3072
-    }
3073
-
3074
-    /**
3075
-     * Set variable
3076
-     *
3077
-     * @param string                              $name
3078
-     * @param mixed                               $value
3079
-     * @param boolean                             $shadow
3080
-     * @param \Leafo\ScssPhp\Compiler\Environment $env
3081
-     */
3082
-    protected function set($name, $value, $shadow = false, Environment $env = null)
3083
-    {
3084
-        $name = $this->normalizeName($name);
3085
-
3086
-        if (! isset($env)) {
3087
-            $env = $this->getStoreEnv();
3088
-        }
3089
-
3090
-        if ($shadow) {
3091
-            $this->setRaw($name, $value, $env);
3092
-        } else {
3093
-            $this->setExisting($name, $value, $env);
3094
-        }
3095
-    }
3096
-
3097
-    /**
3098
-     * Set existing variable
3099
-     *
3100
-     * @param string                              $name
3101
-     * @param mixed                               $value
3102
-     * @param \Leafo\ScssPhp\Compiler\Environment $env
3103
-     */
3104
-    protected function setExisting($name, $value, Environment $env)
3105
-    {
3106
-        $storeEnv = $env;
3107
-
3108
-        $hasNamespace = $name[0] === '^' || $name[0] === '@' || $name[0] === '%';
3109
-
3110
-        for (;;) {
3111
-            if (array_key_exists($name, $env->store)) {
3112
-                break;
3113
-            }
3114
-
3115
-            if (! $hasNamespace && isset($env->marker)) {
3116
-                $env = $storeEnv;
3117
-                break;
3118
-            }
3119
-
3120
-            if (! isset($env->parent)) {
3121
-                $env = $storeEnv;
3122
-                break;
3123
-            }
3124
-
3125
-            $env = $env->parent;
3126
-        }
3127
-
3128
-        $env->store[$name] = $value;
3129
-    }
3130
-
3131
-    /**
3132
-     * Set raw variable
3133
-     *
3134
-     * @param string                              $name
3135
-     * @param mixed                               $value
3136
-     * @param \Leafo\ScssPhp\Compiler\Environment $env
3137
-     */
3138
-    protected function setRaw($name, $value, Environment $env)
3139
-    {
3140
-        $env->store[$name] = $value;
3141
-    }
3142
-
3143
-    /**
3144
-     * Get variable
3145
-     *
3146
-     * @api
3147
-     *
3148
-     * @param string                              $name
3149
-     * @param boolean                             $shouldThrow
3150
-     * @param \Leafo\ScssPhp\Compiler\Environment $env
3151
-     *
3152
-     * @return mixed
3153
-     */
3154
-    public function get($name, $shouldThrow = true, Environment $env = null)
3155
-    {
3156
-        $normalizedName = $this->normalizeName($name);
3157
-        $specialContentKey = static::$namespaces['special'] . 'content';
3158
-
3159
-        if (! isset($env)) {
3160
-            $env = $this->getStoreEnv();
3161
-        }
3162
-
3163
-        $nextIsRoot = false;
3164
-        $hasNamespace = $normalizedName[0] === '^' || $normalizedName[0] === '@' || $normalizedName[0] === '%';
3165
-
3166
-        for (;;) {
3167
-            if (array_key_exists($normalizedName, $env->store)) {
3168
-                return $env->store[$normalizedName];
3169
-            }
3170
-
3171
-            if (! $hasNamespace && isset($env->marker)) {
3172
-                if (! $nextIsRoot && ! empty($env->store[$specialContentKey])) {
3173
-                    $env = $env->store[$specialContentKey]->scope;
3174
-                    $nextIsRoot = true;
3175
-                    continue;
3176
-                }
3177
-
3178
-                $env = $this->rootEnv;
3179
-                continue;
3180
-            }
3181
-
3182
-            if (! isset($env->parent)) {
3183
-                break;
3184
-            }
3185
-
3186
-            $env = $env->parent;
3187
-        }
3188
-
3189
-        if ($shouldThrow) {
3190
-            $this->throwError("Undefined variable \$$name");
3191
-        }
3192
-
3193
-        // found nothing
3194
-    }
3195
-
3196
-    /**
3197
-     * Has variable?
3198
-     *
3199
-     * @param string                              $name
3200
-     * @param \Leafo\ScssPhp\Compiler\Environment $env
3201
-     *
3202
-     * @return boolean
3203
-     */
3204
-    protected function has($name, Environment $env = null)
3205
-    {
3206
-        return $this->get($name, false, $env) !== null;
3207
-    }
3208
-
3209
-    /**
3210
-     * Inject variables
3211
-     *
3212
-     * @param array $args
3213
-     */
3214
-    protected function injectVariables(array $args)
3215
-    {
3216
-        if (empty($args)) {
3217
-            return;
3218
-        }
3219
-
3220
-        $parser = $this->parserFactory(__METHOD__);
3221
-
3222
-        foreach ($args as $name => $strValue) {
3223
-            if ($name[0] === '$') {
3224
-                $name = substr($name, 1);
3225
-            }
3226
-
3227
-            if (! $parser->parseValue($strValue, $value)) {
3228
-                $value = $this->coerceValue($strValue);
3229
-            }
3230
-
3231
-            $this->set($name, $value);
3232
-        }
3233
-    }
3234
-
3235
-    /**
3236
-     * Set variables
3237
-     *
3238
-     * @api
3239
-     *
3240
-     * @param array $variables
3241
-     */
3242
-    public function setVariables(array $variables)
3243
-    {
3244
-        $this->registeredVars = array_merge($this->registeredVars, $variables);
3245
-    }
3246
-
3247
-    /**
3248
-     * Unset variable
3249
-     *
3250
-     * @api
3251
-     *
3252
-     * @param string $name
3253
-     */
3254
-    public function unsetVariable($name)
3255
-    {
3256
-        unset($this->registeredVars[$name]);
3257
-    }
3258
-
3259
-    /**
3260
-     * Returns list of variables
3261
-     *
3262
-     * @api
3263
-     *
3264
-     * @return array
3265
-     */
3266
-    public function getVariables()
3267
-    {
3268
-        return $this->registeredVars;
3269
-    }
3270
-
3271
-    /**
3272
-     * Adds to list of parsed files
3273
-     *
3274
-     * @api
3275
-     *
3276
-     * @param string $path
3277
-     */
3278
-    public function addParsedFile($path)
3279
-    {
3280
-        if (isset($path) && file_exists($path)) {
3281
-            $this->parsedFiles[realpath($path)] = filemtime($path);
3282
-        }
3283
-    }
3284
-
3285
-    /**
3286
-     * Returns list of parsed files
3287
-     *
3288
-     * @api
3289
-     *
3290
-     * @return array
3291
-     */
3292
-    public function getParsedFiles()
3293
-    {
3294
-        return $this->parsedFiles;
3295
-    }
3296
-
3297
-    /**
3298
-     * Add import path
3299
-     *
3300
-     * @api
3301
-     *
3302
-     * @param string $path
3303
-     */
3304
-    public function addImportPath($path)
3305
-    {
3306
-        if (! in_array($path, $this->importPaths)) {
3307
-            $this->importPaths[] = $path;
3308
-        }
3309
-    }
3310
-
3311
-    /**
3312
-     * Set import paths
3313
-     *
3314
-     * @api
3315
-     *
3316
-     * @param string|array $path
3317
-     */
3318
-    public function setImportPaths($path)
3319
-    {
3320
-        $this->importPaths = (array) $path;
3321
-    }
3322
-
3323
-    /**
3324
-     * Set number precision
3325
-     *
3326
-     * @api
3327
-     *
3328
-     * @param integer $numberPrecision
3329
-     */
3330
-    public function setNumberPrecision($numberPrecision)
3331
-    {
3332
-        Node\Number::$precision = $numberPrecision;
3333
-    }
3334
-
3335
-    /**
3336
-     * Set formatter
3337
-     *
3338
-     * @api
3339
-     *
3340
-     * @param string $formatterName
3341
-     */
3342
-    public function setFormatter($formatterName)
3343
-    {
3344
-        $this->formatter = $formatterName;
3345
-    }
3346
-
3347
-    /**
3348
-     * Set line number style
3349
-     *
3350
-     * @api
3351
-     *
3352
-     * @param string $lineNumberStyle
3353
-     */
3354
-    public function setLineNumberStyle($lineNumberStyle)
3355
-    {
3356
-        $this->lineNumberStyle = $lineNumberStyle;
3357
-    }
3358
-
3359
-    /**
3360
-     * Enable/disable source maps
3361
-     *
3362
-     * @api
3363
-     *
3364
-     * @param integer $sourceMap
3365
-     */
3366
-    public function setSourceMap($sourceMap)
3367
-    {
3368
-        $this->sourceMap = $sourceMap;
3369
-    }
3370
-
3371
-    /**
3372
-     * Set source map options
3373
-     *
3374
-     * @api
3375
-     *
3376
-     * @param array $sourceMapOptions
3377
-     */
3378
-    public function setSourceMapOptions($sourceMapOptions)
3379
-    {
3380
-        $this->sourceMapOptions = $sourceMapOptions;
3381
-    }
3382
-
3383
-    /**
3384
-     * Register function
3385
-     *
3386
-     * @api
3387
-     *
3388
-     * @param string   $name
3389
-     * @param callable $func
3390
-     * @param array    $prototype
3391
-     */
3392
-    public function registerFunction($name, $func, $prototype = null)
3393
-    {
3394
-        $this->userFunctions[$this->normalizeName($name)] = [$func, $prototype];
3395
-    }
3396
-
3397
-    /**
3398
-     * Unregister function
3399
-     *
3400
-     * @api
3401
-     *
3402
-     * @param string $name
3403
-     */
3404
-    public function unregisterFunction($name)
3405
-    {
3406
-        unset($this->userFunctions[$this->normalizeName($name)]);
3407
-    }
3408
-
3409
-    /**
3410
-     * Add feature
3411
-     *
3412
-     * @api
3413
-     *
3414
-     * @param string $name
3415
-     */
3416
-    public function addFeature($name)
3417
-    {
3418
-        $this->registeredFeatures[$name] = true;
3419
-    }
3420
-
3421
-    /**
3422
-     * Import file
3423
-     *
3424
-     * @param string $path
3425
-     * @param array  $out
3426
-     */
3427
-    protected function importFile($path, $out)
3428
-    {
3429
-        // see if tree is cached
3430
-        $realPath = realpath($path);
3431
-
3432
-        if (isset($this->importCache[$realPath])) {
3433
-            $this->handleImportLoop($realPath);
3434
-
3435
-            $tree = $this->importCache[$realPath];
3436
-        } else {
3437
-            $code   = file_get_contents($path);
3438
-            $parser = $this->parserFactory($path);
3439
-            $tree   = $parser->parse($code);
3440
-
3441
-            $this->importCache[$realPath] = $tree;
3442
-        }
3443
-
3444
-        $pi = pathinfo($path);
3445
-        array_unshift($this->importPaths, $pi['dirname']);
3446
-        $this->compileChildrenNoReturn($tree->children, $out);
3447
-        array_shift($this->importPaths);
3448
-    }
3449
-
3450
-    /**
3451
-     * Return the file path for an import url if it exists
3452
-     *
3453
-     * @api
3454
-     *
3455
-     * @param string $url
3456
-     *
3457
-     * @return string|null
3458
-     */
3459
-    public function findImport($url)
3460
-    {
3461
-        $urls = [];
3462
-
3463
-        // for "normal" scss imports (ignore vanilla css and external requests)
3464
-        if (! preg_match('/\.css$|^https?:\/\//', $url)) {
3465
-            // try both normal and the _partial filename
3466
-            $urls = [$url, preg_replace('/[^\/]+$/', '_\0', $url)];
3467
-        }
3468
-
3469
-        $hasExtension = preg_match('/[.]s?css$/', $url);
3470
-
3471
-        foreach ($this->importPaths as $dir) {
3472
-            if (is_string($dir)) {
3473
-                // check urls for normal import paths
3474
-                foreach ($urls as $full) {
3475
-                    $full = $dir
3476
-                        . (! empty($dir) && substr($dir, -1) !== '/' ? '/' : '')
3477
-                        . $full;
3478
-
3479
-                    if ($this->fileExists($file = $full . '.scss') ||
3480
-                        ($hasExtension && $this->fileExists($file = $full))
3481
-                    ) {
3482
-                        return $file;
3483
-                    }
3484
-                }
3485
-            } elseif (is_callable($dir)) {
3486
-                // check custom callback for import path
3487
-                $file = call_user_func($dir, $url);
3488
-
3489
-                if ($file !== null) {
3490
-                    return $file;
3491
-                }
3492
-            }
3493
-        }
3494
-
3495
-        return null;
3496
-    }
3497
-
3498
-    /**
3499
-     * Set encoding
3500
-     *
3501
-     * @api
3502
-     *
3503
-     * @param string $encoding
3504
-     */
3505
-    public function setEncoding($encoding)
3506
-    {
3507
-        $this->encoding = $encoding;
3508
-    }
3509
-
3510
-    /**
3511
-     * Ignore errors?
3512
-     *
3513
-     * @api
3514
-     *
3515
-     * @param boolean $ignoreErrors
3516
-     *
3517
-     * @return \Leafo\ScssPhp\Compiler
3518
-     */
3519
-    public function setIgnoreErrors($ignoreErrors)
3520
-    {
3521
-        $this->ignoreErrors = $ignoreErrors;
3522
-    }
3523
-
3524
-    /**
3525
-     * Throw error (exception)
3526
-     *
3527
-     * @api
3528
-     *
3529
-     * @param string $msg Message with optional sprintf()-style vararg parameters
3530
-     *
3531
-     * @throws \Leafo\ScssPhp\Exception\CompilerException
3532
-     */
3533
-    public function throwError($msg)
3534
-    {
3535
-        if ($this->ignoreErrors) {
3536
-            return;
3537
-        }
3538
-
3539
-        if (func_num_args() > 1) {
3540
-            $msg = call_user_func_array('sprintf', func_get_args());
3541
-        }
3542
-
3543
-        $line = $this->sourceLine;
3544
-        $msg = "$msg: line: $line";
3545
-
3546
-        throw new CompilerException($msg);
3547
-    }
3548
-
3549
-    /**
3550
-     * Handle import loop
3551
-     *
3552
-     * @param string $name
3553
-     *
3554
-     * @throws \Exception
3555
-     */
3556
-    protected function handleImportLoop($name)
3557
-    {
3558
-        for ($env = $this->env; $env; $env = $env->parent) {
3559
-            $file = $this->sourceNames[$env->block->sourceIndex];
3560
-
3561
-            if (realpath($file) === $name) {
3562
-                $this->throwError('An @import loop has been found: %s imports %s', $file, basename($file));
3563
-                break;
3564
-            }
3565
-        }
3566
-    }
3567
-
3568
-    /**
3569
-     * Does file exist?
3570
-     *
3571
-     * @param string $name
3572
-     *
3573
-     * @return boolean
3574
-     */
3575
-    protected function fileExists($name)
3576
-    {
3577
-        return file_exists($name) && is_file($name);
3578
-    }
3579
-
3580
-    /**
3581
-     * Call SCSS @function
3582
-     *
3583
-     * @param string $name
3584
-     * @param array  $argValues
3585
-     * @param array  $returnValue
3586
-     *
3587
-     * @return boolean Returns true if returnValue is set; otherwise, false
3588
-     */
3589
-    protected function callScssFunction($name, $argValues, &$returnValue)
3590
-    {
3591
-        $func = $this->get(static::$namespaces['function'] . $name, false);
3592
-
3593
-        if (! $func) {
3594
-            return false;
3595
-        }
3596
-
3597
-        $this->pushEnv();
3598
-
3599
-        $storeEnv = $this->storeEnv;
3600
-        $this->storeEnv = $this->env;
3601
-
3602
-        // set the args
3603
-        if (isset($func->args)) {
3604
-            $this->applyArguments($func->args, $argValues);
3605
-        }
3606
-
3607
-        // throw away lines and children
3608
-        $tmp = new OutputBlock;
3609
-        $tmp->lines    = [];
3610
-        $tmp->children = [];
3611
-
3612
-        $this->env->marker = 'function';
3613
-
3614
-        $ret = $this->compileChildren($func->children, $tmp);
3615
-
3616
-        $this->storeEnv = $storeEnv;
3617
-
3618
-        $this->popEnv();
3619
-
3620
-        $returnValue = ! isset($ret) ? static::$defaultValue : $ret;
3621
-
3622
-        return true;
3623
-    }
3624
-
3625
-    /**
3626
-     * Call built-in and registered (PHP) functions
3627
-     *
3628
-     * @param string $name
3629
-     * @param array  $args
3630
-     * @param array  $returnValue
3631
-     *
3632
-     * @return boolean Returns true if returnValue is set; otherwise, false
3633
-     */
3634
-    protected function callNativeFunction($name, $args, &$returnValue)
3635
-    {
3636
-        // try a lib function
3637
-        $name = $this->normalizeName($name);
3638
-
3639
-        if (isset($this->userFunctions[$name])) {
3640
-            // see if we can find a user function
3641
-            list($f, $prototype) = $this->userFunctions[$name];
3642
-        } elseif (($f = $this->getBuiltinFunction($name)) && is_callable($f)) {
3643
-            $libName   = $f[1];
3644
-            $prototype = isset(static::$$libName) ? static::$$libName : null;
3645
-        } else {
3646
-            return false;
3647
-        }
3648
-
3649
-        list($sorted, $kwargs) = $this->sortArgs($prototype, $args);
3650
-
3651
-        if ($name !== 'if' && $name !== 'call') {
3652
-            foreach ($sorted as &$val) {
3653
-                $val = $this->reduce($val, true);
3654
-            }
3655
-        }
3656
-
3657
-        $returnValue = call_user_func($f, $sorted, $kwargs);
3658
-
3659
-        if (! isset($returnValue)) {
3660
-            return false;
3661
-        }
3662
-
3663
-        $returnValue = $this->coerceValue($returnValue);
3664
-
3665
-        return true;
3666
-    }
3667
-
3668
-    /**
3669
-     * Get built-in function
3670
-     *
3671
-     * @param string $name Normalized name
3672
-     *
3673
-     * @return array
3674
-     */
3675
-    protected function getBuiltinFunction($name)
3676
-    {
3677
-        $libName = 'lib' . preg_replace_callback(
3678
-            '/_(.)/',
3679
-            function ($m) {
3680
-                return ucfirst($m[1]);
3681
-            },
3682
-            ucfirst($name)
3683
-        );
3684
-
3685
-        return [$this, $libName];
3686
-    }
3687
-
3688
-    /**
3689
-     * Sorts keyword arguments
3690
-     *
3691
-     * @param array $prototype
3692
-     * @param array $args
3693
-     *
3694
-     * @return array
3695
-     */
3696
-    protected function sortArgs($prototype, $args)
3697
-    {
3698
-        $keyArgs = [];
3699
-        $posArgs = [];
3700
-
3701
-        // separate positional and keyword arguments
3702
-        foreach ($args as $arg) {
3703
-            list($key, $value) = $arg;
3704
-
3705
-            $key = $key[1];
3706
-
3707
-            if (empty($key)) {
3708
-                $posArgs[] = $value;
3709
-            } else {
3710
-                $keyArgs[$key] = $value;
3711
-            }
3712
-        }
3713
-
3714
-        if (! isset($prototype)) {
3715
-            return [$posArgs, $keyArgs];
3716
-        }
3717
-
3718
-        // copy positional args
3719
-        $finalArgs = array_pad($posArgs, count($prototype), null);
3720
-
3721
-        // overwrite positional args with keyword args
3722
-        foreach ($prototype as $i => $names) {
3723
-            foreach ((array) $names as $name) {
3724
-                if (isset($keyArgs[$name])) {
3725
-                    $finalArgs[$i] = $keyArgs[$name];
3726
-                }
3727
-            }
3728
-        }
3729
-
3730
-        return [$finalArgs, $keyArgs];
3731
-    }
3732
-
3733
-    /**
3734
-     * Apply argument values per definition
3735
-     *
3736
-     * @param array $argDef
3737
-     * @param array $argValues
3738
-     *
3739
-     * @throws \Exception
3740
-     */
3741
-    protected function applyArguments($argDef, $argValues)
3742
-    {
3743
-        $storeEnv = $this->getStoreEnv();
3744
-
3745
-        $env = new Environment;
3746
-        $env->store = $storeEnv->store;
3747
-
3748
-        $hasVariable = false;
3749
-        $args = [];
3750
-
3751
-        foreach ($argDef as $i => $arg) {
3752
-            list($name, $default, $isVariable) = $argDef[$i];
3753
-
3754
-            $args[$name] = [$i, $name, $default, $isVariable];
3755
-            $hasVariable |= $isVariable;
3756
-        }
3757
-
3758
-        $keywordArgs = [];
3759
-        $deferredKeywordArgs = [];
3760
-        $remaining = [];
3761
-
3762
-        // assign the keyword args
3763
-        foreach ((array) $argValues as $arg) {
3764
-            if (! empty($arg[0])) {
3765
-                if (! isset($args[$arg[0][1]])) {
3766
-                    if ($hasVariable) {
3767
-                        $deferredKeywordArgs[$arg[0][1]] = $arg[1];
3768
-                    } else {
3769
-                        $this->throwError("Mixin or function doesn't have an argument named $%s.", $arg[0][1]);
3770
-                        break;
3771
-                    }
3772
-                } elseif ($args[$arg[0][1]][0] < count($remaining)) {
3773
-                    $this->throwError("The argument $%s was passed both by position and by name.", $arg[0][1]);
3774
-                    break;
3775
-                } else {
3776
-                    $keywordArgs[$arg[0][1]] = $arg[1];
3777
-                }
3778
-            } elseif (count($keywordArgs)) {
3779
-                $this->throwError('Positional arguments must come before keyword arguments.');
3780
-                break;
3781
-            } elseif ($arg[2] === true) {
3782
-                $val = $this->reduce($arg[1], true);
3783
-
3784
-                if ($val[0] === Type::T_LIST) {
3785
-                    foreach ($val[2] as $name => $item) {
3786
-                        if (! is_numeric($name)) {
3787
-                            $keywordArgs[$name] = $item;
3788
-                        } else {
3789
-                            $remaining[] = $item;
3790
-                        }
3791
-                    }
3792
-                } elseif ($val[0] === Type::T_MAP) {
3793
-                    foreach ($val[1] as $i => $name) {
3794
-                        $name = $this->compileStringContent($this->coerceString($name));
3795
-                        $item = $val[2][$i];
3796
-
3797
-                        if (! is_numeric($name)) {
3798
-                            $keywordArgs[$name] = $item;
3799
-                        } else {
3800
-                            $remaining[] = $item;
3801
-                        }
3802
-                    }
3803
-                } else {
3804
-                    $remaining[] = $val;
3805
-                }
3806
-            } else {
3807
-                $remaining[] = $arg[1];
3808
-            }
3809
-        }
3810
-
3811
-        foreach ($args as $arg) {
3812
-            list($i, $name, $default, $isVariable) = $arg;
3813
-
3814
-            if ($isVariable) {
3815
-                $val = [Type::T_LIST, ',', [], $isVariable];
3816
-
3817
-                for ($count = count($remaining); $i < $count; $i++) {
3818
-                    $val[2][] = $remaining[$i];
3819
-                }
3820
-
3821
-                foreach ($deferredKeywordArgs as $itemName => $item) {
3822
-                    $val[2][$itemName] = $item;
3823
-                }
3824
-            } elseif (isset($remaining[$i])) {
3825
-                $val = $remaining[$i];
3826
-            } elseif (isset($keywordArgs[$name])) {
3827
-                $val = $keywordArgs[$name];
3828
-            } elseif (! empty($default)) {
3829
-                continue;
3830
-            } else {
3831
-                $this->throwError("Missing argument $name");
3832
-                break;
3833
-            }
3834
-
3835
-            $this->set($name, $this->reduce($val, true), true, $env);
3836
-        }
3837
-
3838
-        $storeEnv->store = $env->store;
3839
-
3840
-        foreach ($args as $arg) {
3841
-            list($i, $name, $default, $isVariable) = $arg;
3842
-
3843
-            if ($isVariable || isset($remaining[$i]) || isset($keywordArgs[$name]) || empty($default)) {
3844
-                continue;
3845
-            }
3846
-
3847
-            $this->set($name, $this->reduce($default, true), true);
3848
-        }
3849
-    }
3850
-
3851
-    /**
3852
-     * Coerce a php value into a scss one
3853
-     *
3854
-     * @param mixed $value
3855
-     *
3856
-     * @return array|\Leafo\ScssPhp\Node\Number
3857
-     */
3858
-    private function coerceValue($value)
3859
-    {
3860
-        if (is_array($value) || $value instanceof \ArrayAccess) {
3861
-            return $value;
3862
-        }
3863
-
3864
-        if (is_bool($value)) {
3865
-            return $this->toBool($value);
3866
-        }
3867
-
3868
-        if ($value === null) {
3869
-            return static::$null;
3870
-        }
3871
-
3872
-        if (is_numeric($value)) {
3873
-            return new Node\Number($value, '');
3874
-        }
3875
-
3876
-        if ($value === '') {
3877
-            return static::$emptyString;
3878
-        }
3879
-
3880
-        if (preg_match('/^(#([0-9a-f]{6})|#([0-9a-f]{3}))$/i', $value, $m)) {
3881
-            $color = [Type::T_COLOR];
3882
-
3883
-            if (isset($m[3])) {
3884
-                $num = hexdec($m[3]);
3885
-
3886
-                foreach ([3, 2, 1] as $i) {
3887
-                    $t = $num & 0xf;
3888
-                    $color[$i] = $t << 4 | $t;
3889
-                    $num >>= 4;
3890
-                }
3891
-            } else {
3892
-                $num = hexdec($m[2]);
3893
-
3894
-                foreach ([3, 2, 1] as $i) {
3895
-                    $color[$i] = $num & 0xff;
3896
-                    $num >>= 8;
3897
-                }
3898
-            }
3899
-
3900
-            return $color;
3901
-        }
3902
-
3903
-        return [Type::T_KEYWORD, $value];
3904
-    }
3905
-
3906
-    /**
3907
-     * Coerce something to map
3908
-     *
3909
-     * @param array $item
3910
-     *
3911
-     * @return array
3912
-     */
3913
-    protected function coerceMap($item)
3914
-    {
3915
-        if ($item[0] === Type::T_MAP) {
3916
-            return $item;
3917
-        }
3918
-
3919
-        if ($item === static::$emptyList) {
3920
-            return static::$emptyMap;
3921
-        }
3922
-
3923
-        return [Type::T_MAP, [$item], [static::$null]];
3924
-    }
3925
-
3926
-    /**
3927
-     * Coerce something to list
3928
-     *
3929
-     * @param array  $item
3930
-     * @param string $delim
3931
-     *
3932
-     * @return array
3933
-     */
3934
-    protected function coerceList($item, $delim = ',')
3935
-    {
3936
-        if (isset($item) && $item[0] === Type::T_LIST) {
3937
-            return $item;
3938
-        }
3939
-
3940
-        if (isset($item) && $item[0] === Type::T_MAP) {
3941
-            $keys = $item[1];
3942
-            $values = $item[2];
3943
-            $list = [];
3944
-
3945
-            for ($i = 0, $s = count($keys); $i < $s; $i++) {
3946
-                $key = $keys[$i];
3947
-                $value = $values[$i];
3948
-
3949
-                $list[] = [
3950
-                    Type::T_LIST,
3951
-                    '',
3952
-                    [[Type::T_KEYWORD, $this->compileStringContent($this->coerceString($key))], $value]
3953
-                ];
3954
-            }
3955
-
3956
-            return [Type::T_LIST, ',', $list];
3957
-        }
3958
-
3959
-        return [Type::T_LIST, $delim, ! isset($item) ? []: [$item]];
3960
-    }
3961
-
3962
-    /**
3963
-     * Coerce color for expression
3964
-     *
3965
-     * @param array $value
3966
-     *
3967
-     * @return array|null
3968
-     */
3969
-    protected function coerceForExpression($value)
3970
-    {
3971
-        if ($color = $this->coerceColor($value)) {
3972
-            return $color;
3973
-        }
3974
-
3975
-        return $value;
3976
-    }
3977
-
3978
-    /**
3979
-     * Coerce value to color
3980
-     *
3981
-     * @param array $value
3982
-     *
3983
-     * @return array|null
3984
-     */
3985
-    protected function coerceColor($value)
3986
-    {
3987
-        switch ($value[0]) {
3988
-            case Type::T_COLOR:
3989
-                return $value;
3990
-
3991
-            case Type::T_KEYWORD:
3992
-                $name = strtolower($value[1]);
3993
-
3994
-                if (isset(Colors::$cssColors[$name])) {
3995
-                    $rgba = explode(',', Colors::$cssColors[$name]);
3996
-
3997
-                    return isset($rgba[3])
3998
-                        ? [Type::T_COLOR, (int) $rgba[0], (int) $rgba[1], (int) $rgba[2], (int) $rgba[3]]
3999
-                        : [Type::T_COLOR, (int) $rgba[0], (int) $rgba[1], (int) $rgba[2]];
4000
-                }
4001
-
4002
-                return null;
4003
-        }
4004
-
4005
-        return null;
4006
-    }
4007
-
4008
-    /**
4009
-     * Coerce value to string
4010
-     *
4011
-     * @param array $value
4012
-     *
4013
-     * @return array|null
4014
-     */
4015
-    protected function coerceString($value)
4016
-    {
4017
-        if ($value[0] === Type::T_STRING) {
4018
-            return $value;
4019
-        }
4020
-
4021
-        return [Type::T_STRING, '', [$this->compileValue($value)]];
4022
-    }
4023
-
4024
-    /**
4025
-     * Coerce value to a percentage
4026
-     *
4027
-     * @param array $value
4028
-     *
4029
-     * @return integer|float
4030
-     */
4031
-    protected function coercePercent($value)
4032
-    {
4033
-        if ($value[0] === Type::T_NUMBER) {
4034
-            if (! empty($value[2]['%'])) {
4035
-                return $value[1] / 100;
4036
-            }
4037
-
4038
-            return $value[1];
4039
-        }
4040
-
4041
-        return 0;
4042
-    }
4043
-
4044
-    /**
4045
-     * Assert value is a map
4046
-     *
4047
-     * @api
4048
-     *
4049
-     * @param array $value
4050
-     *
4051
-     * @return array
4052
-     *
4053
-     * @throws \Exception
4054
-     */
4055
-    public function assertMap($value)
4056
-    {
4057
-        $value = $this->coerceMap($value);
4058
-
4059
-        if ($value[0] !== Type::T_MAP) {
4060
-            $this->throwError('expecting map');
4061
-        }
4062
-
4063
-        return $value;
4064
-    }
4065
-
4066
-    /**
4067
-     * Assert value is a list
4068
-     *
4069
-     * @api
4070
-     *
4071
-     * @param array $value
4072
-     *
4073
-     * @return array
4074
-     *
4075
-     * @throws \Exception
4076
-     */
4077
-    public function assertList($value)
4078
-    {
4079
-        if ($value[0] !== Type::T_LIST) {
4080
-            $this->throwError('expecting list');
4081
-        }
4082
-
4083
-        return $value;
4084
-    }
4085
-
4086
-    /**
4087
-     * Assert value is a color
4088
-     *
4089
-     * @api
4090
-     *
4091
-     * @param array $value
4092
-     *
4093
-     * @return array
4094
-     *
4095
-     * @throws \Exception
4096
-     */
4097
-    public function assertColor($value)
4098
-    {
4099
-        if ($color = $this->coerceColor($value)) {
4100
-            return $color;
4101
-        }
4102
-
4103
-        $this->throwError('expecting color');
4104
-    }
4105
-
4106
-    /**
4107
-     * Assert value is a number
4108
-     *
4109
-     * @api
4110
-     *
4111
-     * @param array $value
4112
-     *
4113
-     * @return integer|float
4114
-     *
4115
-     * @throws \Exception
4116
-     */
4117
-    public function assertNumber($value)
4118
-    {
4119
-        if ($value[0] !== Type::T_NUMBER) {
4120
-            $this->throwError('expecting number');
4121
-        }
4122
-
4123
-        return $value[1];
4124
-    }
4125
-
4126
-    /**
4127
-     * Make sure a color's components don't go out of bounds
4128
-     *
4129
-     * @param array $c
4130
-     *
4131
-     * @return array
4132
-     */
4133
-    protected function fixColor($c)
4134
-    {
4135
-        foreach ([1, 2, 3] as $i) {
4136
-            if ($c[$i] < 0) {
4137
-                $c[$i] = 0;
4138
-            }
4139
-
4140
-            if ($c[$i] > 255) {
4141
-                $c[$i] = 255;
4142
-            }
4143
-        }
4144
-
4145
-        return $c;
4146
-    }
4147
-
4148
-    /**
4149
-     * Convert RGB to HSL
4150
-     *
4151
-     * @api
4152
-     *
4153
-     * @param integer $red
4154
-     * @param integer $green
4155
-     * @param integer $blue
4156
-     *
4157
-     * @return array
4158
-     */
4159
-    public function toHSL($red, $green, $blue)
4160
-    {
4161
-        $min = min($red, $green, $blue);
4162
-        $max = max($red, $green, $blue);
4163
-
4164
-        $l = $min + $max;
4165
-        $d = $max - $min;
4166
-
4167
-        if ((int) $d === 0) {
4168
-            $h = $s = 0;
4169
-        } else {
4170
-            if ($l < 255) {
4171
-                $s = $d / $l;
4172
-            } else {
4173
-                $s = $d / (510 - $l);
4174
-            }
4175
-
4176
-            if ($red == $max) {
4177
-                $h = 60 * ($green - $blue) / $d;
4178
-            } elseif ($green == $max) {
4179
-                $h = 60 * ($blue - $red) / $d + 120;
4180
-            } elseif ($blue == $max) {
4181
-                $h = 60 * ($red - $green) / $d + 240;
4182
-            }
4183
-        }
4184
-
4185
-        return [Type::T_HSL, fmod($h, 360), $s * 100, $l / 5.1];
4186
-    }
4187
-
4188
-    /**
4189
-     * Hue to RGB helper
4190
-     *
4191
-     * @param float $m1
4192
-     * @param float $m2
4193
-     * @param float $h
4194
-     *
4195
-     * @return float
4196
-     */
4197
-    private function hueToRGB($m1, $m2, $h)
4198
-    {
4199
-        if ($h < 0) {
4200
-            $h += 1;
4201
-        } elseif ($h > 1) {
4202
-            $h -= 1;
4203
-        }
4204
-
4205
-        if ($h * 6 < 1) {
4206
-            return $m1 + ($m2 - $m1) * $h * 6;
4207
-        }
4208
-
4209
-        if ($h * 2 < 1) {
4210
-            return $m2;
4211
-        }
4212
-
4213
-        if ($h * 3 < 2) {
4214
-            return $m1 + ($m2 - $m1) * (2/3 - $h) * 6;
4215
-        }
4216
-
4217
-        return $m1;
4218
-    }
4219
-
4220
-    /**
4221
-     * Convert HSL to RGB
4222
-     *
4223
-     * @api
4224
-     *
4225
-     * @param integer $hue        H from 0 to 360
4226
-     * @param integer $saturation S from 0 to 100
4227
-     * @param integer $lightness  L from 0 to 100
4228
-     *
4229
-     * @return array
4230
-     */
4231
-    public function toRGB($hue, $saturation, $lightness)
4232
-    {
4233
-        if ($hue < 0) {
4234
-            $hue += 360;
4235
-        }
4236
-
4237
-        $h = $hue / 360;
4238
-        $s = min(100, max(0, $saturation)) / 100;
4239
-        $l = min(100, max(0, $lightness)) / 100;
4240
-
4241
-        $m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s;
4242
-        $m1 = $l * 2 - $m2;
4243
-
4244
-        $r = $this->hueToRGB($m1, $m2, $h + 1/3) * 255;
4245
-        $g = $this->hueToRGB($m1, $m2, $h) * 255;
4246
-        $b = $this->hueToRGB($m1, $m2, $h - 1/3) * 255;
4247
-
4248
-        $out = [Type::T_COLOR, $r, $g, $b];
4249
-
4250
-        return $out;
4251
-    }
4252
-
4253
-    // Built in functions
4254
-
4255
-    //protected static $libCall = ['name', 'args...'];
4256
-    protected function libCall($args, $kwargs)
4257
-    {
4258
-        $name = $this->compileStringContent($this->coerceString($this->reduce(array_shift($args), true)));
4259
-
4260
-        $args = array_map(
4261
-            function ($a) {
4262
-                return [null, $a, false];
4263
-            },
4264
-            $args
4265
-        );
4266
-
4267
-        if (count($kwargs)) {
4268
-            foreach ($kwargs as $key => $value) {
4269
-                $args[] = [[Type::T_VARIABLE, $key], $value, false];
4270
-            }
4271
-        }
4272
-
4273
-        return $this->reduce([Type::T_FUNCTION_CALL, $name, $args]);
4274
-    }
4275
-
4276
-    protected static $libIf = ['condition', 'if-true', 'if-false'];
4277
-    protected function libIf($args)
4278
-    {
4279
-        list($cond, $t, $f) = $args;
4280
-
4281
-        if (! $this->isTruthy($this->reduce($cond, true))) {
4282
-            return $this->reduce($f, true);
4283
-        }
4284
-
4285
-        return $this->reduce($t, true);
4286
-    }
4287
-
4288
-    protected static $libIndex = ['list', 'value'];
4289
-    protected function libIndex($args)
4290
-    {
4291
-        list($list, $value) = $args;
4292
-
4293
-        if ($value[0] === Type::T_MAP) {
4294
-            return static::$null;
4295
-        }
4296
-
4297
-        if ($list[0] === Type::T_MAP ||
4298
-            $list[0] === Type::T_STRING ||
4299
-            $list[0] === Type::T_KEYWORD ||
4300
-            $list[0] === Type::T_INTERPOLATE
4301
-        ) {
4302
-            $list = $this->coerceList($list, ' ');
4303
-        }
4304
-
4305
-        if ($list[0] !== Type::T_LIST) {
4306
-            return static::$null;
4307
-        }
4308
-
4309
-        $values = [];
4310
-
4311
-        foreach ($list[2] as $item) {
4312
-            $values[] = $this->normalizeValue($item);
4313
-        }
4314
-
4315
-        $key = array_search($this->normalizeValue($value), $values);
4316
-
4317
-        return false === $key ? static::$null : $key + 1;
4318
-    }
4319
-
4320
-    protected static $libRgb = ['red', 'green', 'blue'];
4321
-    protected function libRgb($args)
4322
-    {
4323
-        list($r, $g, $b) = $args;
4324
-
4325
-        return [Type::T_COLOR, $r[1], $g[1], $b[1]];
4326
-    }
4327
-
4328
-    protected static $libRgba = [
4329
-        ['red', 'color'],
4330
-        'green', 'blue', 'alpha'];
4331
-    protected function libRgba($args)
4332
-    {
4333
-        if ($color = $this->coerceColor($args[0])) {
4334
-            $num = ! isset($args[1]) ? $args[3] : $args[1];
4335
-            $alpha = $this->assertNumber($num);
4336
-            $color[4] = $alpha;
4337
-
4338
-            return $color;
4339
-        }
4340
-
4341
-        list($r, $g, $b, $a) = $args;
4342
-
4343
-        return [Type::T_COLOR, $r[1], $g[1], $b[1], $a[1]];
4344
-    }
4345
-
4346
-    // helper function for adjust_color, change_color, and scale_color
4347
-    protected function alterColor($args, $fn)
4348
-    {
4349
-        $color = $this->assertColor($args[0]);
4350
-
4351
-        foreach ([1, 2, 3, 7] as $i) {
4352
-            if (isset($args[$i])) {
4353
-                $val = $this->assertNumber($args[$i]);
4354
-                $ii = $i === 7 ? 4 : $i; // alpha
4355
-                $color[$ii] = call_user_func($fn, isset($color[$ii]) ? $color[$ii] : 0, $val, $i);
4356
-            }
4357
-        }
4358
-
4359
-        if (isset($args[4]) || isset($args[5]) || isset($args[6])) {
4360
-            $hsl = $this->toHSL($color[1], $color[2], $color[3]);
4361
-
4362
-            foreach ([4, 5, 6] as $i) {
4363
-                if (isset($args[$i])) {
4364
-                    $val = $this->assertNumber($args[$i]);
4365
-                    $hsl[$i - 3] = call_user_func($fn, $hsl[$i - 3], $val, $i);
4366
-                }
4367
-            }
4368
-
4369
-            $rgb = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
4370
-
4371
-            if (isset($color[4])) {
4372
-                $rgb[4] = $color[4];
4373
-            }
4374
-
4375
-            $color = $rgb;
4376
-        }
4377
-
4378
-        return $color;
4379
-    }
4380
-
4381
-    protected static $libAdjustColor = [
4382
-        'color', 'red', 'green', 'blue',
4383
-        'hue', 'saturation', 'lightness', 'alpha'
4384
-    ];
4385
-    protected function libAdjustColor($args)
4386
-    {
4387
-        return $this->alterColor($args, function ($base, $alter, $i) {
4388
-            return $base + $alter;
4389
-        });
4390
-    }
4391
-
4392
-    protected static $libChangeColor = [
4393
-        'color', 'red', 'green', 'blue',
4394
-        'hue', 'saturation', 'lightness', 'alpha'
4395
-    ];
4396
-    protected function libChangeColor($args)
4397
-    {
4398
-        return $this->alterColor($args, function ($base, $alter, $i) {
4399
-            return $alter;
4400
-        });
4401
-    }
4402
-
4403
-    protected static $libScaleColor = [
4404
-        'color', 'red', 'green', 'blue',
4405
-        'hue', 'saturation', 'lightness', 'alpha'
4406
-    ];
4407
-    protected function libScaleColor($args)
4408
-    {
4409
-        return $this->alterColor($args, function ($base, $scale, $i) {
4410
-            // 1, 2, 3 - rgb
4411
-            // 4, 5, 6 - hsl
4412
-            // 7 - a
4413
-            switch ($i) {
4414
-                case 1:
4415
-                case 2:
4416
-                case 3:
4417
-                    $max = 255;
4418
-                    break;
4419
-
4420
-                case 4:
4421
-                    $max = 360;
4422
-                    break;
4423
-
4424
-                case 7:
4425
-                    $max = 1;
4426
-                    break;
4427
-
4428
-                default:
4429
-                    $max = 100;
4430
-            }
4431
-
4432
-            $scale = $scale / 100;
4433
-
4434
-            if ($scale < 0) {
4435
-                return $base * $scale + $base;
4436
-            }
4437
-
4438
-            return ($max - $base) * $scale + $base;
4439
-        });
4440
-    }
4441
-
4442
-    protected static $libIeHexStr = ['color'];
4443
-    protected function libIeHexStr($args)
4444
-    {
4445
-        $color = $this->coerceColor($args[0]);
4446
-        $color[4] = isset($color[4]) ? round(255 * $color[4]) : 255;
4447
-
4448
-        return sprintf('#%02X%02X%02X%02X', $color[4], $color[1], $color[2], $color[3]);
4449
-    }
4450
-
4451
-    protected static $libRed = ['color'];
4452
-    protected function libRed($args)
4453
-    {
4454
-        $color = $this->coerceColor($args[0]);
4455
-
4456
-        return $color[1];
4457
-    }
4458
-
4459
-    protected static $libGreen = ['color'];
4460
-    protected function libGreen($args)
4461
-    {
4462
-        $color = $this->coerceColor($args[0]);
4463
-
4464
-        return $color[2];
4465
-    }
4466
-
4467
-    protected static $libBlue = ['color'];
4468
-    protected function libBlue($args)
4469
-    {
4470
-        $color = $this->coerceColor($args[0]);
4471
-
4472
-        return $color[3];
4473
-    }
4474
-
4475
-    protected static $libAlpha = ['color'];
4476
-    protected function libAlpha($args)
4477
-    {
4478
-        if ($color = $this->coerceColor($args[0])) {
4479
-            return isset($color[4]) ? $color[4] : 1;
4480
-        }
4481
-
4482
-        // this might be the IE function, so return value unchanged
4483
-        return null;
4484
-    }
4485
-
4486
-    protected static $libOpacity = ['color'];
4487
-    protected function libOpacity($args)
4488
-    {
4489
-        $value = $args[0];
4490
-
4491
-        if ($value[0] === Type::T_NUMBER) {
4492
-            return null;
4493
-        }
4494
-
4495
-        return $this->libAlpha($args);
4496
-    }
4497
-
4498
-    // mix two colors
4499
-    protected static $libMix = ['color-1', 'color-2', 'weight'];
4500
-    protected function libMix($args)
4501
-    {
4502
-        list($first, $second, $weight) = $args;
4503
-
4504
-        $first = $this->assertColor($first);
4505
-        $second = $this->assertColor($second);
4506
-
4507
-        if (! isset($weight)) {
4508
-            $weight = 0.5;
4509
-        } else {
4510
-            $weight = $this->coercePercent($weight);
4511
-        }
4512
-
4513
-        $firstAlpha = isset($first[4]) ? $first[4] : 1;
4514
-        $secondAlpha = isset($second[4]) ? $second[4] : 1;
4515
-
4516
-        $w = $weight * 2 - 1;
4517
-        $a = $firstAlpha - $secondAlpha;
4518
-
4519
-        $w1 = (($w * $a === -1 ? $w : ($w + $a) / (1 + $w * $a)) + 1) / 2.0;
4520
-        $w2 = 1.0 - $w1;
4521
-
4522
-        $new = [Type::T_COLOR,
4523
-            $w1 * $first[1] + $w2 * $second[1],
4524
-            $w1 * $first[2] + $w2 * $second[2],
4525
-            $w1 * $first[3] + $w2 * $second[3],
4526
-        ];
4527
-
4528
-        if ($firstAlpha != 1.0 || $secondAlpha != 1.0) {
4529
-            $new[] = $firstAlpha * $weight + $secondAlpha * ($weight - 1);
4530
-        }
4531
-
4532
-        return $this->fixColor($new);
4533
-    }
4534
-
4535
-    protected static $libHsl = ['hue', 'saturation', 'lightness'];
4536
-    protected function libHsl($args)
4537
-    {
4538
-        list($h, $s, $l) = $args;
4539
-
4540
-        return $this->toRGB($h[1], $s[1], $l[1]);
4541
-    }
4542
-
4543
-    protected static $libHsla = ['hue', 'saturation', 'lightness', 'alpha'];
4544
-    protected function libHsla($args)
4545
-    {
4546
-        list($h, $s, $l, $a) = $args;
4547
-
4548
-        $color = $this->toRGB($h[1], $s[1], $l[1]);
4549
-        $color[4] = $a[1];
4550
-
4551
-        return $color;
4552
-    }
4553
-
4554
-    protected static $libHue = ['color'];
4555
-    protected function libHue($args)
4556
-    {
4557
-        $color = $this->assertColor($args[0]);
4558
-        $hsl = $this->toHSL($color[1], $color[2], $color[3]);
4559
-
4560
-        return new Node\Number($hsl[1], 'deg');
4561
-    }
4562
-
4563
-    protected static $libSaturation = ['color'];
4564
-    protected function libSaturation($args)
4565
-    {
4566
-        $color = $this->assertColor($args[0]);
4567
-        $hsl = $this->toHSL($color[1], $color[2], $color[3]);
4568
-
4569
-        return new Node\Number($hsl[2], '%');
4570
-    }
4571
-
4572
-    protected static $libLightness = ['color'];
4573
-    protected function libLightness($args)
4574
-    {
4575
-        $color = $this->assertColor($args[0]);
4576
-        $hsl = $this->toHSL($color[1], $color[2], $color[3]);
4577
-
4578
-        return new Node\Number($hsl[3], '%');
4579
-    }
4580
-
4581
-    protected function adjustHsl($color, $idx, $amount)
4582
-    {
4583
-        $hsl = $this->toHSL($color[1], $color[2], $color[3]);
4584
-        $hsl[$idx] += $amount;
4585
-        $out = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
4586
-
4587
-        if (isset($color[4])) {
4588
-            $out[4] = $color[4];
4589
-        }
4590
-
4591
-        return $out;
4592
-    }
4593
-
4594
-    protected static $libAdjustHue = ['color', 'degrees'];
4595
-    protected function libAdjustHue($args)
4596
-    {
4597
-        $color = $this->assertColor($args[0]);
4598
-        $degrees = $this->assertNumber($args[1]);
4599
-
4600
-        return $this->adjustHsl($color, 1, $degrees);
4601
-    }
4602
-
4603
-    protected static $libLighten = ['color', 'amount'];
4604
-    protected function libLighten($args)
4605
-    {
4606
-        $color = $this->assertColor($args[0]);
4607
-        $amount = Util::checkRange('amount', new Range(0, 100), $args[1], '%');
4608
-
4609
-        return $this->adjustHsl($color, 3, $amount);
4610
-    }
4611
-
4612
-    protected static $libDarken = ['color', 'amount'];
4613
-    protected function libDarken($args)
4614
-    {
4615
-        $color = $this->assertColor($args[0]);
4616
-        $amount = Util::checkRange('amount', new Range(0, 100), $args[1], '%');
4617
-
4618
-        return $this->adjustHsl($color, 3, -$amount);
4619
-    }
4620
-
4621
-    protected static $libSaturate = ['color', 'amount'];
4622
-    protected function libSaturate($args)
4623
-    {
4624
-        $value = $args[0];
4625
-
4626
-        if ($value[0] === Type::T_NUMBER) {
4627
-            return null;
4628
-        }
4629
-
4630
-        $color = $this->assertColor($value);
4631
-        $amount = 100 * $this->coercePercent($args[1]);
4632
-
4633
-        return $this->adjustHsl($color, 2, $amount);
4634
-    }
4635
-
4636
-    protected static $libDesaturate = ['color', 'amount'];
4637
-    protected function libDesaturate($args)
4638
-    {
4639
-        $color = $this->assertColor($args[0]);
4640
-        $amount = 100 * $this->coercePercent($args[1]);
4641
-
4642
-        return $this->adjustHsl($color, 2, -$amount);
4643
-    }
4644
-
4645
-    protected static $libGrayscale = ['color'];
4646
-    protected function libGrayscale($args)
4647
-    {
4648
-        $value = $args[0];
4649
-
4650
-        if ($value[0] === Type::T_NUMBER) {
4651
-            return null;
4652
-        }
4653
-
4654
-        return $this->adjustHsl($this->assertColor($value), 2, -100);
4655
-    }
4656
-
4657
-    protected static $libComplement = ['color'];
4658
-    protected function libComplement($args)
4659
-    {
4660
-        return $this->adjustHsl($this->assertColor($args[0]), 1, 180);
4661
-    }
4662
-
4663
-    protected static $libInvert = ['color'];
4664
-    protected function libInvert($args)
4665
-    {
4666
-        $value = $args[0];
4667
-
4668
-        if ($value[0] === Type::T_NUMBER) {
4669
-            return null;
4670
-        }
4671
-
4672
-        $color = $this->assertColor($value);
4673
-        $color[1] = 255 - $color[1];
4674
-        $color[2] = 255 - $color[2];
4675
-        $color[3] = 255 - $color[3];
4676
-
4677
-        return $color;
4678
-    }
4679
-
4680
-    // increases opacity by amount
4681
-    protected static $libOpacify = ['color', 'amount'];
4682
-    protected function libOpacify($args)
4683
-    {
4684
-        $color = $this->assertColor($args[0]);
4685
-        $amount = $this->coercePercent($args[1]);
4686
-
4687
-        $color[4] = (isset($color[4]) ? $color[4] : 1) + $amount;
4688
-        $color[4] = min(1, max(0, $color[4]));
4689
-
4690
-        return $color;
4691
-    }
4692
-
4693
-    protected static $libFadeIn = ['color', 'amount'];
4694
-    protected function libFadeIn($args)
4695
-    {
4696
-        return $this->libOpacify($args);
4697
-    }
4698
-
4699
-    // decreases opacity by amount
4700
-    protected static $libTransparentize = ['color', 'amount'];
4701
-    protected function libTransparentize($args)
4702
-    {
4703
-        $color = $this->assertColor($args[0]);
4704
-        $amount = $this->coercePercent($args[1]);
4705
-
4706
-        $color[4] = (isset($color[4]) ? $color[4] : 1) - $amount;
4707
-        $color[4] = min(1, max(0, $color[4]));
4708
-
4709
-        return $color;
4710
-    }
4711
-
4712
-    protected static $libFadeOut = ['color', 'amount'];
4713
-    protected function libFadeOut($args)
4714
-    {
4715
-        return $this->libTransparentize($args);
4716
-    }
4717
-
4718
-    protected static $libUnquote = ['string'];
4719
-    protected function libUnquote($args)
4720
-    {
4721
-        $str = $args[0];
4722
-
4723
-        if ($str[0] === Type::T_STRING) {
4724
-            $str[1] = '';
4725
-        }
4726
-
4727
-        return $str;
4728
-    }
4729
-
4730
-    protected static $libQuote = ['string'];
4731
-    protected function libQuote($args)
4732
-    {
4733
-        $value = $args[0];
4734
-
4735
-        if ($value[0] === Type::T_STRING && ! empty($value[1])) {
4736
-            return $value;
4737
-        }
4738
-
4739
-        return [Type::T_STRING, '"', [$value]];
4740
-    }
4741
-
4742
-    protected static $libPercentage = ['value'];
4743
-    protected function libPercentage($args)
4744
-    {
4745
-        return new Node\Number($this->coercePercent($args[0]) * 100, '%');
4746
-    }
4747
-
4748
-    protected static $libRound = ['value'];
4749
-    protected function libRound($args)
4750
-    {
4751
-        $num = $args[0];
4752
-
4753
-        return new Node\Number(round($num[1]), $num[2]);
4754
-    }
4755
-
4756
-    protected static $libFloor = ['value'];
4757
-    protected function libFloor($args)
4758
-    {
4759
-        $num = $args[0];
4760
-
4761
-        return new Node\Number(floor($num[1]), $num[2]);
4762
-    }
4763
-
4764
-    protected static $libCeil = ['value'];
4765
-    protected function libCeil($args)
4766
-    {
4767
-        $num = $args[0];
4768
-
4769
-        return new Node\Number(ceil($num[1]), $num[2]);
4770
-    }
4771
-
4772
-    protected static $libAbs = ['value'];
4773
-    protected function libAbs($args)
4774
-    {
4775
-        $num = $args[0];
4776
-
4777
-        return new Node\Number(abs($num[1]), $num[2]);
4778
-    }
4779
-
4780
-    protected function libMin($args)
4781
-    {
4782
-        $numbers = $this->getNormalizedNumbers($args);
4783
-        $min = null;
4784
-
4785
-        foreach ($numbers as $key => $number) {
4786
-            if (null === $min || $number[1] <= $min[1]) {
4787
-                $min = [$key, $number[1]];
4788
-            }
4789
-        }
4790
-
4791
-        return $args[$min[0]];
4792
-    }
4793
-
4794
-    protected function libMax($args)
4795
-    {
4796
-        $numbers = $this->getNormalizedNumbers($args);
4797
-        $max = null;
4798
-
4799
-        foreach ($numbers as $key => $number) {
4800
-            if (null === $max || $number[1] >= $max[1]) {
4801
-                $max = [$key, $number[1]];
4802
-            }
4803
-        }
4804
-
4805
-        return $args[$max[0]];
4806
-    }
4807
-
4808
-    /**
4809
-     * Helper to normalize args containing numbers
4810
-     *
4811
-     * @param array $args
4812
-     *
4813
-     * @return array
4814
-     */
4815
-    protected function getNormalizedNumbers($args)
4816
-    {
4817
-        $unit = null;
4818
-        $originalUnit = null;
4819
-        $numbers = [];
4820
-
4821
-        foreach ($args as $key => $item) {
4822
-            if ($item[0] !== Type::T_NUMBER) {
4823
-                $this->throwError('%s is not a number', $item[0]);
4824
-                break;
4825
-            }
4826
-
4827
-            $number = $item->normalize();
4828
-
4829
-            if (null === $unit) {
4830
-                $unit = $number[2];
4831
-                $originalUnit = $item->unitStr();
4832
-            } elseif ($unit !== $number[2]) {
4833
-                $this->throwError('Incompatible units: "%s" and "%s".', $originalUnit, $item->unitStr());
4834
-                break;
4835
-            }
4836
-
4837
-            $numbers[$key] = $number;
4838
-        }
4839
-
4840
-        return $numbers;
4841
-    }
4842
-
4843
-    protected static $libLength = ['list'];
4844
-    protected function libLength($args)
4845
-    {
4846
-        $list = $this->coerceList($args[0]);
4847
-
4848
-        return count($list[2]);
4849
-    }
4850
-
4851
-    //protected static $libListSeparator = ['list...'];
4852
-    protected function libListSeparator($args)
4853
-    {
4854
-        if (count($args) > 1) {
4855
-            return 'comma';
4856
-        }
4857
-
4858
-        $list = $this->coerceList($args[0]);
4859
-
4860
-        if (count($list[2]) <= 1) {
4861
-            return 'space';
4862
-        }
4863
-
4864
-        if ($list[1] === ',') {
4865
-            return 'comma';
4866
-        }
4867
-
4868
-        return 'space';
4869
-    }
4870
-
4871
-    protected static $libNth = ['list', 'n'];
4872
-    protected function libNth($args)
4873
-    {
4874
-        $list = $this->coerceList($args[0]);
4875
-        $n = $this->assertNumber($args[1]);
4876
-
4877
-        if ($n > 0) {
4878
-            $n--;
4879
-        } elseif ($n < 0) {
4880
-            $n += count($list[2]);
4881
-        }
4882
-
4883
-        return isset($list[2][$n]) ? $list[2][$n] : static::$defaultValue;
4884
-    }
4885
-
4886
-    protected static $libSetNth = ['list', 'n', 'value'];
4887
-    protected function libSetNth($args)
4888
-    {
4889
-        $list = $this->coerceList($args[0]);
4890
-        $n = $this->assertNumber($args[1]);
4891
-
4892
-        if ($n > 0) {
4893
-            $n--;
4894
-        } elseif ($n < 0) {
4895
-            $n += count($list[2]);
4896
-        }
4897
-
4898
-        if (! isset($list[2][$n])) {
4899
-            $this->throwError('Invalid argument for "n"');
4900
-
4901
-            return;
4902
-        }
4903
-
4904
-        $list[2][$n] = $args[2];
4905
-
4906
-        return $list;
4907
-    }
4908
-
4909
-    protected static $libMapGet = ['map', 'key'];
4910
-    protected function libMapGet($args)
4911
-    {
4912
-        $map = $this->assertMap($args[0]);
4913
-        $key = $this->compileStringContent($this->coerceString($args[1]));
4914
-
4915
-        for ($i = count($map[1]) - 1; $i >= 0; $i--) {
4916
-            if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) {
4917
-                return $map[2][$i];
4918
-            }
4919
-        }
4920
-
4921
-        return static::$null;
4922
-    }
4923
-
4924
-    protected static $libMapKeys = ['map'];
4925
-    protected function libMapKeys($args)
4926
-    {
4927
-        $map = $this->assertMap($args[0]);
4928
-        $keys = $map[1];
4929
-
4930
-        return [Type::T_LIST, ',', $keys];
4931
-    }
4932
-
4933
-    protected static $libMapValues = ['map'];
4934
-    protected function libMapValues($args)
4935
-    {
4936
-        $map = $this->assertMap($args[0]);
4937
-        $values = $map[2];
4938
-
4939
-        return [Type::T_LIST, ',', $values];
4940
-    }
4941
-
4942
-    protected static $libMapRemove = ['map', 'key'];
4943
-    protected function libMapRemove($args)
4944
-    {
4945
-        $map = $this->assertMap($args[0]);
4946
-        $key = $this->compileStringContent($this->coerceString($args[1]));
4947
-
4948
-        for ($i = count($map[1]) - 1; $i >= 0; $i--) {
4949
-            if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) {
4950
-                array_splice($map[1], $i, 1);
4951
-                array_splice($map[2], $i, 1);
4952
-            }
4953
-        }
4954
-
4955
-        return $map;
4956
-    }
4957
-
4958
-    protected static $libMapHasKey = ['map', 'key'];
4959
-    protected function libMapHasKey($args)
4960
-    {
4961
-        $map = $this->assertMap($args[0]);
4962
-        $key = $this->compileStringContent($this->coerceString($args[1]));
4963
-
4964
-        for ($i = count($map[1]) - 1; $i >= 0; $i--) {
4965
-            if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) {
4966
-                return true;
4967
-            }
4968
-        }
4969
-
4970
-        return false;
4971
-    }
4972
-
4973
-    protected static $libMapMerge = ['map-1', 'map-2'];
4974
-    protected function libMapMerge($args)
4975
-    {
4976
-        $map1 = $this->assertMap($args[0]);
4977
-        $map2 = $this->assertMap($args[1]);
4978
-
4979
-        return [Type::T_MAP, array_merge($map1[1], $map2[1]), array_merge($map1[2], $map2[2])];
4980
-    }
4981
-
4982
-    protected static $libKeywords = ['args'];
4983
-    protected function libKeywords($args)
4984
-    {
4985
-        $this->assertList($args[0]);
4986
-
4987
-        $keys = [];
4988
-        $values = [];
4989
-
4990
-        foreach ($args[0][2] as $name => $arg) {
4991
-            $keys[] = [Type::T_KEYWORD, $name];
4992
-            $values[] = $arg;
4993
-        }
4994
-
4995
-        return [Type::T_MAP, $keys, $values];
4996
-    }
4997
-
4998
-    protected function listSeparatorForJoin($list1, $sep)
4999
-    {
5000
-        if (! isset($sep)) {
5001
-            return $list1[1];
5002
-        }
5003
-
5004
-        switch ($this->compileValue($sep)) {
5005
-            case 'comma':
5006
-                return ',';
5007
-
5008
-            case 'space':
5009
-                return '';
5010
-
5011
-            default:
5012
-                return $list1[1];
5013
-        }
5014
-    }
5015
-
5016
-    protected static $libJoin = ['list1', 'list2', 'separator'];
5017
-    protected function libJoin($args)
5018
-    {
5019
-        list($list1, $list2, $sep) = $args;
5020
-
5021
-        $list1 = $this->coerceList($list1, ' ');
5022
-        $list2 = $this->coerceList($list2, ' ');
5023
-        $sep = $this->listSeparatorForJoin($list1, $sep);
5024
-
5025
-        return [Type::T_LIST, $sep, array_merge($list1[2], $list2[2])];
5026
-    }
5027
-
5028
-    protected static $libAppend = ['list', 'val', 'separator'];
5029
-    protected function libAppend($args)
5030
-    {
5031
-        list($list1, $value, $sep) = $args;
5032
-
5033
-        $list1 = $this->coerceList($list1, ' ');
5034
-        $sep = $this->listSeparatorForJoin($list1, $sep);
5035
-
5036
-        return [Type::T_LIST, $sep, array_merge($list1[2], [$value])];
5037
-    }
5038
-
5039
-    protected function libZip($args)
5040
-    {
5041
-        foreach ($args as $arg) {
5042
-            $this->assertList($arg);
5043
-        }
5044
-
5045
-        $lists = [];
5046
-        $firstList = array_shift($args);
5047
-
5048
-        foreach ($firstList[2] as $key => $item) {
5049
-            $list = [Type::T_LIST, '', [$item]];
5050
-
5051
-            foreach ($args as $arg) {
5052
-                if (isset($arg[2][$key])) {
5053
-                    $list[2][] = $arg[2][$key];
5054
-                } else {
5055
-                    break 2;
5056
-                }
5057
-            }
5058
-
5059
-            $lists[] = $list;
5060
-        }
5061
-
5062
-        return [Type::T_LIST, ',', $lists];
5063
-    }
5064
-
5065
-    protected static $libTypeOf = ['value'];
5066
-    protected function libTypeOf($args)
5067
-    {
5068
-        $value = $args[0];
1888
+				if (! $content) {
1889
+					$content = new \stdClass();
1890
+					$content->scope = new \stdClass();
1891
+					$content->children = $this->storeEnv->parent->block->children;
1892
+					break;
1893
+				}
1894
+
1895
+				$storeEnv = $this->storeEnv;
1896
+				$this->storeEnv = $content->scope;
1897
+
1898
+				$this->compileChildrenNoReturn($content->children, $out);
1899
+
1900
+				$this->storeEnv = $storeEnv;
1901
+				break;
1902
+
1903
+			case Type::T_DEBUG:
1904
+				list(, $value) = $child;
1905
+
1906
+				$line = $this->sourceLine;
1907
+				$value = $this->compileValue($this->reduce($value, true));
1908
+				fwrite($this->stderr, "Line $line DEBUG: $value\n");
1909
+				break;
1910
+
1911
+			case Type::T_WARN:
1912
+				list(, $value) = $child;
1913
+
1914
+				$line = $this->sourceLine;
1915
+				$value = $this->compileValue($this->reduce($value, true));
1916
+				fwrite($this->stderr, "Line $line WARN: $value\n");
1917
+				break;
1918
+
1919
+			case Type::T_ERROR:
1920
+				list(, $value) = $child;
1921
+
1922
+				$line = $this->sourceLine;
1923
+				$value = $this->compileValue($this->reduce($value, true));
1924
+				$this->throwError("Line $line ERROR: $value\n");
1925
+				break;
1926
+
1927
+			case Type::T_CONTROL:
1928
+				$this->throwError('@break/@continue not permitted in this scope');
1929
+				break;
1930
+
1931
+			default:
1932
+				$this->throwError("unknown child type: $child[0]");
1933
+		}
1934
+	}
1935
+
1936
+	/**
1937
+	 * Reduce expression to string
1938
+	 *
1939
+	 * @param array $exp
1940
+	 *
1941
+	 * @return array
1942
+	 */
1943
+	protected function expToString($exp)
1944
+	{
1945
+		list(, $op, $left, $right, /* $inParens */, $whiteLeft, $whiteRight) = $exp;
1946
+
1947
+		$content = [$this->reduce($left)];
1948
+
1949
+		if ($whiteLeft) {
1950
+			$content[] = ' ';
1951
+		}
1952
+
1953
+		$content[] = $op;
1954
+
1955
+		if ($whiteRight) {
1956
+			$content[] = ' ';
1957
+		}
1958
+
1959
+		$content[] = $this->reduce($right);
1960
+
1961
+		return [Type::T_STRING, '', $content];
1962
+	}
1963
+
1964
+	/**
1965
+	 * Is truthy?
1966
+	 *
1967
+	 * @param array $value
1968
+	 *
1969
+	 * @return array
1970
+	 */
1971
+	protected function isTruthy($value)
1972
+	{
1973
+		return $value !== static::$false && $value !== static::$null;
1974
+	}
1975
+
1976
+	/**
1977
+	 * Is the value a direct relationship combinator?
1978
+	 *
1979
+	 * @param string $value
1980
+	 *
1981
+	 * @return boolean
1982
+	 */
1983
+	protected function isImmediateRelationshipCombinator($value)
1984
+	{
1985
+		return $value === '>' || $value === '+' || $value === '~';
1986
+	}
1987
+
1988
+	/**
1989
+	 * Should $value cause its operand to eval
1990
+	 *
1991
+	 * @param array $value
1992
+	 *
1993
+	 * @return boolean
1994
+	 */
1995
+	protected function shouldEval($value)
1996
+	{
1997
+		switch ($value[0]) {
1998
+			case Type::T_EXPRESSION:
1999
+				if ($value[1] === '/') {
2000
+					return $this->shouldEval($value[2], $value[3]);
2001
+				}
2002
+
2003
+				// fall-thru
2004
+			case Type::T_VARIABLE:
2005
+			case Type::T_FUNCTION_CALL:
2006
+				return true;
2007
+		}
2008
+
2009
+		return false;
2010
+	}
2011
+
2012
+	/**
2013
+	 * Reduce value
2014
+	 *
2015
+	 * @param array   $value
2016
+	 * @param boolean $inExp
2017
+	 *
2018
+	 * @return array|\Leafo\ScssPhp\Node\Number
2019
+	 */
2020
+	protected function reduce($value, $inExp = false)
2021
+	{
2022
+		list($type) = $value;
2023
+
2024
+		switch ($type) {
2025
+			case Type::T_EXPRESSION:
2026
+				list(, $op, $left, $right, $inParens) = $value;
2027
+
2028
+				$opName = isset(static::$operatorNames[$op]) ? static::$operatorNames[$op] : $op;
2029
+				$inExp = $inExp || $this->shouldEval($left) || $this->shouldEval($right);
2030
+
2031
+				$left = $this->reduce($left, true);
2032
+
2033
+				if ($op !== 'and' && $op !== 'or') {
2034
+					$right = $this->reduce($right, true);
2035
+				}
2036
+
2037
+				// special case: looks like css shorthand
2038
+				if ($opName == 'div' && ! $inParens && ! $inExp && isset($right[2])
2039
+					&& (($right[0] !== Type::T_NUMBER && $right[2] != '')
2040
+					|| ($right[0] === Type::T_NUMBER && ! $right->unitless()))
2041
+				) {
2042
+					return $this->expToString($value);
2043
+				}
2044
+
2045
+				$left = $this->coerceForExpression($left);
2046
+				$right = $this->coerceForExpression($right);
2047
+
2048
+				$ltype = $left[0];
2049
+				$rtype = $right[0];
2050
+
2051
+				$ucOpName = ucfirst($opName);
2052
+				$ucLType  = ucfirst($ltype);
2053
+				$ucRType  = ucfirst($rtype);
2054
+
2055
+				// this tries:
2056
+				// 1. op[op name][left type][right type]
2057
+				// 2. op[left type][right type] (passing the op as first arg
2058
+				// 3. op[op name]
2059
+				$fn = "op${ucOpName}${ucLType}${ucRType}";
2060
+
2061
+				if (is_callable([$this, $fn]) ||
2062
+					(($fn = "op${ucLType}${ucRType}") &&
2063
+						is_callable([$this, $fn]) &&
2064
+						$passOp = true) ||
2065
+					(($fn = "op${ucOpName}") &&
2066
+						is_callable([$this, $fn]) &&
2067
+						$genOp = true)
2068
+				) {
2069
+					$coerceUnit = false;
2070
+
2071
+					if (! isset($genOp) &&
2072
+						$left[0] === Type::T_NUMBER && $right[0] === Type::T_NUMBER
2073
+					) {
2074
+						$coerceUnit = true;
2075
+
2076
+						switch ($opName) {
2077
+							case 'mul':
2078
+								$targetUnit = $left[2];
2079
+
2080
+								foreach ($right[2] as $unit => $exp) {
2081
+									$targetUnit[$unit] = (isset($targetUnit[$unit]) ? $targetUnit[$unit] : 0) + $exp;
2082
+								}
2083
+								break;
2084
+
2085
+							case 'div':
2086
+								$targetUnit = $left[2];
2087
+
2088
+								foreach ($right[2] as $unit => $exp) {
2089
+									$targetUnit[$unit] = (isset($targetUnit[$unit]) ? $targetUnit[$unit] : 0) - $exp;
2090
+								}
2091
+								break;
2092
+
2093
+							case 'mod':
2094
+								$targetUnit = $left[2];
2095
+								break;
2096
+
2097
+							default:
2098
+								$targetUnit = $left->unitless() ? $right[2] : $left[2];
2099
+						}
2100
+
2101
+						if (! $left->unitless() && ! $right->unitless()) {
2102
+							$left = $left->normalize();
2103
+							$right = $right->normalize();
2104
+						}
2105
+					}
2106
+
2107
+					$shouldEval = $inParens || $inExp;
2108
+
2109
+					if (isset($passOp)) {
2110
+						$out = $this->$fn($op, $left, $right, $shouldEval);
2111
+					} else {
2112
+						$out = $this->$fn($left, $right, $shouldEval);
2113
+					}
2114
+
2115
+					if (isset($out)) {
2116
+						if ($coerceUnit && $out[0] === Type::T_NUMBER) {
2117
+							$out = $out->coerce($targetUnit);
2118
+						}
2119
+
2120
+						return $out;
2121
+					}
2122
+				}
2123
+
2124
+				return $this->expToString($value);
2125
+
2126
+			case Type::T_UNARY:
2127
+				list(, $op, $exp, $inParens) = $value;
2128
+
2129
+				$inExp = $inExp || $this->shouldEval($exp);
2130
+				$exp = $this->reduce($exp);
2131
+
2132
+				if ($exp[0] === Type::T_NUMBER) {
2133
+					switch ($op) {
2134
+						case '+':
2135
+							return new Node\Number($exp[1], $exp[2]);
2136
+
2137
+						case '-':
2138
+							return new Node\Number(-$exp[1], $exp[2]);
2139
+					}
2140
+				}
2141
+
2142
+				if ($op === 'not') {
2143
+					if ($inExp || $inParens) {
2144
+						if ($exp === static::$false || $exp === static::$null) {
2145
+							return static::$true;
2146
+						}
2147
+
2148
+						return static::$false;
2149
+					}
2150
+
2151
+					$op = $op . ' ';
2152
+				}
2153
+
2154
+				return [Type::T_STRING, '', [$op, $exp]];
2155
+
2156
+			case Type::T_VARIABLE:
2157
+				list(, $name) = $value;
2158
+
2159
+				return $this->reduce($this->get($name));
2160
+
2161
+			case Type::T_LIST:
2162
+				foreach ($value[2] as &$item) {
2163
+					$item = $this->reduce($item);
2164
+				}
2165
+
2166
+				return $value;
2167
+
2168
+			case Type::T_MAP:
2169
+				foreach ($value[1] as &$item) {
2170
+					$item = $this->reduce($item);
2171
+				}
2172
+
2173
+				foreach ($value[2] as &$item) {
2174
+					$item = $this->reduce($item);
2175
+				}
2176
+
2177
+				return $value;
2178
+
2179
+			case Type::T_STRING:
2180
+				foreach ($value[2] as &$item) {
2181
+					if (is_array($item) || $item instanceof \ArrayAccess) {
2182
+						$item = $this->reduce($item);
2183
+					}
2184
+				}
2185
+
2186
+				return $value;
2187
+
2188
+			case Type::T_INTERPOLATE:
2189
+				$value[1] = $this->reduce($value[1]);
2190
+
2191
+				return $value;
2192
+
2193
+			case Type::T_FUNCTION_CALL:
2194
+				list(, $name, $argValues) = $value;
2195
+
2196
+				return $this->fncall($name, $argValues);
2197
+
2198
+			default:
2199
+				return $value;
2200
+		}
2201
+	}
2202
+
2203
+	/**
2204
+	 * Function caller
2205
+	 *
2206
+	 * @param string $name
2207
+	 * @param array  $argValues
2208
+	 *
2209
+	 * @return array|null
2210
+	 */
2211
+	private function fncall($name, $argValues)
2212
+	{
2213
+		// SCSS @function
2214
+		if ($this->callScssFunction($name, $argValues, $returnValue)) {
2215
+			return $returnValue;
2216
+		}
2217
+
2218
+		// native PHP functions
2219
+		if ($this->callNativeFunction($name, $argValues, $returnValue)) {
2220
+			return $returnValue;
2221
+		}
2222
+
2223
+		// for CSS functions, simply flatten the arguments into a list
2224
+		$listArgs = [];
2225
+
2226
+		foreach ((array) $argValues as $arg) {
2227
+			if (empty($arg[0])) {
2228
+				$listArgs[] = $this->reduce($arg[1]);
2229
+			}
2230
+		}
2231
+
2232
+		return [Type::T_FUNCTION, $name, [Type::T_LIST, ',', $listArgs]];
2233
+	}
2234
+
2235
+	/**
2236
+	 * Normalize name
2237
+	 *
2238
+	 * @param string $name
2239
+	 *
2240
+	 * @return string
2241
+	 */
2242
+	protected function normalizeName($name)
2243
+	{
2244
+		return str_replace('-', '_', $name);
2245
+	}
2246
+
2247
+	/**
2248
+	 * Normalize value
2249
+	 *
2250
+	 * @param array $value
2251
+	 *
2252
+	 * @return array
2253
+	 */
2254
+	public function normalizeValue($value)
2255
+	{
2256
+		$value = $this->coerceForExpression($this->reduce($value));
2257
+		list($type) = $value;
2258
+
2259
+		switch ($type) {
2260
+			case Type::T_LIST:
2261
+				$value = $this->extractInterpolation($value);
2262
+
2263
+				if ($value[0] !== Type::T_LIST) {
2264
+					return [Type::T_KEYWORD, $this->compileValue($value)];
2265
+				}
2266
+
2267
+				foreach ($value[2] as $key => $item) {
2268
+					$value[2][$key] = $this->normalizeValue($item);
2269
+				}
2270
+
2271
+				return $value;
2272
+
2273
+			case Type::T_STRING:
2274
+				return [$type, '"', [$this->compileStringContent($value)]];
2275
+
2276
+			case Type::T_NUMBER:
2277
+				return $value->normalize();
2278
+
2279
+			case Type::T_INTERPOLATE:
2280
+				return [Type::T_KEYWORD, $this->compileValue($value)];
2281
+
2282
+			default:
2283
+				return $value;
2284
+		}
2285
+	}
2286
+
2287
+	/**
2288
+	 * Add numbers
2289
+	 *
2290
+	 * @param array $left
2291
+	 * @param array $right
2292
+	 *
2293
+	 * @return \Leafo\ScssPhp\Node\Number
2294
+	 */
2295
+	protected function opAddNumberNumber($left, $right)
2296
+	{
2297
+		return new Node\Number($left[1] + $right[1], $left[2]);
2298
+	}
2299
+
2300
+	/**
2301
+	 * Multiply numbers
2302
+	 *
2303
+	 * @param array $left
2304
+	 * @param array $right
2305
+	 *
2306
+	 * @return \Leafo\ScssPhp\Node\Number
2307
+	 */
2308
+	protected function opMulNumberNumber($left, $right)
2309
+	{
2310
+		return new Node\Number($left[1] * $right[1], $left[2]);
2311
+	}
2312
+
2313
+	/**
2314
+	 * Subtract numbers
2315
+	 *
2316
+	 * @param array $left
2317
+	 * @param array $right
2318
+	 *
2319
+	 * @return \Leafo\ScssPhp\Node\Number
2320
+	 */
2321
+	protected function opSubNumberNumber($left, $right)
2322
+	{
2323
+		return new Node\Number($left[1] - $right[1], $left[2]);
2324
+	}
2325
+
2326
+	/**
2327
+	 * Divide numbers
2328
+	 *
2329
+	 * @param array $left
2330
+	 * @param array $right
2331
+	 *
2332
+	 * @return array|\Leafo\ScssPhp\Node\Number
2333
+	 */
2334
+	protected function opDivNumberNumber($left, $right)
2335
+	{
2336
+		if ($right[1] == 0) {
2337
+			return [Type::T_STRING, '', [$left[1] . $left[2] . '/' . $right[1] . $right[2]]];
2338
+		}
2339
+
2340
+		return new Node\Number($left[1] / $right[1], $left[2]);
2341
+	}
2342
+
2343
+	/**
2344
+	 * Mod numbers
2345
+	 *
2346
+	 * @param array $left
2347
+	 * @param array $right
2348
+	 *
2349
+	 * @return \Leafo\ScssPhp\Node\Number
2350
+	 */
2351
+	protected function opModNumberNumber($left, $right)
2352
+	{
2353
+		return new Node\Number($left[1] % $right[1], $left[2]);
2354
+	}
2355
+
2356
+	/**
2357
+	 * Add strings
2358
+	 *
2359
+	 * @param array $left
2360
+	 * @param array $right
2361
+	 *
2362
+	 * @return array
2363
+	 */
2364
+	protected function opAdd($left, $right)
2365
+	{
2366
+		if ($strLeft = $this->coerceString($left)) {
2367
+			if ($right[0] === Type::T_STRING) {
2368
+				$right[1] = '';
2369
+			}
2370
+
2371
+			$strLeft[2][] = $right;
2372
+
2373
+			return $strLeft;
2374
+		}
2375
+
2376
+		if ($strRight = $this->coerceString($right)) {
2377
+			if ($left[0] === Type::T_STRING) {
2378
+				$left[1] = '';
2379
+			}
2380
+
2381
+			array_unshift($strRight[2], $left);
2382
+
2383
+			return $strRight;
2384
+		}
2385
+	}
2386
+
2387
+	/**
2388
+	 * Boolean and
2389
+	 *
2390
+	 * @param array   $left
2391
+	 * @param array   $right
2392
+	 * @param boolean $shouldEval
2393
+	 *
2394
+	 * @return array
2395
+	 */
2396
+	protected function opAnd($left, $right, $shouldEval)
2397
+	{
2398
+		if (! $shouldEval) {
2399
+			return;
2400
+		}
2401
+
2402
+		if ($left !== static::$false and $left !== static::$null) {
2403
+			return $this->reduce($right, true);
2404
+		}
2405
+
2406
+		return $left;
2407
+	}
2408
+
2409
+	/**
2410
+	 * Boolean or
2411
+	 *
2412
+	 * @param array   $left
2413
+	 * @param array   $right
2414
+	 * @param boolean $shouldEval
2415
+	 *
2416
+	 * @return array
2417
+	 */
2418
+	protected function opOr($left, $right, $shouldEval)
2419
+	{
2420
+		if (! $shouldEval) {
2421
+			return;
2422
+		}
2423
+
2424
+		if ($left !== static::$false and $left !== static::$null) {
2425
+			return $left;
2426
+		}
2427
+
2428
+		return $this->reduce($right, true);
2429
+	}
2430
+
2431
+	/**
2432
+	 * Compare colors
2433
+	 *
2434
+	 * @param string $op
2435
+	 * @param array  $left
2436
+	 * @param array  $right
2437
+	 *
2438
+	 * @return array
2439
+	 */
2440
+	protected function opColorColor($op, $left, $right)
2441
+	{
2442
+		$out = [Type::T_COLOR];
2443
+
2444
+		foreach ([1, 2, 3] as $i) {
2445
+			$lval = isset($left[$i]) ? $left[$i] : 0;
2446
+			$rval = isset($right[$i]) ? $right[$i] : 0;
2447
+
2448
+			switch ($op) {
2449
+				case '+':
2450
+					$out[] = $lval + $rval;
2451
+					break;
2452
+
2453
+				case '-':
2454
+					$out[] = $lval - $rval;
2455
+					break;
2456
+
2457
+				case '*':
2458
+					$out[] = $lval * $rval;
2459
+					break;
2460
+
2461
+				case '%':
2462
+					$out[] = $lval % $rval;
2463
+					break;
2464
+
2465
+				case '/':
2466
+					if ($rval == 0) {
2467
+						$this->throwError("color: Can't divide by zero");
2468
+						break 2;
2469
+					}
2470
+
2471
+					$out[] = (int) ($lval / $rval);
2472
+					break;
2473
+
2474
+				case '==':
2475
+					return $this->opEq($left, $right);
2476
+
2477
+				case '!=':
2478
+					return $this->opNeq($left, $right);
2479
+
2480
+				default:
2481
+					$this->throwError("color: unknown op $op");
2482
+					break 2;
2483
+			}
2484
+		}
2485
+
2486
+		if (isset($left[4])) {
2487
+			$out[4] = $left[4];
2488
+		} elseif (isset($right[4])) {
2489
+			$out[4] = $right[4];
2490
+		}
2491
+
2492
+		return $this->fixColor($out);
2493
+	}
2494
+
2495
+	/**
2496
+	 * Compare color and number
2497
+	 *
2498
+	 * @param string $op
2499
+	 * @param array  $left
2500
+	 * @param array  $right
2501
+	 *
2502
+	 * @return array
2503
+	 */
2504
+	protected function opColorNumber($op, $left, $right)
2505
+	{
2506
+		$value = $right[1];
2507
+
2508
+		return $this->opColorColor(
2509
+			$op,
2510
+			$left,
2511
+			[Type::T_COLOR, $value, $value, $value]
2512
+		);
2513
+	}
2514
+
2515
+	/**
2516
+	 * Compare number and color
2517
+	 *
2518
+	 * @param string $op
2519
+	 * @param array  $left
2520
+	 * @param array  $right
2521
+	 *
2522
+	 * @return array
2523
+	 */
2524
+	protected function opNumberColor($op, $left, $right)
2525
+	{
2526
+		$value = $left[1];
2527
+
2528
+		return $this->opColorColor(
2529
+			$op,
2530
+			[Type::T_COLOR, $value, $value, $value],
2531
+			$right
2532
+		);
2533
+	}
2534
+
2535
+	/**
2536
+	 * Compare number1 == number2
2537
+	 *
2538
+	 * @param array $left
2539
+	 * @param array $right
2540
+	 *
2541
+	 * @return array
2542
+	 */
2543
+	protected function opEq($left, $right)
2544
+	{
2545
+		if (($lStr = $this->coerceString($left)) && ($rStr = $this->coerceString($right))) {
2546
+			$lStr[1] = '';
2547
+			$rStr[1] = '';
2548
+
2549
+			$left = $this->compileValue($lStr);
2550
+			$right = $this->compileValue($rStr);
2551
+		}
2552
+
2553
+		return $this->toBool($left === $right);
2554
+	}
2555
+
2556
+	/**
2557
+	 * Compare number1 != number2
2558
+	 *
2559
+	 * @param array $left
2560
+	 * @param array $right
2561
+	 *
2562
+	 * @return array
2563
+	 */
2564
+	protected function opNeq($left, $right)
2565
+	{
2566
+		if (($lStr = $this->coerceString($left)) && ($rStr = $this->coerceString($right))) {
2567
+			$lStr[1] = '';
2568
+			$rStr[1] = '';
2569
+
2570
+			$left = $this->compileValue($lStr);
2571
+			$right = $this->compileValue($rStr);
2572
+		}
2573
+
2574
+		return $this->toBool($left !== $right);
2575
+	}
2576
+
2577
+	/**
2578
+	 * Compare number1 >= number2
2579
+	 *
2580
+	 * @param array $left
2581
+	 * @param array $right
2582
+	 *
2583
+	 * @return array
2584
+	 */
2585
+	protected function opGteNumberNumber($left, $right)
2586
+	{
2587
+		return $this->toBool($left[1] >= $right[1]);
2588
+	}
2589
+
2590
+	/**
2591
+	 * Compare number1 > number2
2592
+	 *
2593
+	 * @param array $left
2594
+	 * @param array $right
2595
+	 *
2596
+	 * @return array
2597
+	 */
2598
+	protected function opGtNumberNumber($left, $right)
2599
+	{
2600
+		return $this->toBool($left[1] > $right[1]);
2601
+	}
2602
+
2603
+	/**
2604
+	 * Compare number1 <= number2
2605
+	 *
2606
+	 * @param array $left
2607
+	 * @param array $right
2608
+	 *
2609
+	 * @return array
2610
+	 */
2611
+	protected function opLteNumberNumber($left, $right)
2612
+	{
2613
+		return $this->toBool($left[1] <= $right[1]);
2614
+	}
2615
+
2616
+	/**
2617
+	 * Compare number1 < number2
2618
+	 *
2619
+	 * @param array $left
2620
+	 * @param array $right
2621
+	 *
2622
+	 * @return array
2623
+	 */
2624
+	protected function opLtNumberNumber($left, $right)
2625
+	{
2626
+		return $this->toBool($left[1] < $right[1]);
2627
+	}
2628
+
2629
+	/**
2630
+	 * Three-way comparison, aka spaceship operator
2631
+	 *
2632
+	 * @param array $left
2633
+	 * @param array $right
2634
+	 *
2635
+	 * @return \Leafo\ScssPhp\Node\Number
2636
+	 */
2637
+	protected function opCmpNumberNumber($left, $right)
2638
+	{
2639
+		$n = $left[1] - $right[1];
2640
+
2641
+		return new Node\Number($n ? $n / abs($n) : 0, '');
2642
+	}
2643
+
2644
+	/**
2645
+	 * Cast to boolean
2646
+	 *
2647
+	 * @api
2648
+	 *
2649
+	 * @param mixed $thing
2650
+	 *
2651
+	 * @return array
2652
+	 */
2653
+	public function toBool($thing)
2654
+	{
2655
+		return $thing ? static::$true : static::$false;
2656
+	}
2657
+
2658
+	/**
2659
+	 * Compiles a primitive value into a CSS property value.
2660
+	 *
2661
+	 * Values in scssphp are typed by being wrapped in arrays, their format is
2662
+	 * typically:
2663
+	 *
2664
+	 *     array(type, contents [, additional_contents]*)
2665
+	 *
2666
+	 * The input is expected to be reduced. This function will not work on
2667
+	 * things like expressions and variables.
2668
+	 *
2669
+	 * @api
2670
+	 *
2671
+	 * @param array $value
2672
+	 *
2673
+	 * @return string
2674
+	 */
2675
+	public function compileValue($value)
2676
+	{
2677
+		$value = $this->reduce($value);
2678
+
2679
+		list($type) = $value;
2680
+
2681
+		switch ($type) {
2682
+			case Type::T_KEYWORD:
2683
+				return $value[1];
2684
+
2685
+			case Type::T_COLOR:
2686
+				// [1] - red component (either number for a %)
2687
+				// [2] - green component
2688
+				// [3] - blue component
2689
+				// [4] - optional alpha component
2690
+				list(, $r, $g, $b) = $value;
2691
+
2692
+				$r = round($r);
2693
+				$g = round($g);
2694
+				$b = round($b);
2695
+
2696
+				if (count($value) === 5 && $value[4] !== 1) { // rgba
2697
+					return 'rgba(' . $r . ', ' . $g . ', ' . $b . ', ' . $value[4] . ')';
2698
+				}
2699
+
2700
+				$h = sprintf('#%02x%02x%02x', $r, $g, $b);
2701
+
2702
+				// Converting hex color to short notation (e.g. #003399 to #039)
2703
+				if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {
2704
+					$h = '#' . $h[1] . $h[3] . $h[5];
2705
+				}
2706
+
2707
+				return $h;
2708
+
2709
+			case Type::T_NUMBER:
2710
+				return $value->output($this);
2711
+
2712
+			case Type::T_STRING:
2713
+				return $value[1] . $this->compileStringContent($value) . $value[1];
2714
+
2715
+			case Type::T_FUNCTION:
2716
+				$args = ! empty($value[2]) ? $this->compileValue($value[2]) : '';
2717
+
2718
+				return "$value[1]($args)";
2719
+
2720
+			case Type::T_LIST:
2721
+				$value = $this->extractInterpolation($value);
2722
+
2723
+				if ($value[0] !== Type::T_LIST) {
2724
+					return $this->compileValue($value);
2725
+				}
2726
+
2727
+				list(, $delim, $items) = $value;
2728
+
2729
+				if ($delim !== ' ') {
2730
+					$delim .= ' ';
2731
+				}
2732
+
2733
+				$filtered = [];
2734
+
2735
+				foreach ($items as $item) {
2736
+					if ($item[0] === Type::T_NULL) {
2737
+						continue;
2738
+					}
2739
+
2740
+					$filtered[] = $this->compileValue($item);
2741
+				}
2742
+
2743
+				return implode("$delim", $filtered);
2744
+
2745
+			case Type::T_MAP:
2746
+				$keys = $value[1];
2747
+				$values = $value[2];
2748
+				$filtered = [];
2749
+
2750
+				for ($i = 0, $s = count($keys); $i < $s; $i++) {
2751
+					$filtered[$this->compileValue($keys[$i])] = $this->compileValue($values[$i]);
2752
+				}
2753
+
2754
+				array_walk($filtered, function (&$value, $key) {
2755
+					$value = $key . ': ' . $value;
2756
+				});
2757
+
2758
+				return '(' . implode(', ', $filtered) . ')';
2759
+
2760
+			case Type::T_INTERPOLATED:
2761
+				// node created by extractInterpolation
2762
+				list(, $interpolate, $left, $right) = $value;
2763
+				list(,, $whiteLeft, $whiteRight) = $interpolate;
2764
+
2765
+				$left = count($left[2]) > 0 ?
2766
+					$this->compileValue($left) . $whiteLeft : '';
2767
+
2768
+				$right = count($right[2]) > 0 ?
2769
+					$whiteRight . $this->compileValue($right) : '';
2770
+
2771
+				return $left . $this->compileValue($interpolate) . $right;
2772
+
2773
+			case Type::T_INTERPOLATE:
2774
+				// raw parse node
2775
+				list(, $exp) = $value;
2776
+
2777
+				// strip quotes if it's a string
2778
+				$reduced = $this->reduce($exp);
2779
+
2780
+				switch ($reduced[0]) {
2781
+					case Type::T_LIST:
2782
+						$reduced = $this->extractInterpolation($reduced);
2783
+
2784
+						if ($reduced[0] !== Type::T_LIST) {
2785
+							break;
2786
+						}
2787
+
2788
+						list(, $delim, $items) = $reduced;
2789
+
2790
+						if ($delim !== ' ') {
2791
+							$delim .= ' ';
2792
+						}
2793
+
2794
+						$filtered = [];
2795
+
2796
+						foreach ($items as $item) {
2797
+							if ($item[0] === Type::T_NULL) {
2798
+								continue;
2799
+							}
2800
+
2801
+							$temp = $this->compileValue([Type::T_KEYWORD, $item]);
2802
+							if ($temp[0] === Type::T_STRING) {
2803
+								$filtered[] = $this->compileStringContent($temp);
2804
+							} elseif ($temp[0] === Type::T_KEYWORD) {
2805
+								$filtered[] = $temp[1];
2806
+							} else {
2807
+								$filtered[] = $this->compileValue($temp);
2808
+							}
2809
+						}
2810
+
2811
+						$reduced = [Type::T_KEYWORD, implode("$delim", $filtered)];
2812
+						break;
2813
+
2814
+					case Type::T_STRING:
2815
+						$reduced = [Type::T_KEYWORD, $this->compileStringContent($reduced)];
2816
+						break;
2817
+
2818
+					case Type::T_NULL:
2819
+						$reduced = [Type::T_KEYWORD, ''];
2820
+				}
2821
+
2822
+				return $this->compileValue($reduced);
2823
+
2824
+			case Type::T_NULL:
2825
+				return 'null';
2826
+
2827
+			default:
2828
+				$this->throwError("unknown value type: $type");
2829
+		}
2830
+	}
2831
+
2832
+	/**
2833
+	 * Flatten list
2834
+	 *
2835
+	 * @param array $list
2836
+	 *
2837
+	 * @return string
2838
+	 */
2839
+	protected function flattenList($list)
2840
+	{
2841
+		return $this->compileValue($list);
2842
+	}
2843
+
2844
+	/**
2845
+	 * Compile string content
2846
+	 *
2847
+	 * @param array $string
2848
+	 *
2849
+	 * @return string
2850
+	 */
2851
+	protected function compileStringContent($string)
2852
+	{
2853
+		$parts = [];
2854
+
2855
+		foreach ($string[2] as $part) {
2856
+			if (is_array($part) || $part instanceof \ArrayAccess) {
2857
+				$parts[] = $this->compileValue($part);
2858
+			} else {
2859
+				$parts[] = $part;
2860
+			}
2861
+		}
2862
+
2863
+		return implode($parts);
2864
+	}
2865
+
2866
+	/**
2867
+	 * Extract interpolation; it doesn't need to be recursive, compileValue will handle that
2868
+	 *
2869
+	 * @param array $list
2870
+	 *
2871
+	 * @return array
2872
+	 */
2873
+	protected function extractInterpolation($list)
2874
+	{
2875
+		$items = $list[2];
2876
+
2877
+		foreach ($items as $i => $item) {
2878
+			if ($item[0] === Type::T_INTERPOLATE) {
2879
+				$before = [Type::T_LIST, $list[1], array_slice($items, 0, $i)];
2880
+				$after  = [Type::T_LIST, $list[1], array_slice($items, $i + 1)];
2881
+
2882
+				return [Type::T_INTERPOLATED, $item, $before, $after];
2883
+			}
2884
+		}
2885
+
2886
+		return $list;
2887
+	}
2888
+
2889
+	/**
2890
+	 * Find the final set of selectors
2891
+	 *
2892
+	 * @param \Leafo\ScssPhp\Compiler\Environment $env
2893
+	 *
2894
+	 * @return array
2895
+	 */
2896
+	protected function multiplySelectors(Environment $env)
2897
+	{
2898
+		$envs            = $this->compactEnv($env);
2899
+		$selectors       = [];
2900
+		$parentSelectors = [[]];
2901
+
2902
+		while ($env = array_pop($envs)) {
2903
+			if (empty($env->selectors)) {
2904
+				continue;
2905
+			}
2906
+
2907
+			$selectors = [];
2908
+
2909
+			foreach ($env->selectors as $selector) {
2910
+				foreach ($parentSelectors as $parent) {
2911
+					$selectors[] = $this->joinSelectors($parent, $selector);
2912
+				}
2913
+			}
2914
+
2915
+			$parentSelectors = $selectors;
2916
+		}
2917
+
2918
+		return $selectors;
2919
+	}
2920
+
2921
+	/**
2922
+	 * Join selectors; looks for & to replace, or append parent before child
2923
+	 *
2924
+	 * @param array $parent
2925
+	 * @param array $child
2926
+	 *
2927
+	 * @return array
2928
+	 */
2929
+	protected function joinSelectors($parent, $child)
2930
+	{
2931
+		$setSelf = false;
2932
+		$out = [];
2933
+
2934
+		foreach ($child as $part) {
2935
+			$newPart = [];
2936
+
2937
+			foreach ($part as $p) {
2938
+				if ($p === static::$selfSelector) {
2939
+					$setSelf = true;
2940
+
2941
+					foreach ($parent as $i => $parentPart) {
2942
+						if ($i > 0) {
2943
+							$out[] = $newPart;
2944
+							$newPart = [];
2945
+						}
2946
+
2947
+						foreach ($parentPart as $pp) {
2948
+							$newPart[] = $pp;
2949
+						}
2950
+					}
2951
+				} else {
2952
+					$newPart[] = $p;
2953
+				}
2954
+			}
2955
+
2956
+			$out[] = $newPart;
2957
+		}
2958
+
2959
+		return $setSelf ? $out : array_merge($parent, $child);
2960
+	}
2961
+
2962
+	/**
2963
+	 * Multiply media
2964
+	 *
2965
+	 * @param \Leafo\ScssPhp\Compiler\Environment $env
2966
+	 * @param array                               $childQueries
2967
+	 *
2968
+	 * @return array
2969
+	 */
2970
+	protected function multiplyMedia(Environment $env = null, $childQueries = null)
2971
+	{
2972
+		if (! isset($env) ||
2973
+			! empty($env->block->type) && $env->block->type !== Type::T_MEDIA
2974
+		) {
2975
+			return $childQueries;
2976
+		}
2977
+
2978
+		// plain old block, skip
2979
+		if (empty($env->block->type)) {
2980
+			return $this->multiplyMedia($env->parent, $childQueries);
2981
+		}
2982
+
2983
+		$parentQueries = isset($env->block->queryList)
2984
+			? $env->block->queryList
2985
+			: [[[Type::T_MEDIA_VALUE, $env->block->value]]];
2986
+
2987
+		if ($childQueries === null) {
2988
+			$childQueries = $parentQueries;
2989
+		} else {
2990
+			$originalQueries = $childQueries;
2991
+			$childQueries = [];
2992
+
2993
+			foreach ($parentQueries as $parentQuery) {
2994
+				foreach ($originalQueries as $childQuery) {
2995
+					$childQueries []= array_merge($parentQuery, $childQuery);
2996
+				}
2997
+			}
2998
+		}
2999
+
3000
+		return $this->multiplyMedia($env->parent, $childQueries);
3001
+	}
3002
+
3003
+	/**
3004
+	 * Convert env linked list to stack
3005
+	 *
3006
+	 * @param \Leafo\ScssPhp\Compiler\Environment $env
3007
+	 *
3008
+	 * @return array
3009
+	 */
3010
+	private function compactEnv(Environment $env)
3011
+	{
3012
+		for ($envs = []; $env; $env = $env->parent) {
3013
+			$envs[] = $env;
3014
+		}
3015
+
3016
+		return $envs;
3017
+	}
3018
+
3019
+	/**
3020
+	 * Convert env stack to singly linked list
3021
+	 *
3022
+	 * @param array $envs
3023
+	 *
3024
+	 * @return \Leafo\ScssPhp\Compiler\Environment
3025
+	 */
3026
+	private function extractEnv($envs)
3027
+	{
3028
+		for ($env = null; $e = array_pop($envs);) {
3029
+			$e->parent = $env;
3030
+			$env = $e;
3031
+		}
3032
+
3033
+		return $env;
3034
+	}
3035
+
3036
+	/**
3037
+	 * Push environment
3038
+	 *
3039
+	 * @param \Leafo\ScssPhp\Block $block
3040
+	 *
3041
+	 * @return \Leafo\ScssPhp\Compiler\Environment
3042
+	 */
3043
+	protected function pushEnv(Block $block = null)
3044
+	{
3045
+		$env = new Environment;
3046
+		$env->parent = $this->env;
3047
+		$env->store  = [];
3048
+		$env->block  = $block;
3049
+		$env->depth  = isset($this->env->depth) ? $this->env->depth + 1 : 0;
3050
+
3051
+		$this->env = $env;
3052
+
3053
+		return $env;
3054
+	}
3055
+
3056
+	/**
3057
+	 * Pop environment
3058
+	 */
3059
+	protected function popEnv()
3060
+	{
3061
+		$this->env = $this->env->parent;
3062
+	}
3063
+
3064
+	/**
3065
+	 * Get store environment
3066
+	 *
3067
+	 * @return \Leafo\ScssPhp\Compiler\Environment
3068
+	 */
3069
+	protected function getStoreEnv()
3070
+	{
3071
+		return isset($this->storeEnv) ? $this->storeEnv : $this->env;
3072
+	}
3073
+
3074
+	/**
3075
+	 * Set variable
3076
+	 *
3077
+	 * @param string                              $name
3078
+	 * @param mixed                               $value
3079
+	 * @param boolean                             $shadow
3080
+	 * @param \Leafo\ScssPhp\Compiler\Environment $env
3081
+	 */
3082
+	protected function set($name, $value, $shadow = false, Environment $env = null)
3083
+	{
3084
+		$name = $this->normalizeName($name);
3085
+
3086
+		if (! isset($env)) {
3087
+			$env = $this->getStoreEnv();
3088
+		}
3089
+
3090
+		if ($shadow) {
3091
+			$this->setRaw($name, $value, $env);
3092
+		} else {
3093
+			$this->setExisting($name, $value, $env);
3094
+		}
3095
+	}
3096
+
3097
+	/**
3098
+	 * Set existing variable
3099
+	 *
3100
+	 * @param string                              $name
3101
+	 * @param mixed                               $value
3102
+	 * @param \Leafo\ScssPhp\Compiler\Environment $env
3103
+	 */
3104
+	protected function setExisting($name, $value, Environment $env)
3105
+	{
3106
+		$storeEnv = $env;
3107
+
3108
+		$hasNamespace = $name[0] === '^' || $name[0] === '@' || $name[0] === '%';
3109
+
3110
+		for (;;) {
3111
+			if (array_key_exists($name, $env->store)) {
3112
+				break;
3113
+			}
3114
+
3115
+			if (! $hasNamespace && isset($env->marker)) {
3116
+				$env = $storeEnv;
3117
+				break;
3118
+			}
3119
+
3120
+			if (! isset($env->parent)) {
3121
+				$env = $storeEnv;
3122
+				break;
3123
+			}
3124
+
3125
+			$env = $env->parent;
3126
+		}
3127
+
3128
+		$env->store[$name] = $value;
3129
+	}
3130
+
3131
+	/**
3132
+	 * Set raw variable
3133
+	 *
3134
+	 * @param string                              $name
3135
+	 * @param mixed                               $value
3136
+	 * @param \Leafo\ScssPhp\Compiler\Environment $env
3137
+	 */
3138
+	protected function setRaw($name, $value, Environment $env)
3139
+	{
3140
+		$env->store[$name] = $value;
3141
+	}
3142
+
3143
+	/**
3144
+	 * Get variable
3145
+	 *
3146
+	 * @api
3147
+	 *
3148
+	 * @param string                              $name
3149
+	 * @param boolean                             $shouldThrow
3150
+	 * @param \Leafo\ScssPhp\Compiler\Environment $env
3151
+	 *
3152
+	 * @return mixed
3153
+	 */
3154
+	public function get($name, $shouldThrow = true, Environment $env = null)
3155
+	{
3156
+		$normalizedName = $this->normalizeName($name);
3157
+		$specialContentKey = static::$namespaces['special'] . 'content';
3158
+
3159
+		if (! isset($env)) {
3160
+			$env = $this->getStoreEnv();
3161
+		}
3162
+
3163
+		$nextIsRoot = false;
3164
+		$hasNamespace = $normalizedName[0] === '^' || $normalizedName[0] === '@' || $normalizedName[0] === '%';
3165
+
3166
+		for (;;) {
3167
+			if (array_key_exists($normalizedName, $env->store)) {
3168
+				return $env->store[$normalizedName];
3169
+			}
3170
+
3171
+			if (! $hasNamespace && isset($env->marker)) {
3172
+				if (! $nextIsRoot && ! empty($env->store[$specialContentKey])) {
3173
+					$env = $env->store[$specialContentKey]->scope;
3174
+					$nextIsRoot = true;
3175
+					continue;
3176
+				}
3177
+
3178
+				$env = $this->rootEnv;
3179
+				continue;
3180
+			}
3181
+
3182
+			if (! isset($env->parent)) {
3183
+				break;
3184
+			}
3185
+
3186
+			$env = $env->parent;
3187
+		}
3188
+
3189
+		if ($shouldThrow) {
3190
+			$this->throwError("Undefined variable \$$name");
3191
+		}
3192
+
3193
+		// found nothing
3194
+	}
3195
+
3196
+	/**
3197
+	 * Has variable?
3198
+	 *
3199
+	 * @param string                              $name
3200
+	 * @param \Leafo\ScssPhp\Compiler\Environment $env
3201
+	 *
3202
+	 * @return boolean
3203
+	 */
3204
+	protected function has($name, Environment $env = null)
3205
+	{
3206
+		return $this->get($name, false, $env) !== null;
3207
+	}
3208
+
3209
+	/**
3210
+	 * Inject variables
3211
+	 *
3212
+	 * @param array $args
3213
+	 */
3214
+	protected function injectVariables(array $args)
3215
+	{
3216
+		if (empty($args)) {
3217
+			return;
3218
+		}
3219
+
3220
+		$parser = $this->parserFactory(__METHOD__);
3221
+
3222
+		foreach ($args as $name => $strValue) {
3223
+			if ($name[0] === '$') {
3224
+				$name = substr($name, 1);
3225
+			}
3226
+
3227
+			if (! $parser->parseValue($strValue, $value)) {
3228
+				$value = $this->coerceValue($strValue);
3229
+			}
3230
+
3231
+			$this->set($name, $value);
3232
+		}
3233
+	}
3234
+
3235
+	/**
3236
+	 * Set variables
3237
+	 *
3238
+	 * @api
3239
+	 *
3240
+	 * @param array $variables
3241
+	 */
3242
+	public function setVariables(array $variables)
3243
+	{
3244
+		$this->registeredVars = array_merge($this->registeredVars, $variables);
3245
+	}
3246
+
3247
+	/**
3248
+	 * Unset variable
3249
+	 *
3250
+	 * @api
3251
+	 *
3252
+	 * @param string $name
3253
+	 */
3254
+	public function unsetVariable($name)
3255
+	{
3256
+		unset($this->registeredVars[$name]);
3257
+	}
3258
+
3259
+	/**
3260
+	 * Returns list of variables
3261
+	 *
3262
+	 * @api
3263
+	 *
3264
+	 * @return array
3265
+	 */
3266
+	public function getVariables()
3267
+	{
3268
+		return $this->registeredVars;
3269
+	}
3270
+
3271
+	/**
3272
+	 * Adds to list of parsed files
3273
+	 *
3274
+	 * @api
3275
+	 *
3276
+	 * @param string $path
3277
+	 */
3278
+	public function addParsedFile($path)
3279
+	{
3280
+		if (isset($path) && file_exists($path)) {
3281
+			$this->parsedFiles[realpath($path)] = filemtime($path);
3282
+		}
3283
+	}
3284
+
3285
+	/**
3286
+	 * Returns list of parsed files
3287
+	 *
3288
+	 * @api
3289
+	 *
3290
+	 * @return array
3291
+	 */
3292
+	public function getParsedFiles()
3293
+	{
3294
+		return $this->parsedFiles;
3295
+	}
3296
+
3297
+	/**
3298
+	 * Add import path
3299
+	 *
3300
+	 * @api
3301
+	 *
3302
+	 * @param string $path
3303
+	 */
3304
+	public function addImportPath($path)
3305
+	{
3306
+		if (! in_array($path, $this->importPaths)) {
3307
+			$this->importPaths[] = $path;
3308
+		}
3309
+	}
3310
+
3311
+	/**
3312
+	 * Set import paths
3313
+	 *
3314
+	 * @api
3315
+	 *
3316
+	 * @param string|array $path
3317
+	 */
3318
+	public function setImportPaths($path)
3319
+	{
3320
+		$this->importPaths = (array) $path;
3321
+	}
3322
+
3323
+	/**
3324
+	 * Set number precision
3325
+	 *
3326
+	 * @api
3327
+	 *
3328
+	 * @param integer $numberPrecision
3329
+	 */
3330
+	public function setNumberPrecision($numberPrecision)
3331
+	{
3332
+		Node\Number::$precision = $numberPrecision;
3333
+	}
3334
+
3335
+	/**
3336
+	 * Set formatter
3337
+	 *
3338
+	 * @api
3339
+	 *
3340
+	 * @param string $formatterName
3341
+	 */
3342
+	public function setFormatter($formatterName)
3343
+	{
3344
+		$this->formatter = $formatterName;
3345
+	}
3346
+
3347
+	/**
3348
+	 * Set line number style
3349
+	 *
3350
+	 * @api
3351
+	 *
3352
+	 * @param string $lineNumberStyle
3353
+	 */
3354
+	public function setLineNumberStyle($lineNumberStyle)
3355
+	{
3356
+		$this->lineNumberStyle = $lineNumberStyle;
3357
+	}
3358
+
3359
+	/**
3360
+	 * Enable/disable source maps
3361
+	 *
3362
+	 * @api
3363
+	 *
3364
+	 * @param integer $sourceMap
3365
+	 */
3366
+	public function setSourceMap($sourceMap)
3367
+	{
3368
+		$this->sourceMap = $sourceMap;
3369
+	}
3370
+
3371
+	/**
3372
+	 * Set source map options
3373
+	 *
3374
+	 * @api
3375
+	 *
3376
+	 * @param array $sourceMapOptions
3377
+	 */
3378
+	public function setSourceMapOptions($sourceMapOptions)
3379
+	{
3380
+		$this->sourceMapOptions = $sourceMapOptions;
3381
+	}
3382
+
3383
+	/**
3384
+	 * Register function
3385
+	 *
3386
+	 * @api
3387
+	 *
3388
+	 * @param string   $name
3389
+	 * @param callable $func
3390
+	 * @param array    $prototype
3391
+	 */
3392
+	public function registerFunction($name, $func, $prototype = null)
3393
+	{
3394
+		$this->userFunctions[$this->normalizeName($name)] = [$func, $prototype];
3395
+	}
3396
+
3397
+	/**
3398
+	 * Unregister function
3399
+	 *
3400
+	 * @api
3401
+	 *
3402
+	 * @param string $name
3403
+	 */
3404
+	public function unregisterFunction($name)
3405
+	{
3406
+		unset($this->userFunctions[$this->normalizeName($name)]);
3407
+	}
3408
+
3409
+	/**
3410
+	 * Add feature
3411
+	 *
3412
+	 * @api
3413
+	 *
3414
+	 * @param string $name
3415
+	 */
3416
+	public function addFeature($name)
3417
+	{
3418
+		$this->registeredFeatures[$name] = true;
3419
+	}
3420
+
3421
+	/**
3422
+	 * Import file
3423
+	 *
3424
+	 * @param string $path
3425
+	 * @param array  $out
3426
+	 */
3427
+	protected function importFile($path, $out)
3428
+	{
3429
+		// see if tree is cached
3430
+		$realPath = realpath($path);
3431
+
3432
+		if (isset($this->importCache[$realPath])) {
3433
+			$this->handleImportLoop($realPath);
3434
+
3435
+			$tree = $this->importCache[$realPath];
3436
+		} else {
3437
+			$code   = file_get_contents($path);
3438
+			$parser = $this->parserFactory($path);
3439
+			$tree   = $parser->parse($code);
3440
+
3441
+			$this->importCache[$realPath] = $tree;
3442
+		}
3443
+
3444
+		$pi = pathinfo($path);
3445
+		array_unshift($this->importPaths, $pi['dirname']);
3446
+		$this->compileChildrenNoReturn($tree->children, $out);
3447
+		array_shift($this->importPaths);
3448
+	}
3449
+
3450
+	/**
3451
+	 * Return the file path for an import url if it exists
3452
+	 *
3453
+	 * @api
3454
+	 *
3455
+	 * @param string $url
3456
+	 *
3457
+	 * @return string|null
3458
+	 */
3459
+	public function findImport($url)
3460
+	{
3461
+		$urls = [];
3462
+
3463
+		// for "normal" scss imports (ignore vanilla css and external requests)
3464
+		if (! preg_match('/\.css$|^https?:\/\//', $url)) {
3465
+			// try both normal and the _partial filename
3466
+			$urls = [$url, preg_replace('/[^\/]+$/', '_\0', $url)];
3467
+		}
3468
+
3469
+		$hasExtension = preg_match('/[.]s?css$/', $url);
3470
+
3471
+		foreach ($this->importPaths as $dir) {
3472
+			if (is_string($dir)) {
3473
+				// check urls for normal import paths
3474
+				foreach ($urls as $full) {
3475
+					$full = $dir
3476
+						. (! empty($dir) && substr($dir, -1) !== '/' ? '/' : '')
3477
+						. $full;
3478
+
3479
+					if ($this->fileExists($file = $full . '.scss') ||
3480
+						($hasExtension && $this->fileExists($file = $full))
3481
+					) {
3482
+						return $file;
3483
+					}
3484
+				}
3485
+			} elseif (is_callable($dir)) {
3486
+				// check custom callback for import path
3487
+				$file = call_user_func($dir, $url);
3488
+
3489
+				if ($file !== null) {
3490
+					return $file;
3491
+				}
3492
+			}
3493
+		}
3494
+
3495
+		return null;
3496
+	}
3497
+
3498
+	/**
3499
+	 * Set encoding
3500
+	 *
3501
+	 * @api
3502
+	 *
3503
+	 * @param string $encoding
3504
+	 */
3505
+	public function setEncoding($encoding)
3506
+	{
3507
+		$this->encoding = $encoding;
3508
+	}
3509
+
3510
+	/**
3511
+	 * Ignore errors?
3512
+	 *
3513
+	 * @api
3514
+	 *
3515
+	 * @param boolean $ignoreErrors
3516
+	 *
3517
+	 * @return \Leafo\ScssPhp\Compiler
3518
+	 */
3519
+	public function setIgnoreErrors($ignoreErrors)
3520
+	{
3521
+		$this->ignoreErrors = $ignoreErrors;
3522
+	}
3523
+
3524
+	/**
3525
+	 * Throw error (exception)
3526
+	 *
3527
+	 * @api
3528
+	 *
3529
+	 * @param string $msg Message with optional sprintf()-style vararg parameters
3530
+	 *
3531
+	 * @throws \Leafo\ScssPhp\Exception\CompilerException
3532
+	 */
3533
+	public function throwError($msg)
3534
+	{
3535
+		if ($this->ignoreErrors) {
3536
+			return;
3537
+		}
3538
+
3539
+		if (func_num_args() > 1) {
3540
+			$msg = call_user_func_array('sprintf', func_get_args());
3541
+		}
3542
+
3543
+		$line = $this->sourceLine;
3544
+		$msg = "$msg: line: $line";
3545
+
3546
+		throw new CompilerException($msg);
3547
+	}
3548
+
3549
+	/**
3550
+	 * Handle import loop
3551
+	 *
3552
+	 * @param string $name
3553
+	 *
3554
+	 * @throws \Exception
3555
+	 */
3556
+	protected function handleImportLoop($name)
3557
+	{
3558
+		for ($env = $this->env; $env; $env = $env->parent) {
3559
+			$file = $this->sourceNames[$env->block->sourceIndex];
3560
+
3561
+			if (realpath($file) === $name) {
3562
+				$this->throwError('An @import loop has been found: %s imports %s', $file, basename($file));
3563
+				break;
3564
+			}
3565
+		}
3566
+	}
3567
+
3568
+	/**
3569
+	 * Does file exist?
3570
+	 *
3571
+	 * @param string $name
3572
+	 *
3573
+	 * @return boolean
3574
+	 */
3575
+	protected function fileExists($name)
3576
+	{
3577
+		return file_exists($name) && is_file($name);
3578
+	}
3579
+
3580
+	/**
3581
+	 * Call SCSS @function
3582
+	 *
3583
+	 * @param string $name
3584
+	 * @param array  $argValues
3585
+	 * @param array  $returnValue
3586
+	 *
3587
+	 * @return boolean Returns true if returnValue is set; otherwise, false
3588
+	 */
3589
+	protected function callScssFunction($name, $argValues, &$returnValue)
3590
+	{
3591
+		$func = $this->get(static::$namespaces['function'] . $name, false);
3592
+
3593
+		if (! $func) {
3594
+			return false;
3595
+		}
3596
+
3597
+		$this->pushEnv();
3598
+
3599
+		$storeEnv = $this->storeEnv;
3600
+		$this->storeEnv = $this->env;
3601
+
3602
+		// set the args
3603
+		if (isset($func->args)) {
3604
+			$this->applyArguments($func->args, $argValues);
3605
+		}
3606
+
3607
+		// throw away lines and children
3608
+		$tmp = new OutputBlock;
3609
+		$tmp->lines    = [];
3610
+		$tmp->children = [];
3611
+
3612
+		$this->env->marker = 'function';
3613
+
3614
+		$ret = $this->compileChildren($func->children, $tmp);
3615
+
3616
+		$this->storeEnv = $storeEnv;
3617
+
3618
+		$this->popEnv();
3619
+
3620
+		$returnValue = ! isset($ret) ? static::$defaultValue : $ret;
3621
+
3622
+		return true;
3623
+	}
3624
+
3625
+	/**
3626
+	 * Call built-in and registered (PHP) functions
3627
+	 *
3628
+	 * @param string $name
3629
+	 * @param array  $args
3630
+	 * @param array  $returnValue
3631
+	 *
3632
+	 * @return boolean Returns true if returnValue is set; otherwise, false
3633
+	 */
3634
+	protected function callNativeFunction($name, $args, &$returnValue)
3635
+	{
3636
+		// try a lib function
3637
+		$name = $this->normalizeName($name);
3638
+
3639
+		if (isset($this->userFunctions[$name])) {
3640
+			// see if we can find a user function
3641
+			list($f, $prototype) = $this->userFunctions[$name];
3642
+		} elseif (($f = $this->getBuiltinFunction($name)) && is_callable($f)) {
3643
+			$libName   = $f[1];
3644
+			$prototype = isset(static::$$libName) ? static::$$libName : null;
3645
+		} else {
3646
+			return false;
3647
+		}
3648
+
3649
+		list($sorted, $kwargs) = $this->sortArgs($prototype, $args);
3650
+
3651
+		if ($name !== 'if' && $name !== 'call') {
3652
+			foreach ($sorted as &$val) {
3653
+				$val = $this->reduce($val, true);
3654
+			}
3655
+		}
3656
+
3657
+		$returnValue = call_user_func($f, $sorted, $kwargs);
3658
+
3659
+		if (! isset($returnValue)) {
3660
+			return false;
3661
+		}
3662
+
3663
+		$returnValue = $this->coerceValue($returnValue);
3664
+
3665
+		return true;
3666
+	}
3667
+
3668
+	/**
3669
+	 * Get built-in function
3670
+	 *
3671
+	 * @param string $name Normalized name
3672
+	 *
3673
+	 * @return array
3674
+	 */
3675
+	protected function getBuiltinFunction($name)
3676
+	{
3677
+		$libName = 'lib' . preg_replace_callback(
3678
+			'/_(.)/',
3679
+			function ($m) {
3680
+				return ucfirst($m[1]);
3681
+			},
3682
+			ucfirst($name)
3683
+		);
3684
+
3685
+		return [$this, $libName];
3686
+	}
3687
+
3688
+	/**
3689
+	 * Sorts keyword arguments
3690
+	 *
3691
+	 * @param array $prototype
3692
+	 * @param array $args
3693
+	 *
3694
+	 * @return array
3695
+	 */
3696
+	protected function sortArgs($prototype, $args)
3697
+	{
3698
+		$keyArgs = [];
3699
+		$posArgs = [];
3700
+
3701
+		// separate positional and keyword arguments
3702
+		foreach ($args as $arg) {
3703
+			list($key, $value) = $arg;
3704
+
3705
+			$key = $key[1];
3706
+
3707
+			if (empty($key)) {
3708
+				$posArgs[] = $value;
3709
+			} else {
3710
+				$keyArgs[$key] = $value;
3711
+			}
3712
+		}
3713
+
3714
+		if (! isset($prototype)) {
3715
+			return [$posArgs, $keyArgs];
3716
+		}
3717
+
3718
+		// copy positional args
3719
+		$finalArgs = array_pad($posArgs, count($prototype), null);
3720
+
3721
+		// overwrite positional args with keyword args
3722
+		foreach ($prototype as $i => $names) {
3723
+			foreach ((array) $names as $name) {
3724
+				if (isset($keyArgs[$name])) {
3725
+					$finalArgs[$i] = $keyArgs[$name];
3726
+				}
3727
+			}
3728
+		}
3729
+
3730
+		return [$finalArgs, $keyArgs];
3731
+	}
3732
+
3733
+	/**
3734
+	 * Apply argument values per definition
3735
+	 *
3736
+	 * @param array $argDef
3737
+	 * @param array $argValues
3738
+	 *
3739
+	 * @throws \Exception
3740
+	 */
3741
+	protected function applyArguments($argDef, $argValues)
3742
+	{
3743
+		$storeEnv = $this->getStoreEnv();
3744
+
3745
+		$env = new Environment;
3746
+		$env->store = $storeEnv->store;
3747
+
3748
+		$hasVariable = false;
3749
+		$args = [];
3750
+
3751
+		foreach ($argDef as $i => $arg) {
3752
+			list($name, $default, $isVariable) = $argDef[$i];
3753
+
3754
+			$args[$name] = [$i, $name, $default, $isVariable];
3755
+			$hasVariable |= $isVariable;
3756
+		}
3757
+
3758
+		$keywordArgs = [];
3759
+		$deferredKeywordArgs = [];
3760
+		$remaining = [];
3761
+
3762
+		// assign the keyword args
3763
+		foreach ((array) $argValues as $arg) {
3764
+			if (! empty($arg[0])) {
3765
+				if (! isset($args[$arg[0][1]])) {
3766
+					if ($hasVariable) {
3767
+						$deferredKeywordArgs[$arg[0][1]] = $arg[1];
3768
+					} else {
3769
+						$this->throwError("Mixin or function doesn't have an argument named $%s.", $arg[0][1]);
3770
+						break;
3771
+					}
3772
+				} elseif ($args[$arg[0][1]][0] < count($remaining)) {
3773
+					$this->throwError("The argument $%s was passed both by position and by name.", $arg[0][1]);
3774
+					break;
3775
+				} else {
3776
+					$keywordArgs[$arg[0][1]] = $arg[1];
3777
+				}
3778
+			} elseif (count($keywordArgs)) {
3779
+				$this->throwError('Positional arguments must come before keyword arguments.');
3780
+				break;
3781
+			} elseif ($arg[2] === true) {
3782
+				$val = $this->reduce($arg[1], true);
3783
+
3784
+				if ($val[0] === Type::T_LIST) {
3785
+					foreach ($val[2] as $name => $item) {
3786
+						if (! is_numeric($name)) {
3787
+							$keywordArgs[$name] = $item;
3788
+						} else {
3789
+							$remaining[] = $item;
3790
+						}
3791
+					}
3792
+				} elseif ($val[0] === Type::T_MAP) {
3793
+					foreach ($val[1] as $i => $name) {
3794
+						$name = $this->compileStringContent($this->coerceString($name));
3795
+						$item = $val[2][$i];
3796
+
3797
+						if (! is_numeric($name)) {
3798
+							$keywordArgs[$name] = $item;
3799
+						} else {
3800
+							$remaining[] = $item;
3801
+						}
3802
+					}
3803
+				} else {
3804
+					$remaining[] = $val;
3805
+				}
3806
+			} else {
3807
+				$remaining[] = $arg[1];
3808
+			}
3809
+		}
3810
+
3811
+		foreach ($args as $arg) {
3812
+			list($i, $name, $default, $isVariable) = $arg;
3813
+
3814
+			if ($isVariable) {
3815
+				$val = [Type::T_LIST, ',', [], $isVariable];
3816
+
3817
+				for ($count = count($remaining); $i < $count; $i++) {
3818
+					$val[2][] = $remaining[$i];
3819
+				}
3820
+
3821
+				foreach ($deferredKeywordArgs as $itemName => $item) {
3822
+					$val[2][$itemName] = $item;
3823
+				}
3824
+			} elseif (isset($remaining[$i])) {
3825
+				$val = $remaining[$i];
3826
+			} elseif (isset($keywordArgs[$name])) {
3827
+				$val = $keywordArgs[$name];
3828
+			} elseif (! empty($default)) {
3829
+				continue;
3830
+			} else {
3831
+				$this->throwError("Missing argument $name");
3832
+				break;
3833
+			}
3834
+
3835
+			$this->set($name, $this->reduce($val, true), true, $env);
3836
+		}
3837
+
3838
+		$storeEnv->store = $env->store;
3839
+
3840
+		foreach ($args as $arg) {
3841
+			list($i, $name, $default, $isVariable) = $arg;
3842
+
3843
+			if ($isVariable || isset($remaining[$i]) || isset($keywordArgs[$name]) || empty($default)) {
3844
+				continue;
3845
+			}
3846
+
3847
+			$this->set($name, $this->reduce($default, true), true);
3848
+		}
3849
+	}
3850
+
3851
+	/**
3852
+	 * Coerce a php value into a scss one
3853
+	 *
3854
+	 * @param mixed $value
3855
+	 *
3856
+	 * @return array|\Leafo\ScssPhp\Node\Number
3857
+	 */
3858
+	private function coerceValue($value)
3859
+	{
3860
+		if (is_array($value) || $value instanceof \ArrayAccess) {
3861
+			return $value;
3862
+		}
3863
+
3864
+		if (is_bool($value)) {
3865
+			return $this->toBool($value);
3866
+		}
3867
+
3868
+		if ($value === null) {
3869
+			return static::$null;
3870
+		}
3871
+
3872
+		if (is_numeric($value)) {
3873
+			return new Node\Number($value, '');
3874
+		}
3875
+
3876
+		if ($value === '') {
3877
+			return static::$emptyString;
3878
+		}
3879
+
3880
+		if (preg_match('/^(#([0-9a-f]{6})|#([0-9a-f]{3}))$/i', $value, $m)) {
3881
+			$color = [Type::T_COLOR];
3882
+
3883
+			if (isset($m[3])) {
3884
+				$num = hexdec($m[3]);
3885
+
3886
+				foreach ([3, 2, 1] as $i) {
3887
+					$t = $num & 0xf;
3888
+					$color[$i] = $t << 4 | $t;
3889
+					$num >>= 4;
3890
+				}
3891
+			} else {
3892
+				$num = hexdec($m[2]);
3893
+
3894
+				foreach ([3, 2, 1] as $i) {
3895
+					$color[$i] = $num & 0xff;
3896
+					$num >>= 8;
3897
+				}
3898
+			}
3899
+
3900
+			return $color;
3901
+		}
3902
+
3903
+		return [Type::T_KEYWORD, $value];
3904
+	}
3905
+
3906
+	/**
3907
+	 * Coerce something to map
3908
+	 *
3909
+	 * @param array $item
3910
+	 *
3911
+	 * @return array
3912
+	 */
3913
+	protected function coerceMap($item)
3914
+	{
3915
+		if ($item[0] === Type::T_MAP) {
3916
+			return $item;
3917
+		}
3918
+
3919
+		if ($item === static::$emptyList) {
3920
+			return static::$emptyMap;
3921
+		}
3922
+
3923
+		return [Type::T_MAP, [$item], [static::$null]];
3924
+	}
3925
+
3926
+	/**
3927
+	 * Coerce something to list
3928
+	 *
3929
+	 * @param array  $item
3930
+	 * @param string $delim
3931
+	 *
3932
+	 * @return array
3933
+	 */
3934
+	protected function coerceList($item, $delim = ',')
3935
+	{
3936
+		if (isset($item) && $item[0] === Type::T_LIST) {
3937
+			return $item;
3938
+		}
3939
+
3940
+		if (isset($item) && $item[0] === Type::T_MAP) {
3941
+			$keys = $item[1];
3942
+			$values = $item[2];
3943
+			$list = [];
3944
+
3945
+			for ($i = 0, $s = count($keys); $i < $s; $i++) {
3946
+				$key = $keys[$i];
3947
+				$value = $values[$i];
3948
+
3949
+				$list[] = [
3950
+					Type::T_LIST,
3951
+					'',
3952
+					[[Type::T_KEYWORD, $this->compileStringContent($this->coerceString($key))], $value]
3953
+				];
3954
+			}
3955
+
3956
+			return [Type::T_LIST, ',', $list];
3957
+		}
3958
+
3959
+		return [Type::T_LIST, $delim, ! isset($item) ? []: [$item]];
3960
+	}
3961
+
3962
+	/**
3963
+	 * Coerce color for expression
3964
+	 *
3965
+	 * @param array $value
3966
+	 *
3967
+	 * @return array|null
3968
+	 */
3969
+	protected function coerceForExpression($value)
3970
+	{
3971
+		if ($color = $this->coerceColor($value)) {
3972
+			return $color;
3973
+		}
3974
+
3975
+		return $value;
3976
+	}
3977
+
3978
+	/**
3979
+	 * Coerce value to color
3980
+	 *
3981
+	 * @param array $value
3982
+	 *
3983
+	 * @return array|null
3984
+	 */
3985
+	protected function coerceColor($value)
3986
+	{
3987
+		switch ($value[0]) {
3988
+			case Type::T_COLOR:
3989
+				return $value;
3990
+
3991
+			case Type::T_KEYWORD:
3992
+				$name = strtolower($value[1]);
3993
+
3994
+				if (isset(Colors::$cssColors[$name])) {
3995
+					$rgba = explode(',', Colors::$cssColors[$name]);
3996
+
3997
+					return isset($rgba[3])
3998
+						? [Type::T_COLOR, (int) $rgba[0], (int) $rgba[1], (int) $rgba[2], (int) $rgba[3]]
3999
+						: [Type::T_COLOR, (int) $rgba[0], (int) $rgba[1], (int) $rgba[2]];
4000
+				}
4001
+
4002
+				return null;
4003
+		}
4004
+
4005
+		return null;
4006
+	}
4007
+
4008
+	/**
4009
+	 * Coerce value to string
4010
+	 *
4011
+	 * @param array $value
4012
+	 *
4013
+	 * @return array|null
4014
+	 */
4015
+	protected function coerceString($value)
4016
+	{
4017
+		if ($value[0] === Type::T_STRING) {
4018
+			return $value;
4019
+		}
4020
+
4021
+		return [Type::T_STRING, '', [$this->compileValue($value)]];
4022
+	}
4023
+
4024
+	/**
4025
+	 * Coerce value to a percentage
4026
+	 *
4027
+	 * @param array $value
4028
+	 *
4029
+	 * @return integer|float
4030
+	 */
4031
+	protected function coercePercent($value)
4032
+	{
4033
+		if ($value[0] === Type::T_NUMBER) {
4034
+			if (! empty($value[2]['%'])) {
4035
+				return $value[1] / 100;
4036
+			}
4037
+
4038
+			return $value[1];
4039
+		}
4040
+
4041
+		return 0;
4042
+	}
4043
+
4044
+	/**
4045
+	 * Assert value is a map
4046
+	 *
4047
+	 * @api
4048
+	 *
4049
+	 * @param array $value
4050
+	 *
4051
+	 * @return array
4052
+	 *
4053
+	 * @throws \Exception
4054
+	 */
4055
+	public function assertMap($value)
4056
+	{
4057
+		$value = $this->coerceMap($value);
4058
+
4059
+		if ($value[0] !== Type::T_MAP) {
4060
+			$this->throwError('expecting map');
4061
+		}
4062
+
4063
+		return $value;
4064
+	}
4065
+
4066
+	/**
4067
+	 * Assert value is a list
4068
+	 *
4069
+	 * @api
4070
+	 *
4071
+	 * @param array $value
4072
+	 *
4073
+	 * @return array
4074
+	 *
4075
+	 * @throws \Exception
4076
+	 */
4077
+	public function assertList($value)
4078
+	{
4079
+		if ($value[0] !== Type::T_LIST) {
4080
+			$this->throwError('expecting list');
4081
+		}
4082
+
4083
+		return $value;
4084
+	}
4085
+
4086
+	/**
4087
+	 * Assert value is a color
4088
+	 *
4089
+	 * @api
4090
+	 *
4091
+	 * @param array $value
4092
+	 *
4093
+	 * @return array
4094
+	 *
4095
+	 * @throws \Exception
4096
+	 */
4097
+	public function assertColor($value)
4098
+	{
4099
+		if ($color = $this->coerceColor($value)) {
4100
+			return $color;
4101
+		}
4102
+
4103
+		$this->throwError('expecting color');
4104
+	}
4105
+
4106
+	/**
4107
+	 * Assert value is a number
4108
+	 *
4109
+	 * @api
4110
+	 *
4111
+	 * @param array $value
4112
+	 *
4113
+	 * @return integer|float
4114
+	 *
4115
+	 * @throws \Exception
4116
+	 */
4117
+	public function assertNumber($value)
4118
+	{
4119
+		if ($value[0] !== Type::T_NUMBER) {
4120
+			$this->throwError('expecting number');
4121
+		}
4122
+
4123
+		return $value[1];
4124
+	}
4125
+
4126
+	/**
4127
+	 * Make sure a color's components don't go out of bounds
4128
+	 *
4129
+	 * @param array $c
4130
+	 *
4131
+	 * @return array
4132
+	 */
4133
+	protected function fixColor($c)
4134
+	{
4135
+		foreach ([1, 2, 3] as $i) {
4136
+			if ($c[$i] < 0) {
4137
+				$c[$i] = 0;
4138
+			}
4139
+
4140
+			if ($c[$i] > 255) {
4141
+				$c[$i] = 255;
4142
+			}
4143
+		}
4144
+
4145
+		return $c;
4146
+	}
4147
+
4148
+	/**
4149
+	 * Convert RGB to HSL
4150
+	 *
4151
+	 * @api
4152
+	 *
4153
+	 * @param integer $red
4154
+	 * @param integer $green
4155
+	 * @param integer $blue
4156
+	 *
4157
+	 * @return array
4158
+	 */
4159
+	public function toHSL($red, $green, $blue)
4160
+	{
4161
+		$min = min($red, $green, $blue);
4162
+		$max = max($red, $green, $blue);
4163
+
4164
+		$l = $min + $max;
4165
+		$d = $max - $min;
4166
+
4167
+		if ((int) $d === 0) {
4168
+			$h = $s = 0;
4169
+		} else {
4170
+			if ($l < 255) {
4171
+				$s = $d / $l;
4172
+			} else {
4173
+				$s = $d / (510 - $l);
4174
+			}
4175
+
4176
+			if ($red == $max) {
4177
+				$h = 60 * ($green - $blue) / $d;
4178
+			} elseif ($green == $max) {
4179
+				$h = 60 * ($blue - $red) / $d + 120;
4180
+			} elseif ($blue == $max) {
4181
+				$h = 60 * ($red - $green) / $d + 240;
4182
+			}
4183
+		}
4184
+
4185
+		return [Type::T_HSL, fmod($h, 360), $s * 100, $l / 5.1];
4186
+	}
4187
+
4188
+	/**
4189
+	 * Hue to RGB helper
4190
+	 *
4191
+	 * @param float $m1
4192
+	 * @param float $m2
4193
+	 * @param float $h
4194
+	 *
4195
+	 * @return float
4196
+	 */
4197
+	private function hueToRGB($m1, $m2, $h)
4198
+	{
4199
+		if ($h < 0) {
4200
+			$h += 1;
4201
+		} elseif ($h > 1) {
4202
+			$h -= 1;
4203
+		}
4204
+
4205
+		if ($h * 6 < 1) {
4206
+			return $m1 + ($m2 - $m1) * $h * 6;
4207
+		}
4208
+
4209
+		if ($h * 2 < 1) {
4210
+			return $m2;
4211
+		}
4212
+
4213
+		if ($h * 3 < 2) {
4214
+			return $m1 + ($m2 - $m1) * (2/3 - $h) * 6;
4215
+		}
4216
+
4217
+		return $m1;
4218
+	}
4219
+
4220
+	/**
4221
+	 * Convert HSL to RGB
4222
+	 *
4223
+	 * @api
4224
+	 *
4225
+	 * @param integer $hue        H from 0 to 360
4226
+	 * @param integer $saturation S from 0 to 100
4227
+	 * @param integer $lightness  L from 0 to 100
4228
+	 *
4229
+	 * @return array
4230
+	 */
4231
+	public function toRGB($hue, $saturation, $lightness)
4232
+	{
4233
+		if ($hue < 0) {
4234
+			$hue += 360;
4235
+		}
4236
+
4237
+		$h = $hue / 360;
4238
+		$s = min(100, max(0, $saturation)) / 100;
4239
+		$l = min(100, max(0, $lightness)) / 100;
4240
+
4241
+		$m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s;
4242
+		$m1 = $l * 2 - $m2;
4243
+
4244
+		$r = $this->hueToRGB($m1, $m2, $h + 1/3) * 255;
4245
+		$g = $this->hueToRGB($m1, $m2, $h) * 255;
4246
+		$b = $this->hueToRGB($m1, $m2, $h - 1/3) * 255;
4247
+
4248
+		$out = [Type::T_COLOR, $r, $g, $b];
4249
+
4250
+		return $out;
4251
+	}
4252
+
4253
+	// Built in functions
4254
+
4255
+	//protected static $libCall = ['name', 'args...'];
4256
+	protected function libCall($args, $kwargs)
4257
+	{
4258
+		$name = $this->compileStringContent($this->coerceString($this->reduce(array_shift($args), true)));
4259
+
4260
+		$args = array_map(
4261
+			function ($a) {
4262
+				return [null, $a, false];
4263
+			},
4264
+			$args
4265
+		);
4266
+
4267
+		if (count($kwargs)) {
4268
+			foreach ($kwargs as $key => $value) {
4269
+				$args[] = [[Type::T_VARIABLE, $key], $value, false];
4270
+			}
4271
+		}
4272
+
4273
+		return $this->reduce([Type::T_FUNCTION_CALL, $name, $args]);
4274
+	}
4275
+
4276
+	protected static $libIf = ['condition', 'if-true', 'if-false'];
4277
+	protected function libIf($args)
4278
+	{
4279
+		list($cond, $t, $f) = $args;
4280
+
4281
+		if (! $this->isTruthy($this->reduce($cond, true))) {
4282
+			return $this->reduce($f, true);
4283
+		}
4284
+
4285
+		return $this->reduce($t, true);
4286
+	}
4287
+
4288
+	protected static $libIndex = ['list', 'value'];
4289
+	protected function libIndex($args)
4290
+	{
4291
+		list($list, $value) = $args;
4292
+
4293
+		if ($value[0] === Type::T_MAP) {
4294
+			return static::$null;
4295
+		}
4296
+
4297
+		if ($list[0] === Type::T_MAP ||
4298
+			$list[0] === Type::T_STRING ||
4299
+			$list[0] === Type::T_KEYWORD ||
4300
+			$list[0] === Type::T_INTERPOLATE
4301
+		) {
4302
+			$list = $this->coerceList($list, ' ');
4303
+		}
4304
+
4305
+		if ($list[0] !== Type::T_LIST) {
4306
+			return static::$null;
4307
+		}
4308
+
4309
+		$values = [];
4310
+
4311
+		foreach ($list[2] as $item) {
4312
+			$values[] = $this->normalizeValue($item);
4313
+		}
4314
+
4315
+		$key = array_search($this->normalizeValue($value), $values);
4316
+
4317
+		return false === $key ? static::$null : $key + 1;
4318
+	}
4319
+
4320
+	protected static $libRgb = ['red', 'green', 'blue'];
4321
+	protected function libRgb($args)
4322
+	{
4323
+		list($r, $g, $b) = $args;
4324
+
4325
+		return [Type::T_COLOR, $r[1], $g[1], $b[1]];
4326
+	}
4327
+
4328
+	protected static $libRgba = [
4329
+		['red', 'color'],
4330
+		'green', 'blue', 'alpha'];
4331
+	protected function libRgba($args)
4332
+	{
4333
+		if ($color = $this->coerceColor($args[0])) {
4334
+			$num = ! isset($args[1]) ? $args[3] : $args[1];
4335
+			$alpha = $this->assertNumber($num);
4336
+			$color[4] = $alpha;
4337
+
4338
+			return $color;
4339
+		}
4340
+
4341
+		list($r, $g, $b, $a) = $args;
4342
+
4343
+		return [Type::T_COLOR, $r[1], $g[1], $b[1], $a[1]];
4344
+	}
4345
+
4346
+	// helper function for adjust_color, change_color, and scale_color
4347
+	protected function alterColor($args, $fn)
4348
+	{
4349
+		$color = $this->assertColor($args[0]);
4350
+
4351
+		foreach ([1, 2, 3, 7] as $i) {
4352
+			if (isset($args[$i])) {
4353
+				$val = $this->assertNumber($args[$i]);
4354
+				$ii = $i === 7 ? 4 : $i; // alpha
4355
+				$color[$ii] = call_user_func($fn, isset($color[$ii]) ? $color[$ii] : 0, $val, $i);
4356
+			}
4357
+		}
4358
+
4359
+		if (isset($args[4]) || isset($args[5]) || isset($args[6])) {
4360
+			$hsl = $this->toHSL($color[1], $color[2], $color[3]);
4361
+
4362
+			foreach ([4, 5, 6] as $i) {
4363
+				if (isset($args[$i])) {
4364
+					$val = $this->assertNumber($args[$i]);
4365
+					$hsl[$i - 3] = call_user_func($fn, $hsl[$i - 3], $val, $i);
4366
+				}
4367
+			}
4368
+
4369
+			$rgb = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
4370
+
4371
+			if (isset($color[4])) {
4372
+				$rgb[4] = $color[4];
4373
+			}
4374
+
4375
+			$color = $rgb;
4376
+		}
4377
+
4378
+		return $color;
4379
+	}
4380
+
4381
+	protected static $libAdjustColor = [
4382
+		'color', 'red', 'green', 'blue',
4383
+		'hue', 'saturation', 'lightness', 'alpha'
4384
+	];
4385
+	protected function libAdjustColor($args)
4386
+	{
4387
+		return $this->alterColor($args, function ($base, $alter, $i) {
4388
+			return $base + $alter;
4389
+		});
4390
+	}
4391
+
4392
+	protected static $libChangeColor = [
4393
+		'color', 'red', 'green', 'blue',
4394
+		'hue', 'saturation', 'lightness', 'alpha'
4395
+	];
4396
+	protected function libChangeColor($args)
4397
+	{
4398
+		return $this->alterColor($args, function ($base, $alter, $i) {
4399
+			return $alter;
4400
+		});
4401
+	}
4402
+
4403
+	protected static $libScaleColor = [
4404
+		'color', 'red', 'green', 'blue',
4405
+		'hue', 'saturation', 'lightness', 'alpha'
4406
+	];
4407
+	protected function libScaleColor($args)
4408
+	{
4409
+		return $this->alterColor($args, function ($base, $scale, $i) {
4410
+			// 1, 2, 3 - rgb
4411
+			// 4, 5, 6 - hsl
4412
+			// 7 - a
4413
+			switch ($i) {
4414
+				case 1:
4415
+				case 2:
4416
+				case 3:
4417
+					$max = 255;
4418
+					break;
4419
+
4420
+				case 4:
4421
+					$max = 360;
4422
+					break;
4423
+
4424
+				case 7:
4425
+					$max = 1;
4426
+					break;
4427
+
4428
+				default:
4429
+					$max = 100;
4430
+			}
4431
+
4432
+			$scale = $scale / 100;
4433
+
4434
+			if ($scale < 0) {
4435
+				return $base * $scale + $base;
4436
+			}
4437
+
4438
+			return ($max - $base) * $scale + $base;
4439
+		});
4440
+	}
4441
+
4442
+	protected static $libIeHexStr = ['color'];
4443
+	protected function libIeHexStr($args)
4444
+	{
4445
+		$color = $this->coerceColor($args[0]);
4446
+		$color[4] = isset($color[4]) ? round(255 * $color[4]) : 255;
4447
+
4448
+		return sprintf('#%02X%02X%02X%02X', $color[4], $color[1], $color[2], $color[3]);
4449
+	}
4450
+
4451
+	protected static $libRed = ['color'];
4452
+	protected function libRed($args)
4453
+	{
4454
+		$color = $this->coerceColor($args[0]);
4455
+
4456
+		return $color[1];
4457
+	}
4458
+
4459
+	protected static $libGreen = ['color'];
4460
+	protected function libGreen($args)
4461
+	{
4462
+		$color = $this->coerceColor($args[0]);
4463
+
4464
+		return $color[2];
4465
+	}
4466
+
4467
+	protected static $libBlue = ['color'];
4468
+	protected function libBlue($args)
4469
+	{
4470
+		$color = $this->coerceColor($args[0]);
4471
+
4472
+		return $color[3];
4473
+	}
4474
+
4475
+	protected static $libAlpha = ['color'];
4476
+	protected function libAlpha($args)
4477
+	{
4478
+		if ($color = $this->coerceColor($args[0])) {
4479
+			return isset($color[4]) ? $color[4] : 1;
4480
+		}
4481
+
4482
+		// this might be the IE function, so return value unchanged
4483
+		return null;
4484
+	}
4485
+
4486
+	protected static $libOpacity = ['color'];
4487
+	protected function libOpacity($args)
4488
+	{
4489
+		$value = $args[0];
4490
+
4491
+		if ($value[0] === Type::T_NUMBER) {
4492
+			return null;
4493
+		}
4494
+
4495
+		return $this->libAlpha($args);
4496
+	}
4497
+
4498
+	// mix two colors
4499
+	protected static $libMix = ['color-1', 'color-2', 'weight'];
4500
+	protected function libMix($args)
4501
+	{
4502
+		list($first, $second, $weight) = $args;
4503
+
4504
+		$first = $this->assertColor($first);
4505
+		$second = $this->assertColor($second);
4506
+
4507
+		if (! isset($weight)) {
4508
+			$weight = 0.5;
4509
+		} else {
4510
+			$weight = $this->coercePercent($weight);
4511
+		}
4512
+
4513
+		$firstAlpha = isset($first[4]) ? $first[4] : 1;
4514
+		$secondAlpha = isset($second[4]) ? $second[4] : 1;
4515
+
4516
+		$w = $weight * 2 - 1;
4517
+		$a = $firstAlpha - $secondAlpha;
4518
+
4519
+		$w1 = (($w * $a === -1 ? $w : ($w + $a) / (1 + $w * $a)) + 1) / 2.0;
4520
+		$w2 = 1.0 - $w1;
4521
+
4522
+		$new = [Type::T_COLOR,
4523
+			$w1 * $first[1] + $w2 * $second[1],
4524
+			$w1 * $first[2] + $w2 * $second[2],
4525
+			$w1 * $first[3] + $w2 * $second[3],
4526
+		];
4527
+
4528
+		if ($firstAlpha != 1.0 || $secondAlpha != 1.0) {
4529
+			$new[] = $firstAlpha * $weight + $secondAlpha * ($weight - 1);
4530
+		}
4531
+
4532
+		return $this->fixColor($new);
4533
+	}
4534
+
4535
+	protected static $libHsl = ['hue', 'saturation', 'lightness'];
4536
+	protected function libHsl($args)
4537
+	{
4538
+		list($h, $s, $l) = $args;
4539
+
4540
+		return $this->toRGB($h[1], $s[1], $l[1]);
4541
+	}
4542
+
4543
+	protected static $libHsla = ['hue', 'saturation', 'lightness', 'alpha'];
4544
+	protected function libHsla($args)
4545
+	{
4546
+		list($h, $s, $l, $a) = $args;
4547
+
4548
+		$color = $this->toRGB($h[1], $s[1], $l[1]);
4549
+		$color[4] = $a[1];
4550
+
4551
+		return $color;
4552
+	}
4553
+
4554
+	protected static $libHue = ['color'];
4555
+	protected function libHue($args)
4556
+	{
4557
+		$color = $this->assertColor($args[0]);
4558
+		$hsl = $this->toHSL($color[1], $color[2], $color[3]);
4559
+
4560
+		return new Node\Number($hsl[1], 'deg');
4561
+	}
4562
+
4563
+	protected static $libSaturation = ['color'];
4564
+	protected function libSaturation($args)
4565
+	{
4566
+		$color = $this->assertColor($args[0]);
4567
+		$hsl = $this->toHSL($color[1], $color[2], $color[3]);
4568
+
4569
+		return new Node\Number($hsl[2], '%');
4570
+	}
4571
+
4572
+	protected static $libLightness = ['color'];
4573
+	protected function libLightness($args)
4574
+	{
4575
+		$color = $this->assertColor($args[0]);
4576
+		$hsl = $this->toHSL($color[1], $color[2], $color[3]);
4577
+
4578
+		return new Node\Number($hsl[3], '%');
4579
+	}
4580
+
4581
+	protected function adjustHsl($color, $idx, $amount)
4582
+	{
4583
+		$hsl = $this->toHSL($color[1], $color[2], $color[3]);
4584
+		$hsl[$idx] += $amount;
4585
+		$out = $this->toRGB($hsl[1], $hsl[2], $hsl[3]);
4586
+
4587
+		if (isset($color[4])) {
4588
+			$out[4] = $color[4];
4589
+		}
4590
+
4591
+		return $out;
4592
+	}
4593
+
4594
+	protected static $libAdjustHue = ['color', 'degrees'];
4595
+	protected function libAdjustHue($args)
4596
+	{
4597
+		$color = $this->assertColor($args[0]);
4598
+		$degrees = $this->assertNumber($args[1]);
4599
+
4600
+		return $this->adjustHsl($color, 1, $degrees);
4601
+	}
4602
+
4603
+	protected static $libLighten = ['color', 'amount'];
4604
+	protected function libLighten($args)
4605
+	{
4606
+		$color = $this->assertColor($args[0]);
4607
+		$amount = Util::checkRange('amount', new Range(0, 100), $args[1], '%');
4608
+
4609
+		return $this->adjustHsl($color, 3, $amount);
4610
+	}
4611
+
4612
+	protected static $libDarken = ['color', 'amount'];
4613
+	protected function libDarken($args)
4614
+	{
4615
+		$color = $this->assertColor($args[0]);
4616
+		$amount = Util::checkRange('amount', new Range(0, 100), $args[1], '%');
4617
+
4618
+		return $this->adjustHsl($color, 3, -$amount);
4619
+	}
4620
+
4621
+	protected static $libSaturate = ['color', 'amount'];
4622
+	protected function libSaturate($args)
4623
+	{
4624
+		$value = $args[0];
4625
+
4626
+		if ($value[0] === Type::T_NUMBER) {
4627
+			return null;
4628
+		}
4629
+
4630
+		$color = $this->assertColor($value);
4631
+		$amount = 100 * $this->coercePercent($args[1]);
4632
+
4633
+		return $this->adjustHsl($color, 2, $amount);
4634
+	}
4635
+
4636
+	protected static $libDesaturate = ['color', 'amount'];
4637
+	protected function libDesaturate($args)
4638
+	{
4639
+		$color = $this->assertColor($args[0]);
4640
+		$amount = 100 * $this->coercePercent($args[1]);
4641
+
4642
+		return $this->adjustHsl($color, 2, -$amount);
4643
+	}
4644
+
4645
+	protected static $libGrayscale = ['color'];
4646
+	protected function libGrayscale($args)
4647
+	{
4648
+		$value = $args[0];
4649
+
4650
+		if ($value[0] === Type::T_NUMBER) {
4651
+			return null;
4652
+		}
4653
+
4654
+		return $this->adjustHsl($this->assertColor($value), 2, -100);
4655
+	}
4656
+
4657
+	protected static $libComplement = ['color'];
4658
+	protected function libComplement($args)
4659
+	{
4660
+		return $this->adjustHsl($this->assertColor($args[0]), 1, 180);
4661
+	}
4662
+
4663
+	protected static $libInvert = ['color'];
4664
+	protected function libInvert($args)
4665
+	{
4666
+		$value = $args[0];
4667
+
4668
+		if ($value[0] === Type::T_NUMBER) {
4669
+			return null;
4670
+		}
4671
+
4672
+		$color = $this->assertColor($value);
4673
+		$color[1] = 255 - $color[1];
4674
+		$color[2] = 255 - $color[2];
4675
+		$color[3] = 255 - $color[3];
4676
+
4677
+		return $color;
4678
+	}
4679
+
4680
+	// increases opacity by amount
4681
+	protected static $libOpacify = ['color', 'amount'];
4682
+	protected function libOpacify($args)
4683
+	{
4684
+		$color = $this->assertColor($args[0]);
4685
+		$amount = $this->coercePercent($args[1]);
4686
+
4687
+		$color[4] = (isset($color[4]) ? $color[4] : 1) + $amount;
4688
+		$color[4] = min(1, max(0, $color[4]));
4689
+
4690
+		return $color;
4691
+	}
4692
+
4693
+	protected static $libFadeIn = ['color', 'amount'];
4694
+	protected function libFadeIn($args)
4695
+	{
4696
+		return $this->libOpacify($args);
4697
+	}
4698
+
4699
+	// decreases opacity by amount
4700
+	protected static $libTransparentize = ['color', 'amount'];
4701
+	protected function libTransparentize($args)
4702
+	{
4703
+		$color = $this->assertColor($args[0]);
4704
+		$amount = $this->coercePercent($args[1]);
4705
+
4706
+		$color[4] = (isset($color[4]) ? $color[4] : 1) - $amount;
4707
+		$color[4] = min(1, max(0, $color[4]));
4708
+
4709
+		return $color;
4710
+	}
4711
+
4712
+	protected static $libFadeOut = ['color', 'amount'];
4713
+	protected function libFadeOut($args)
4714
+	{
4715
+		return $this->libTransparentize($args);
4716
+	}
4717
+
4718
+	protected static $libUnquote = ['string'];
4719
+	protected function libUnquote($args)
4720
+	{
4721
+		$str = $args[0];
4722
+
4723
+		if ($str[0] === Type::T_STRING) {
4724
+			$str[1] = '';
4725
+		}
4726
+
4727
+		return $str;
4728
+	}
4729
+
4730
+	protected static $libQuote = ['string'];
4731
+	protected function libQuote($args)
4732
+	{
4733
+		$value = $args[0];
4734
+
4735
+		if ($value[0] === Type::T_STRING && ! empty($value[1])) {
4736
+			return $value;
4737
+		}
4738
+
4739
+		return [Type::T_STRING, '"', [$value]];
4740
+	}
4741
+
4742
+	protected static $libPercentage = ['value'];
4743
+	protected function libPercentage($args)
4744
+	{
4745
+		return new Node\Number($this->coercePercent($args[0]) * 100, '%');
4746
+	}
4747
+
4748
+	protected static $libRound = ['value'];
4749
+	protected function libRound($args)
4750
+	{
4751
+		$num = $args[0];
4752
+
4753
+		return new Node\Number(round($num[1]), $num[2]);
4754
+	}
4755
+
4756
+	protected static $libFloor = ['value'];
4757
+	protected function libFloor($args)
4758
+	{
4759
+		$num = $args[0];
4760
+
4761
+		return new Node\Number(floor($num[1]), $num[2]);
4762
+	}
4763
+
4764
+	protected static $libCeil = ['value'];
4765
+	protected function libCeil($args)
4766
+	{
4767
+		$num = $args[0];
4768
+
4769
+		return new Node\Number(ceil($num[1]), $num[2]);
4770
+	}
4771
+
4772
+	protected static $libAbs = ['value'];
4773
+	protected function libAbs($args)
4774
+	{
4775
+		$num = $args[0];
4776
+
4777
+		return new Node\Number(abs($num[1]), $num[2]);
4778
+	}
4779
+
4780
+	protected function libMin($args)
4781
+	{
4782
+		$numbers = $this->getNormalizedNumbers($args);
4783
+		$min = null;
4784
+
4785
+		foreach ($numbers as $key => $number) {
4786
+			if (null === $min || $number[1] <= $min[1]) {
4787
+				$min = [$key, $number[1]];
4788
+			}
4789
+		}
4790
+
4791
+		return $args[$min[0]];
4792
+	}
4793
+
4794
+	protected function libMax($args)
4795
+	{
4796
+		$numbers = $this->getNormalizedNumbers($args);
4797
+		$max = null;
4798
+
4799
+		foreach ($numbers as $key => $number) {
4800
+			if (null === $max || $number[1] >= $max[1]) {
4801
+				$max = [$key, $number[1]];
4802
+			}
4803
+		}
4804
+
4805
+		return $args[$max[0]];
4806
+	}
4807
+
4808
+	/**
4809
+	 * Helper to normalize args containing numbers
4810
+	 *
4811
+	 * @param array $args
4812
+	 *
4813
+	 * @return array
4814
+	 */
4815
+	protected function getNormalizedNumbers($args)
4816
+	{
4817
+		$unit = null;
4818
+		$originalUnit = null;
4819
+		$numbers = [];
4820
+
4821
+		foreach ($args as $key => $item) {
4822
+			if ($item[0] !== Type::T_NUMBER) {
4823
+				$this->throwError('%s is not a number', $item[0]);
4824
+				break;
4825
+			}
4826
+
4827
+			$number = $item->normalize();
4828
+
4829
+			if (null === $unit) {
4830
+				$unit = $number[2];
4831
+				$originalUnit = $item->unitStr();
4832
+			} elseif ($unit !== $number[2]) {
4833
+				$this->throwError('Incompatible units: "%s" and "%s".', $originalUnit, $item->unitStr());
4834
+				break;
4835
+			}
4836
+
4837
+			$numbers[$key] = $number;
4838
+		}
4839
+
4840
+		return $numbers;
4841
+	}
4842
+
4843
+	protected static $libLength = ['list'];
4844
+	protected function libLength($args)
4845
+	{
4846
+		$list = $this->coerceList($args[0]);
4847
+
4848
+		return count($list[2]);
4849
+	}
4850
+
4851
+	//protected static $libListSeparator = ['list...'];
4852
+	protected function libListSeparator($args)
4853
+	{
4854
+		if (count($args) > 1) {
4855
+			return 'comma';
4856
+		}
4857
+
4858
+		$list = $this->coerceList($args[0]);
4859
+
4860
+		if (count($list[2]) <= 1) {
4861
+			return 'space';
4862
+		}
4863
+
4864
+		if ($list[1] === ',') {
4865
+			return 'comma';
4866
+		}
4867
+
4868
+		return 'space';
4869
+	}
4870
+
4871
+	protected static $libNth = ['list', 'n'];
4872
+	protected function libNth($args)
4873
+	{
4874
+		$list = $this->coerceList($args[0]);
4875
+		$n = $this->assertNumber($args[1]);
4876
+
4877
+		if ($n > 0) {
4878
+			$n--;
4879
+		} elseif ($n < 0) {
4880
+			$n += count($list[2]);
4881
+		}
4882
+
4883
+		return isset($list[2][$n]) ? $list[2][$n] : static::$defaultValue;
4884
+	}
4885
+
4886
+	protected static $libSetNth = ['list', 'n', 'value'];
4887
+	protected function libSetNth($args)
4888
+	{
4889
+		$list = $this->coerceList($args[0]);
4890
+		$n = $this->assertNumber($args[1]);
4891
+
4892
+		if ($n > 0) {
4893
+			$n--;
4894
+		} elseif ($n < 0) {
4895
+			$n += count($list[2]);
4896
+		}
4897
+
4898
+		if (! isset($list[2][$n])) {
4899
+			$this->throwError('Invalid argument for "n"');
4900
+
4901
+			return;
4902
+		}
4903
+
4904
+		$list[2][$n] = $args[2];
4905
+
4906
+		return $list;
4907
+	}
4908
+
4909
+	protected static $libMapGet = ['map', 'key'];
4910
+	protected function libMapGet($args)
4911
+	{
4912
+		$map = $this->assertMap($args[0]);
4913
+		$key = $this->compileStringContent($this->coerceString($args[1]));
4914
+
4915
+		for ($i = count($map[1]) - 1; $i >= 0; $i--) {
4916
+			if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) {
4917
+				return $map[2][$i];
4918
+			}
4919
+		}
4920
+
4921
+		return static::$null;
4922
+	}
4923
+
4924
+	protected static $libMapKeys = ['map'];
4925
+	protected function libMapKeys($args)
4926
+	{
4927
+		$map = $this->assertMap($args[0]);
4928
+		$keys = $map[1];
4929
+
4930
+		return [Type::T_LIST, ',', $keys];
4931
+	}
4932
+
4933
+	protected static $libMapValues = ['map'];
4934
+	protected function libMapValues($args)
4935
+	{
4936
+		$map = $this->assertMap($args[0]);
4937
+		$values = $map[2];
4938
+
4939
+		return [Type::T_LIST, ',', $values];
4940
+	}
4941
+
4942
+	protected static $libMapRemove = ['map', 'key'];
4943
+	protected function libMapRemove($args)
4944
+	{
4945
+		$map = $this->assertMap($args[0]);
4946
+		$key = $this->compileStringContent($this->coerceString($args[1]));
4947
+
4948
+		for ($i = count($map[1]) - 1; $i >= 0; $i--) {
4949
+			if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) {
4950
+				array_splice($map[1], $i, 1);
4951
+				array_splice($map[2], $i, 1);
4952
+			}
4953
+		}
4954
+
4955
+		return $map;
4956
+	}
4957
+
4958
+	protected static $libMapHasKey = ['map', 'key'];
4959
+	protected function libMapHasKey($args)
4960
+	{
4961
+		$map = $this->assertMap($args[0]);
4962
+		$key = $this->compileStringContent($this->coerceString($args[1]));
4963
+
4964
+		for ($i = count($map[1]) - 1; $i >= 0; $i--) {
4965
+			if ($key === $this->compileStringContent($this->coerceString($map[1][$i]))) {
4966
+				return true;
4967
+			}
4968
+		}
4969
+
4970
+		return false;
4971
+	}
4972
+
4973
+	protected static $libMapMerge = ['map-1', 'map-2'];
4974
+	protected function libMapMerge($args)
4975
+	{
4976
+		$map1 = $this->assertMap($args[0]);
4977
+		$map2 = $this->assertMap($args[1]);
4978
+
4979
+		return [Type::T_MAP, array_merge($map1[1], $map2[1]), array_merge($map1[2], $map2[2])];
4980
+	}
4981
+
4982
+	protected static $libKeywords = ['args'];
4983
+	protected function libKeywords($args)
4984
+	{
4985
+		$this->assertList($args[0]);
4986
+
4987
+		$keys = [];
4988
+		$values = [];
4989
+
4990
+		foreach ($args[0][2] as $name => $arg) {
4991
+			$keys[] = [Type::T_KEYWORD, $name];
4992
+			$values[] = $arg;
4993
+		}
4994
+
4995
+		return [Type::T_MAP, $keys, $values];
4996
+	}
4997
+
4998
+	protected function listSeparatorForJoin($list1, $sep)
4999
+	{
5000
+		if (! isset($sep)) {
5001
+			return $list1[1];
5002
+		}
5003
+
5004
+		switch ($this->compileValue($sep)) {
5005
+			case 'comma':
5006
+				return ',';
5007
+
5008
+			case 'space':
5009
+				return '';
5010
+
5011
+			default:
5012
+				return $list1[1];
5013
+		}
5014
+	}
5015
+
5016
+	protected static $libJoin = ['list1', 'list2', 'separator'];
5017
+	protected function libJoin($args)
5018
+	{
5019
+		list($list1, $list2, $sep) = $args;
5020
+
5021
+		$list1 = $this->coerceList($list1, ' ');
5022
+		$list2 = $this->coerceList($list2, ' ');
5023
+		$sep = $this->listSeparatorForJoin($list1, $sep);
5024
+
5025
+		return [Type::T_LIST, $sep, array_merge($list1[2], $list2[2])];
5026
+	}
5027
+
5028
+	protected static $libAppend = ['list', 'val', 'separator'];
5029
+	protected function libAppend($args)
5030
+	{
5031
+		list($list1, $value, $sep) = $args;
5032
+
5033
+		$list1 = $this->coerceList($list1, ' ');
5034
+		$sep = $this->listSeparatorForJoin($list1, $sep);
5035
+
5036
+		return [Type::T_LIST, $sep, array_merge($list1[2], [$value])];
5037
+	}
5038
+
5039
+	protected function libZip($args)
5040
+	{
5041
+		foreach ($args as $arg) {
5042
+			$this->assertList($arg);
5043
+		}
5044
+
5045
+		$lists = [];
5046
+		$firstList = array_shift($args);
5047
+
5048
+		foreach ($firstList[2] as $key => $item) {
5049
+			$list = [Type::T_LIST, '', [$item]];
5050
+
5051
+			foreach ($args as $arg) {
5052
+				if (isset($arg[2][$key])) {
5053
+					$list[2][] = $arg[2][$key];
5054
+				} else {
5055
+					break 2;
5056
+				}
5057
+			}
5058
+
5059
+			$lists[] = $list;
5060
+		}
5061
+
5062
+		return [Type::T_LIST, ',', $lists];
5063
+	}
5064
+
5065
+	protected static $libTypeOf = ['value'];
5066
+	protected function libTypeOf($args)
5067
+	{
5068
+		$value = $args[0];
5069 5069
 
5070
-        switch ($value[0]) {
5071
-            case Type::T_KEYWORD:
5072
-                if ($value === static::$true || $value === static::$false) {
5073
-                    return 'bool';
5074
-                }
5075
-
5076
-                if ($this->coerceColor($value)) {
5077
-                    return 'color';
5078
-                }
5079
-
5080
-                // fall-thru
5081
-            case Type::T_FUNCTION:
5082
-                return 'string';
5083
-
5084
-            case Type::T_LIST:
5085
-                if (isset($value[3]) && $value[3]) {
5086
-                    return 'arglist';
5087
-                }
5070
+		switch ($value[0]) {
5071
+			case Type::T_KEYWORD:
5072
+				if ($value === static::$true || $value === static::$false) {
5073
+					return 'bool';
5074
+				}
5075
+
5076
+				if ($this->coerceColor($value)) {
5077
+					return 'color';
5078
+				}
5079
+
5080
+				// fall-thru
5081
+			case Type::T_FUNCTION:
5082
+				return 'string';
5083
+
5084
+			case Type::T_LIST:
5085
+				if (isset($value[3]) && $value[3]) {
5086
+					return 'arglist';
5087
+				}
5088 5088
 
5089
-                // fall-thru
5090
-            default:
5091
-                return $value[0];
5092
-        }
5093
-    }
5089
+				// fall-thru
5090
+			default:
5091
+				return $value[0];
5092
+		}
5093
+	}
5094 5094
 
5095
-    protected static $libUnit = ['number'];
5096
-    protected function libUnit($args)
5097
-    {
5098
-        $num = $args[0];
5095
+	protected static $libUnit = ['number'];
5096
+	protected function libUnit($args)
5097
+	{
5098
+		$num = $args[0];
5099 5099
 
5100
-        if ($num[0] === Type::T_NUMBER) {
5101
-            return [Type::T_STRING, '"', [$num->unitStr()]];
5102
-        }
5100
+		if ($num[0] === Type::T_NUMBER) {
5101
+			return [Type::T_STRING, '"', [$num->unitStr()]];
5102
+		}
5103 5103
 
5104
-        return '';
5105
-    }
5104
+		return '';
5105
+	}
5106 5106
 
5107
-    protected static $libUnitless = ['number'];
5108
-    protected function libUnitless($args)
5109
-    {
5110
-        $value = $args[0];
5107
+	protected static $libUnitless = ['number'];
5108
+	protected function libUnitless($args)
5109
+	{
5110
+		$value = $args[0];
5111 5111
 
5112
-        return $value[0] === Type::T_NUMBER && $value->unitless();
5113
-    }
5112
+		return $value[0] === Type::T_NUMBER && $value->unitless();
5113
+	}
5114 5114
 
5115
-    protected static $libComparable = ['number-1', 'number-2'];
5116
-    protected function libComparable($args)
5117
-    {
5118
-        list($number1, $number2) = $args;
5115
+	protected static $libComparable = ['number-1', 'number-2'];
5116
+	protected function libComparable($args)
5117
+	{
5118
+		list($number1, $number2) = $args;
5119 5119
 
5120
-        if (! isset($number1[0]) || $number1[0] !== Type::T_NUMBER ||
5121
-            ! isset($number2[0]) || $number2[0] !== Type::T_NUMBER
5122
-        ) {
5123
-            $this->throwError('Invalid argument(s) for "comparable"');
5120
+		if (! isset($number1[0]) || $number1[0] !== Type::T_NUMBER ||
5121
+			! isset($number2[0]) || $number2[0] !== Type::T_NUMBER
5122
+		) {
5123
+			$this->throwError('Invalid argument(s) for "comparable"');
5124 5124
 
5125
-            return;
5126
-        }
5125
+			return;
5126
+		}
5127 5127
 
5128
-        $number1 = $number1->normalize();
5129
-        $number2 = $number2->normalize();
5128
+		$number1 = $number1->normalize();
5129
+		$number2 = $number2->normalize();
5130 5130
 
5131
-        return $number1[2] === $number2[2] || $number1->unitless() || $number2->unitless();
5132
-    }
5131
+		return $number1[2] === $number2[2] || $number1->unitless() || $number2->unitless();
5132
+	}
5133 5133
 
5134
-    protected static $libStrIndex = ['string', 'substring'];
5135
-    protected function libStrIndex($args)
5136
-    {
5137
-        $string = $this->coerceString($args[0]);
5138
-        $stringContent = $this->compileStringContent($string);
5139
-
5140
-        $substring = $this->coerceString($args[1]);
5141
-        $substringContent = $this->compileStringContent($substring);
5134
+	protected static $libStrIndex = ['string', 'substring'];
5135
+	protected function libStrIndex($args)
5136
+	{
5137
+		$string = $this->coerceString($args[0]);
5138
+		$stringContent = $this->compileStringContent($string);
5139
+
5140
+		$substring = $this->coerceString($args[1]);
5141
+		$substringContent = $this->compileStringContent($substring);
5142 5142
 
5143
-        $result = strpos($stringContent, $substringContent);
5143
+		$result = strpos($stringContent, $substringContent);
5144 5144
 
5145
-        return $result === false ? static::$null : new Node\Number($result + 1, '');
5146
-    }
5145
+		return $result === false ? static::$null : new Node\Number($result + 1, '');
5146
+	}
5147 5147
 
5148
-    protected static $libStrInsert = ['string', 'insert', 'index'];
5149
-    protected function libStrInsert($args)
5150
-    {
5151
-        $string = $this->coerceString($args[0]);
5152
-        $stringContent = $this->compileStringContent($string);
5148
+	protected static $libStrInsert = ['string', 'insert', 'index'];
5149
+	protected function libStrInsert($args)
5150
+	{
5151
+		$string = $this->coerceString($args[0]);
5152
+		$stringContent = $this->compileStringContent($string);
5153 5153
 
5154
-        $insert = $this->coerceString($args[1]);
5155
-        $insertContent = $this->compileStringContent($insert);
5154
+		$insert = $this->coerceString($args[1]);
5155
+		$insertContent = $this->compileStringContent($insert);
5156 5156
 
5157
-        list(, $index) = $args[2];
5157
+		list(, $index) = $args[2];
5158 5158
 
5159
-        $string[2] = [substr_replace($stringContent, $insertContent, $index - 1, 0)];
5159
+		$string[2] = [substr_replace($stringContent, $insertContent, $index - 1, 0)];
5160 5160
 
5161
-        return $string;
5162
-    }
5161
+		return $string;
5162
+	}
5163 5163
 
5164
-    protected static $libStrLength = ['string'];
5165
-    protected function libStrLength($args)
5166
-    {
5167
-        $string = $this->coerceString($args[0]);
5168
-        $stringContent = $this->compileStringContent($string);
5164
+	protected static $libStrLength = ['string'];
5165
+	protected function libStrLength($args)
5166
+	{
5167
+		$string = $this->coerceString($args[0]);
5168
+		$stringContent = $this->compileStringContent($string);
5169 5169
 
5170
-        return new Node\Number(strlen($stringContent), '');
5171
-    }
5170
+		return new Node\Number(strlen($stringContent), '');
5171
+	}
5172 5172
 
5173
-    protected static $libStrSlice = ['string', 'start-at', 'end-at'];
5174
-    protected function libStrSlice($args)
5175
-    {
5176
-        if (isset($args[2]) && $args[2][1] == 0) {
5177
-            return static::$nullString;
5178
-        }
5173
+	protected static $libStrSlice = ['string', 'start-at', 'end-at'];
5174
+	protected function libStrSlice($args)
5175
+	{
5176
+		if (isset($args[2]) && $args[2][1] == 0) {
5177
+			return static::$nullString;
5178
+		}
5179 5179
 
5180
-        $string = $this->coerceString($args[0]);
5181
-        $stringContent = $this->compileStringContent($string);
5180
+		$string = $this->coerceString($args[0]);
5181
+		$stringContent = $this->compileStringContent($string);
5182 5182
 
5183
-        $start = (int) $args[1][1];
5183
+		$start = (int) $args[1][1];
5184 5184
 
5185
-        if ($start > 0) {
5186
-            $start--;
5187
-        }
5185
+		if ($start > 0) {
5186
+			$start--;
5187
+		}
5188 5188
 
5189
-        $end    = (int) $args[2][1];
5190
-        $length = $end < 0 ? $end + 1 : ($end > 0 ? $end - $start : $end);
5191
-
5192
-        $string[2] = $length
5193
-            ? [substr($stringContent, $start, $length)]
5194
-            : [substr($stringContent, $start)];
5189
+		$end    = (int) $args[2][1];
5190
+		$length = $end < 0 ? $end + 1 : ($end > 0 ? $end - $start : $end);
5191
+
5192
+		$string[2] = $length
5193
+			? [substr($stringContent, $start, $length)]
5194
+			: [substr($stringContent, $start)];
5195 5195
 
5196
-        return $string;
5197
-    }
5196
+		return $string;
5197
+	}
5198 5198
 
5199
-    protected static $libToLowerCase = ['string'];
5200
-    protected function libToLowerCase($args)
5201
-    {
5202
-        $string = $this->coerceString($args[0]);
5203
-        $stringContent = $this->compileStringContent($string);
5199
+	protected static $libToLowerCase = ['string'];
5200
+	protected function libToLowerCase($args)
5201
+	{
5202
+		$string = $this->coerceString($args[0]);
5203
+		$stringContent = $this->compileStringContent($string);
5204 5204
 
5205
-        $string[2] = [function_exists('mb_strtolower') ? mb_strtolower($stringContent) : strtolower($stringContent)];
5205
+		$string[2] = [function_exists('mb_strtolower') ? mb_strtolower($stringContent) : strtolower($stringContent)];
5206 5206
 
5207
-        return $string;
5208
-    }
5207
+		return $string;
5208
+	}
5209 5209
 
5210
-    protected static $libToUpperCase = ['string'];
5211
-    protected function libToUpperCase($args)
5212
-    {
5213
-        $string = $this->coerceString($args[0]);
5214
-        $stringContent = $this->compileStringContent($string);
5210
+	protected static $libToUpperCase = ['string'];
5211
+	protected function libToUpperCase($args)
5212
+	{
5213
+		$string = $this->coerceString($args[0]);
5214
+		$stringContent = $this->compileStringContent($string);
5215 5215
 
5216
-        $string[2] = [function_exists('mb_strtoupper') ? mb_strtoupper($stringContent) : strtoupper($stringContent)];
5216
+		$string[2] = [function_exists('mb_strtoupper') ? mb_strtoupper($stringContent) : strtoupper($stringContent)];
5217 5217
 
5218
-        return $string;
5219
-    }
5218
+		return $string;
5219
+	}
5220 5220
 
5221
-    protected static $libFeatureExists = ['feature'];
5222
-    protected function libFeatureExists($args)
5223
-    {
5224
-        $string = $this->coerceString($args[0]);
5225
-        $name = $this->compileStringContent($string);
5221
+	protected static $libFeatureExists = ['feature'];
5222
+	protected function libFeatureExists($args)
5223
+	{
5224
+		$string = $this->coerceString($args[0]);
5225
+		$name = $this->compileStringContent($string);
5226 5226
 
5227
-        return $this->toBool(
5228
-            array_key_exists($name, $this->registeredFeatures) ? $this->registeredFeatures[$name] : false
5229
-        );
5230
-    }
5227
+		return $this->toBool(
5228
+			array_key_exists($name, $this->registeredFeatures) ? $this->registeredFeatures[$name] : false
5229
+		);
5230
+	}
5231 5231
 
5232
-    protected static $libFunctionExists = ['name'];
5233
-    protected function libFunctionExists($args)
5234
-    {
5235
-        $string = $this->coerceString($args[0]);
5236
-        $name = $this->compileStringContent($string);
5232
+	protected static $libFunctionExists = ['name'];
5233
+	protected function libFunctionExists($args)
5234
+	{
5235
+		$string = $this->coerceString($args[0]);
5236
+		$name = $this->compileStringContent($string);
5237 5237
 
5238
-        // user defined functions
5239
-        if ($this->has(static::$namespaces['function'] . $name)) {
5240
-            return true;
5241
-        }
5238
+		// user defined functions
5239
+		if ($this->has(static::$namespaces['function'] . $name)) {
5240
+			return true;
5241
+		}
5242 5242
 
5243
-        $name = $this->normalizeName($name);
5243
+		$name = $this->normalizeName($name);
5244 5244
 
5245
-        if (isset($this->userFunctions[$name])) {
5246
-            return true;
5247
-        }
5248
-
5249
-        // built-in functions
5250
-        $f = $this->getBuiltinFunction($name);
5245
+		if (isset($this->userFunctions[$name])) {
5246
+			return true;
5247
+		}
5248
+
5249
+		// built-in functions
5250
+		$f = $this->getBuiltinFunction($name);
5251 5251
 
5252
-        return $this->toBool(is_callable($f));
5253
-    }
5252
+		return $this->toBool(is_callable($f));
5253
+	}
5254 5254
 
5255
-    protected static $libGlobalVariableExists = ['name'];
5256
-    protected function libGlobalVariableExists($args)
5257
-    {
5258
-        $string = $this->coerceString($args[0]);
5259
-        $name = $this->compileStringContent($string);
5260
-
5261
-        return $this->has($name, $this->rootEnv);
5262
-    }
5263
-
5264
-    protected static $libMixinExists = ['name'];
5265
-    protected function libMixinExists($args)
5266
-    {
5267
-        $string = $this->coerceString($args[0]);
5268
-        $name = $this->compileStringContent($string);
5255
+	protected static $libGlobalVariableExists = ['name'];
5256
+	protected function libGlobalVariableExists($args)
5257
+	{
5258
+		$string = $this->coerceString($args[0]);
5259
+		$name = $this->compileStringContent($string);
5260
+
5261
+		return $this->has($name, $this->rootEnv);
5262
+	}
5263
+
5264
+	protected static $libMixinExists = ['name'];
5265
+	protected function libMixinExists($args)
5266
+	{
5267
+		$string = $this->coerceString($args[0]);
5268
+		$name = $this->compileStringContent($string);
5269 5269
 
5270
-        return $this->has(static::$namespaces['mixin'] . $name);
5271
-    }
5272
-
5273
-    protected static $libVariableExists = ['name'];
5274
-    protected function libVariableExists($args)
5275
-    {
5276
-        $string = $this->coerceString($args[0]);
5277
-        $name = $this->compileStringContent($string);
5270
+		return $this->has(static::$namespaces['mixin'] . $name);
5271
+	}
5272
+
5273
+	protected static $libVariableExists = ['name'];
5274
+	protected function libVariableExists($args)
5275
+	{
5276
+		$string = $this->coerceString($args[0]);
5277
+		$name = $this->compileStringContent($string);
5278 5278
 
5279
-        return $this->has($name);
5280
-    }
5279
+		return $this->has($name);
5280
+	}
5281 5281
 
5282
-    /**
5283
-     * Workaround IE7's content counter bug.
5284
-     *
5285
-     * @param array $args
5286
-     *
5287
-     * @return array
5288
-     */
5289
-    protected function libCounter($args)
5290
-    {
5291
-        $list = array_map([$this, 'compileValue'], $args);
5292
-
5293
-        return [Type::T_STRING, '', ['counter(' . implode(',', $list) . ')']];
5294
-    }
5295
-
5296
-    protected static $libRandom = ['limit'];
5297
-    protected function libRandom($args)
5298
-    {
5299
-        if (isset($args[0])) {
5300
-            $n = $this->assertNumber($args[0]);
5301
-
5302
-            if ($n < 1) {
5303
-                $this->throwError("limit must be greater than or equal to 1");
5304
-
5305
-                return;
5306
-            }
5307
-
5308
-            return new Node\Number(mt_rand(1, $n), '');
5309
-        }
5310
-
5311
-        return new Node\Number(mt_rand(1, mt_getrandmax()), '');
5312
-    }
5313
-
5314
-    protected function libUniqueId()
5315
-    {
5316
-        static $id;
5317
-
5318
-        if (! isset($id)) {
5319
-            $id = mt_rand(0, pow(36, 8));
5320
-        }
5321
-
5322
-        $id += mt_rand(0, 10) + 1;
5323
-
5324
-        return [Type::T_STRING, '', ['u' . str_pad(base_convert($id, 10, 36), 8, '0', STR_PAD_LEFT)]];
5325
-    }
5326
-
5327
-    protected static $libInspect = ['value'];
5328
-    protected function libInspect($args)
5329
-    {
5330
-        if ($args[0] === static::$null) {
5331
-            return [Type::T_KEYWORD, 'null'];
5332
-        }
5333
-
5334
-        return $args[0];
5335
-    }
5282
+	/**
5283
+	 * Workaround IE7's content counter bug.
5284
+	 *
5285
+	 * @param array $args
5286
+	 *
5287
+	 * @return array
5288
+	 */
5289
+	protected function libCounter($args)
5290
+	{
5291
+		$list = array_map([$this, 'compileValue'], $args);
5292
+
5293
+		return [Type::T_STRING, '', ['counter(' . implode(',', $list) . ')']];
5294
+	}
5295
+
5296
+	protected static $libRandom = ['limit'];
5297
+	protected function libRandom($args)
5298
+	{
5299
+		if (isset($args[0])) {
5300
+			$n = $this->assertNumber($args[0]);
5301
+
5302
+			if ($n < 1) {
5303
+				$this->throwError("limit must be greater than or equal to 1");
5304
+
5305
+				return;
5306
+			}
5307
+
5308
+			return new Node\Number(mt_rand(1, $n), '');
5309
+		}
5310
+
5311
+		return new Node\Number(mt_rand(1, mt_getrandmax()), '');
5312
+	}
5313
+
5314
+	protected function libUniqueId()
5315
+	{
5316
+		static $id;
5317
+
5318
+		if (! isset($id)) {
5319
+			$id = mt_rand(0, pow(36, 8));
5320
+		}
5321
+
5322
+		$id += mt_rand(0, 10) + 1;
5323
+
5324
+		return [Type::T_STRING, '', ['u' . str_pad(base_convert($id, 10, 36), 8, '0', STR_PAD_LEFT)]];
5325
+	}
5326
+
5327
+	protected static $libInspect = ['value'];
5328
+	protected function libInspect($args)
5329
+	{
5330
+		if ($args[0] === static::$null) {
5331
+			return [Type::T_KEYWORD, 'null'];
5332
+		}
5333
+
5334
+		return $args[0];
5335
+	}
5336 5336
 }
Please login to merge, or discard this patch.
Spacing   +76 added lines, -76 removed lines patch added patch discarded remove patch
@@ -218,7 +218,7 @@  discard block
 block discarded – undo
218 218
 
219 219
         $out = $this->formatter->format($this->scope, $sourceMapGenerator);
220 220
 
221
-        if (! empty($out) && $this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) {
221
+        if ( ! empty($out) && $this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) {
222 222
             $sourceMap    = $sourceMapGenerator->generateJson();
223 223
             $sourceMapUrl = null;
224 224
 
@@ -376,22 +376,22 @@  discard block
 block discarded – undo
376 376
             foreach ($block->selectors as $s) {
377 377
                 $selectors[] = $s;
378 378
 
379
-                if (! is_array($s)) {
379
+                if ( ! is_array($s)) {
380 380
                     continue;
381 381
                 }
382 382
 
383 383
                 // check extends
384
-                if (! empty($this->extendsMap)) {
384
+                if ( ! empty($this->extendsMap)) {
385 385
                     $this->matchExtends($s, $selectors);
386 386
 
387 387
                     // remove duplicates
388
-                    array_walk($selectors, function (&$value) {
388
+                    array_walk($selectors, function(&$value) {
389 389
                         $value = serialize($value);
390 390
                     });
391 391
 
392 392
                     $selectors = array_unique($selectors);
393 393
 
394
-                    array_walk($selectors, function (&$value) {
394
+                    array_walk($selectors, function(&$value) {
395 395
                         $value = unserialize($value);
396 396
                     });
397 397
                 }
@@ -459,7 +459,7 @@  discard block
 block discarded – undo
459 459
                         $slice = $tempReplacement[$l];
460 460
                         array_unshift($replacement, $slice);
461 461
 
462
-                        if (! $this->isImmediateRelationshipCombinator(end($slice))) {
462
+                        if ( ! $this->isImmediateRelationshipCombinator(end($slice))) {
463 463
                             break;
464 464
                         }
465 465
                     }
@@ -486,7 +486,7 @@  discard block
 block discarded – undo
486 486
                     $this->matchExtends($result, $out, count($before) + count($mergedBefore), false);
487 487
 
488 488
                     // selector sequence merging
489
-                    if (! empty($before) && count($new) > 1) {
489
+                    if ( ! empty($before) && count($new) > 1) {
490 490
                         $sharedParts = $k > 0 ? array_slice($before, 0, $k) : [];
491 491
                         $postSharedParts = $k > 0 ? array_slice($before, $k) : $before;
492 492
 
@@ -524,11 +524,11 @@  discard block
 block discarded – undo
524 524
 
525 525
         foreach ($rawSingle as $part) {
526 526
             // matches Number
527
-            if (! is_string($part)) {
527
+            if ( ! is_string($part)) {
528 528
                 return false;
529 529
             }
530 530
 
531
-            if (! preg_match('/^[\[.:#%]/', $part) && count($single)) {
531
+            if ( ! preg_match('/^[\[.:#%]/', $part) && count($single)) {
532 532
                 $single[count($single) - 1] .= $part;
533 533
             } else {
534 534
                 $single[] = $part;
@@ -677,7 +677,7 @@  discard block
 block discarded – undo
677 677
 
678 678
         $mediaQuery = $this->compileMediaQuery($this->multiplyMedia($this->env));
679 679
 
680
-        if (! empty($mediaQuery)) {
680
+        if ( ! empty($mediaQuery)) {
681 681
             $this->scope = $this->makeOutputBlock(Type::T_MEDIA, [$mediaQuery]);
682 682
 
683 683
             $parentScope = $this->mediaParent($this->scope);
@@ -730,8 +730,8 @@  discard block
 block discarded – undo
730 730
      */
731 731
     protected function mediaParent(OutputBlock $scope)
732 732
     {
733
-        while (! empty($scope->parent)) {
734
-            if (! empty($scope->type) && $scope->type !== Type::T_MEDIA) {
733
+        while ( ! empty($scope->parent)) {
734
+            if ( ! empty($scope->type) && $scope->type !== Type::T_MEDIA) {
735 735
                 break;
736 736
             }
737 737
 
@@ -750,7 +750,7 @@  discard block
 block discarded – undo
750 750
     {
751 751
         $s = '@' . $block->name;
752 752
 
753
-        if (! empty($block->value)) {
753
+        if ( ! empty($block->value)) {
754 754
             $s .= ' ' . $this->compileValue($block->value);
755 755
         }
756 756
 
@@ -815,7 +815,7 @@  discard block
 block discarded – undo
815 815
         $newBlock = null;
816 816
 
817 817
         foreach ($envs as $e) {
818
-            if (! isset($e->block)) {
818
+            if ( ! isset($e->block)) {
819 819
                 continue;
820 820
             }
821 821
 
@@ -988,7 +988,7 @@  discard block
 block discarded – undo
988 988
 
989 989
         $envs = $this->compactEnv($env);
990 990
 
991
-        $this->env = $this->extractEnv(array_filter($envs, function (Environment $e) {
991
+        $this->env = $this->extractEnv(array_filter($envs, function(Environment $e) {
992 992
             return ! isset($e->block->selectors);
993 993
         }));
994 994
 
@@ -1180,7 +1180,7 @@  discard block
 block discarded – undo
1180 1180
 
1181 1181
             array_walk_recursive(
1182 1182
                 $selector,
1183
-                function ($value, $key) use (&$output) {
1183
+                function($value, $key) use (&$output) {
1184 1184
                     $output .= $value;
1185 1185
                 }
1186 1186
             );
@@ -1230,7 +1230,7 @@  discard block
 block discarded – undo
1230 1230
      */
1231 1231
     protected function compileSelector($selector)
1232 1232
     {
1233
-        if (! is_array($selector)) {
1233
+        if ( ! is_array($selector)) {
1234 1234
             return $selector; // media and the like
1235 1235
         }
1236 1236
 
@@ -1253,7 +1253,7 @@  discard block
 block discarded – undo
1253 1253
     protected function compileSelectorPart($piece)
1254 1254
     {
1255 1255
         foreach ($piece as &$p) {
1256
-            if (! is_array($p)) {
1256
+            if ( ! is_array($p)) {
1257 1257
                 continue;
1258 1258
             }
1259 1259
 
@@ -1280,7 +1280,7 @@  discard block
 block discarded – undo
1280 1280
      */
1281 1281
     protected function hasSelectorPlaceholder($selector)
1282 1282
     {
1283
-        if (! is_array($selector)) {
1283
+        if ( ! is_array($selector)) {
1284 1284
             return false;
1285 1285
         }
1286 1286
 
@@ -1392,7 +1392,7 @@  discard block
 block discarded – undo
1392 1392
                 array_unshift($parts, implode(' ', array_filter($type)));
1393 1393
             }
1394 1394
 
1395
-            if (! empty($parts)) {
1395
+            if ( ! empty($parts)) {
1396 1396
                 if ($first) {
1397 1397
                     $first = false;
1398 1398
                     $out .= ' ';
@@ -1416,7 +1416,7 @@  discard block
 block discarded – undo
1416 1416
         $part1 = end($selectors1);
1417 1417
         $part2 = end($selectors2);
1418 1418
 
1419
-        if (! $this->isImmediateRelationshipCombinator($part1[0]) || $part1 !== $part2) {
1419
+        if ( ! $this->isImmediateRelationshipCombinator($part1[0]) || $part1 !== $part2) {
1420 1420
             return array_merge($selectors1, $selectors2);
1421 1421
         }
1422 1422
 
@@ -1433,7 +1433,7 @@  discard block
 block discarded – undo
1433 1433
 
1434 1434
             array_unshift($merged, $part1);
1435 1435
             array_unshift($merged, [array_pop($selectors1)[0] . array_pop($selectors2)[0]]);
1436
-        } while (! empty($selectors1) && ! empty($selectors2));
1436
+        } while ( ! empty($selectors1) && ! empty($selectors2));
1437 1437
 
1438 1438
         return $merged;
1439 1439
     }
@@ -1460,8 +1460,8 @@  discard block
 block discarded – undo
1460 1460
         $t1 = '';
1461 1461
 
1462 1462
         if (count($type1) > 1) {
1463
-            $m1= strtolower($type1[0]);
1464
-            $t1= strtolower($type1[1]);
1463
+            $m1 = strtolower($type1[0]);
1464
+            $t1 = strtolower($type1[1]);
1465 1465
         } else {
1466 1466
             $t1 = strtolower($type1[0]);
1467 1467
         }
@@ -1501,7 +1501,7 @@  discard block
 block discarded – undo
1501 1501
         }
1502 1502
 
1503 1503
         // t1 == t2, neither m1 nor m2 are "not"
1504
-        return [empty($m1)? $m2 : $m1, $t1];
1504
+        return [empty($m1) ? $m2 : $m1, $t1];
1505 1505
     }
1506 1506
 
1507 1507
     /**
@@ -1519,7 +1519,7 @@  discard block
 block discarded – undo
1519 1519
             $path = $this->compileStringContent($rawPath);
1520 1520
 
1521 1521
             if ($path = $this->findImport($path)) {
1522
-                if (! $once || ! in_array($path, $this->importedFiles)) {
1522
+                if ( ! $once || ! in_array($path, $this->importedFiles)) {
1523 1523
                     $this->importFile($path, $out);
1524 1524
                     $this->importedFiles[] = $path;
1525 1525
                 }
@@ -1572,7 +1572,7 @@  discard block
 block discarded – undo
1572 1572
 
1573 1573
                 $rawPath = $this->reduce($rawPath);
1574 1574
 
1575
-                if (! $this->compileImport($rawPath, $out, true)) {
1575
+                if ( ! $this->compileImport($rawPath, $out, true)) {
1576 1576
                     $out->lines[] = '@import ' . $this->compileValue($rawPath) . ';';
1577 1577
                 }
1578 1578
                 break;
@@ -1582,7 +1582,7 @@  discard block
 block discarded – undo
1582 1582
 
1583 1583
                 $rawPath = $this->reduce($rawPath);
1584 1584
 
1585
-                if (! $this->compileImport($rawPath, $out)) {
1585
+                if ( ! $this->compileImport($rawPath, $out)) {
1586 1586
                     $out->lines[] = '@import ' . $this->compileValue($rawPath) . ';';
1587 1587
                 }
1588 1588
                 break;
@@ -1604,7 +1604,7 @@  discard block
 block discarded – undo
1604 1604
                 break;
1605 1605
 
1606 1606
             case Type::T_CHARSET:
1607
-                if (! $this->charsetSeen) {
1607
+                if ( ! $this->charsetSeen) {
1608 1608
                     $this->charsetSeen = true;
1609 1609
 
1610 1610
                     $out->lines[] = '@charset ' . $this->compileValue($child[1]) . ';';
@@ -1628,7 +1628,7 @@  discard block
 block discarded – undo
1628 1628
                         (($result = $this->get($name[1], false)) === null
1629 1629
                         || $result === static::$null);
1630 1630
 
1631
-                    if (! $isDefault || $shouldSet) {
1631
+                    if ( ! $isDefault || $shouldSet) {
1632 1632
                         $this->set($name[1], $this->reduce($value));
1633 1633
                     }
1634 1634
                     break;
@@ -1774,7 +1774,7 @@  discard block
 block discarded – undo
1774 1774
                 $start = $this->reduce($for->start, true);
1775 1775
                 $end   = $this->reduce($for->end, true);
1776 1776
 
1777
-                if (! ($start[2] == $end[2] || $end->unitless())) {
1777
+                if ( ! ($start[2] == $end[2] || $end->unitless())) {
1778 1778
                     $this->throwError('Incompatible units: "%s" and "%s".', $start->unitStr(), $end->unitStr());
1779 1779
 
1780 1780
                     break;
@@ -1787,7 +1787,7 @@  discard block
 block discarded – undo
1787 1787
                 $d = $start < $end ? 1 : -1;
1788 1788
 
1789 1789
                 for (;;) {
1790
-                    if ((! $for->until && $start - $d == $end) ||
1790
+                    if (( ! $for->until && $start - $d == $end) ||
1791 1791
                         ($for->until && $start == $end)
1792 1792
                     ) {
1793 1793
                         break;
@@ -1848,7 +1848,7 @@  discard block
 block discarded – undo
1848 1848
 
1849 1849
                 $mixin = $this->get(static::$namespaces['mixin'] . $name, false);
1850 1850
 
1851
-                if (! $mixin) {
1851
+                if ( ! $mixin) {
1852 1852
                     $this->throwError("Undefined mixin $name");
1853 1853
                     break;
1854 1854
                 }
@@ -1885,7 +1885,7 @@  discard block
 block discarded – undo
1885 1885
                 $content = $this->get(static::$namespaces['special'] . 'content', false, $this->getStoreEnv())
1886 1886
                          ?: $this->get(static::$namespaces['special'] . 'content', false, $this->env);
1887 1887
 
1888
-                if (! $content) {
1888
+                if ( ! $content) {
1889 1889
                     $content = new \stdClass();
1890 1890
                     $content->scope = new \stdClass();
1891 1891
                     $content->children = $this->storeEnv->parent->block->children;
@@ -2068,7 +2068,7 @@  discard block
 block discarded – undo
2068 2068
                 ) {
2069 2069
                     $coerceUnit = false;
2070 2070
 
2071
-                    if (! isset($genOp) &&
2071
+                    if ( ! isset($genOp) &&
2072 2072
                         $left[0] === Type::T_NUMBER && $right[0] === Type::T_NUMBER
2073 2073
                     ) {
2074 2074
                         $coerceUnit = true;
@@ -2098,7 +2098,7 @@  discard block
 block discarded – undo
2098 2098
                                 $targetUnit = $left->unitless() ? $right[2] : $left[2];
2099 2099
                         }
2100 2100
 
2101
-                        if (! $left->unitless() && ! $right->unitless()) {
2101
+                        if ( ! $left->unitless() && ! $right->unitless()) {
2102 2102
                             $left = $left->normalize();
2103 2103
                             $right = $right->normalize();
2104 2104
                         }
@@ -2395,7 +2395,7 @@  discard block
 block discarded – undo
2395 2395
      */
2396 2396
     protected function opAnd($left, $right, $shouldEval)
2397 2397
     {
2398
-        if (! $shouldEval) {
2398
+        if ( ! $shouldEval) {
2399 2399
             return;
2400 2400
         }
2401 2401
 
@@ -2417,7 +2417,7 @@  discard block
 block discarded – undo
2417 2417
      */
2418 2418
     protected function opOr($left, $right, $shouldEval)
2419 2419
     {
2420
-        if (! $shouldEval) {
2420
+        if ( ! $shouldEval) {
2421 2421
             return;
2422 2422
         }
2423 2423
 
@@ -2751,7 +2751,7 @@  discard block
 block discarded – undo
2751 2751
                     $filtered[$this->compileValue($keys[$i])] = $this->compileValue($values[$i]);
2752 2752
                 }
2753 2753
 
2754
-                array_walk($filtered, function (&$value, $key) {
2754
+                array_walk($filtered, function(&$value, $key) {
2755 2755
                     $value = $key . ': ' . $value;
2756 2756
                 });
2757 2757
 
@@ -2969,7 +2969,7 @@  discard block
 block discarded – undo
2969 2969
      */
2970 2970
     protected function multiplyMedia(Environment $env = null, $childQueries = null)
2971 2971
     {
2972
-        if (! isset($env) ||
2972
+        if ( ! isset($env) ||
2973 2973
             ! empty($env->block->type) && $env->block->type !== Type::T_MEDIA
2974 2974
         ) {
2975 2975
             return $childQueries;
@@ -2992,7 +2992,7 @@  discard block
 block discarded – undo
2992 2992
 
2993 2993
             foreach ($parentQueries as $parentQuery) {
2994 2994
                 foreach ($originalQueries as $childQuery) {
2995
-                    $childQueries []= array_merge($parentQuery, $childQuery);
2995
+                    $childQueries [] = array_merge($parentQuery, $childQuery);
2996 2996
                 }
2997 2997
             }
2998 2998
         }
@@ -3083,7 +3083,7 @@  discard block
 block discarded – undo
3083 3083
     {
3084 3084
         $name = $this->normalizeName($name);
3085 3085
 
3086
-        if (! isset($env)) {
3086
+        if ( ! isset($env)) {
3087 3087
             $env = $this->getStoreEnv();
3088 3088
         }
3089 3089
 
@@ -3112,12 +3112,12 @@  discard block
 block discarded – undo
3112 3112
                 break;
3113 3113
             }
3114 3114
 
3115
-            if (! $hasNamespace && isset($env->marker)) {
3115
+            if ( ! $hasNamespace && isset($env->marker)) {
3116 3116
                 $env = $storeEnv;
3117 3117
                 break;
3118 3118
             }
3119 3119
 
3120
-            if (! isset($env->parent)) {
3120
+            if ( ! isset($env->parent)) {
3121 3121
                 $env = $storeEnv;
3122 3122
                 break;
3123 3123
             }
@@ -3156,7 +3156,7 @@  discard block
 block discarded – undo
3156 3156
         $normalizedName = $this->normalizeName($name);
3157 3157
         $specialContentKey = static::$namespaces['special'] . 'content';
3158 3158
 
3159
-        if (! isset($env)) {
3159
+        if ( ! isset($env)) {
3160 3160
             $env = $this->getStoreEnv();
3161 3161
         }
3162 3162
 
@@ -3168,8 +3168,8 @@  discard block
 block discarded – undo
3168 3168
                 return $env->store[$normalizedName];
3169 3169
             }
3170 3170
 
3171
-            if (! $hasNamespace && isset($env->marker)) {
3172
-                if (! $nextIsRoot && ! empty($env->store[$specialContentKey])) {
3171
+            if ( ! $hasNamespace && isset($env->marker)) {
3172
+                if ( ! $nextIsRoot && ! empty($env->store[$specialContentKey])) {
3173 3173
                     $env = $env->store[$specialContentKey]->scope;
3174 3174
                     $nextIsRoot = true;
3175 3175
                     continue;
@@ -3179,7 +3179,7 @@  discard block
 block discarded – undo
3179 3179
                 continue;
3180 3180
             }
3181 3181
 
3182
-            if (! isset($env->parent)) {
3182
+            if ( ! isset($env->parent)) {
3183 3183
                 break;
3184 3184
             }
3185 3185
 
@@ -3224,7 +3224,7 @@  discard block
 block discarded – undo
3224 3224
                 $name = substr($name, 1);
3225 3225
             }
3226 3226
 
3227
-            if (! $parser->parseValue($strValue, $value)) {
3227
+            if ( ! $parser->parseValue($strValue, $value)) {
3228 3228
                 $value = $this->coerceValue($strValue);
3229 3229
             }
3230 3230
 
@@ -3303,7 +3303,7 @@  discard block
 block discarded – undo
3303 3303
      */
3304 3304
     public function addImportPath($path)
3305 3305
     {
3306
-        if (! in_array($path, $this->importPaths)) {
3306
+        if ( ! in_array($path, $this->importPaths)) {
3307 3307
             $this->importPaths[] = $path;
3308 3308
         }
3309 3309
     }
@@ -3461,7 +3461,7 @@  discard block
 block discarded – undo
3461 3461
         $urls = [];
3462 3462
 
3463 3463
         // for "normal" scss imports (ignore vanilla css and external requests)
3464
-        if (! preg_match('/\.css$|^https?:\/\//', $url)) {
3464
+        if ( ! preg_match('/\.css$|^https?:\/\//', $url)) {
3465 3465
             // try both normal and the _partial filename
3466 3466
             $urls = [$url, preg_replace('/[^\/]+$/', '_\0', $url)];
3467 3467
         }
@@ -3473,7 +3473,7 @@  discard block
 block discarded – undo
3473 3473
                 // check urls for normal import paths
3474 3474
                 foreach ($urls as $full) {
3475 3475
                     $full = $dir
3476
-                        . (! empty($dir) && substr($dir, -1) !== '/' ? '/' : '')
3476
+                        . ( ! empty($dir) && substr($dir, -1) !== '/' ? '/' : '')
3477 3477
                         . $full;
3478 3478
 
3479 3479
                     if ($this->fileExists($file = $full . '.scss') ||
@@ -3590,7 +3590,7 @@  discard block
 block discarded – undo
3590 3590
     {
3591 3591
         $func = $this->get(static::$namespaces['function'] . $name, false);
3592 3592
 
3593
-        if (! $func) {
3593
+        if ( ! $func) {
3594 3594
             return false;
3595 3595
         }
3596 3596
 
@@ -3656,7 +3656,7 @@  discard block
 block discarded – undo
3656 3656
 
3657 3657
         $returnValue = call_user_func($f, $sorted, $kwargs);
3658 3658
 
3659
-        if (! isset($returnValue)) {
3659
+        if ( ! isset($returnValue)) {
3660 3660
             return false;
3661 3661
         }
3662 3662
 
@@ -3676,7 +3676,7 @@  discard block
 block discarded – undo
3676 3676
     {
3677 3677
         $libName = 'lib' . preg_replace_callback(
3678 3678
             '/_(.)/',
3679
-            function ($m) {
3679
+            function($m) {
3680 3680
                 return ucfirst($m[1]);
3681 3681
             },
3682 3682
             ucfirst($name)
@@ -3711,7 +3711,7 @@  discard block
 block discarded – undo
3711 3711
             }
3712 3712
         }
3713 3713
 
3714
-        if (! isset($prototype)) {
3714
+        if ( ! isset($prototype)) {
3715 3715
             return [$posArgs, $keyArgs];
3716 3716
         }
3717 3717
 
@@ -3761,8 +3761,8 @@  discard block
 block discarded – undo
3761 3761
 
3762 3762
         // assign the keyword args
3763 3763
         foreach ((array) $argValues as $arg) {
3764
-            if (! empty($arg[0])) {
3765
-                if (! isset($args[$arg[0][1]])) {
3764
+            if ( ! empty($arg[0])) {
3765
+                if ( ! isset($args[$arg[0][1]])) {
3766 3766
                     if ($hasVariable) {
3767 3767
                         $deferredKeywordArgs[$arg[0][1]] = $arg[1];
3768 3768
                     } else {
@@ -3783,7 +3783,7 @@  discard block
 block discarded – undo
3783 3783
 
3784 3784
                 if ($val[0] === Type::T_LIST) {
3785 3785
                     foreach ($val[2] as $name => $item) {
3786
-                        if (! is_numeric($name)) {
3786
+                        if ( ! is_numeric($name)) {
3787 3787
                             $keywordArgs[$name] = $item;
3788 3788
                         } else {
3789 3789
                             $remaining[] = $item;
@@ -3794,7 +3794,7 @@  discard block
 block discarded – undo
3794 3794
                         $name = $this->compileStringContent($this->coerceString($name));
3795 3795
                         $item = $val[2][$i];
3796 3796
 
3797
-                        if (! is_numeric($name)) {
3797
+                        if ( ! is_numeric($name)) {
3798 3798
                             $keywordArgs[$name] = $item;
3799 3799
                         } else {
3800 3800
                             $remaining[] = $item;
@@ -3825,7 +3825,7 @@  discard block
 block discarded – undo
3825 3825
                 $val = $remaining[$i];
3826 3826
             } elseif (isset($keywordArgs[$name])) {
3827 3827
                 $val = $keywordArgs[$name];
3828
-            } elseif (! empty($default)) {
3828
+            } elseif ( ! empty($default)) {
3829 3829
                 continue;
3830 3830
             } else {
3831 3831
                 $this->throwError("Missing argument $name");
@@ -3956,7 +3956,7 @@  discard block
 block discarded – undo
3956 3956
             return [Type::T_LIST, ',', $list];
3957 3957
         }
3958 3958
 
3959
-        return [Type::T_LIST, $delim, ! isset($item) ? []: [$item]];
3959
+        return [Type::T_LIST, $delim, ! isset($item) ? [] : [$item]];
3960 3960
     }
3961 3961
 
3962 3962
     /**
@@ -4031,7 +4031,7 @@  discard block
 block discarded – undo
4031 4031
     protected function coercePercent($value)
4032 4032
     {
4033 4033
         if ($value[0] === Type::T_NUMBER) {
4034
-            if (! empty($value[2]['%'])) {
4034
+            if ( ! empty($value[2]['%'])) {
4035 4035
                 return $value[1] / 100;
4036 4036
             }
4037 4037
 
@@ -4211,7 +4211,7 @@  discard block
 block discarded – undo
4211 4211
         }
4212 4212
 
4213 4213
         if ($h * 3 < 2) {
4214
-            return $m1 + ($m2 - $m1) * (2/3 - $h) * 6;
4214
+            return $m1 + ($m2 - $m1) * (2 / 3 - $h) * 6;
4215 4215
         }
4216 4216
 
4217 4217
         return $m1;
@@ -4241,9 +4241,9 @@  discard block
 block discarded – undo
4241 4241
         $m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s;
4242 4242
         $m1 = $l * 2 - $m2;
4243 4243
 
4244
-        $r = $this->hueToRGB($m1, $m2, $h + 1/3) * 255;
4244
+        $r = $this->hueToRGB($m1, $m2, $h + 1 / 3) * 255;
4245 4245
         $g = $this->hueToRGB($m1, $m2, $h) * 255;
4246
-        $b = $this->hueToRGB($m1, $m2, $h - 1/3) * 255;
4246
+        $b = $this->hueToRGB($m1, $m2, $h - 1 / 3) * 255;
4247 4247
 
4248 4248
         $out = [Type::T_COLOR, $r, $g, $b];
4249 4249
 
@@ -4258,7 +4258,7 @@  discard block
 block discarded – undo
4258 4258
         $name = $this->compileStringContent($this->coerceString($this->reduce(array_shift($args), true)));
4259 4259
 
4260 4260
         $args = array_map(
4261
-            function ($a) {
4261
+            function($a) {
4262 4262
                 return [null, $a, false];
4263 4263
             },
4264 4264
             $args
@@ -4278,7 +4278,7 @@  discard block
 block discarded – undo
4278 4278
     {
4279 4279
         list($cond, $t, $f) = $args;
4280 4280
 
4281
-        if (! $this->isTruthy($this->reduce($cond, true))) {
4281
+        if ( ! $this->isTruthy($this->reduce($cond, true))) {
4282 4282
             return $this->reduce($f, true);
4283 4283
         }
4284 4284
 
@@ -4384,7 +4384,7 @@  discard block
 block discarded – undo
4384 4384
     ];
4385 4385
     protected function libAdjustColor($args)
4386 4386
     {
4387
-        return $this->alterColor($args, function ($base, $alter, $i) {
4387
+        return $this->alterColor($args, function($base, $alter, $i) {
4388 4388
             return $base + $alter;
4389 4389
         });
4390 4390
     }
@@ -4395,7 +4395,7 @@  discard block
 block discarded – undo
4395 4395
     ];
4396 4396
     protected function libChangeColor($args)
4397 4397
     {
4398
-        return $this->alterColor($args, function ($base, $alter, $i) {
4398
+        return $this->alterColor($args, function($base, $alter, $i) {
4399 4399
             return $alter;
4400 4400
         });
4401 4401
     }
@@ -4406,7 +4406,7 @@  discard block
 block discarded – undo
4406 4406
     ];
4407 4407
     protected function libScaleColor($args)
4408 4408
     {
4409
-        return $this->alterColor($args, function ($base, $scale, $i) {
4409
+        return $this->alterColor($args, function($base, $scale, $i) {
4410 4410
             // 1, 2, 3 - rgb
4411 4411
             // 4, 5, 6 - hsl
4412 4412
             // 7 - a
@@ -4504,7 +4504,7 @@  discard block
 block discarded – undo
4504 4504
         $first = $this->assertColor($first);
4505 4505
         $second = $this->assertColor($second);
4506 4506
 
4507
-        if (! isset($weight)) {
4507
+        if ( ! isset($weight)) {
4508 4508
             $weight = 0.5;
4509 4509
         } else {
4510 4510
             $weight = $this->coercePercent($weight);
@@ -4895,7 +4895,7 @@  discard block
 block discarded – undo
4895 4895
             $n += count($list[2]);
4896 4896
         }
4897 4897
 
4898
-        if (! isset($list[2][$n])) {
4898
+        if ( ! isset($list[2][$n])) {
4899 4899
             $this->throwError('Invalid argument for "n"');
4900 4900
 
4901 4901
             return;
@@ -4997,7 +4997,7 @@  discard block
 block discarded – undo
4997 4997
 
4998 4998
     protected function listSeparatorForJoin($list1, $sep)
4999 4999
     {
5000
-        if (! isset($sep)) {
5000
+        if ( ! isset($sep)) {
5001 5001
             return $list1[1];
5002 5002
         }
5003 5003
 
@@ -5117,7 +5117,7 @@  discard block
 block discarded – undo
5117 5117
     {
5118 5118
         list($number1, $number2) = $args;
5119 5119
 
5120
-        if (! isset($number1[0]) || $number1[0] !== Type::T_NUMBER ||
5120
+        if ( ! isset($number1[0]) || $number1[0] !== Type::T_NUMBER ||
5121 5121
             ! isset($number2[0]) || $number2[0] !== Type::T_NUMBER
5122 5122
         ) {
5123 5123
             $this->throwError('Invalid argument(s) for "comparable"');
@@ -5315,7 +5315,7 @@  discard block
 block discarded – undo
5315 5315
     {
5316 5316
         static $id;
5317 5317
 
5318
-        if (! isset($id)) {
5318
+        if ( ! isset($id)) {
5319 5319
             $id = mt_rand(0, pow(36, 8));
5320 5320
         }
5321 5321
 
Please login to merge, or discard this patch.
vendor/leafo/scssphp/src/Block.php 1 patch
Indentation   +36 added lines, -36 removed lines patch added patch discarded remove patch
@@ -18,48 +18,48 @@
 block discarded – undo
18 18
  */
19 19
 class Block
20 20
 {
21
-    /**
22
-     * @var string
23
-     */
24
-    public $type;
21
+	/**
22
+	 * @var string
23
+	 */
24
+	public $type;
25 25
 
26
-    /**
27
-     * @var \Leafo\ScssPhp\Block
28
-     */
29
-    public $parent;
26
+	/**
27
+	 * @var \Leafo\ScssPhp\Block
28
+	 */
29
+	public $parent;
30 30
 
31
-    /**
32
-     * @var string
33
-     */
34
-    public $sourceName;
31
+	/**
32
+	 * @var string
33
+	 */
34
+	public $sourceName;
35 35
 
36
-    /**
37
-     * @var integer
38
-     */
39
-    public $sourceIndex;
36
+	/**
37
+	 * @var integer
38
+	 */
39
+	public $sourceIndex;
40 40
 
41
-    /**
42
-     * @var integer
43
-     */
44
-    public $sourceLine;
41
+	/**
42
+	 * @var integer
43
+	 */
44
+	public $sourceLine;
45 45
 
46
-    /**
47
-     * @var integer
48
-     */
49
-    public $sourceColumn;
46
+	/**
47
+	 * @var integer
48
+	 */
49
+	public $sourceColumn;
50 50
 
51
-    /**
52
-     * @var array
53
-     */
54
-    public $selectors;
51
+	/**
52
+	 * @var array
53
+	 */
54
+	public $selectors;
55 55
 
56
-    /**
57
-     * @var array
58
-     */
59
-    public $comments;
56
+	/**
57
+	 * @var array
58
+	 */
59
+	public $comments;
60 60
 
61
-    /**
62
-     * @var array
63
-     */
64
-    public $children;
61
+	/**
62
+	 * @var array
63
+	 */
64
+	public $children;
65 65
 }
Please login to merge, or discard this patch.
vendor/leafo/scssphp/src/Node.php 1 patch
Indentation   +16 added lines, -16 removed lines patch added patch discarded remove patch
@@ -18,23 +18,23 @@
 block discarded – undo
18 18
  */
19 19
 abstract class Node
20 20
 {
21
-    /**
22
-     * @var string
23
-     */
24
-    public $type;
21
+	/**
22
+	 * @var string
23
+	 */
24
+	public $type;
25 25
 
26
-    /**
27
-     * @var integer
28
-     */
29
-    public $sourceIndex;
26
+	/**
27
+	 * @var integer
28
+	 */
29
+	public $sourceIndex;
30 30
 
31
-    /**
32
-     * @var integer
33
-     */
34
-    public $sourceLine;
31
+	/**
32
+	 * @var integer
33
+	 */
34
+	public $sourceLine;
35 35
 
36
-    /**
37
-     * @var integer
38
-     */
39
-    public $sourceColumn;
36
+	/**
37
+	 * @var integer
38
+	 */
39
+	public $sourceColumn;
40 40
 }
Please login to merge, or discard this patch.
vendor/leafo/scssphp/src/Compiler/Environment.php 1 patch
Indentation   +16 added lines, -16 removed lines patch added patch discarded remove patch
@@ -18,23 +18,23 @@
 block discarded – undo
18 18
  */
19 19
 class Environment
20 20
 {
21
-    /**
22
-     * @var \Leafo\ScssPhp\Block
23
-     */
24
-    public $block;
21
+	/**
22
+	 * @var \Leafo\ScssPhp\Block
23
+	 */
24
+	public $block;
25 25
 
26
-    /**
27
-     * @var \Leafo\ScssPhp\Compiler\Environment
28
-     */
29
-    public $parent;
26
+	/**
27
+	 * @var \Leafo\ScssPhp\Compiler\Environment
28
+	 */
29
+	public $parent;
30 30
 
31
-    /**
32
-     * @var array
33
-     */
34
-    public $store;
31
+	/**
32
+	 * @var array
33
+	 */
34
+	public $store;
35 35
 
36
-    /**
37
-     * @var integer
38
-     */
39
-    public $depth;
36
+	/**
37
+	 * @var integer
38
+	 */
39
+	public $depth;
40 40
 }
Please login to merge, or discard this patch.
vendor/leafo/scssphp/src/Node/Number.php 2 patches
Indentation   +299 added lines, -299 removed lines patch added patch discarded remove patch
@@ -28,303 +28,303 @@
 block discarded – undo
28 28
  */
29 29
 class Number extends Node implements \ArrayAccess
30 30
 {
31
-    /**
32
-     * @var integer
33
-     */
34
-    static public $precision = 10;
35
-
36
-    /**
37
-     * @see http://www.w3.org/TR/2012/WD-css3-values-20120308/
38
-     *
39
-     * @var array
40
-     */
41
-    static protected $unitTable = [
42
-        'in' => [
43
-            'in' => 1,
44
-            'pc' => 6,
45
-            'pt' => 72,
46
-            'px' => 96,
47
-            'cm' => 2.54,
48
-            'mm' => 25.4,
49
-            'q'  => 101.6,
50
-        ],
51
-        'turn' => [
52
-            'deg'  => 360,
53
-            'grad' => 400,
54
-            'rad'  => 6.28318530717958647692528676, // 2 * M_PI
55
-            'turn' => 1,
56
-        ],
57
-        's' => [
58
-            's'  => 1,
59
-            'ms' => 1000,
60
-        ],
61
-        'Hz' => [
62
-            'Hz'  => 1,
63
-            'kHz' => 0.001,
64
-        ],
65
-        'dpi' => [
66
-            'dpi'  => 1,
67
-            'dpcm' => 2.54,
68
-            'dppx' => 96,
69
-        ],
70
-    ];
71
-
72
-    /**
73
-     * @var integer|float
74
-     */
75
-    public $dimension;
76
-
77
-    /**
78
-     * @var array
79
-     */
80
-    public $units;
81
-
82
-    /**
83
-     * Initialize number
84
-     *
85
-     * @param mixed $dimension
86
-     * @param mixed $initialUnit
87
-     */
88
-    public function __construct($dimension, $initialUnit)
89
-    {
90
-        $this->type      = Type::T_NUMBER;
91
-        $this->dimension = $dimension;
92
-        $this->units     = is_array($initialUnit)
93
-            ? $initialUnit
94
-            : ($initialUnit ? [$initialUnit => 1]
95
-                            : []);
96
-    }
97
-
98
-    /**
99
-     * Coerce number to target units
100
-     *
101
-     * @param array $units
102
-     *
103
-     * @return \Leafo\ScssPhp\Node\Number
104
-     */
105
-    public function coerce($units)
106
-    {
107
-        if ($this->unitless()) {
108
-            return new Number($this->dimension, $units);
109
-        }
110
-
111
-        $dimension = $this->dimension;
112
-
113
-        foreach (static::$unitTable['in'] as $unit => $conv) {
114
-            $from       = isset($this->units[$unit]) ? $this->units[$unit] : 0;
115
-            $to         = isset($units[$unit]) ? $units[$unit] : 0;
116
-            $factor     = pow($conv, $from - $to);
117
-            $dimension /= $factor;
118
-        }
119
-
120
-        return new Number($dimension, $units);
121
-    }
122
-
123
-    /**
124
-     * Normalize number
125
-     *
126
-     * @return \Leafo\ScssPhp\Node\Number
127
-     */
128
-    public function normalize()
129
-    {
130
-        $dimension = $this->dimension;
131
-        $units     = [];
132
-
133
-        $this->normalizeUnits($dimension, $units, 'in');
134
-
135
-        return new Number($dimension, $units);
136
-    }
137
-
138
-    /**
139
-     * {@inheritdoc}
140
-     */
141
-    public function offsetExists($offset)
142
-    {
143
-        if ($offset === -3) {
144
-            return $this->sourceColumn !== null;
145
-        }
146
-
147
-        if ($offset === -2) {
148
-            return $this->sourceLine !== null;
149
-        }
150
-
151
-        if ($offset === -1
152
-            || $offset === 0
153
-            || $offset === 1
154
-            || $offset === 2
155
-        ) {
156
-            return true;
157
-        }
158
-
159
-        return false;
160
-    }
161
-
162
-    /**
163
-     * {@inheritdoc}
164
-     */
165
-    public function offsetGet($offset)
166
-    {
167
-        switch ($offset) {
168
-            case -3:
169
-                return $this->sourceColumn;
170
-
171
-            case -2:
172
-                return $this->sourceLine;
173
-
174
-            case -1:
175
-                return $this->sourceIndex;
176
-
177
-            case 0:
178
-                return $this->type;
179
-
180
-            case 1:
181
-                return $this->dimension;
182
-
183
-            case 2:
184
-                return $this->units;
185
-        }
186
-    }
187
-
188
-    /**
189
-     * {@inheritdoc}
190
-     */
191
-    public function offsetSet($offset, $value)
192
-    {
193
-        if ($offset === 1) {
194
-            $this->dimension = $value;
195
-        } elseif ($offset === 2) {
196
-            $this->units = $value;
197
-        } elseif ($offset == -1) {
198
-            $this->sourceIndex = $value;
199
-        } elseif ($offset == -2) {
200
-            $this->sourceLine = $value;
201
-        } elseif ($offset == -3) {
202
-            $this->sourceColumn = $value;
203
-        }
204
-    }
205
-
206
-    /**
207
-     * {@inheritdoc}
208
-     */
209
-    public function offsetUnset($offset)
210
-    {
211
-        if ($offset === 1) {
212
-            $this->dimension = null;
213
-        } elseif ($offset === 2) {
214
-            $this->units = null;
215
-        } elseif ($offset === -1) {
216
-            $this->sourceIndex = null;
217
-        } elseif ($offset === -2) {
218
-            $this->sourceLine = null;
219
-        } elseif ($offset === -3) {
220
-            $this->sourceColumn = null;
221
-        }
222
-    }
223
-
224
-    /**
225
-     * Returns true if the number is unitless
226
-     *
227
-     * @return boolean
228
-     */
229
-    public function unitless()
230
-    {
231
-        return ! array_sum($this->units);
232
-    }
233
-
234
-    /**
235
-     * Returns unit(s) as the product of numerator units divided by the product of denominator units
236
-     *
237
-     * @return string
238
-     */
239
-    public function unitStr()
240
-    {
241
-        $numerators   = [];
242
-        $denominators = [];
243
-
244
-        foreach ($this->units as $unit => $unitSize) {
245
-            if ($unitSize > 0) {
246
-                $numerators = array_pad($numerators, count($numerators) + $unitSize, $unit);
247
-                continue;
248
-            }
249
-
250
-            if ($unitSize < 0) {
251
-                $denominators = array_pad($denominators, count($denominators) + $unitSize, $unit);
252
-                continue;
253
-            }
254
-        }
255
-
256
-        return implode('*', $numerators) . (count($denominators) ? '/' . implode('*', $denominators) : '');
257
-    }
258
-
259
-    /**
260
-     * Output number
261
-     *
262
-     * @param \Leafo\ScssPhp\Compiler $compiler
263
-     *
264
-     * @return string
265
-     */
266
-    public function output(Compiler $compiler = null)
267
-    {
268
-        $dimension = round($this->dimension, static::$precision);
269
-
270
-        $units = array_filter($this->units, function ($unitSize) {
271
-            return $unitSize;
272
-        });
273
-
274
-        if (count($units) > 1 && array_sum($units) === 0) {
275
-            $dimension = $this->dimension;
276
-            $units     = [];
277
-
278
-            $this->normalizeUnits($dimension, $units, 'in');
279
-
280
-            $dimension = round($dimension, static::$precision);
281
-            $units     = array_filter($units, function ($unitSize) {
282
-                return $unitSize;
283
-            });
284
-        }
285
-
286
-        $unitSize = array_sum($units);
287
-
288
-        if ($compiler && ($unitSize > 1 || $unitSize < 0 || count($units) > 1)) {
289
-            $compiler->throwError((string) $dimension . $this->unitStr() . " isn't a valid CSS value.");
290
-        }
291
-
292
-        reset($units);
293
-        $unit = key($units);
294
-        $dimension = number_format($dimension, static::$precision, '.', '');
295
-
296
-        return (static::$precision ? rtrim(rtrim($dimension, '0'), '.') : $dimension) . $unit;
297
-    }
298
-
299
-    /**
300
-     * {@inheritdoc}
301
-     */
302
-    public function __toString()
303
-    {
304
-        return $this->output();
305
-    }
306
-
307
-    /**
308
-     * Normalize units
309
-     *
310
-     * @param integer|float $dimension
311
-     * @param array         $units
312
-     * @param string        $baseUnit
313
-     */
314
-    private function normalizeUnits(&$dimension, &$units, $baseUnit = 'in')
315
-    {
316
-        $dimension = $this->dimension;
317
-        $units     = [];
318
-
319
-        foreach ($this->units as $unit => $exp) {
320
-            if (isset(static::$unitTable[$baseUnit][$unit])) {
321
-                $factor = pow(static::$unitTable[$baseUnit][$unit], $exp);
322
-
323
-                $unit = $baseUnit;
324
-                $dimension /= $factor;
325
-            }
326
-
327
-            $units[$unit] = $exp + (isset($units[$unit]) ? $units[$unit] : 0);
328
-        }
329
-    }
31
+	/**
32
+	 * @var integer
33
+	 */
34
+	static public $precision = 10;
35
+
36
+	/**
37
+	 * @see http://www.w3.org/TR/2012/WD-css3-values-20120308/
38
+	 *
39
+	 * @var array
40
+	 */
41
+	static protected $unitTable = [
42
+		'in' => [
43
+			'in' => 1,
44
+			'pc' => 6,
45
+			'pt' => 72,
46
+			'px' => 96,
47
+			'cm' => 2.54,
48
+			'mm' => 25.4,
49
+			'q'  => 101.6,
50
+		],
51
+		'turn' => [
52
+			'deg'  => 360,
53
+			'grad' => 400,
54
+			'rad'  => 6.28318530717958647692528676, // 2 * M_PI
55
+			'turn' => 1,
56
+		],
57
+		's' => [
58
+			's'  => 1,
59
+			'ms' => 1000,
60
+		],
61
+		'Hz' => [
62
+			'Hz'  => 1,
63
+			'kHz' => 0.001,
64
+		],
65
+		'dpi' => [
66
+			'dpi'  => 1,
67
+			'dpcm' => 2.54,
68
+			'dppx' => 96,
69
+		],
70
+	];
71
+
72
+	/**
73
+	 * @var integer|float
74
+	 */
75
+	public $dimension;
76
+
77
+	/**
78
+	 * @var array
79
+	 */
80
+	public $units;
81
+
82
+	/**
83
+	 * Initialize number
84
+	 *
85
+	 * @param mixed $dimension
86
+	 * @param mixed $initialUnit
87
+	 */
88
+	public function __construct($dimension, $initialUnit)
89
+	{
90
+		$this->type      = Type::T_NUMBER;
91
+		$this->dimension = $dimension;
92
+		$this->units     = is_array($initialUnit)
93
+			? $initialUnit
94
+			: ($initialUnit ? [$initialUnit => 1]
95
+							: []);
96
+	}
97
+
98
+	/**
99
+	 * Coerce number to target units
100
+	 *
101
+	 * @param array $units
102
+	 *
103
+	 * @return \Leafo\ScssPhp\Node\Number
104
+	 */
105
+	public function coerce($units)
106
+	{
107
+		if ($this->unitless()) {
108
+			return new Number($this->dimension, $units);
109
+		}
110
+
111
+		$dimension = $this->dimension;
112
+
113
+		foreach (static::$unitTable['in'] as $unit => $conv) {
114
+			$from       = isset($this->units[$unit]) ? $this->units[$unit] : 0;
115
+			$to         = isset($units[$unit]) ? $units[$unit] : 0;
116
+			$factor     = pow($conv, $from - $to);
117
+			$dimension /= $factor;
118
+		}
119
+
120
+		return new Number($dimension, $units);
121
+	}
122
+
123
+	/**
124
+	 * Normalize number
125
+	 *
126
+	 * @return \Leafo\ScssPhp\Node\Number
127
+	 */
128
+	public function normalize()
129
+	{
130
+		$dimension = $this->dimension;
131
+		$units     = [];
132
+
133
+		$this->normalizeUnits($dimension, $units, 'in');
134
+
135
+		return new Number($dimension, $units);
136
+	}
137
+
138
+	/**
139
+	 * {@inheritdoc}
140
+	 */
141
+	public function offsetExists($offset)
142
+	{
143
+		if ($offset === -3) {
144
+			return $this->sourceColumn !== null;
145
+		}
146
+
147
+		if ($offset === -2) {
148
+			return $this->sourceLine !== null;
149
+		}
150
+
151
+		if ($offset === -1
152
+			|| $offset === 0
153
+			|| $offset === 1
154
+			|| $offset === 2
155
+		) {
156
+			return true;
157
+		}
158
+
159
+		return false;
160
+	}
161
+
162
+	/**
163
+	 * {@inheritdoc}
164
+	 */
165
+	public function offsetGet($offset)
166
+	{
167
+		switch ($offset) {
168
+			case -3:
169
+				return $this->sourceColumn;
170
+
171
+			case -2:
172
+				return $this->sourceLine;
173
+
174
+			case -1:
175
+				return $this->sourceIndex;
176
+
177
+			case 0:
178
+				return $this->type;
179
+
180
+			case 1:
181
+				return $this->dimension;
182
+
183
+			case 2:
184
+				return $this->units;
185
+		}
186
+	}
187
+
188
+	/**
189
+	 * {@inheritdoc}
190
+	 */
191
+	public function offsetSet($offset, $value)
192
+	{
193
+		if ($offset === 1) {
194
+			$this->dimension = $value;
195
+		} elseif ($offset === 2) {
196
+			$this->units = $value;
197
+		} elseif ($offset == -1) {
198
+			$this->sourceIndex = $value;
199
+		} elseif ($offset == -2) {
200
+			$this->sourceLine = $value;
201
+		} elseif ($offset == -3) {
202
+			$this->sourceColumn = $value;
203
+		}
204
+	}
205
+
206
+	/**
207
+	 * {@inheritdoc}
208
+	 */
209
+	public function offsetUnset($offset)
210
+	{
211
+		if ($offset === 1) {
212
+			$this->dimension = null;
213
+		} elseif ($offset === 2) {
214
+			$this->units = null;
215
+		} elseif ($offset === -1) {
216
+			$this->sourceIndex = null;
217
+		} elseif ($offset === -2) {
218
+			$this->sourceLine = null;
219
+		} elseif ($offset === -3) {
220
+			$this->sourceColumn = null;
221
+		}
222
+	}
223
+
224
+	/**
225
+	 * Returns true if the number is unitless
226
+	 *
227
+	 * @return boolean
228
+	 */
229
+	public function unitless()
230
+	{
231
+		return ! array_sum($this->units);
232
+	}
233
+
234
+	/**
235
+	 * Returns unit(s) as the product of numerator units divided by the product of denominator units
236
+	 *
237
+	 * @return string
238
+	 */
239
+	public function unitStr()
240
+	{
241
+		$numerators   = [];
242
+		$denominators = [];
243
+
244
+		foreach ($this->units as $unit => $unitSize) {
245
+			if ($unitSize > 0) {
246
+				$numerators = array_pad($numerators, count($numerators) + $unitSize, $unit);
247
+				continue;
248
+			}
249
+
250
+			if ($unitSize < 0) {
251
+				$denominators = array_pad($denominators, count($denominators) + $unitSize, $unit);
252
+				continue;
253
+			}
254
+		}
255
+
256
+		return implode('*', $numerators) . (count($denominators) ? '/' . implode('*', $denominators) : '');
257
+	}
258
+
259
+	/**
260
+	 * Output number
261
+	 *
262
+	 * @param \Leafo\ScssPhp\Compiler $compiler
263
+	 *
264
+	 * @return string
265
+	 */
266
+	public function output(Compiler $compiler = null)
267
+	{
268
+		$dimension = round($this->dimension, static::$precision);
269
+
270
+		$units = array_filter($this->units, function ($unitSize) {
271
+			return $unitSize;
272
+		});
273
+
274
+		if (count($units) > 1 && array_sum($units) === 0) {
275
+			$dimension = $this->dimension;
276
+			$units     = [];
277
+
278
+			$this->normalizeUnits($dimension, $units, 'in');
279
+
280
+			$dimension = round($dimension, static::$precision);
281
+			$units     = array_filter($units, function ($unitSize) {
282
+				return $unitSize;
283
+			});
284
+		}
285
+
286
+		$unitSize = array_sum($units);
287
+
288
+		if ($compiler && ($unitSize > 1 || $unitSize < 0 || count($units) > 1)) {
289
+			$compiler->throwError((string) $dimension . $this->unitStr() . " isn't a valid CSS value.");
290
+		}
291
+
292
+		reset($units);
293
+		$unit = key($units);
294
+		$dimension = number_format($dimension, static::$precision, '.', '');
295
+
296
+		return (static::$precision ? rtrim(rtrim($dimension, '0'), '.') : $dimension) . $unit;
297
+	}
298
+
299
+	/**
300
+	 * {@inheritdoc}
301
+	 */
302
+	public function __toString()
303
+	{
304
+		return $this->output();
305
+	}
306
+
307
+	/**
308
+	 * Normalize units
309
+	 *
310
+	 * @param integer|float $dimension
311
+	 * @param array         $units
312
+	 * @param string        $baseUnit
313
+	 */
314
+	private function normalizeUnits(&$dimension, &$units, $baseUnit = 'in')
315
+	{
316
+		$dimension = $this->dimension;
317
+		$units     = [];
318
+
319
+		foreach ($this->units as $unit => $exp) {
320
+			if (isset(static::$unitTable[$baseUnit][$unit])) {
321
+				$factor = pow(static::$unitTable[$baseUnit][$unit], $exp);
322
+
323
+				$unit = $baseUnit;
324
+				$dimension /= $factor;
325
+			}
326
+
327
+			$units[$unit] = $exp + (isset($units[$unit]) ? $units[$unit] : 0);
328
+		}
329
+	}
330 330
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -267,7 +267,7 @@  discard block
 block discarded – undo
267 267
     {
268 268
         $dimension = round($this->dimension, static::$precision);
269 269
 
270
-        $units = array_filter($this->units, function ($unitSize) {
270
+        $units = array_filter($this->units, function($unitSize) {
271 271
             return $unitSize;
272 272
         });
273 273
 
@@ -278,7 +278,7 @@  discard block
 block discarded – undo
278 278
             $this->normalizeUnits($dimension, $units, 'in');
279 279
 
280 280
             $dimension = round($dimension, static::$precision);
281
-            $units     = array_filter($units, function ($unitSize) {
281
+            $units     = array_filter($units, function($unitSize) {
282 282
                 return $unitSize;
283 283
             });
284 284
         }
Please login to merge, or discard this patch.
vendor/leafo/scssphp/src/Formatter/Nested.php 2 patches
Indentation   +177 added lines, -177 removed lines patch added patch discarded remove patch
@@ -21,181 +21,181 @@
 block discarded – undo
21 21
  */
22 22
 class Nested extends Formatter
23 23
 {
24
-    /**
25
-     * @var integer
26
-     */
27
-    private $depth;
28
-
29
-    /**
30
-     * {@inheritdoc}
31
-     */
32
-    public function __construct()
33
-    {
34
-        $this->indentLevel = 0;
35
-        $this->indentChar = '  ';
36
-        $this->break = "\n";
37
-        $this->open = ' {';
38
-        $this->close = ' }';
39
-        $this->tagSeparator = ', ';
40
-        $this->assignSeparator = ': ';
41
-        $this->keepSemicolons = true;
42
-    }
43
-
44
-    /**
45
-     * {@inheritdoc}
46
-     */
47
-    protected function indentStr()
48
-    {
49
-        $n = $this->depth - 1;
50
-
51
-        return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
52
-    }
53
-
54
-    /**
55
-     * {@inheritdoc}
56
-     */
57
-    protected function blockLines(OutputBlock $block)
58
-    {
59
-        $inner = $this->indentStr();
60
-
61
-        $glue = $this->break . $inner;
62
-
63
-        foreach ($block->lines as $index => $line) {
64
-            if (substr($line, 0, 2) === '/*') {
65
-                $block->lines[$index] = preg_replace('/(\r|\n)+/', $glue, $line);
66
-            }
67
-        }
68
-
69
-        $this->write($inner . implode($glue, $block->lines));
70
-
71
-        if (! empty($block->children)) {
72
-            $this->write($this->break);
73
-        }
74
-    }
75
-
76
-    /**
77
-     * {@inheritdoc}
78
-     */
79
-    protected function blockSelectors(OutputBlock $block)
80
-    {
81
-        $inner = $this->indentStr();
82
-
83
-        $this->write($inner
84
-            . implode($this->tagSeparator, $block->selectors)
85
-            . $this->open . $this->break);
86
-    }
87
-
88
-    /**
89
-     * {@inheritdoc}
90
-     */
91
-    protected function blockChildren(OutputBlock $block)
92
-    {
93
-        foreach ($block->children as $i => $child) {
94
-            $this->block($child);
95
-
96
-            if ($i < count($block->children) - 1) {
97
-                $this->write($this->break);
98
-
99
-                if (isset($block->children[$i + 1])) {
100
-                    $next = $block->children[$i + 1];
101
-
102
-                    if ($next->depth === max($block->depth, 1) && $child->depth >= $next->depth) {
103
-                        $this->write($this->break);
104
-                    }
105
-                }
106
-            }
107
-        }
108
-    }
109
-
110
-    /**
111
-     * {@inheritdoc}
112
-     */
113
-    protected function block(OutputBlock $block)
114
-    {
115
-        if ($block->type === 'root') {
116
-            $this->adjustAllChildren($block);
117
-        }
118
-
119
-        if (empty($block->lines) && empty($block->children)) {
120
-            return;
121
-        }
122
-
123
-        $this->currentBlock = $block;
124
-
125
-
126
-        $this->depth = $block->depth;
127
-
128
-        if (! empty($block->selectors)) {
129
-            $this->blockSelectors($block);
130
-
131
-            $this->indentLevel++;
132
-        }
133
-
134
-        if (! empty($block->lines)) {
135
-            $this->blockLines($block);
136
-        }
137
-
138
-        if (! empty($block->children)) {
139
-            $this->blockChildren($block);
140
-        }
141
-
142
-        if (! empty($block->selectors)) {
143
-            $this->indentLevel--;
144
-
145
-            $this->write($this->close);
146
-        }
147
-
148
-        if ($block->type === 'root') {
149
-            $this->write($this->break);
150
-        }
151
-    }
152
-
153
-    /**
154
-     * Adjust the depths of all children, depth first
155
-     *
156
-     * @param \Leafo\ScssPhp\Formatter\OutputBlock $block
157
-     */
158
-    private function adjustAllChildren(OutputBlock $block)
159
-    {
160
-        // flatten empty nested blocks
161
-        $children = [];
162
-
163
-        foreach ($block->children as $i => $child) {
164
-            if (empty($child->lines) && empty($child->children)) {
165
-                if (isset($block->children[$i + 1])) {
166
-                    $block->children[$i + 1]->depth = $child->depth;
167
-                }
168
-
169
-                continue;
170
-            }
171
-
172
-            $children[] = $child;
173
-        }
174
-
175
-        $count = count($children);
176
-
177
-        for ($i = 0; $i < $count; $i++) {
178
-            $depth = $children[$i]->depth;
179
-            $j = $i + 1;
180
-
181
-            if (isset($children[$j]) && $depth < $children[$j]->depth) {
182
-                $childDepth = $children[$j]->depth;
183
-
184
-                for (; $j < $count; $j++) {
185
-                    if ($depth < $children[$j]->depth && $childDepth >= $children[$j]->depth) {
186
-                        $children[$j]->depth = $depth + 1;
187
-                    }
188
-                }
189
-            }
190
-        }
191
-
192
-        $block->children = $children;
193
-
194
-        // make relative to parent
195
-        foreach ($block->children as $child) {
196
-            $this->adjustAllChildren($child);
197
-
198
-            $child->depth = $child->depth - $block->depth;
199
-        }
200
-    }
24
+	/**
25
+	 * @var integer
26
+	 */
27
+	private $depth;
28
+
29
+	/**
30
+	 * {@inheritdoc}
31
+	 */
32
+	public function __construct()
33
+	{
34
+		$this->indentLevel = 0;
35
+		$this->indentChar = '  ';
36
+		$this->break = "\n";
37
+		$this->open = ' {';
38
+		$this->close = ' }';
39
+		$this->tagSeparator = ', ';
40
+		$this->assignSeparator = ': ';
41
+		$this->keepSemicolons = true;
42
+	}
43
+
44
+	/**
45
+	 * {@inheritdoc}
46
+	 */
47
+	protected function indentStr()
48
+	{
49
+		$n = $this->depth - 1;
50
+
51
+		return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
52
+	}
53
+
54
+	/**
55
+	 * {@inheritdoc}
56
+	 */
57
+	protected function blockLines(OutputBlock $block)
58
+	{
59
+		$inner = $this->indentStr();
60
+
61
+		$glue = $this->break . $inner;
62
+
63
+		foreach ($block->lines as $index => $line) {
64
+			if (substr($line, 0, 2) === '/*') {
65
+				$block->lines[$index] = preg_replace('/(\r|\n)+/', $glue, $line);
66
+			}
67
+		}
68
+
69
+		$this->write($inner . implode($glue, $block->lines));
70
+
71
+		if (! empty($block->children)) {
72
+			$this->write($this->break);
73
+		}
74
+	}
75
+
76
+	/**
77
+	 * {@inheritdoc}
78
+	 */
79
+	protected function blockSelectors(OutputBlock $block)
80
+	{
81
+		$inner = $this->indentStr();
82
+
83
+		$this->write($inner
84
+			. implode($this->tagSeparator, $block->selectors)
85
+			. $this->open . $this->break);
86
+	}
87
+
88
+	/**
89
+	 * {@inheritdoc}
90
+	 */
91
+	protected function blockChildren(OutputBlock $block)
92
+	{
93
+		foreach ($block->children as $i => $child) {
94
+			$this->block($child);
95
+
96
+			if ($i < count($block->children) - 1) {
97
+				$this->write($this->break);
98
+
99
+				if (isset($block->children[$i + 1])) {
100
+					$next = $block->children[$i + 1];
101
+
102
+					if ($next->depth === max($block->depth, 1) && $child->depth >= $next->depth) {
103
+						$this->write($this->break);
104
+					}
105
+				}
106
+			}
107
+		}
108
+	}
109
+
110
+	/**
111
+	 * {@inheritdoc}
112
+	 */
113
+	protected function block(OutputBlock $block)
114
+	{
115
+		if ($block->type === 'root') {
116
+			$this->adjustAllChildren($block);
117
+		}
118
+
119
+		if (empty($block->lines) && empty($block->children)) {
120
+			return;
121
+		}
122
+
123
+		$this->currentBlock = $block;
124
+
125
+
126
+		$this->depth = $block->depth;
127
+
128
+		if (! empty($block->selectors)) {
129
+			$this->blockSelectors($block);
130
+
131
+			$this->indentLevel++;
132
+		}
133
+
134
+		if (! empty($block->lines)) {
135
+			$this->blockLines($block);
136
+		}
137
+
138
+		if (! empty($block->children)) {
139
+			$this->blockChildren($block);
140
+		}
141
+
142
+		if (! empty($block->selectors)) {
143
+			$this->indentLevel--;
144
+
145
+			$this->write($this->close);
146
+		}
147
+
148
+		if ($block->type === 'root') {
149
+			$this->write($this->break);
150
+		}
151
+	}
152
+
153
+	/**
154
+	 * Adjust the depths of all children, depth first
155
+	 *
156
+	 * @param \Leafo\ScssPhp\Formatter\OutputBlock $block
157
+	 */
158
+	private function adjustAllChildren(OutputBlock $block)
159
+	{
160
+		// flatten empty nested blocks
161
+		$children = [];
162
+
163
+		foreach ($block->children as $i => $child) {
164
+			if (empty($child->lines) && empty($child->children)) {
165
+				if (isset($block->children[$i + 1])) {
166
+					$block->children[$i + 1]->depth = $child->depth;
167
+				}
168
+
169
+				continue;
170
+			}
171
+
172
+			$children[] = $child;
173
+		}
174
+
175
+		$count = count($children);
176
+
177
+		for ($i = 0; $i < $count; $i++) {
178
+			$depth = $children[$i]->depth;
179
+			$j = $i + 1;
180
+
181
+			if (isset($children[$j]) && $depth < $children[$j]->depth) {
182
+				$childDepth = $children[$j]->depth;
183
+
184
+				for (; $j < $count; $j++) {
185
+					if ($depth < $children[$j]->depth && $childDepth >= $children[$j]->depth) {
186
+						$children[$j]->depth = $depth + 1;
187
+					}
188
+				}
189
+			}
190
+		}
191
+
192
+		$block->children = $children;
193
+
194
+		// make relative to parent
195
+		foreach ($block->children as $child) {
196
+			$this->adjustAllChildren($child);
197
+
198
+			$child->depth = $child->depth - $block->depth;
199
+		}
200
+	}
201 201
 }
Please login to merge, or discard this patch.
Spacing   +5 added lines, -5 removed lines patch added patch discarded remove patch
@@ -68,7 +68,7 @@  discard block
 block discarded – undo
68 68
 
69 69
         $this->write($inner . implode($glue, $block->lines));
70 70
 
71
-        if (! empty($block->children)) {
71
+        if ( ! empty($block->children)) {
72 72
             $this->write($this->break);
73 73
         }
74 74
     }
@@ -125,21 +125,21 @@  discard block
 block discarded – undo
125 125
 
126 126
         $this->depth = $block->depth;
127 127
 
128
-        if (! empty($block->selectors)) {
128
+        if ( ! empty($block->selectors)) {
129 129
             $this->blockSelectors($block);
130 130
 
131 131
             $this->indentLevel++;
132 132
         }
133 133
 
134
-        if (! empty($block->lines)) {
134
+        if ( ! empty($block->lines)) {
135 135
             $this->blockLines($block);
136 136
         }
137 137
 
138
-        if (! empty($block->children)) {
138
+        if ( ! empty($block->children)) {
139 139
             $this->blockChildren($block);
140 140
         }
141 141
 
142
-        if (! empty($block->selectors)) {
142
+        if ( ! empty($block->selectors)) {
143 143
             $this->indentLevel--;
144 144
 
145 145
             $this->write($this->close);
Please login to merge, or discard this patch.
vendor/leafo/scssphp/src/Formatter/Expanded.php 1 patch
Indentation   +38 added lines, -38 removed lines patch added patch discarded remove patch
@@ -21,48 +21,48 @@
 block discarded – undo
21 21
  */
22 22
 class Expanded extends Formatter
23 23
 {
24
-    /**
25
-     * {@inheritdoc}
26
-     */
27
-    public function __construct()
28
-    {
29
-        $this->indentLevel = 0;
30
-        $this->indentChar = '  ';
31
-        $this->break = "\n";
32
-        $this->open = ' {';
33
-        $this->close = '}';
34
-        $this->tagSeparator = ', ';
35
-        $this->assignSeparator = ': ';
36
-        $this->keepSemicolons = true;
37
-    }
24
+	/**
25
+	 * {@inheritdoc}
26
+	 */
27
+	public function __construct()
28
+	{
29
+		$this->indentLevel = 0;
30
+		$this->indentChar = '  ';
31
+		$this->break = "\n";
32
+		$this->open = ' {';
33
+		$this->close = '}';
34
+		$this->tagSeparator = ', ';
35
+		$this->assignSeparator = ': ';
36
+		$this->keepSemicolons = true;
37
+	}
38 38
 
39
-    /**
40
-     * {@inheritdoc}
41
-     */
42
-    protected function indentStr()
43
-    {
44
-        return str_repeat($this->indentChar, $this->indentLevel);
45
-    }
39
+	/**
40
+	 * {@inheritdoc}
41
+	 */
42
+	protected function indentStr()
43
+	{
44
+		return str_repeat($this->indentChar, $this->indentLevel);
45
+	}
46 46
 
47
-    /**
48
-     * {@inheritdoc}
49
-     */
50
-    protected function blockLines(OutputBlock $block)
51
-    {
52
-        $inner = $this->indentStr();
47
+	/**
48
+	 * {@inheritdoc}
49
+	 */
50
+	protected function blockLines(OutputBlock $block)
51
+	{
52
+		$inner = $this->indentStr();
53 53
 
54
-        $glue = $this->break . $inner;
54
+		$glue = $this->break . $inner;
55 55
 
56
-        foreach ($block->lines as $index => $line) {
57
-            if (substr($line, 0, 2) === '/*') {
58
-                $block->lines[$index] = preg_replace('/(\r|\n)+/', $glue, $line);
59
-            }
60
-        }
56
+		foreach ($block->lines as $index => $line) {
57
+			if (substr($line, 0, 2) === '/*') {
58
+				$block->lines[$index] = preg_replace('/(\r|\n)+/', $glue, $line);
59
+			}
60
+		}
61 61
 
62
-        $this->write($inner . implode($glue, $block->lines));
62
+		$this->write($inner . implode($glue, $block->lines));
63 63
 
64
-        if (empty($block->selectors) || ! empty($block->children)) {
65
-            $this->write($this->break);
66
-        }
67
-    }
64
+		if (empty($block->selectors) || ! empty($block->children)) {
65
+			$this->write($this->break);
66
+		}
67
+	}
68 68
 }
Please login to merge, or discard this patch.
vendor/leafo/scssphp/src/Formatter/Debug.php 1 patch
Indentation   +97 added lines, -97 removed lines patch added patch discarded remove patch
@@ -21,101 +21,101 @@
 block discarded – undo
21 21
  */
22 22
 class Debug extends Formatter
23 23
 {
24
-    /**
25
-     * {@inheritdoc}
26
-     */
27
-    public function __construct()
28
-    {
29
-        $this->indentLevel = 0;
30
-        $this->indentChar = '';
31
-        $this->break = "\n";
32
-        $this->open = ' {';
33
-        $this->close = ' }';
34
-        $this->tagSeparator = ', ';
35
-        $this->assignSeparator = ': ';
36
-        $this->keepSemicolons = true;
37
-    }
38
-
39
-    /**
40
-     * {@inheritdoc}
41
-     */
42
-    protected function indentStr()
43
-    {
44
-        return str_repeat('  ', $this->indentLevel);
45
-    }
46
-
47
-    /**
48
-     * {@inheritdoc}
49
-     */
50
-    protected function blockLines(OutputBlock $block)
51
-    {
52
-        $indent = $this->indentStr();
53
-
54
-        if (empty($block->lines)) {
55
-            $this->write("{$indent}block->lines: []\n");
56
-
57
-            return;
58
-        }
59
-
60
-        foreach ($block->lines as $index => $line) {
61
-            $this->write("{$indent}block->lines[{$index}]: $line\n");
62
-        }
63
-    }
64
-
65
-    /**
66
-     * {@inheritdoc}
67
-     */
68
-    protected function blockSelectors(OutputBlock $block)
69
-    {
70
-        $indent = $this->indentStr();
71
-
72
-        if (empty($block->selectors)) {
73
-            $this->write("{$indent}block->selectors: []\n");
74
-
75
-            return;
76
-        }
77
-
78
-        foreach ($block->selectors as $index => $selector) {
79
-            $this->write("{$indent}block->selectors[{$index}]: $selector\n");
80
-        }
81
-    }
82
-
83
-    /**
84
-     * {@inheritdoc}
85
-     */
86
-    protected function blockChildren(OutputBlock $block)
87
-    {
88
-        $indent = $this->indentStr();
89
-
90
-        if (empty($block->children)) {
91
-            $this->write("{$indent}block->children: []\n");
92
-
93
-            return;
94
-        }
95
-
96
-        $this->indentLevel++;
97
-
98
-        foreach ($block->children as $i => $child) {
99
-            $this->block($child);
100
-        }
101
-
102
-        $this->indentLevel--;
103
-    }
104
-
105
-    /**
106
-     * {@inheritdoc}
107
-     */
108
-    protected function block(OutputBlock $block)
109
-    {
110
-        $indent = $this->indentStr();
111
-
112
-        $this->write("{$indent}block->type: {$block->type}\n" .
113
-             "{$indent}block->depth: {$block->depth}\n");
114
-
115
-        $this->currentBlock = $block;
116
-
117
-        $this->blockSelectors($block);
118
-        $this->blockLines($block);
119
-        $this->blockChildren($block);
120
-    }
24
+	/**
25
+	 * {@inheritdoc}
26
+	 */
27
+	public function __construct()
28
+	{
29
+		$this->indentLevel = 0;
30
+		$this->indentChar = '';
31
+		$this->break = "\n";
32
+		$this->open = ' {';
33
+		$this->close = ' }';
34
+		$this->tagSeparator = ', ';
35
+		$this->assignSeparator = ': ';
36
+		$this->keepSemicolons = true;
37
+	}
38
+
39
+	/**
40
+	 * {@inheritdoc}
41
+	 */
42
+	protected function indentStr()
43
+	{
44
+		return str_repeat('  ', $this->indentLevel);
45
+	}
46
+
47
+	/**
48
+	 * {@inheritdoc}
49
+	 */
50
+	protected function blockLines(OutputBlock $block)
51
+	{
52
+		$indent = $this->indentStr();
53
+
54
+		if (empty($block->lines)) {
55
+			$this->write("{$indent}block->lines: []\n");
56
+
57
+			return;
58
+		}
59
+
60
+		foreach ($block->lines as $index => $line) {
61
+			$this->write("{$indent}block->lines[{$index}]: $line\n");
62
+		}
63
+	}
64
+
65
+	/**
66
+	 * {@inheritdoc}
67
+	 */
68
+	protected function blockSelectors(OutputBlock $block)
69
+	{
70
+		$indent = $this->indentStr();
71
+
72
+		if (empty($block->selectors)) {
73
+			$this->write("{$indent}block->selectors: []\n");
74
+
75
+			return;
76
+		}
77
+
78
+		foreach ($block->selectors as $index => $selector) {
79
+			$this->write("{$indent}block->selectors[{$index}]: $selector\n");
80
+		}
81
+	}
82
+
83
+	/**
84
+	 * {@inheritdoc}
85
+	 */
86
+	protected function blockChildren(OutputBlock $block)
87
+	{
88
+		$indent = $this->indentStr();
89
+
90
+		if (empty($block->children)) {
91
+			$this->write("{$indent}block->children: []\n");
92
+
93
+			return;
94
+		}
95
+
96
+		$this->indentLevel++;
97
+
98
+		foreach ($block->children as $i => $child) {
99
+			$this->block($child);
100
+		}
101
+
102
+		$this->indentLevel--;
103
+	}
104
+
105
+	/**
106
+	 * {@inheritdoc}
107
+	 */
108
+	protected function block(OutputBlock $block)
109
+	{
110
+		$indent = $this->indentStr();
111
+
112
+		$this->write("{$indent}block->type: {$block->type}\n" .
113
+			 "{$indent}block->depth: {$block->depth}\n");
114
+
115
+		$this->currentBlock = $block;
116
+
117
+		$this->blockSelectors($block);
118
+		$this->blockLines($block);
119
+		$this->blockChildren($block);
120
+	}
121 121
 }
Please login to merge, or discard this patch.